《Java 网络编程基础》9.3 多路复用、流量控制与优先级
从 linkd 一条连接上 6 个并发请求抢 65535 字节窗口出发,讲清 stream id 分配、WINDOW_UPDATE、连接级与流级窗口的区别,给出 Http2Connection 的写数据、窗口回收与轮转调度骨架,并说明 RFC 9113 弃用优先树后的取舍。
tag
从 linkd 一条连接上 6 个并发请求抢 65535 字节窗口出发,讲清 stream id 分配、WINDOW_UPDATE、连接级与流级窗口的区别,给出 Http2Connection 的写数据、窗口回收与轮转调度骨架,并说明 RFC 9113 弃用优先树后的取舍。
手写 HPACK 的最小可用实现:静态表、动态表、整数编码与 Huffman 解码,用 RFC 7541 的 C.4.1、C.6.1 测试向量逐字节验证解码结果,并说明编码端不支持 Huffman 时如何通过 H=0 字面量合法降级、解码端为何仍必须支持。
从 linkd 一条连接只能串行处理请求的瓶颈出发,拆开 HTTP/2 的 9 字节帧头,逐字节解析 SETTINGS、HEADERS、DATA、RST_STREAM 与 GOAWAY,给出 Http2Frame、Http2FrameCodec 的编解码实现,并用 preface 握手把帧接进 9090 的读循环。
路由决定一个 HttpRequest 最终由谁处理。本节实现 HttpRouter 与 HttpRouterImpl,注册 /healthz 与 /books/{id},区分 404、405 与 500,并在静态文件服务里做百分号解码、路径规范化与根目录前缀校验,挡住 ../、%2e%2e、符号链接与编码斜杠。
HTTP/1.1 的连接复用依赖消息边界。本节实现 ChunkedCodec 的 chunk 解码与响应编码,用 KeepAliveHandler 处理 Connection: keep-alive/close、HTTP/1.0 兼容、分块请求与按序回写,并在 loopback 的真实 SocketChannel 上验证两个请求的响应顺序。
HTTP/1.1 的第一层是字节到消息的边界。本节实现 HttpRequest、HttpResponse 与 HttpParser,解析请求行和头部,给请求行、头部总字节与消息体设上限,用 400、413、431、414 区分格式错误与超限,并用分片读取、管线化输入验证解析器的增量行为。
连接卡在 CLOSE_WAIT、空闲连接占着内存,靠 if-else 堆状态迟早漏分支。本节用 linkd 的 ConnectionStateMachine 枚举 CONNECTED、READING、CLOSING、CLOSED 四种状态,配合 lastReadNanos 与 IdleTimeoutHandler 定时扫描,给出半关闭与资源释放顺序。
一次 read 可能只拿到半个帧,一次 write 也可能只写出一部分。本节用 linkd 的 AccumulatingReader 累积字节、compact 保留残余、按帧上限扩容,并用 WriteQueue 暂存未写完的 ByteBuffer,给出半包、粘包与背压的可编译实现和实测输出。
非阻塞通道上的 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 总预算与「超时不是重试」的边界。