孤舟笔记 IO 与网络编程篇五 网络编程你真的懂吗?从Socket到TCP连接全解析
文章目录
个人网站
面试官问"说说你对网络编程的理解",很多人只能说出"Socket 编程",但追问"TCP 和 UDP 的 Socket 有什么区别"、“服务端怎么处理多连接”、“TIME_WAIT 是什么”,就答不全了。网络编程是后端开发的基础功,理解 Socket 才能理解上层框架。
今天咱们把 Java 网络编程的核心知识讲透。
一、先说结论:网络编程核心事实
| 维度 | 说明 |
|---|---|
| 核心 API | ServerSocket + Socket(TCP),DatagramSocket(UDP) |
| TCP Socket | 面向连接,可靠传输,字节流 |
| UDP Socket | 无连接,不可靠,数据报 |
| 线程模型 | 一连接一线程 → 线程池 → NIO Selector |
| 关键状态 | TIME_WAIT、CLOSE_WAIT、ESTABLISHED |
一句话记住:TCP Socket 像打电话(先拨号再通话),UDP Socket 像发短信(直接发不管对方在不在)。
二、TCP 编程:三次握手的 Socket 视角
服务端:
ServerSocket server = new ServerSocket(8080); // 1. 绑定端口
Socket client = server.accept(); // 2. 等待连接(三次握手)👈
InputStream in = client.getInputStream(); // 3. 获取输入流
OutputStream out = client.getOutputStream(); // 4. 获取输出流
客户端:
Socket socket = new Socket("127.0.0.1", 8080); // 1. 发起连接(三次握手)👈
OutputStream out = socket.getOutputStream(); // 2. 获取输出流
InputStream in = socket.getInputStream(); // 3. 获取输入流
Socket 与三次握手的对应:
客户端 服务端
| |
|──── SYN ────────────────────→| server.accept() 等待
| |
|←─── SYN+ACK ────────────────| 三次握手建立连接
| |
|──── ACK ────────────────────→| accept() 返回 Socket 👈
| |
|──── 数据 ──────────────────→| 读写数据
|←─── 数据 ───────────────────|
accept() 返回的 Socket 和客户端的 Socket 是一对——它们代表同一个 TCP 连接的两端。
三、UDP 编程:无连接的数据报
// 服务端
DatagramSocket server = new DatagramSocket(9090);
byte[] buf = new byte[1024];
DatagramPacket packet = new DatagramPacket(buf, buf.length);
server.receive(packet); // 接收数据报 👈
// 客户端
DatagramSocket client = new DatagramSocket();
byte[] data = "hello".getBytes();
InetAddress addr = InetAddress.getByName("127.0.0.1");
DatagramPacket packet = new DatagramPacket(data, data.length, addr, 9090);
client.send(packet); // 发送数据报 👈
TCP vs UDP 对比:
| 维度 | TCP | UDP |
|---|---|---|
| 连接 | 面向连接(三次握手) | 无连接 |
| 可靠性 | 可靠(确认+重传) | 不可靠 |
| 传输方式 | 字节流 | 数据报 |
| 顺序 | 保证有序 | 不保证 |
| 速度 | 较慢 | 较快 |
| 适用场景 | 文件传输、HTTP | 视频直播、DNS |
四、服务端线程模型演进
模型一:一连接一线程(最原始)
while (true) {
Socket client = server.accept();
new Thread(() -> handle(client)).start(); // 每个连接一个线程 👈
}
// 问题:1000 连接 = 1000 线程,OOM!
模型二:线程池(改进)
ExecutorService pool = Executors.newFixedThreadPool(200);
while (true) {
Socket client = server.accept();
pool.submit(() -> handle(client)); // 线程池复用 👈
}
// 改进:限制了线程数量
// 问题:200 个线程都在阻塞等待,浪费资源
模型三:NIO + Selector(最优)
Selector selector = Selector.open();
ServerSocketChannel ssc = ServerSocketChannel.open();
ssc.configureBlocking(false);
ssc.register(selector, SelectionKey.OP_ACCEPT);
while (true) {
selector.select(); // 等待事件 👈
for (SelectionKey key : selector.selectedKeys()) {
if (key.isAcceptable()) {
// 接受新连接
} else if (key.isReadable()) {
// 读取数据
}
}
}
// 1~4 个线程处理成千上万个连接!
五、TCP 连接的状态流转
TCP 连接从建立到关闭的完整状态:
客户端 服务端
CLOSED CLOSED
| |
| SYN_SENT ──── SYN ────→ LISTEN |
| |
| ESTABLISHED ← SYN+ACK ─ SYN_RCVD |
| |
| ──── ACK ────→ ESTABLISHED |
| |
| ──── FIN ────→ CLOSE_WAIT |
| FIN_WAIT_1 |
| |
| FIN_WAIT_2 ←──── ACK ──── |
| |
| TIME_WAIT ←──── FIN ──── LAST_ACK |
| |
| ──── ACK ────→ CLOSED |
| TIME_WAIT(等 2MSL) |
| CLOSED |
两个关键状态:
| 状态 | 出现在 | 原因 | 解决 |
|---|---|---|---|
| TIME_WAIT | 主动关闭方 | 等待 2MSL 确保对方收到最后的 ACK | SO_REUSEADDR |
| CLOSE_WAIT | 被动关闭方 | 应用层没有调用 close() | 检查代码是否关闭 Socket |
大量 TIME_WAIT: 主动关闭连接的一方会进入 TIME_WAIT,等待 2MSL(约 60 秒)。高并发短连接场景下可能导致端口耗尽。
大量 CLOSE_WAIT: 说明应用层 Bug——对方关闭了连接,但你没有调用 close()。
六、Socket 常用参数
// 地址复用——TIME_WAIT 状态下也能绑定同一端口
server.setReuseAddress(true); // 👈 解决 TIME_WAIT 端口占用
// 关闭 Nagle 算法——小数据立即发送
socket.setTcpNoDelay(true); // 👈 适合实时性要求高的场景
// Keep-Alive——检测死连接
socket.setKeepAlive(true);
// 超时设置
socket.setSoTimeout(5000); // 读超时 5 秒
server.setSoTimeout(3000); // accept 超时 3 秒
// 发送/接收缓冲区大小
socket.setSendBufferSize(64 * 1024);
socket.setReceiveBufferSize(64 * 1024);
网络编程 全景
网络编程 全景
核心 API
├── TCP ── ServerSocket + Socket
├── UDP ── DatagramSocket + DatagramPacket
└── NIO ── ServerSocketChannel + SocketChannel + Selector
TCP vs UDP
├── TCP ── 可靠、有序、字节流、有连接
└── UDP ── 不可靠、无序、数据报、无连接
线程模型演进
├── 一连接一线程 ── OOM
├── 线程池 ── 限制线程数但浪费资源
└── NIO Selector ── 一线程多连接
TCP 关键状态
├── TIME_WAIT ── 主动关闭方等 2MSL
├── CLOSE_WAIT ── 被动关闭方未 close
└── ESTABLISHED ── 连接建立
常用参数
├── SO_REUSEADDR ── 地址复用
├── TCP_NODELAY ── 关闭 Nagle
├── SO_KEEPALIVE ── 保活探测
└── SO_TIMEOUT ── 超时设置
口诀:TCP 可靠三次握,UDP 无连接快但丢,
线程模型三级跳,NIO Selector 是最优,
TIME_WAIT 主动等,CLOSE_WAIT 忘了关,
参数设置要合理,网络编程才稳定。
回答技巧与点评
标准回答
Java 网络编程基于 Socket API。TCP 编程使用 ServerSocket 监听 + Socket 通信,面向连接、可靠传输;UDP 编程使用 DatagramSocket 发送/接收数据报,无连接、不可靠但快速。服务端线程模型经历了从一连接一线程到线程池再到 NIO Selector 的演进,NIO 通过多路复用实现一线程处理多连接,是高并发场景的最优方案。TCP 连接关闭时,主动关闭方进入 TIME_WAIT(等 2MSL),被动关闭方如果没调用 close() 会停留在 CLOSE_WAIT。
加分回答
- Netty 的线程模型:Netty 采用主从 Reactor 多线程模型——boss group(主 Reactor)负责 accept 连接,worker group(从 Reactor)负责 IO 读写。每个 EventLoop 是一个 Reactor,内部是一个 Selector + 一个任务队列。这种模型比直接用 NIO 编程简单得多,性能也更好
- 半包和粘包问题:TCP 是字节流协议,不保证消息边界——发送"hello"和"world"可能被接收为"helloworld"(粘包)或"hel"+“loworld”(半包)。解决方案:固定长度、分隔符、长度字段(最常用)。Netty 提供了 LengthFieldBasedFrameDecoder 开箱即用
- WebSocket 和 HTTP/2:传统 HTTP 是短连接请求-响应模式,WebSocket 在 TCP 上实现全双工通信,HTTP/2 在 TCP 上实现多路复用。它们都是基于 TCP Socket 的应用层协议,但通过不同的帧格式实现了更高效的通信
面试官点评
这道题考的是你对网络编程基础的理解。能说出"ServerSocket/Socket、TCP vs UDP、线程模型演进"是基本要求,能讲清楚 TIME_WAIT 和 CLOSE_WAIT 的区别、NIO 的优势、常用 Socket 参数,才算及格。如果你能提到 Netty 的线程模型、半包粘包问题、WebSocket/HTTP2,面试官会认为你对网络编程的理解不只在 Socket API 层面,还延伸到了协议和框架层面。
内容有帮助?点赞、收藏、关注三连!评论区等你 💪
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐


所有评论(0)