本节摘要:Channel 是一条连接在车间里的身份载体,EventLoop 是它终身专属的工人。本节讲清两者绑定与再绑定的规则、Channel 的核心能力面(读写、配置、属性),并用可运行代码验证"同一连接永远同一线程"这条铁律。
第一章里"传送带"是个比喻,落到代码里它就是 Channel 接口。一个 NioSocketChannel 对象封装了一条 TCP 连接的一切:底层的 java socket 通道、它的配置(收发缓冲区、TCP 参数)、它的专属工序表(pipeline)、它的专属工人(eventLoop)、以及挂在它身上的属性袋(attr)。
按能力拆开看,Channel 面向开发者的面大致是四块:
| 能力 | 主要入口 | 车间类比 |
|---|---|---|
| 网络读写 | write、flush、read | 发货口、取货口 |
| 连接管理 | connect、bind、close、closeFuture | 开线、停线 |
| 配置查询 | config 方法族 | 工位参数表 |
| 附属状态 | attr、isActive、isOpen、id | 工牌与铭牌 |
常见的实现类对应不同"材质的托盘":NioSocketChannel、NioServerSocketChannel、EpollSocketChannel、EmbeddedChannel(本地测试用的"桌面模拟流水线",写单测不用真起网络)。选材质就是第二章讲的选传送带,接口行为一致。
第二章留了个结论:一条连接从注册进 worker 组开始,读写永远由同一个 EventLoop 线程串行处理。这里把规则说完整:
下面这段代码可以把这条铁律亲眼验证一遍:
public class ThreadRuleHandler extends ChannelInboundHandlerAdapter { @Override public void channelActive(ChannelHandlerContext ctx) { System.out.printf("连接建立 回调线程=%s eventLoop=%s 是否一致=%b%n", Thread.currentThread().getName(), ctx.channel().eventLoop().toString(), ctx.channel().eventLoop().inEventLoop()); } @Override public void channelRead(ChannelHandlerContext ctx, Object msg) { System.out.printf("读到数据 回调线程=%s%n", Thread.currentThread().getName()); ctx.fireChannelRead(msg); } } // 用十个客户端连接打同一个服务端,输出里 eventLoop 只会在有限的几个名字间轮换, // 且每个连接的 channelActive 与 channelRead 线程名始终一致。
跑一遍你会发现输出里线程名形如 nioEventLoopGroup-3-1 到 -3-8(视核数而定),每个连接的多次回调永远是同一个名字。这就是"免锁串行"的实物证据。

写与刷是两件事。 write(msg) 只是把消息放进出站缓冲区暂存,flush() 才真正推给内核发送。writeAndFlush 是合并操作。高吞吐场景下攒一批再 flush 能显著减少系统调用,这是第七章调优的常规手段。
关闭要等确认。 channel.close() 立即返回,但真正落盘与挥手在异步进行;要"等它彻底关了"需 closeFuture().sync(),第一章的服务端主循环就是这么守住车间的。
属性袋跨工位传状态。 Channel attr 相当于给每条连接挂一个类型安全的储物柜,登录态、会话 ID 这类"随连接走"的状态放这里,比静态 Map 干净(不会被别的线程摸到)。
// 属性袋用法:给连接挂登录态 AttributeKey<String> SESSION = AttributeKey.valueOf("session"); ch.attr(SESSION).set("user-9527"); // 登录成功时 String user = ch.attr(SESSION).get(); // 任意工位随时取
注册与再注册。 正常流程 Channel 只注册一次;但 Netty 允许 deregister 后再 register 到别的 EventLoop——一些网关做连接迁移( draining worker)时会用,属于高级操作,初学阶段知道存在即可。
⚠️ 常见坑:在非 EventLoop 线程直接调
channel.writeAndFlush是允许的(Netty 会把操作投递到该 Channel 的 EventLoop 执行),但拿到底层 socket 直接操作不走这套保护,会破坏线程归属,务必始终通过 Channel 的 API。
💡 关键直觉:把 Channel 理解为"身份证 + 工牌":证上写着它属于哪个工人(eventLoop)、走哪条工序表(pipeline)、口袋里装着什么(attr)。所有跨工位的协作都以它为中转。
inEventLoop(),铁律肉眼可见。