- 可以让设备中的程序与网络上其他设备中的程序进行数据交互(实现网络通信的)。
1. 基本的通信架构
- 基本的通信架构有2种形式:CS架构(Client客户端/Server服务端)、BS架构(Browser浏览器/Server服务端)
- 无论CS架构,还是BS架构的软件都必须依赖网络编程
2. 网络通信的三要素
2.1. IP
- 全称互联网协议地址,是设备在网络中的地址,是唯一的标识。
2.1.1. IP地址的形式
- IPv4(32位,点分十进制表示)
- IPv6(128位,分成8段表示,每段每四位编码成一个十六进制位表示,每段中间用:隔开)
2.1.2. 公网IP、内网IP
- 公网IP:是可以连接互联网的IP地址
- 内网IP:也叫局域网IP,只能组织或机构内部使用
- 192.168.开头的就是常见的局域网地址,范围为192.168.0.0-192.168.255.255,专门为组织机构内部使用
- 127.0.0.1、localhost代表本机,只会寻找当前所在的主机。
2.1.3. 常用命令
- ipconfig:查看本机IP地址
- ping IP地址:检查网络是否连通
2.1.4. InetAddress
代表IP地址
2.2. 端口
- 应用程序在设备中唯一的标识
- 是一个16位的二进制,范围是0~65535
2.2.1. 分类
- 周知端口:0~1023,内预先定义的知名应用占用(如,HTTP占用80,FTP占用21)
- 注册端口:1024~49151,分配给用户进程或某些应用程序
- 动态端口:49151~65535,一般不固定分配给某种进程,而是动态分配
- 我们自己开发的程序一般选择注册端口,且一个设备中不能出现两个程序的端口号一样,否则出错。
2.3. 协议
- 网络中的通信设备,事先规定的连接规则,以及传输数据的规则被称为网络通信协议。
2.3.1. 网络模型
2.3.2. UDP协议
- 用户数据报协议
- 无连接、不可靠通信
- 通信效率高,适合语音通话、视频直播等
2.3.3. TCP协议
- 传输控制协议
- 面向连接、可靠通信
- 目的是要保证在不可靠的信道上实现可靠的传输
- 三个步骤实现可靠通信:三次握手建立连接,传输数据进行确认,四次挥手断开连接。
- 可靠连接:客户端和服务器都能发送和接收数据,全双工
- 通信效率相对不高,但可靠,适用于网页、文件下载、支付等
3. UDP通信
- Java提供了一个java.net.DatagramSocket类来实现UDP通信。
3.1. 提供方法
3.2. 一发一收
public class Client {public static void main(String[] args) throws IOException {// 1. 创建客户端对象DatagramSocket socket = new DatagramSocket();// 2. 创建数据包对象, 封装要发的数据/*参数一 要发送的数据的字节数组参数二 要发送数据的字节数参数三 目标服务端地址参数四 目标服务端端口*/byte[] bytes = "我是客户端, 123".getBytes();DatagramPacket packet = new DatagramPacket(bytes, bytes.length, InetAddress.getLocalHost(), 6666);// 3. 开始发送数据到服务端socket.send(packet);System.out.println("客户端发送数据完毕");socket.close(); // 释放系统资源}
}
public class Server {public static void main(String[] args) throws Exception {// 1. 创建一个服务端对象 并注册端口DatagramSocket server = new DatagramSocket(6666);// 2. 创建数据包对象 用于接收数据byte[] buffer = new byte[1024 * 64]; // 64kbDatagramPacket packet = new DatagramPacket(buffer, buffer.length);// 3. 接收数据server.receive(packet);// 4. 将接收的数据输出 接收多少 输出多少System.out.println(new String(buffer, 0, packet.getLength()));server.close(); // 释放资源}
}
3.3. 多发多收
public class Client {public static void main(String[] args) throws IOException {// 1. 创建客户端对象DatagramSocket socket = new DatagramSocket();// 2. 创建数据包对象, 封装要发的数据/*参数一 要发送的数据的字节数组参数二 要发送数据的字节数参数三 目标服务端地址参数四 目标服务端端口*/Scanner scanner = new Scanner(System.in);while (true) {System.out.println("请说");String s = scanner.nextLine();byte[] bytes = s.getBytes();if("exit".equals(s)){socket.close();System.out.println("退出成功");break;}DatagramPacket packet = new DatagramPacket(bytes, bytes.length, InetAddress.getLocalHost(), 6666);// 3. 开始发送数据到服务端socket.send(packet);}}
}
public class Server {public static void main(String[] args) throws Exception {// 1. 创建一个服务端对象 并注册端口DatagramSocket server = new DatagramSocket(6666);// 2. 创建数据包对象 用于接收数据byte[] buffer = new byte[1024 * 64]; // 64kbDatagramPacket packet = new DatagramPacket(buffer, buffer.length);while (true) {// 3. 接收数据server.receive(packet);// 4. 将接收的数据输出 接收多少 输出多少System.out.println(new String(buffer, 0, packet.getLength()));// 5. 获取客户端主机名 和 端口System.out.println(packet.getAddress().getHostAddress());System.out.println(packet.getPort());System.out.println("-----------------------------");}}
}
4. TCP通信
- Java提供了一个java.net.Socket类来实现TCP通信
4.1. 客户端
public class Client {public static void main(String[] args) throws Exception {// 1. 创建一个Socket对象 请求与服务端进行连接Socket socket = new Socket(InetAddress.getLocalHost(), 8888);// 2. 获取输出流 向服务端发送数据OutputStream outputStream = socket.getOutputStream();// 3. 使用高级流包装原始流DataOutputStream dataOutputStream = new DataOutputStream(outputStream);// 4. 向服务端发送数据dataOutputStream.writeUTF("你好, 我是客户端");dataOutputStream.close(); // 释放资源socket.close();}
}
4.2. 服务端
- 服务端是通过java.net.ServerSocket类来实现的。
public class Server {public static void main(String[] args) throws Exception {// 1. 创建ServerSocket对象, 并指名端口号ServerSocket serverSocket = new ServerSocket(8888);// 2. 使用accept方法, 等待客户端的连接请求Socket socket = serverSocket.accept();// 3. 获取字节输入流InputStream inputStream = socket.getInputStream();// 4. 包装成数据输入流DataInputStream dataInputStream = new DataInputStream(inputStream);// 5. 读取客户端发来的数据String s = dataInputStream.readUTF();System.out.println(s);// 释放资源dataInputStream.close();socket.close();}
}
4.3. 多发多收
- 客户端
public class Client {public static void main(String[] args) throws Exception {// 1. 创建一个Socket对象 请求与服务端进行连接Socket socket = new Socket(InetAddress.getLocalHost(), 8888);// 2. 获取输出流 向服务端发送数据OutputStream outputStream = socket.getOutputStream();// 3. 使用高级流包装原始流DataOutputStream dataOutputStream = new DataOutputStream(outputStream);Scanner scanner = new Scanner(System.in);while (true) {System.out.println("请说");String s = scanner.nextLine();if("exit".equals(s)){dataOutputStream.close(); // 释放资源socket.close();System.out.println("退出成功");break;}// 4. 向服务端发送数据dataOutputStream.writeUTF(s);dataOutputStream.flush();}}
}
- 服务端
public class Server {public static void main(String[] args) throws Exception {// 1. 创建ServerSocket对象, 并指名端口号ServerSocket serverSocket = new ServerSocket(8888);// 2. 使用accept方法, 等待客户端的连接请求Socket socket = serverSocket.accept();// 3. 获取字节输入流InputStream inputStream = socket.getInputStream();// 4. 包装成数据输入流DataInputStream dataInputStream = new DataInputStream(inputStream);while (true) {try {// 5. 读取客户端发来的数据String s = dataInputStream.readUTF();System.out.println(s);} catch (Exception e) {System.out.println(socket.getRemoteSocketAddress() + "离线了");dataInputStream.close();socket.close();break;}}}
}
4.4. 实现与多个客户端同时通信
- 上述的服务端程序是不支持与多个客户端同时通信的,因为服务端现在只有一个主线程,只能处理一个客户端的消息。
- 可以通过多线程实现与多个客户端的同时通信
客户端
public class Client {public static void main(String[] args) throws Exception {// 1. 创建一个Socket对象 请求与服务端进行连接Socket socket = new Socket(InetAddress.getLocalHost(), 8888);// 2. 获取输出流 向服务端发送数据OutputStream outputStream = socket.getOutputStream();// 3. 使用高级流包装原始流DataOutputStream dataOutputStream = new DataOutputStream(outputStream);Scanner scanner = new Scanner(System.in);while (true) {System.out.println("请说");String s = scanner.nextLine();if("exit".equals(s)){dataOutputStream.close(); // 释放资源socket.close();System.out.println("退出成功");break;}// 4. 向服务端发送数据dataOutputStream.writeUTF(s);dataOutputStream.flush();}}
}
服务端
public class Server {public static void main(String[] args) throws Exception {// 1. 创建ServerSocket对象, 并指名端口号ServerSocket serverSocket = new ServerSocket(8888);while (true) {// 2. 使用accept方法, 等待客户端的连接请求Socket socket = serverSocket.accept();System.out.println("有人上线了" + socket.getRemoteSocketAddress());// 3. 将这个客户端对应的Socket管道交给独立的线程处理new ServerReaderThread(socket).start();}}
}
自定义线程
public class ServerReaderThread extends Thread{private Socket socket;public ServerReaderThread(Socket socket){this.socket = socket;}@Overridepublic void run() {try {// 1. 获取字节输入流InputStream inputStream = this.socket.getInputStream();// 2. 使用高级流进行封装DataInputStream dataInputStream = new DataInputStream(inputStream);while (true){// 3. 读取信息try {String s = dataInputStream.readUTF();System.out.println(s);} catch (IOException e) {System.out.println("有人下线了" + socket.getRemoteSocketAddress());dataInputStream.close();socket.close();break;}}} catch (IOException e) {throw new RuntimeException(e);}}
}
4.5. TCP通信实现BS架构
- 服务端
public class Server {public static void main(String[] args) throws Exception {// 1. 创建ServerSocket对象, 并指名端口号ServerSocket serverSocket = new ServerSocket(8080);while (true) {// 2. 使用accept方法, 等待浏览器的连接请求Socket socket = serverSocket.accept();System.out.println("有人上线了" + socket.getRemoteSocketAddress());// 3. 将这个浏览器对应的Socket管道交给独立的线程处理new ServerReaderThread(socket).start();}}
}
- 自定义线程
public class ServerReaderThread extends Thread{private Socket socket;public ServerReaderThread(Socket socket){this.socket = socket;}@Overridepublic void run() {// 处理浏览器请求, 返回一个你好,666try {OutputStream outputStream = socket.getOutputStream();PrintStream printStream = new PrintStream(outputStream);printStream.println("HTTP/1.1 200 OK");printStream.println("Content-Type:text/html;charset=UTF-8");printStream.println(); // 必须换行printStream.println("<h1>你好, 666</h1>");printStream.close();socket.close();} catch (IOException e) {throw new RuntimeException(e);}}
}
- 使用线程池优化
public class Server {public static void main(String[] args) throws Exception {// 1. 创建ServerSocket对象, 并指名端口号ServerSocket serverSocket = new ServerSocket(8080);// 创建线程池ThreadPoolExecutor pool = new ThreadPoolExecutor(8 * 2, 8 * 2, 0,TimeUnit.SECONDS, new ArrayBlockingQueue<>(8), Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());while (true) {// 2. 使用accept方法, 等待浏览器的连接请求Socket socket = serverSocket.accept();System.out.println("有人上线了" + socket.getRemoteSocketAddress());// 3. 将这个浏览器对应的Socket管道交给独立的线程处理// 创建Runnable任务对象ServerReaderRunnable runnable = new ServerReaderRunnable(socket);pool.execute(runnable);}}
}