java网络编程精解

 人参与 | 时间:2026-10-17 07:32:48

Socket 就是网络网络编程里的那根管子。两端各插一个,编程数据从一头倒进去,精解另一头流出来。网络Java 把这根管子抽象成了 java.net.Socket,编程用起来倒也直接——但坑全藏在细节里。精解

我见过太多人写 Java 网络代码,网络new Socket("host",编程 port) 一扔,getInputStream() 一读,精解觉得完事了。网络跑起来也没报错。编程然后线上流量一上来,精解线程全卡死,网络CPU 飙到 100%,编程日志里全是精解 SocketTimeoutException。问题出在哪?出在他们把网络当本地方法调用了。

阻塞,是第一个绕不开的坎

InputStream.read() 这个方法,没有数据可读的时候,它不会返回 -1,也不会抛异常,它就——挂着。线程状态变成 RUNNABLE,但实际在等内核把数据送到缓冲区。一个线程挂一个连接,一千个连接就是一千个线程。JVM 光线程栈就吃掉 1GB 内存,上下文切换的开销还没算。

这不是 Java 的锅。BIO 模型就这样,Accept 阻塞、Read 阻塞、Write 也可能阻塞。想解决?要么上 NIO 的 Selector,要么用 Netty 把这事包了。但很多人连 BIO 都没搞明白就直接跳 Netty,出了故障连线程 dump 都看不懂。

TCP 不会帮你划消息边界

这是新手最容易翻车的地方。客户端 write("hello"),服务端 read(buf),你以为读到的是 "hello",结果可能只读到 "he",剩下的 "llo" 在下一次 read 里。TCP 是字节流协议,它只保证字节顺序,不保证你 write 几次对方就 read 几次。

解决办法?自定义协议。要么在消息头里放 4 个字节的长度字段,要么用换行符分隔,要么定长。我见过一个项目,客户端发 JSON,服务端用 BufferedReader.readLine() 读——JSON 里只要出现换行符,消息直接截断。调试了三天才发现是协议设计的问题。

// 定长消息头的典型写法 DataInputStream in = new DataInputStream(socket.getInputStream()); int length = in.readInt(); // 先读长度 byte[] body = new byte[length]; in.readFully(body); // 再读内容

readFully 这个方法名起得好,它确实会一直读,直到把 body 填满或者抛异常。比循环 read 自己拼要省心得多。

关闭连接不是调个 close 就完事

socket.close() 之后,内核会尝试把发送缓冲区里的数据发完,然后走四次挥手。但如果你先 shutdownOutput(),对方会收到 EOF,知道你没数据要发了,但还能继续给你发。这个半关闭机制在 HTTP 协议里用得很多,但 Java 里很多人不知道。

还有 SO_LINGER 这个参数。默认情况下 close 是"优雅关闭",但如果把 setSoLinger(true, 0),close 会直接发 RST,丢弃缓冲区。有些高并发场景用这招避免 TIME_WAIT 堆积,但代价是对方可能收到 Connection reset。用不用,看你的业务能不能容忍。

超时设置,不设就是给自己埋雷

socket.connect(new InetSocketAddress("10.0.0.1", 8080), 3000)——这个 3000 毫秒的连接超时,不设的话默认是无限等待。操作系统层面的 TCP 握手超时可能是 20 秒甚至更长。你的线程就白白挂在那里。

读超时也一样。socket.setSoTimeout(5000) 之后,read() 超过 5 秒没数据就抛 SocketTimeoutException。注意,这个异常抛出后 Socket 还是可用的,你可以继续读——它只是告诉你"这段时间没数据",不是连接断了。

写超时?Java 的 Socket API 没有直接提供。你得靠 SO_SNDBUF 和操作系统的 TCP 重传机制间接控制。真要精细控制写超时,得上 NIO 或者 Netty 的 WriteTimeoutHandler。

NIO 不是银弹

很多人听说 NIO 能解决阻塞问题,兴冲冲把 BIO 代码改成 NIO,结果发现代码复杂度翻了三倍,性能没提升多少。Selector 的空轮询 bug、半包粘包要自己处理、ByteBuffer 的 flip/clear 搞不清楚——这些坑一个不少。

Netty 把这些都封装好了,但封装不等于不存在。线上出现 OutOfDirectMemoryError 的时候,你得知道 -XX:MaxDirectMemorySize 和 Netty 的 PooledByteBufAllocator 之间是什么关系。出现 TooLongFrameException 的时候,你得知道 LengthFieldBasedFrameDecoder 的 maxFrameLength 设了多少。

Java 网络编程这件事,API 看着简单,但每一个方法背后都是 TCP/IP 协议栈的行为。写代码的时候多想一步"内核这时候在干什么",能省下很多半夜起来查日志的时间。

顶: 6踩: 1248