Java TCP 网络编程:从可靠传输到 Socket 实战
TCP 与 UDP 的区别与相同点
- TCP 是有连接的,UDP 无连接。
- TCP 是面向字节流的,UDP 是面向数据报的。
- TCP 是可靠传输的,UDP 是不可靠传输的。
- TCP 和 UDP 都是全双工的。
连接机制
- UDP:每次发送数据报都要指定对方的地址(UDP 没有存储这个信息)。
- TCP:不需要手动指定,内核自动和客户端建立连接(TCP 三次握手)。对于应用程序来说,客户端发起'建立连接',服务器端通过
ServerSocket获取内核中已建立的连接。
ServerSocket 负责绑定端口号,并通过 accept() 方法将建立好的连接拿过来。如果瞬间有很多连接,类似于生产者消费者模型中的阻塞等待。
ServerSocket 与 Socket API 说明
- ServerSocket:给服务器使用的类,用来绑定端口号,负责把系统内核里面已经建立好的连接从队列里面拿过来。
- Socket:既会给服务器使用,也会给客户端使用,通过 socket 对象和客户端进行交互。
- 这两个类都是用来表示 socket 文件的,抽象了网卡这样的硬件设备。
TCP 服务端实战代码演示
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class TcpEchoServer {
private ServerSocket serverSocket = null;
public TcpEchoServer(int serverPort) throws IOException {
// 利用系统 API 从内核中取到已经建立好的连接
serverSocket = new ServerSocket(serverPort);
}
public void start() throws IOException {
System.out.println("服务器启动");
while (true) {
// 一直不断地从内核里面拿我们已经建立好的连接
Socket clientSocket = serverSocket.accept();
// 线程池是更好的选择,避免频繁创建和销毁线程的开销
ExecutorService service = Executors.newCachedThreadPool();
service.submit(() -> possessCollection(clientSocket));
}
}
public void possessCollection(Socket clientSocket) {
try (InputStream inputStream = clientSocket.getInputStream();
OutputStream outputStream = clientSocket.getOutputStream()) {
Scanner scanner = new Scanner(inputStream);
while (true) {
if (!scanner.hasNext()) {
System.out.printf("[%s,%d] 客户端下线\n", clientSocket.getInetAddress(), clientSocket.getPort());
break;
}
String request = scanner.next();
String response = process(request);
PrintWriter printWriter = new PrintWriter(outputStream);
printWriter.println(response);
printWriter.flush();
System.out.printf("[%s,%d] req=%s resp=%s\n", clientSocket.getInetAddress(), clientSocket.getPort(), request, response);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
clientSocket.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
private String process(String request) {
return request;
}
public static void main(String[] args) throws IOException {
TcpEchoServer tcpEchoServer = new TcpEchoServer(9090);
tcpEchoServer.start();
}
}
资源管理说明
为什么 ServerSocket 对象没有进行 close 操作,而 Socket 对象需要 close?
- 文件资源泄露:一直频繁申请但是一直不释放会造成泄露。
- ServerSocket:从头到尾只创建过一次对象,生命周期伴随着进程消失,JVM 会在进程结束时回收,因此不需要特地回收。
- Socket:每个连接对应一个 Socket 对象,处理完后必须关闭以释放资源。
TCP 客户端实战代码演示
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.Scanner;
public class TcpEchoClient {
private Socket socket = null;
public TcpEchoClient(String serverIP, int serverPort) throws IOException {
socket = new Socket(serverIP, serverPort);
}
public void start() {
System.out.println("->");
Scanner scanner = new Scanner(System.in);
try (OutputStream outputStream = socket.getOutputStream();
InputStream inputStream = socket.getInputStream()) {
while (true) {
String request = scanner.next();
PrintWriter printWriter = new PrintWriter(outputStream);
printWriter.println(request);
printWriter.flush();
Scanner scannerNetwork = new Scanner(inputStream);
String response = scannerNetwork.next();
System.out.println(response);
}
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) throws IOException {
TcpEchoClient tcpEchoClient = new TcpEchoClient("127.0.0.1", 9090);
tcpEchoClient.start();
}
}
上述代码展示了如何使用 Java Socket 进行基本的 TCP 通信,包括数据的发送、接收及刷新缓存区。


