TCP (Transmission Control Protocol,传输控制协议) 是互联网协议族(TCP/IP)中的核心协议之一。它是一种面向连接、可靠、基于字节流的传输层通信协议。
面向连接:在数据传输前,必须通过“三次握手”建立连接,确保通信双方都准备好。
可靠性:通过确认机制、重传机制、流量控制和拥塞控制,保证数据无差错、不丢失、不重复且按序到达。
点对点通信:一条 TCP 连接只能有两个端点(一个客户端,一个服务端)。
全双工通信:连接建立后,双方可以同时发送和接收数据。
注意:在 Java 中,只要使用
java.net.Socket类进行通信,底层默认使用的就是 TCP 协议。如果需要 UDP 协议,则需使用DatagramSocket。

客户端是主动发起连接的一方。在 Java 中,通过 java.net.Socket 类来实现。
TCP 通信的本质是IO 流的读写。
getInputStream(): 获取输入流。用于接收服务端发回的数据。
getOutputStream(): 获取输出流。用于发送数据给服务端。
connect(SocketAddress endpoint): 将未连接的 Socket 连接到远程主机。可配合 Socket 无参构造使用。
close(): 关闭 Socket 连接,释放系统资源。务必在 finally 块或使用 try-with-resources 中调用。
isConnected(): 检查 Socket 是否已成功连接。
getInetAddress(): 获取远程主机的 IP 地址。
getPort(): 获取远程主机的端口号。
getLocalAddress(): 获取本地主机的 IP 地址。
getLocalPort(): 获取本地主机的端口号(通常由系统随机分配)。
以下示例演示了如何连接服务器并发送一条消息。
import java.io.OutputStream;
import java.net.Socket;
public class TCPClient {
public static void main(String[] args) {
// 使用 try-with-resources 自动关闭资源,避免资源泄露
try (Socket socket = new Socket("127.0.0.1", 12345);
OutputStream outputStream = socket.getOutputStream()) {
// 要发送的消息
String message = "Hello, TCP Server!";
byte[] data = message.getBytes();
// 发送消息到服务器
outputStream.write(data);
// 重要:flush() 确保缓冲区的数据立即发送出去
outputStream.flush();
System.out.println("消息已发送:" + message);
} catch (Exception e) {
System.err.println("客户端发生错误:" + e.getMessage());
e.printStackTrace();
}
}
}优化点说明:
使用了 try-with-resources 语法,自动关闭
Socket和OutputStream,无需手动调用close()。增加了
flush()调用,确保数据立即发送,防止因缓冲区未满而滞留。优化了异常处理信息的输出。
服务端是被动等待连接的一方。在 Java 中,通过 java.net.ServerSocket 类来监听端口。
accept(): 阻塞方法。侦听客户端连接请求。当有客户端连接时,返回一个新的 Socket 对象用于与该客户端通信,原 ServerSocket 继续监听其他请求。
close(): 关闭 ServerSocket,停止监听端口。
getInetAddress(): 获取 ServerSocket 绑定的本地 IP 地址。
getLocalPort(): 获取 ServerSocket 绑定的本地端口号。
以下示例演示了如何启动服务、接受连接并接收客户端消息。
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
public class TCPServer {
public static void main(String[] args) {
int serverPort = 12345;
// 使用 try-with-resources 确保 ServerSocket 关闭
try (ServerSocket serverSocket = new ServerSocket(serverPort)) {
System.out.println("服务端已启动,监听端口:" + serverPort);
System.out.println("等待客户端连接...");
// accept() 是阻塞的,直到有客户端连接
try (Socket clientSocket = serverSocket.accept();
InputStream inputStream = clientSocket.getInputStream()) {
System.out.println("客户端已连接:" + clientSocket.getInetAddress());
// 接收数据的缓冲区
byte[] buffer = new byte[1024];
int bytesRead;
// 循环读取数据,直到流结束 (客户端关闭连接)
while ((bytesRead = inputStream.read(buffer)) != -1) {
String message = new String(buffer, 0, bytesRead);
System.out.println("接收到消息:" + message);
}
System.out.println("客户端断开连接");
}
} catch (Exception e) {
System.err.println("服务端发生错误:" + e.getMessage());
e.printStackTrace();
}
}
}TCP 是面向字节流的协议,没有消息边界。
现象:如果客户端连续发送两条短消息 "A" 和 "B",服务端可能会一次性收到 "AB",或者把一条长消息分成多次收到。
解决方案:应用层需要定义协议。常见做法包括:
固定长度:每条消息固定 N 个字节。
特殊分隔符:如使用 \n 或 \r\n 结尾(类似 HTTP 协议)。
长度字段:在消息头包含消息体的长度(例如:前 4 个字节表示后续数据的长度)。
必须关闭流:网络资源(Socket、Stream)是有限的。忘记关闭会导致内存泄漏或端口耗尽(Too many open files)。
推荐写法:始终使用 Java 7 引入的 try-with-resources 语句(如上例所示),它会自动调用 close() 方法。
上面的服务端代码一次只能服务一个客户端。如果要支持多人聊天室或高并发服务,需要修改结构:
// 伪代码示例
while (true) {
Socket clientSocket = serverSocket.accept(); // 接受连接
// 为每个客户端启动一个新线程处理
new Thread(() -> {
handleClient(clientSocket);
}).start();
}如果运行服务端时报错 Address already in use,说明该端口已被其他程序占用,或者上一次运行的程序未正常关闭(处于 TIME_WAIT 状态)。
解决:更换端口号,或等待操作系统释放端口,或在代码中设置 serverSocket.setReuseAddress(true)。