Netty源码分析(六)--关于ChannelPipeline

前言

本章将分析Netty中的重要对象ChannelPipeline,探索其生命周期中的一些重难点。

ChannelPipeline添加ChannelHandler

添加handler的核心方法如下,主要分为四个步骤

1.检查是否有重复的Handler。

2.创建节点。

3.添加节点。

4.回调用户方法。

scss 复制代码
@Override
public final ChannelPipeline addLast(EventExecutorGroup group, String name, ChannelHandler handler) {
    final AbstractChannelHandlerContext newCtx;
    synchronized (this) {
        checkMultiplicity(handler);
        newCtx = newContext(group, filterName(name, handler), handler);
        addLast0(newCtx);
        if (!registered) {
            newCtx.setAddPending();
            callHandlerCallbackLater(newCtx, true);
            return this;
        }
        EventExecutor executor = newCtx.executor();
        if (!executor.inEventLoop()) {
            newCtx.setAddPending();
            executor.execute(new Runnable() {
                @Override
                public void run() {
                    callHandlerAdded0(newCtx);
                }
            });
            return this;
        }
    }
    callHandlerAdded0(newCtx);
    return this;
}
  • 检查是否有重复的Handler
java 复制代码
private static void checkMultiplicity(ChannelHandler handler) {
   if (handler instanceof ChannelHandlerAdapter) {
       ChannelHandlerAdapter h = (ChannelHandlerAdapter) handler;
       if (!h.isSharable() && h.added) {
           throw new ChannelPipelineException(
                   h.getClass().getName() +
                   " is not a @Sharable handler, so can't be added or removed multiple times.");
       }
       h.added = true;
   }
}

handler如果集成了ChannelHandlerAdapter,那么会通过ChannelHandlerAdapter内部的成员变量added判断是否重复添加,如果重复添加且没有使用@Sharable标记为可重复添加,那么就会抛出异常。

✅每个连接独立一个 handler 实例

这里可以看到,每一个channel的MyHandler实例都是new出来的,这种方式下的添加的handler不是会重复的。

java 复制代码
// 添加handler
ServerBootstrap b = new ServerBootstrap();
b.childHandler(new ChannelInitializer<SocketChannel>() {
    @Override
    protected void initChannel(SocketChannel ch) {
        ch.pipeline().addLast(new MyHandler()); // 每个连接都 new 一次
    }
});

⚠️ 同一个 handler 被多个 Channel 共享 这种场景下,所有channel的pipeline共用同一个MyHandler对象

java 复制代码
MyHandler shared = new MyHandler();
b.childHandler(new ChannelInitializer<SocketChannel>() {
    @Override
    protected void initChannel(SocketChannel ch) {
        ch.pipeline().addLast(shared); // 重复使用同一个实例
    }
});

值得注意的是,大部分场景下的Handler是有状态的,开发者需要根据场景选择合适的方式。

关于Unsafe

我们在前面的系列文章中提到过unsafe这个类,这个类是netty内部用来实际处理IO事件的类。Unsafe家族的继承关系如下

1.NioUnsafe增加了可以访问底层JDK的SelectableChannel的功能,定义了从SelectableChannel读取数据的read方法。

2.AbstractUnsafe实现了大部分Unsafe的功能。

3.AbstractNioUnsafe主要是通过代理到其外部类AbstractNioChannel获得了与JDK NIO相关的一些信息,比如SelectableChannel、SelectionKey等。

4.把NioSocketChannelUnsafe和NioByteUnsafe放到一起讲,实现了IO的基本操作------读和写,这些操作都与JDK底层相关。

5.NioMessageUnsafe和NioByteUnsafe是处在同一层次的抽象,Netty将一个新连接的建立也当作一个IO操作来处理,这里Message的含义我们可以当作一个SelectableChannel,读的意思就是接收一个SelectableChannel。


Unsafe 的分类

从 Netty 的继承结构来看,Unsafe 可以分成两大类:

一种负责处理新连接的接入 ,另一种负责处理已建立连接的字节读写。它们分别是:

  • NioMessageUnsafe(处理 accept)
  • NioByteUnsafe(处理 read/write)

