《Java 网络编程基础》13.3 内存映射与文件传输
MappedByteBuffer 的映射、刷盘与取消映射各有边界。本节用 MapProbe 验证只读 force 为空操作、通道关闭不解除映射与 Arena 关闭后的 IllegalStateException,再用 MappedFileServer 对比 mmap 与 transferTo,给出静态文件发送的选择依据与 linkd 落地方式。
tag
MappedByteBuffer 的映射、刷盘与取消映射各有边界。本节用 MapProbe 验证只读 force 为空操作、通道关闭不解除映射与 Arena 关闭后的 IllegalStateException,再用 MappedFileServer 对比 mmap 与 transferTo,给出静态文件发送的选择依据与 linkd 落地方式。
DirectByteBuffer 的分配与回收不在一条时间线上。本节拆开 Cleaner、MaxDirectMemorySize 与 NMT 三本账,用 DirectMemoryBudget 复现预算拒绝、切片共享、可达泄漏与不可达回收,并给出 linkd 中 BufferPool 的所有权规则与泄漏判定顺序。
零拷贝不是一个系统调用的别名。本节用 8 MiB 文件在 loopback 上拆开 JDK 25 的 transferTo 三级分派,解释返回值 0 的三种来源,并用包裹通道与 JFR 证明:一次返回 8 MiB 也可能只是 1024 次 8 KB 拷贝,macOS 上不要照搬 Linux sendfile/epoll 的结论。
本节处理跨线程的请求上下文:ThreadLocal 在线程池中的残留与泄漏、ScopedValue 在 JDK 25 的绑定与恢复、事件循环到业务线程的显式传播,以及不可变消息与 happens-before 如何建立可见性;给出可运行的 RequestContext 与本机输出。
本节给 linkd 补业务线程池:用有界队列和命名线程隔离阻塞任务,按 CPU 与等待比估算线程数,解释 CallerRunsPolicy 为何会把阻塞带回事件循环,并给出指标与拒绝路径;最后用 JDK 25 虚拟线程做同负载对照,附可复现命令与本机单次数据。
本节拆开 linkd 事件循环的调度层:任务队列为什么必须是多生产者单消费者、execute 的线程归属与重入边界、selector.wakeup 的合并条件、定时任务的最小堆与 select 超时算法,以及一次 500 毫秒阻塞如何让心跳延迟到任务结束;给出可运行的 EventLoopImpl 与本机输出。
在帧编解码之上补齐连接生命周期:用 ping/pong 检测半开连接,按 1000、1001、1002 等关闭码完成关闭握手,并用 ConcurrentHashMap 会话表与每会话有界发送队列实现线程安全广播,给出 WebSocketSessionImpl 与 WebSocketHub。
从 101 之后的第一段字节开始,拆开 WebSocket 的两字节帧头:FIN、RSV、opcode、MASK 与 7/16/64 位长度编码,讲清客户端掩码的 XOR 算法和控制帧的 125 字节上限,并用 WebSocketFrameCodec 处理半帧、continuation 分片与 UTF-8 校验。
从 linkd-ws 的 /ws/echo 升级请求出发,逐条校验 Upgrade、Connection、Sec-WebSocket-Version 与 Sec-WebSocket-Key,手写 SHA-1 加 GUID 再 Base64 的 Accept 计算,讲清 101 响应写完后协议切换点在哪里,并给出 WebSocketHandshake 的完整实现。
完整握手之后,linkd 还要决定复用哪条会话、用 h2 还是 http/1.1、以及证书到底属于谁。本节讲 TLS 1.3 session ticket 的复用口径、ALPN 协商与主机名校验,给出 TlsSessionPolicy,并用 PKIXParameters 校验证书链。
阻塞式 SSLSocket 会把握手绑在一条线程上,linkd 的 EventLoop 不能这样等。本节把 SSLEngine 封装成 NonBlockingTlsHandshake:逐轮驱动 wrap/unwrap,处理 BUFFER_OVERFLOW、BUFFER_UNDERFLOW 与 NEED_TASK,最后用 TlsHandshakeHandler 接进 6.2 的事件循环。
从 linkd 明文代理必须加密的约束出发,拆解 TLS 1.3 的 1-RTT 握手、ClientHello/ServerHello 与证书链;用 keytool 生成带 SAN 的 PKCS12 自签证书,再用 TlsHandshakeInspector 打印协议、套件、证书链与 SAN,并给出证书不匹配的排查顺序。
从 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,给出半包、粘包与背压的可编译实现和实测输出。