2. 网络协议与Java网络API总览
Java网络编程的根基是TCP/IP协议族。理解协议分层与Java API的对应关系,是选择正确通信方式的前提。本节从协议要点出发,梳理InetAddress、Socket、ServerSocket、DatagramSocket等核心类的职责边界与选型依据。
一、TCP/IP协议要点
TCP/IP并非单一协议,而是一个四层模型:链路层、网络层、传输层、应用层。Java网络API主要工作在传输层与应用层之间。
- IP协议:负责主机到主机的寻址与路由,提供无连接、不可靠的数据报服务。IPv4地址为32位,IPv6为128位。
- TCP协议:面向连接、可靠、基于字节流。通过三次握手建立连接,四次挥手释放连接。提供流量控制、拥塞控制、超时重传机制。
- UDP协议:无连接、不可靠、基于数据报。不保证顺序与到达,但开销小、延迟低,适合实时性要求高的场景。
- HTTP协议:应用层协议,通常基于TCP。请求-响应模型,无状态,通过方法(GET/POST/PUT/DELETE)和状态码表达语义。
端口号用于区分同一主机上的不同服务。0~1023为知名端口,1024~49151为注册端口,49152~65535为动态/私有端口。Java中端口用int表示,有效范围0~65535。
二、Java核心网络类职责
InetAddress:表示IP地址,封装主机名与IP的映射。不包含端口信息。常用方法:getByName(String host)、getLocalHost()、getAllByName(String host)。它不负责建立连接,仅用于地址解析与表示。
Socket:客户端TCP套接字,代表一个已建立的连接端点。通过new Socket(host, port)发起连接,获取输入输出流进行读写。Socket是面向连接的,内部维护本地地址、远程地址、端口及连接状态。
ServerSocket:服务端TCP套接字,用于监听端口并接受连接。调用accept()阻塞等待客户端连接,返回一个新的Socket实例代表该连接。ServerSocket本身不传输数据,只负责建立连接。
DatagramSocket:UDP套接字,用于发送和接收数据报。不建立连接,每个数据报独立寻址。配合DatagramPacket使用,包中需指定目标地址与端口。
DatagramPacket:UDP数据报载体,包含数据字节数组、长度、目标地址与端口。发送时需填充目标地址,接收时由系统填充来源地址。
三、协议与API选择关系
选择哪种API,取决于通信需求:需要可靠传输、顺序保证、大数据量传输时选TCP(Socket/ServerSocket);需要低延迟、容忍丢包、广播/组播时选UDP(DatagramSocket)。HTTP则是在TCP之上封装的应用层协议,Java中可用HttpURLConnection或HttpClient(Java 11+)处理。
四、TCP通信示例
以下是一个最小可运行的TCP回声服务端与客户端示例,展示ServerSocket与Socket的配合方式。
// 服务端
try (ServerSocket server = new ServerSocket(8080)) {
System.out.println("监听 8080...");
try (Socket conn = server.accept();
BufferedReader in = new BufferedReader(
new InputStreamReader(conn.getInputStream()));
PrintWriter out = new PrintWriter(conn.getOutputStream(), true)) {
String line;
while ((line = in.readLine()) != null) {
out.println("echo: " + line);
}
}
}
// 客户端
try (Socket socket = new Socket("127.0.0.1", 8080);
PrintWriter out = new PrintWriter(socket.getOutputStream(), true);
BufferedReader in = new BufferedReader(
new InputStreamReader(socket.getInputStream()))) {
out.println("hello");
System.out.println(in.readLine()); // echo: hello
}
关键点:accept()返回的Socket代表一条独立连接,服务端通常为每个连接分配线程或使用NIO多路复用。输入流读取到null表示对端关闭连接。
五、UDP通信示例
// 接收端
try (DatagramSocket socket = new DatagramSocket(9090)) {
byte[] buf = new byte[1024];
DatagramPacket packet = new DatagramPacket(buf, buf.length);
socket.receive(packet); // 阻塞直到收到数据报
String msg = new String(packet.getData(), 0, packet.getLength());
System.out.println("收到: " + msg + " 来自 " + packet.getAddress());
}
// 发送端
try (DatagramSocket socket = new DatagramSocket()) {
byte[] data = "ping".getBytes(StandardCharsets.UTF_8);
DatagramPacket packet = new DatagramPacket(
data, data.length, InetAddress.getByName("127.0.0.1"), 9090);
socket.send(packet);
}
UDP没有连接建立过程,发送端无需知道接收端是否在线。每个数据报最大理论长度受IP层限制,实际使用中建议控制在1472字节以内以避免IP分片。
六、HTTP协议要点与Java处理
HTTP基于TCP,默认端口80(HTTPS为443)。请求由请求行、请求头、空行、请求体组成;响应由状态行、响应头、空行、响应体组成。Java 11引入的java.net.http.HttpClient支持HTTP/2与异步请求,是当前推荐方式。
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://example.com/api"))
.header("Accept", "application/json")
.GET()
.build();
HttpResponse<String> response = client.send(
request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.statusCode()); // 200
System.out.println(response.body());
七、选型对照
| 维度 | TCP (Socket/ServerSocket) | UDP (DatagramSocket) | HTTP (HttpClient) |
|---|---|---|---|
| 连接方式 | 面向连接 | 无连接 | 基于TCP |
| 可靠性 | 可靠,保证顺序 | 不可靠,可能丢包乱序 | 可靠 |
| 传输单位 | 字节流 | 数据报 | 请求/响应消息 |
| 典型场景 | 文件传输、长连接、RPC | 实时音视频、DNS、游戏 | Web API、REST服务 |
| Java核心类 | Socket、ServerSocket | DatagramSocket、DatagramPacket | HttpClient、HttpURLConnection |
accept()返回新Socket后,原ServerSocket继续监听,不要误以为它只能接受一个连接。UDP的receive()会阻塞,需设置setSoTimeout()避免永久等待。Socket读写默认阻塞,高并发场景应结合线程池或NIO。