个人网站

面试官问"说说你对网络编程的理解",很多人只能说出"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。

加分回答
  1. Netty 的线程模型:Netty 采用主从 Reactor 多线程模型——boss group(主 Reactor)负责 accept 连接,worker group(从 Reactor)负责 IO 读写。每个 EventLoop 是一个 Reactor,内部是一个 Selector + 一个任务队列。这种模型比直接用 NIO 编程简单得多,性能也更好
  2. 半包和粘包问题:TCP 是字节流协议,不保证消息边界——发送"hello"和"world"可能被接收为"helloworld"(粘包)或"hel"+“loworld”(半包)。解决方案:固定长度、分隔符、长度字段(最常用)。Netty 提供了 LengthFieldBasedFrameDecoder 开箱即用
  3. 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 层面,还延伸到了协议和框架层面。

原文阅读


内容有帮助?点赞、收藏、关注三连!评论区等你 💪

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