《Java 网络编程基础》7.1 非阻塞 accept、read 与 write
非阻塞通道上的 accept、read、write 不再阻塞,返回值变成 null、0 和 -1 三种信号。本节用 linkd 的 NonBlockingEchoServer 实测这三种返回值,讲清读循环、写队列与 OP_WRITE 开关,以及漏摘 OP_WRITE 导致 CPU 空转的排查方法。
tag
非阻塞通道上的 accept、read、write 不再阻塞,返回值变成 null、0 和 -1 三种信号。本节用 linkd 的 NonBlockingEchoServer 实测这三种返回值,讲清读循环、写队列与 OP_WRITE 开关,以及漏摘 OP_WRITE 导致 CPU 空转的排查方法。
本节把单线程 Reactor 扩成 accept 线程加多个 I/O 线程的主从模型:round-robin 分配连接、跨线程 execute 提交注册任务、ConcurrentLinkedQueue 与 volatile 的可见性边界。给出与单线程版同一压测脚本的对照实验和真实耗时。
本节用 JDK 25 手写 linkd 的单线程 Reactor:BufferPool、Handler、Connection、EventLoop 四个共享类型,完整实现 accept、read、写队列与 interestOps 开关。给出可运行的 Echo 服务、真实往返输出,并说明线程归属、阻塞 handler 的代价与常见错误。
《Java 网络编程基础》6.1 讲 Selector 的 OP_ACCEPT、OP_READ、OP_WRITE 与 interestOps/readyOps 的区别。用 linkd 的真实探针演示 selectedKeys 不 remove 导致就绪位残留、CancelledKeyException、wakeup 唤醒与 macOS kqueue 空轮询防御,给出单线程事件循环的骨架。
FileChannel 能把文件字节送到 socket,但 transferTo 可能只传一部分,非阻塞 channel 还会返回 0。本节用 64 MB 文件在本机 loopback 对比 transferTo 与 64 KB 拷贝循环,讲清 Scatter/Gather、mmap、sendfile 的平台差异,并给出 linkd 静态文件发送的边界。
allocateDirect 把字节放到堆外,不受 -Xmx 管,回收却依赖 Cleaner。本节在 32 MB MaxDirectMemorySize 下复现 Cannot reserve 报错,用 BufferPoolMXBean 观察 10000 个短命缓冲区的延迟释放,并说明 linkd 的 BufferPool 何时该用堆外、何时不该用。
一次 read 之后 position 落在末尾,直接 get 什么也读不到;flip 早了丢数据,flip 两次会重放旧数据。本节用 16 字节缓冲区画出 position、limit、capacity 的每次变化,讲清 flip/clear/compact/rewind 与 slice、只读视图,并给出 linkd 读写切换的排错清单。
帧切出来只是 byte[],还不能说明协议是否正确。本节把 linkd 的 4 字节信封拆成 magic、version、type,再解析后续 payload,用 ProtocolException 区分可恢复与致命错误,给出错误帧、关闭策略和编码解码分离的 FrameEncoder,并完成每连接一份 carry 的 LinkdFrameHandler。
TCP 只保证字节有序,不保证一次 read 等于一条消息。本节实现可运行的定长、分隔符、长度域三种 FrameDecoder,用每 3 字节喂一次的实验复现半包与粘包,讲清长度字段越界、负长度、最大帧限制和返回切片被复用的坑,最后落到 linkd-core 的装配。
字节序与字符集是 linkd 协议的第一道边界。本节用 ByteBuffer 对比大端与小端的真实字节,拆开 UTF-8 和 UTF-16 对汉字、ASCII、emoji 的编码差异,用 CharsetDecoder 的 REPORT 抓住被截断的 3 字节汉字,并给出每连接独占的 Utf8StreamDecoder 与固定大端的 WireInt。
客户端发完请求只是半关闭,服务端要读到 EOF 才能回响应。本节用 linkd 的 Echo 连接实测 shutdownOutput、SO_LINGER(0) 触发的 RST、Connection reset 与 Broken pipe 的差异,并给出停止 accept、排空写队列、半关闭再 close 的优雅关闭顺序。
connect 超时、SO_TIMEOUT 与写阻塞是三件不同的事。本节用 linkd 访问 libraryd 的真实调用,实测 connect 1000ms 超时、read 超时后连接仍可继续读、发送缓冲区写满后线程卡在 SocketDispatcher.write0,并给出 Deadline 总预算与「超时不是重试」的边界。
从 linkd 的最小阻塞 Echo 入口出发,拆开 ServerSocket 的 bind、listen、accept 与 Socket 的读写阻塞点,用本机实测说明 backlog、SO_REUSEADDR 与 TIME_WAIT 重绑行为,给出可停止的 Echo 服务器、半包边界和 netstat 诊断命令。
InetAddress 解析可能返回多个地址,localhost 同时是 127.0.0.1 和 ::1。本节观察 getAllByName、IPv4/IPv6 回环、端口 0 的临时端口分配、重复绑定的 BindException 原文,以及 lo0 MTU 16384 与 en0 MTU 1500,并用 netstat/lsof 验证,最后落到 LinkdMain 的绑定日志。
UDP 一次 send 就是一条消息,但消息可能丢、可能被截断。本节用 DatagramSocket/DatagramPacket 实测数据报边界、100 字节缓冲接收 2000 字节报文时的静默截断、70000 字节报文的 Message too long,以及 4096 字节接收缓冲下 5000 个包只收到 3 个的丢包结果,并给出 linkd 指标上报的 UDP 边界。
linkd 的 HTTP 响应在 loopback 上被拆成多次 read。本节用最小程序观察三次握手在 Java 里的可见时刻、两次 write 被合并或拆开的真实输出、Nagle 与 setTcpNoDelay 的测量,以及 SO_LINGER(0) 触发 RST 和 TIME_WAIT 的 netstat 证据,最后落到 Connection 与 FrameDecoder 的边界处理。
把 linkd 拆成 core、http、ws、app 四个 Maven 模块,固定 JDK 25.0.5 与 9090/8080 端口约定;给出 JAVA_HOME 设置、父 POM、启动与 curl/lsof/netstat 验证步骤,并记录 macOS 没有 ss/epoll 时的替代诊断命令。
阻塞 Echo 与非阻塞 Selector Echo 各跑一遍,用线程栈、read 返回值和事件日志对比两种模型;再拆开 BIO、NIO、AIO 与 Reactor 的边界,说明 linkd 为什么用单线程事件循环加业务线程池,而不是给每个连接开一个线程。
一次 curl 请求打到 127.0.0.1:9090/healthz,中间经过内核监听队列、收发缓冲区、Selector 就绪事件、业务线程和响应写回。本节用 curl -v、lsof、netstat 与本机服务日志把这条路径逐层标出来,并说明 linkd 的 EventLoop、Handler 和 Connection 分别落在哪一层。
《Java 网络编程基础》全 50 篇的目录与学习路径:15 章 45 节,从请求路径、Socket、NIO Buffer 与 Selector,到手写 HTTP/1.1、HTTP/2、TLS 与 WebSocket,最后以 linkd v1 验收收口;给出按目标、按症状、按项目三条路线,以及各卷衔接、项目状态与实验口径。