Java 网络编程
网络编程
网络编程是指通过因特网(Internet)实现计算机之间的数据传输。
常见软件架构:
- C/S: 客户端/服务端
- B/S: 浏览器/服务器
网络编程三要素
- IP 地址:唯一标识计算机的数字地址。
- 端口号:端口号,标识计算机上的进程。
- 通信协议:描述计算机之间如何通信,如:TCP、UDP、http、ftp、smtp、pop3、imap 等。
IP
IP 地址(Internet Protocol Address)是 Internet 协议栈中的重要组件,用于唯一标识 Internet 中的计算机。IP 分为 IPV4 和 IPV6 两种。
- IPV4 地址格式为:
A.B.C.D,其中 A、B、C、D 为 0-255 的数字。 - IPV6 地址格式为:
ABCD:ABCD:ABCD:ABCD:ABCD:ABCD:ABCD:ABCD,其中 A、B、C、D 为 0-65535 的数字。
本机 IP 地址可以通过 ipconfig 命令查看,但是这个 IP 地址是本机的局域网 IP 地址,不是外网 IP 地址。当我们切换局域网时,会被重新分配新的 IP 地址。我们也可以通过 127.0.0.1 或者 localhost 来访问本机,这两个 IP 地址称为本地回环地址,永远不会被改变。
InetAddress 类
InetAddress 类是 Internet 协议栈中的重要类,用于表示 Internet 中的计算机。InetAddress 类提供了获取 IP 地址的方法。
InetAddress 构造方法
| 构造方法 | 描述 |
|---|---|
| getByAddress(String host, byte[] addr) | 通过主机名和字节数组创建 InetAddress 对象。 |
| getByName(String host) | 通过主机名创建 InetAddress 对象。 |
| getAllByName(String host) | 通过主机名创建 InetAddress 对象。 |
| getLoopbackAddress() | 获取本地回环地址。 |
| getByAddress(byte[] addr) | 通过字节数组创建 InetAddress 对象。 |
| getLocalHost() | 获取本地主机地址。 |
InetAddress 常用方法
| 方法 | 描述 |
|---|---|
| getHostName() | 获取主机名。 |
| getHostAddress() | 获取 IP 地址。 |
InetAddress 代码示例
▼java复制代码InetAddress address = InetAddress.getLocalHost(); System.out.println(address); // 获取主机名 String hostName = address.getHostName(); System.out.println(hostName); // 获取 IP String hostAddress = address.getHostAddress(); System.out.println(hostAddress);
端口
端口(Port)是计算机网络中的一个数字,用于标识计算机上的进程。端口号范围:0-65535。
协议
协议(Protocol)是计算机网络中的重要概念,用于描述计算机之间如何通信。协议定义了计算机如何进行数据传输,以及如何处理数据。
模型
- OSI 模型:应用层 --> 表示层 --> 会话层 --> 传输层 --> 网络层 --> 数据链路层 --> 物理层
- TCP/IP 模型:应用层 --> 传输层 --> 网络层 --> 物理链路层
通信协议
- UDP: 无连接协议,速度快,单次最大传输 64KB,数据不安全,易丢失。
- TCP: 有连接协议,传输速度慢,无大小限制,数据安全。
UDP
UDP 是一种无连接的协议,它不建立连接,只发送数据,不保证数据安全。
UDP 有三种通信方法:单播、组播、广播。
- 单播:发送方发送数据给指定的接收方。
- 组播:发送方发送数据给多个接收方。
- 广播:发送方发送数据给所有接收方。
单播
单播需要指定具体的 IP 地址和端口号。
▼java复制代码// 发送端 DatagramSocket socket = new DatagramSocket(); Scanner sc = new Scanner(System.in); while (true) { String content = sc.nextLine(); byte[] bytes = content.getBytes(); DatagramPacket packet = new DatagramPacket(bytes, 0, bytes.length, InetAddress.getLocalHost(), 8888); socket.send(packet); if ("exit".equals(content)) break; } sc.close(); socket.close();
▼java复制代码// 接收端 DatagramSocket socket = new DatagramSocket(8888); while (true) { byte[] buffer = new byte[1024]; DatagramPacket packet = new DatagramPacket(buffer, buffer.length); socket.receive(packet); byte[] data = packet.getData(); int length = packet.getLength(); InetAddress address = packet.getAddress(); String content = new String(data, 0, length); System.out.println(address.getHostName() + ":" + content); if ("exit".equals(content)) break; } socket.close();
组播
组播需要指定组播地址和组播端口,组播地址范围:224.0.0.0-239.255.255.255
下面的代码示例只有 接收端 1 和 接收端 2 才能接收数据。
▼java复制代码// 发送端 MulticastSocket socket = new MulticastSocket(); Scanner sc = new Scanner(System.in); while (true) { String content = sc.nextLine(); byte[] bytes = content.getBytes(); DatagramPacket packet = new DatagramPacket(bytes, 0, bytes.length, InetAddress.getByName("224.0.0.2"), 10000); socket.send(packet); if ("exit".equals(content)) break; } sc.close(); socket.close();
▼java复制代码// 接收端 1 MulticastSocket socket = new MulticastSocket(10000); // 将当前主机添加到组播地址中 InetAddress group = InetAddress.getByName("224.0.0.2"); socket.joinGroup(group); while (true) { byte[] buffer = new byte[1024]; DatagramPacket packet = new DatagramPacket(buffer, buffer.length); socket.receive(packet); byte[] data = packet.getData(); int length = packet.getLength(); InetAddress address = packet.getAddress(); String content = new String(data, 0, length); System.out.println(address.getHostName() + ":" + content); if ("exit".equals(content)) break; } socket.close();
▼java复制代码// 接收端 2 MulticastSocket socket = new MulticastSocket(10000); // 将当前主机添加到组播地址中 InetAddress group = InetAddress.getByName("224.0.0.2"); socket.joinGroup(group); while (true) { byte[] buffer = new byte[1024]; DatagramPacket packet = new DatagramPacket(buffer, buffer.length); socket.receive(packet); byte[] data = packet.getData(); int length = packet.getLength(); InetAddress address = packet.getAddress(); String content = new String(data, 0, length); System.out.println(address.getHostName() + ":" + content); if ("exit".equals(content)) break; } socket.close();
▼java复制代码// 接收端 3 MulticastSocket socket = new MulticastSocket(10000); // 将当前主机添加到其他组播地址中 InetAddress group = InetAddress.getByName("224.0.0.3"); socket.joinGroup(group); while (true) { byte[] buffer = new byte[1024]; DatagramPacket packet = new DatagramPacket(buffer, buffer.length); socket.receive(packet); byte[] data = packet.getData(); int length = packet.getLength(); InetAddress address = packet.getAddress(); String content = new String(data, 0, length); System.out.println(address.getHostName() + ":" + content); if ("exit".equals(content)) break; } socket.close();
广播
广播需要指定广播地址和端口,广播地址范围:255.255.255.255。
代码同单播一致,只需要将发送端代码中的 InetAddress.getLocalHost() 替换为 InetAddress.getByName("255.255.255.255")
TCP
TCP 是一种有连接的协议,它建立连接,发送数据,并确保数据安全。
代码示例
▼java复制代码Socket socket = new Socket(InetAddress.getLocalHost().getHostAddress(), 8888); OutputStream outputStream = socket.getOutputStream(); OutputStreamWriter outputStreamWriter = new OutputStreamWriter(outputStream); BufferedWriter bufferedWriter = new BufferedWriter(outputStreamWriter); Scanner scanner = new Scanner(System.in); while (true) { String content = scanner.nextLine(); bufferedWriter.write(content); bufferedWriter.newLine(); bufferedWriter.flush(); if ("exit".equals(content)) break; } scanner.close(); bufferedWriter.close(); socket.close();
▼java复制代码ServerSocket serverSocket = new ServerSocket(8888); System.out.println("服务启动,等待客户端连接"); Socket accept = serverSocket.accept(); System.out.println("客户端已连接"); InputStream inputStream = accept.getInputStream(); InputStreamReader inputStreamReader = new InputStreamReader(inputStream); BufferedReader bufferedReader = new BufferedReader(inputStreamReader); String content; while ((content = bufferedReader.readLine()) != null) { System.out.println(content); if ("exit".equals(content)) break; } bufferedReader.close(); accept.close(); serverSocket.close();
三次握手、四次挥手
TCP 三次握手是 TCP 连接的初始阶段,它用于建立连接。
- 客户端发送一个 SYN 包给服务端,表示要建立连接。
- 服务端收到 SYN 包后,发送一个 SYN+ACK 包给客户端,表示服务端已收到连接请求,并准备建立连接。
- 客户端收到 SYN+ACK 包后,发送一个 ACK 包给服务端,表示客户端已收到连接请求,并准备建立连接。
TCP 四次挥手是 TCP 断开的最后阶段,它用于断开连接。
- 客户端发送一个 FIN 包给服务端,表示要断开连接。
- 服务端收到 FIN 包后,发送一个 ACK 包给客户端,表示服务端已收到断开连接请求。
- 客户端收到 ACK 包后,发送一个 ACK 包给服务端,表示客户端已收到断开连接请求。
- 服务端收到 ACK 包后,发送一个 FIN 包给客户端,表示服务端已收到断开连接请求。
综合练习
多发多收
▼java复制代码// 略
互相发送
▼java复制代码Socket socket = new Socket(InetAddress.getLocalHost().getHostAddress(), 8888); OutputStream outputStream = socket.getOutputStream(); OutputStreamWriter outputStreamWriter = new OutputStreamWriter(outputStream); BufferedWriter bufferedWriter = new BufferedWriter(outputStreamWriter); String content = "Hello"; bufferedWriter.write(content); bufferedWriter.newLine(); bufferedWriter.flush(); InputStream inputStream = socket.getInputStream(); InputStreamReader inputStreamReader = new InputStreamReader(inputStream); BufferedReader bufferedReader = new BufferedReader(inputStreamReader); String reply = bufferedReader.readLine(); System.out.println(reply); bufferedWriter.close(); socket.close();
▼java复制代码ServerSocket serverSocket = new ServerSocket(8888); System.out.println("服务启动,等待客户端连接"); Socket accept = serverSocket.accept(); System.out.println("客户端已连接"); InputStream inputStream = accept.getInputStream(); InputStreamReader inputStreamReader = new InputStreamReader(inputStream); BufferedReader bufferedReader = new BufferedReader(inputStreamReader); String content = bufferedReader.readLine(); System.out.println(content); OutputStream outputStream = accept.getOutputStream(); OutputStreamWriter outputStreamWriter = new OutputStreamWriter(outputStream); BufferedWriter bufferedWriter = new BufferedWriter(outputStreamWriter); String reply = "Hello!"; bufferedWriter.write(reply); bufferedWriter.newLine(); bufferedWriter.flush(); bufferedWriter.close(); bufferedReader.close(); accept.close(); serverSocket.close();
文件上传
▼java复制代码Socket socket = new Socket(InetAddress.getLocalHost().getHostAddress(), 8888); OutputStream outputStream = socket.getOutputStream(); BufferedOutputStream bos = new BufferedOutputStream(outputStream); File file = new File(System.getProperty("user.dir") + File.separator + "test.zip"); FileInputStream fis = new FileInputStream(file); BufferedInputStream bis = new BufferedInputStream(fis); byte[] buffer = new byte[1024]; int len; while ((len = bis.read(buffer)) != -1) { bos.write(buffer, 0, len); } socket.shutdownOutput(); InputStream inputStream = socket.getInputStream(); InputStreamReader inputStreamReader = new InputStreamReader(inputStream); BufferedReader bufferedReader = new BufferedReader(inputStreamReader); String reply = bufferedReader.readLine(); System.out.println(reply); bufferedReader.close(); bis.close(); bos.close(); socket.close();
▼java复制代码ServerSocket serverSocket = new ServerSocket(8888); System.out.println("服务启动,等待客户端连接"); Socket accept = serverSocket.accept(); System.out.println("客户端已连接"); InputStream inputStream = accept.getInputStream(); BufferedInputStream bis = new BufferedInputStream(inputStream); File file = new File(System.getProperty("user.dir") + File.separator + "test-copy.zip"); FileOutputStream fos = new FileOutputStream(file); BufferedOutputStream bos = new BufferedOutputStream(fos); byte[] buffer = new byte[1024]; int len; while ((len = bis.read(buffer)) != -1) { bos.write(buffer, 0, len); } OutputStream outputStream = accept.getOutputStream(); OutputStreamWriter outputStreamWriter = new OutputStreamWriter(outputStream); BufferedWriter bufferedWriter = new BufferedWriter(outputStreamWriter); String reply = "Hello!"; bufferedWriter.write(reply); bufferedWriter.newLine(); bufferedWriter.flush(); bufferedWriter.close(); bos.close(); bis.close(); accept.close(); serverSocket.close();
文件上传时文件重名
▼java复制代码// 使用 UUID 创建文件名 UUID uuid = UUID.randomUUID(); File file = new File(System.getProperty("user.dir") + File.separator + uuid + ".zip"); FileOutputStream fos = new FileOutputStream(file); BufferedOutputStream bos = new BufferedOutputStream(fos);
多线程接收文件
▼java复制代码Socket socket = new Socket(InetAddress.getLocalHost().getHostAddress(), 8888); OutputStream outputStream = socket.getOutputStream(); BufferedOutputStream bos = new BufferedOutputStream(outputStream); File file = new File(System.getProperty("user.dir") + File.separator + "test.zip"); FileInputStream fis = new FileInputStream(file); BufferedInputStream bis = new BufferedInputStream(fis); byte[] buffer = new byte[1024]; int len; while ((len = bis.read(buffer)) != -1) { bos.write(buffer, 0, len); } socket.shutdownOutput(); InputStream inputStream = socket.getInputStream(); InputStreamReader inputStreamReader = new InputStreamReader(inputStream); BufferedReader bufferedReader = new BufferedReader(inputStreamReader); String reply = bufferedReader.readLine(); System.out.println(reply); socket.close();
▼java复制代码ServerSocket serverSocket = new ServerSocket(8888); System.out.println("服务启动,等待客户端连接"); while (true) { Socket accept = serverSocket.accept(); System.out.println("客户端已连接"); new MyThread(accept).start(); }
▼java复制代码public class MyThread extends Thread { private Socket socket; public MyThread(Socket socket) { this.socket = socket; } @Override public void run() { try { InputStream inputStream = socket.getInputStream(); BufferedInputStream bis = new BufferedInputStream(inputStream); UUID uuid = UUID.randomUUID(); File file = new File(System.getProperty("user.dir") + File.separator + uuid + ".zip"); FileOutputStream fos = new FileOutputStream(file); BufferedOutputStream bos = new BufferedOutputStream(fos); byte[] buffer = new byte[1024]; int len; while ((len = bis.read(buffer)) != -1) { bos.write(buffer, 0, len); } OutputStream outputStream = socket.getOutputStream(); OutputStreamWriter outputStreamWriter = new OutputStreamWriter(outputStream); BufferedWriter bufferedWriter = new BufferedWriter(outputStreamWriter); String reply = "Hello!"; bufferedWriter.write(reply); bufferedWriter.newLine(); bufferedWriter.flush(); bufferedWriter.close(); socket.close(); } catch (IOException e) { e.printStackTrace(); } } }
线程池优化
▼java复制代码public class MyRunnable implements Runnable { ...... }
▼java复制代码ServerSocket serverSocket = new ServerSocket(8888); System.out.println("服务启动,等待客户端连接"); ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor( 3, 6, 60, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(), Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy() ); while (true) { Socket accept = serverSocket.accept(); System.out.println("客户端已连接"); threadPoolExecutor.execute(new MyRunnable(accept)); }
接收浏览器请求
▼java复制代码// 启动任意 server 代码,在浏览器控制台输入 http://localhost:端口
聊天室
▼java复制代码// 略
