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






评论专区