实现网络通讯,设备中的程序与网络中其他设备中的程序进行数据交互
java.net.*
基本的通信架构:CS(Client客户端/Server服务端)、BS架构(Browser浏览器/Server服务端)
网络通信三要素
操作IP的类和方法: InetAddress
public class InetAddressTest {
public static void main(String[] args) throws Exception {
// 1. 获取本机IP地址对象
InetAddress ip1 = InetAddress.getLocalHost();
System.out.println(ip1);
// 获取主机名称
System.out.println(ip1.getHostName());
// 获取主机IP
System.out.println(ip1.getHostAddress());
// 2. 获取指定IP或域名的IP地址对象
InetAddress ip2 = InetAddress.getByName("www.baidu.com");
System.out.println(ip2);
System.out.println(ip2.getHostName());
System.out.println(ip2.getHostAddress());
// 查看是否可以连同ping
System.out.println(ip2.isReachable(6000));
}
}
端口知识:
端口是用来标记设备中的应用程序的,16位的二进制,范围是0~65535
分类
协议知识:
OSI网络参考模型——全球网络互联标准
TCP/IP网络模型——事实上的国际标准
传输层
UDP(User Datagram Protocal):用户数据报协议——无连接、不可靠通信
TCP(Transmission Control Protovol):传输控制协议——面向连接,可靠通信(三次握手建立连接,传输数据返回确认,四次挥手断开连接)
无连接、不可靠通信
java.net.DatagramSocket类实现UDP通信
案例:实现UDP 通信的一收一发
客户端
public class Client {
// 实现一发一收
public static void main(String[] args) throws Exception {
// 1. 创建一个客户端对象
DatagramSocket socket = new DatagramSocket(); // 系统随机分配一个端口,也可以设置参数,自己定义端口
// 2. 创建一个数据包封装要发送的数据
// 参数一:要发送的数据,是byte数组的形式
// 参数二:数据包的长度
// 参数三:要发送数据的服务端IP地址
// 参数四:端口号
byte[] bytes = "Hello 小鱼0135".getBytes();
DatagramPacket packet = new DatagramPacket(bytes, bytes.length, InetAddress.getLocalHost(), 666);
// 3. 将数据发送出去
socket.send(packet);
System.out.println("客户端发送数据完毕!");
// 4. 将连接关闭,释放资源
socket.close();
}
}
服务端
public class Server {
// 完成UDP通信的服务端开发
public static void main(String[] args) throws Exception {
System.out.println("=====服务端启动=====");
// 1. 创建服务端对象
DatagramSocket socket = new DatagramSocket(666);
// 2. 创建一个数据包对象接受数据
byte[] buffer = new byte[1024 * 64]; // 数据包的数据不超过64KB
DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
// 3. 接收数据
socket.receive(packet);
// 4. 取出数据包中的数据
String rs = new String(buffer, 0, packet.getLength());
System.out.println(rs);
// 获取客户端的信息
System.out.println(packet.getAddress());
System.out.println(packet.getPort());
// 5. 释放资源
socket.close();
}
}
案例:实现UDP通信的多发多收
客户端——IDEA里面进行设置,实现客户端可以多开
public class Client {
// 实现一发一收
public static void main(String[] args) throws Exception {
// 1. 创建一个客户端对象
DatagramSocket socket = new DatagramSocket(); // 系统随机分配一个端口,也可以设置参数,自己定义端口
// 2. 创建一个数据包封装要发送的数据
// 参数一:要发送的数据,是byte数组的形式
// 参数二:数据包的长度
// 参数三:要发送数据的服务端IP地址
// 参数四:端口号
// 让用户自己输入数据进行发送
Scanner sc = new Scanner(System.in);
while (true) {
System.out.print("要发送的内容:");
String msg = sc.nextLine();
// 判断用户是否想跳出此循环
if ("exit".equals(msg)) {
System.out.println("轻轻的我走了~");
socket.close(); // 关闭资源
break; // 跳出死循环
}
byte[] bytes = msg.getBytes();
DatagramPacket packet = new DatagramPacket(bytes, bytes.length, InetAddress.getLocalHost(), 666);
// 3. 将数据发送出去
socket.send(packet);
}
}
}
服务端
public class Server {
// 完成UDP通信的服务端开发
public static void main(String[] args) throws Exception {
System.out.println("=====服务端启动=====");
// 1. 创建服务端对象
DatagramSocket socket = new DatagramSocket(666);
// 2. 创建一个数据包对象接受数据
byte[] buffer = new byte[1024 * 64]; // 数据包的数据不超过64KB
DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
while (true) {
// 3. 接收数据
socket.receive(packet);
// 4. 取出数据包中的数据
String rs = new String(buffer, 0, packet.getLength());
System.out.println(rs);
// 获取客户端的信息
System.out.println(packet.getAddress());
System.out.println(packet.getPort());
System.out.println("=============");
}
}
}
java.net.Socket类来实现TCP通信
案例:一收一发
客户端
/*
* 实现TCP通信,一收一发
*/
public class Client {
public static void main(String[] args) throws Exception {
// 1. 创建Socket对象,并同时请求与服务端程序的连接
Socket socket = new Socket(InetAddress.getLocalHost(), 8888);
// 2. 从socket通信管道中得到一个字节输出流,用来发数据给服务端程序
OutputStream os = socket.getOutputStream();
// 3. 将低级的字节输出流包装成数据输出流——经验
DataOutputStream dos = new DataOutputStream(os);
// 4. 开始写数据出去
dos.writeUTF("小鱼仔");
// 5. 关闭资源,要关闭流资源,也要关闭Scoket
dos.close();
socket.close();
}
}
服务端
/*
* TCP的服务端开发
*/
public class Server {
public static void main(String[] args) throws Exception {
System.out.println("-----服务端启动成功-----");
// 1. 创建SeverSocket对象,同时为服务端注册端口
ServerSocket severSocket = new ServerSocket(8888);
// 2. 创建Socket对象,使用ServerSocket调用accept()方法,等待客户端的连接请求
Socket socket = severSocket.accept();
// 3. 从socket通信管道中得到一个字节输入流
InputStream is = socket.getInputStream();
// 4. 把InputStream字节输入流包装成数据输入流
DataInputStream dis = new DataInputStream(is);
// 5. 使用数据输入流对象读取数据
String re = dis.readUTF();
System.out.println(re);
// 获得客户端的IP
System.out.println(socket.getRemoteSocketAddress());
// 6. 关闭流、关闭资源
dis.close();
socket.close();
}
}
案例:多收多发
客户端
/*
* 实现TCP通信,多收多发
*/
public class Client {
public static void main(String[] args) throws Exception {
// 1. 创建Socket对象,并同时请求与服务端程序的连接
Socket socket = new Socket(InetAddress.getLocalHost(), 8888);
// 2. 从socket通信管道中得到一个字节输出流,用来发数据给服务端程序
OutputStream os = socket.getOutputStream();
// 3. 将低级的字节输出流包装成数据输出流——经验
DataOutputStream dos = new DataOutputStream(os);
// 用户输入数据
Scanner sc = new Scanner(System.in);
while (true) {
// 4. 开始写数据出去
System.out.print("曰:");
String msg = sc.nextLine();
if ("exit".equals(msg)) {
System.out.println("走了~");
dos.close();
socket.close();
break;
}
dos.writeUTF(msg);
dos.flush();
}
}
}
服务端
/*
* TCP的服务端开发
*/
public class Server {
public static void main(String[] args) throws Exception {
System.out.println("-----服务端启动成功-----");
// 1. 创建SeverSocket对象,同时为服务端注册端口
ServerSocket severSocket = new ServerSocket(8888);
// 2. 创建Socket对象,使用ServerSocket调用accept()方法,等待客户端的连接请求
Socket socket = severSocket.accept();
// 3. 从socket通信管道中得到一个字节输入流
InputStream is = socket.getInputStream();
// 4. 把InputStream字节输入流包装成数据输入流
DataInputStream dis = new DataInputStream(is);
while (true) {
try {
// 5. 使用数据输入流对象读取数据
String re = dis.readUTF();
System.out.println(re);
} catch (Exception e) {
System.out.println(socket.getRemoteSocketAddress() + "离线了");
dis.close();
socket.close();
break;
}
}
}
}
问题:不能像UDP那样同时开多个client,因为建立连接的,双方都指定了端口,一心一意;而UDP知识指定了客户端向服务端发送的位置,没有指定服务端只能接受某个端口的数据,来者不拒——>下一节内容:服务端现在只有一个主线程,只能处理一个客户端消息
客户端
/*
* 实现TCP通信,多收多发
*/
public class Client {
public static void main(String[] args) throws Exception {
// 1. 创建Socket对象,并同时请求与服务端程序的连接
Socket socket = new Socket(InetAddress.getLocalHost(), 8888);
// 2. 从socket通信管道中得到一个字节输出流,用来发数据给服务端程序
OutputStream os = socket.getOutputStream();
// 3. 将低级的字节输出流包装成数据输出流——经验
DataOutputStream dos = new DataOutputStream(os);
// 用户输入数据
Scanner sc = new Scanner(System.in);
while (true) {
// 4. 开始写数据出去
System.out.print("曰:");
String msg = sc.nextLine();
if ("exit".equals(msg)) {
System.out.println("走了~");
dos.close();
socket.close();
break;
}
dos.writeUTF(msg);
dos.flush();
}
}
}
服务端
/*
* TCP的服务端开发
*/
public class Server {
public static void main(String[] args) throws Exception {
System.out.println("-----服务端启动成功-----");
// 1. 创建SeverSocket对象,同时为服务端注册端口
ServerSocket severSocket = new ServerSocket(8888);
while (true) {
// 2. 创建Socket对象,使用ServerSocket调用accept()方法,等待客户端的连接请求
Socket socket = severSocket.accept();
System.out.println("有人上线:" + socket.getRemoteSocketAddress());
// 3. 把这个客户端对应的socket通信管道,交给一个独立的线程负责处理
new ServerReaderThread(socket).start();
}
}
}
线程
public class ServerReaderThread extends Thread{
private Socket socket;
public ServerReaderThread() {
}
public ServerReaderThread(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
// 3. 从socket通信管道中得到一个字节输入流
try {
InputStream is = socket.getInputStream();
// 4. 把InputStream字节输入流包装成数据输入流
DataInputStream dis = new DataInputStream(is);
while (true) {
try {
String msg = dis.readUTF();
System.out.println(msg);
} catch (IOException e) {
System.out.println("一个走了" + socket.getRemoteSocketAddress());
dis.close();
socket.close();
}
}
} catch (Exception e) {
e.printStackTrace();
// System.out.println("一个走了"); // nonono
}
}
}
客户端
public class Client {
public static void main(String[] args) throws Exception {
// 1. 创建Socket对象,并同时请求与服务端程序的连接
Socket socket = new Socket(InetAddress.getLocalHost(), 8888);
// 创建一个独立的线程,从服务端接收消息
new ClientReaderThread(socket).start();
// 2. 从socket通信管道中得到一个字节输出流,用来发数据给服务端程序
OutputStream os = socket.getOutputStream();
// 3. 将低级的字节输出流包装成数据输出流——经验
DataOutputStream dos = new DataOutputStream(os);
// 用户输入数据
Scanner sc = new Scanner(System.in);
while (true) {
// 4. 开始写数据出去
System.out.print("曰:");
String msg = sc.nextLine();
if ("exit".equals(msg)) {
System.out.println("走了~");
dos.close();
socket.close();
break;
}
dos.writeUTF(msg);
dos.flush();
}
}
}
客户端线程类
public class ClientReaderThread extends Thread{
private Socket socket;
public ClientReaderThread() {
}
public ClientReaderThread(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
// 3. 从socket通信管道中得到一个字节输入流
try {
InputStream is = socket.getInputStream();
// 4. 把InputStream字节输入流包装成数据输入流
DataInputStream dis = new DataInputStream(is);
while (true) {
try {
String msg = dis.readUTF();
System.out.println(msg);
} catch (IOException e) {
System.out.println("自己" + socket.getRemoteSocketAddress());
dis.close();
socket.close();
break;
}
}
} catch (Exception e) {
e.printStackTrace();
// System.out.println("一个走了"); // nonono
}
}
}
服务器端
public class Server {
// 定义一个在线socket的集合列表
public static List<Socket> onLineSockets = new ArrayList<>();
public static void main(String[] args) throws Exception {
System.out.println("-----服务端启动成功-----");
// 1. 创建SeverSocket对象,同时为服务端注册端口
ServerSocket severSocket = new ServerSocket(8888);
while (true) {
// 2. 创建Socket对象,使用ServerSocket调用accept()方法,等待客户端的连接请求
Socket socket = severSocket.accept();
onLineSockets.add(socket);
System.out.println("有人上线:" + socket.getRemoteSocketAddress());
// 3. 把这个客户端对应的socket通信管道,交给一个独立的线程负责处理
new ServerReaderThread(socket).start();
}
}
}
服务器端线程类
public class ServerReaderThread extends Thread{
private Socket socket;
public ServerReaderThread() {
}
public ServerReaderThread(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
// 3. 从socket通信管道中得到一个字节输入流
try {
InputStream is = socket.getInputStream();
// 4. 把InputStream字节输入流包装成数据输入流
DataInputStream dis = new DataInputStream(is);
while (true) {
try {
String msg = dis.readUTF();
System.out.println(msg);
// 把这个消息分发给全部客户端接收
sendMsgToAll(msg);
} catch (IOException e) {
System.out.println("一个走了" + socket.getRemoteSocketAddress());
Server.onLineSockets.remove(socket);
dis.close();
socket.close();
break;
}
}
} catch (Exception e) {
e.printStackTrace();
// System.out.println("一个走了"); // nonono
}
}
private void sendMsgToAll(String msg) throws Exception {
// 发送给全部在线的socket管道接收
for (Socket onLineSocket : Server.onLineSockets) {
OutputStream os = onLineSocket.getOutputStream();
DataOutputStream dos = new DataOutputStream(os);
dos.writeUTF(msg);
dos.flush();
}
}
}
浏览器端输出的内容格式要满足HTTP格式
服务器端:
public class Server {
public static void main(String[] args) throws Exception {
System.out.println("-----服务端启动成功-----");
// 1. 创建SeverSocket对象,同时为服务端注册端口
ServerSocket severSocket = new ServerSocket(8080);
while (true) {
// 2. 创建Socket对象,使用ServerSocket调用accept()方法,等待客户端的连接请求
Socket socket = severSocket.accept();
System.out.println("有人上线:" + socket.getRemoteSocketAddress());
// 3. 把这个客户端对应的socket通信管道,交给一个独立的线程负责处理
new ServerReaderThread(socket).start();
}
}
}
服务器线程类:
public class ServerReaderThread extends Thread{
private Socket socket;
public ServerReaderThread() {
}
public ServerReaderThread(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
// 3. 从socket通信管道中得到一个字节输出流
try {
OutputStream os = socket.getOutputStream();
// 4. 把OnputStream字节输入流包装成打印输入流
PrintStream pos = new PrintStream(os);
pos.println("HTTP/1.1 200 OK");
pos.println("Content-Type:text/html;charset=UTF-8");
pos.println(); // 这三行是必须有的
pos.println("小鱼0135");
socket.close();
} catch (Exception e) {
e.printStackTrace();
// System.out.println("一个走了"); // nonono
}
}
}
线程池优化——避免死机