下面我们把它们的职责和来源讲清楚。


1. NioMessageUnsafe

NioMessageUnsafeAbstractNioMessageChannel 的内部类。

AbstractNioMessageChannel 又是 NioServerSocketChannel 的父类。

这意味着:

服务端 ServerSocketChannel 使用的 Unsafe,就是 NioMessageUnsafe。

在 Netty 的设计里,新连接的到达同样被当成一种"读事件",因此 accept 流程也是由 read 方法驱动。

也就是说:

NioMessageUnsafe.read() 完成了服务端新连接的整个接入过程。

如果你看过【服务端启动流程】或【客户端连接接入流程解析】,应该知道其中包含:

  • 调用 JDK 的 accept
  • 创建 NioSocketChannel
  • 配置 pipeline
  • 注册到 worker EventLoop

这里就不重复展开。


2. NioByteUnsafe

NioByteUnsafeAbstractNioByteChannel 的内部类。

AbstractNioByteChannel 是客户端 NioSocketChannel 的父类。

这说明:

客户端连接的 IO 读写,都是通过 NioByteUnsafe 来完成的。

它负责:

  • 从 socket 中读取字节
  • 写出字节到对端
  • 处理半包、空读、自动读控制等逻辑

两类 Unsafe 的分工很明确:

Unsafe 类型 适用对象 负责的事情
NioMessageUnsafe NioServerSocketChannel 接受新连接(accept)
NioByteUnsafe NioSocketChannel 读写字节流(read/write)

Unsafe 的调用路径

我们已经了解了两类 Unsafe 接口的功能------分别负责处理新连接和字节流读写。那么程序是如何执行到 Unsafe 的 read() 方法的呢?

实际上,这个调用路径与 Netty 的事件循环机制紧密相关:

NioServerSocketChannel 的 NioMessageUnsafe.read()

  • 在 NioEventLoop 的事件轮询中,当检测到新连接(ACCEPT 事件)时被触发调用
  • 对应的 NioEventLoop 轮询在 Channel 注册时启动

NioSocketChannel 的 NioByteUnsafe.read()

  • 在 NioEventLoop 的事件轮询中,当检测到读写事件(READ/WRITE 事件)时被调用
  • 对应的 NioEventLoop 轮询同样在 Channel 注册时启动

关键区别在于注册时机

  • Boss Group 的注册发生在 initAndRegister() 中,绑定的是 ServerSocketChannel
  • Worker Group 的注册发生在 ServerBootstrapAcceptor.channelRead() 方法的 childGroup.register(child) 中,绑定的是普通的 SocketChannel

这种设计确保了连接建立与数据读写在不同的事件循环中处理,实现了高效的分层架构。

小结

本章主要介绍了ChannelPipeline的一些疑难知识,包括重复handler的检查和unsafe类。下一章稍微提一下netty的writeAndFlush方法。

相关推荐
SheepHappy2 天前
MyBatis-Plus 源码阅读(三)条件构造器原理深度剖析
java·源码阅读
后端小张2 天前
【JAVA 进阶】深入探秘Netty之Reactor模型:从理论到实战
java·开发语言·网络·spring boot·spring·reactor·netty
4z333 天前
Android15 Framework(2):应用进程的孵化器 Zygote 进程解析
android·源码阅读
Chejdj3 天前
ViewModel#onCleared的实现原理
android·源码阅读
❀͜͡傀儡师5 天前
springboot集成mqtt服务,自主下发
java·spring boot·后端·mqtt·netty
心月狐的流火号6 天前
Go sync.Mutex 源码解析:设计哲学与工程智慧
go·源码阅读
onAcorner12 天前
Netty/Redis网络模型——IO多路复用原理(操作系统)
netty·nio
tanxinji12 天前
Netty编写Echo服务器
java·netty
fanly1112 天前
在抖音直播推广开源作品的可行性?
微服务·netty·.net core·microservice
4z3316 天前
Android15 Framework(1): 用户空间启动的第一个进程 Init
android·源码阅读