ChannelPipeline入站事件处理机制详解

netty源码分析之channelpipeline入站事件的传播 在之前的文章中我们看到过ChannelPipeline作为Netty中的数据管道,负责传递Channel中消息的事件传播,事件的传播分为入站和出站两个方向,分别通知ChannelInboundHandler与ChannelOutboundHandler来触发对应事件。这篇文章我们先对Netty中入站事件的传播,也就是ChannelInboundHandler进行下分析: 1、入站事件传播示例 我们... 阅读详情

ChannelPipeline 入站事件处理机制详解

一、入站事件概述

1.1 什么是入站事件?

入站事件(Inbound Event)是指从网络底层向应用层传播的事件,代表数据从外部进入到应用程序的过程。

常见的入站事件包括:

  • channelRegistered() - Channel 注册到 EventLoop
  • channelActive() - Channel 激活(连接建立)
  • channelRead() - 读取到数据
  • channelReadComplete() - 数据读取完成
  • channelInactive() - Channel 失活(连接断开)
  • channelUnregistered() - Channel 从 EventLoop 注销
  • exceptionCaught() - 捕获异常

1.2 入站事件的传播方向

入站事件从 head 节点开始,向 tail 节点传播

head -> Handler1(Inbound) -> Handler2(Inbound) -> Handler3(Inbound) -> tail

这与出站事件相反(出站事件从 tail 到 head)。


二、ChannelPipeline 的双向链表结构

2.1 Pipeline 的初始化

当创建 Channel 时,会自动创建一个 ChannelPipeline:

// AbstractChannel 构造器
protected AbstractChannel(Channel parent) {
    this.parent = parent;
    this.id = this.newId();
    this.unsafe = this.newUnsafe();
    this.pipeline = this.newChannelPipeline();  // 创建 Pipeline
}

protected DefaultChannelPipeline newChannelPipeline() {
    return new DefaultChannelPipeline(this);
}

2.2 DefaultChannelPipeline 的构造

protected DefaultChannelPipeline(Channel channel) {
    this.channel = ObjectUtil.checkNotNull(channel, "channel");
    this.succeededFuture = new SucceededChannelFuture(channel, null);
    this.voidPromise = new VoidChannelPromise(channel, true);
    
    // 创建尾节点
    this.tail = new TailContext(this);
    // 创建头节点
    this.head = new HeadContext(this);
    
    // 构建双向链表:head <-> tail
    this.head.next = this.tail;
    this.tail.prev = this.head;
}

初始状态的 Pipeline 结构:

head <-> tail

2.3 添加 Handler 后的结构

当我们通过 pipeline.addLast(handler) 添加 Handler 后:

head <-> HandlerContext1 <-> HandlerContext2 <-> HandlerContext3 <-> tail

每个 Handler 都被包装在一个 ChannelHandlerContext 中。


三、入站事件的触发机制

3.1 入站事件触发的完整流程(以 OP_ACCEPT 为例)

入站事件由 Netty 底层触发,从网络事件检测到 Pipeline 事件传播的完整流程如下:

客户端发起连接
    ↓
操作系统接收连接请求
    ↓
Selector 检测到 OP_ACCEPT 事件
    ↓
NioEventLoop.run() 循环检测
    ↓
NioEventLoop.processSelectedKeys() 处理就绪事件
    ↓
NioEventLoop.processSelectedKey() 处理单个 Channel
    ↓
NioServerSocketChannel.NioServerSocketChannelUnsafe.read() 接受连接
    ↓
pipeline.fireChannelRead(socketChannel) 触发入站事件
    ↓
事件在 Pipeline 中传播(head → tail)
步骤 1:Channel 注册到 EventLoop

在服务端启动时,ServerSocketChannel 会注册到 bossGroup 的 EventLoop:

// AbstractChannel.AbstractUnsafe
public final void register(EventLoop eventLoop, final ChannelPromise promise) {
    // ...
    AbstractChannel.this.eventLoop = eventLoop;
    
    if (eventLoop.inEventLoop()) {
        register0(promise);
    } else {
        eventLoop.execute(new Runnable() {
            @Override
            public void run() {
                register0(promise);
            }
        });
    }
}

private void register0(ChannelPromise promise) {
    // 调用 JDK 的 register 方法,注册到 Selector
    doRegister();
    
    // 触发 channelRegistered 事件
    pipeline.fireChannelRegistered();
    
    if (isActive()) {
        // 触发 channelActive 事件
        pipeline.fireChannelActive();
        
        // 开始监听 OP_ACCEPT 事件
        readIfIsAutoRead();
    }
}

关键点: 注册完成后,Channel 开始监听 OP_ACCEPT 事件。

步骤 2:NioEventLoop 无限循环检测事件

EventLoop 的 run() 方法会不断循环,检测网络事件:

// NioEventLoop.java
protected void run() {
    for (;;) {  // 无限循环
        try {
            // 1. 计算选择策略
            switch (selectStrategy.calculateStrategy(selectNowSupplier, hasTasks())) {
                case SelectStrategy.CONTINUE:
                    continue;
                case SelectStrategy.SELECT:
                    // 2. 阻塞等待事件(或定时等待)
                    select(wakenUp.getAndSet(false));
                    
                    // 如果被唤醒,唤醒 Selector
                    if (wakenUp.get()) {
                        selector.wakeup();
                    }
                default:
            }

            cancelledKeys = 0;
            needsToSelectAgain = false;
            final int ioRatio = this.ioRatio;
            
            if (ioRatio == 100) {
                try {
                    // 3. 处理所有就绪的 I/O 事件
                    processSelectedKeys();
                } finally {
                    // 4. 执行任务队列中的任务
                    runAllTasks();
                }
            } else {
                final long ioStartTime = System.nanoTime();
                try {
                    // 3. 处理所有就绪的 I/O 事件
                    processSelectedKeys();
                } finally {
                    final long ioTime = System.nanoTime() - ioStartTime;
                    // 4. 执行任务队列中的任务(按比例分配时间)
                    runAllTasks(ioTime * (100 - ioRatio) / ioRatio);
                }
            }
        } catch (Throwable t) {
            handleLoopException(t);
        }
    }
}

关键点:

  • select() 方法会阻塞等待,直到有事件就绪或超时
  • 当客户端发起连接时,Selector 会检测到 OP_ACCEPT 事件
  • processSelectedKeys() 会处理所有就绪的事件
步骤 3:处理所有就绪的 SelectionKey
// NioEventLoop.java
private void processSelectedKeys() {
    if (selectedKeys != null) {
        // 使用优化的 SelectedSelectionKeySet
        processSelectedKeysOptimized();
    } else {
        // 使用 JDK 原生的 Set
        processSelectedKeysPlain(selector.selectedKeys());
    }
}

private void processSelectedKeysOptimized() {
    for (int i = 0; i < selectedKeys.size; ++i) {
        final SelectionKey k = selectedKeys.keys[i];
        // 清空引用,帮助 GC
        selectedKeys.keys[i] = null;

        // 获取 SelectionKey 的 attachment(就是注册时附加的 Channel)
        final Object a = k.attachment();

        if (a instanceof AbstractNioChannel) {
            // 处理这个 Channel 的事件
            processSelectedKey(k, (AbstractNioChannel) a);
        } else {
            @SuppressWarnings("unchecked")
            NioTask<SelectableChannel> task = (NioTask<SelectableChannel>) a;
            processSelectedKey(k, task);
        }

        if (needsToSelectAgain) {
            // 需要重新 select
            selectedKeys.reset(i + 1);
            selectAgain();
            i = -1;
        }
    }
}

关键点:

  • SelectionKey 的 attachment 就是 NioServerSocketChannel
  • 遍历所有就绪的 SelectionKey,逐个处理
步骤 4:处理单个 Channel 的就绪事件
// NioEventLoop.java
private void processSelectedKey(SelectionKey k, AbstractNioChannel ch) {
    // 获取 Channel 的 Unsafe 对象
    final AbstractNioChannel.NioUnsafe unsafe = ch.unsafe();
    
    // 检查 key 是否有效
    if (!k.isValid()) {
        final EventLoop eventLoop;
        try {
            eventLoop = ch.eventLoop();
        } catch (Throwable ignored) {
            return;
        }
        if (eventLoop == this) {
            // key 无效,关闭 Channel
            unsafe.close(unsafe.voidPromise());
        }
        return;
    }

    try {
        // 获取就绪的操作类型
        int readyOps = k.readyOps();
        
        // 处理 OP_CONNECT 事件(客户端连接完成)
        if ((readyOps & SelectionKey.OP_CONNECT) != 0) {
            int ops = k.interestOps();
            ops &= ~SelectionKey.OP_CONNECT;
            k.interestOps(ops);
            unsafe.finishConnect();
        }

        // 处理 OP_WRITE 事件(可写)
        if ((readyOps & SelectionKey.OP_WRITE) != 0) {
            ch.unsafe().forceFlush();
        }

        // 处理 OP_READ 或 OP_ACCEPT 事件
        // 这是入站事件的关键触发点!
        if ((readyOps & (SelectionKey.OP_READ | SelectionKey.OP_ACCEPT)) != 0 || readyOps == 0) {
            // 调用 Unsafe 的 read 方法
            // 对于 ServerSocketChannel,这会接受新连接
            // 对于 SocketChannel,这会读取数据
            unsafe.read();
        }
    } catch (CancelledKeyException ignored) {
        unsafe.close(unsafe.voidPromise());
    }
}

关键点:

  • 通过位运算检查就绪的操作类型
  • 当检测到 OP_ACCEPT 时,调用 unsafe.read()
  • 这里的 unsafe 是 NioServerSocketChannel.NioServerSocketChannelUnsafe
步骤 5:NioServerSocketChannelUnsafe.read() 接受新连接
// NioServerSocketChannel.NioServerSocketChannelUnsafe
public void read() {
    assert eventLoop().inEventLoop();
    
    final ChannelConfig config = config();
    final ChannelPipeline pipeline = pipeline();
    final RecvByteBufAllocator.Handle allocHandle = unsafe().recvBufAllocHandle();
    allocHandle.reset(config);

    boolean closed = false;
    Throwable exception = null;
    try {
        try {
            do {
                // 接受新连接,返回接受的连接数
                // doReadMessages 会调用 ServerSocketChannel.accept()
                // 并将新的 SocketChannel 添加到 readBuf 中
                int localRead = doReadMessages(readBuf);
                
                if (localRead == 0) {
                    // 没有更多连接
                    break;
                }
                if (localRead < 0) {
                    // 连接关闭
                    closed = true;
                    break;
                }

                // 增加已读消息计数
                allocHandle.incMessagesRead(localRead);
            } while (allocHandle.continueReading());
        } catch (Throwable t) {
            exception = t;
        }

        int size = readBuf.size();
        for (int i = 0; i < size; i ++) {
            readPending = false;
            
            // 关键:触发 channelRead 事件!
            // readBuf.get(i) 是新接受的 NioSocketChannel
            pipeline.fireChannelRead(readBuf.get(i));
        }
        
        readBuf.clear();
        allocHandle.readComplete();
        
        // 触发 channelReadComplete 事件
        pipeline.fireChannelReadComplete();

        if (exception != null) {
            closed = closeOnReadError(exception);
            // 触发 exceptionCaught 事件
            pipeline.fireExceptionCaught(exception);
        }

        if (closed) {
            inputShutdown = true;
            if (isOpen()) {
                close(voidPromise());
            }
        }
    } finally {
        if (!readPending && !config.isAutoRead()) {
            removeReadOp();
        }
    }
}

关键点:

  • doReadMessages() 调用 JDK 的 ServerSocketChannel.accept() 接受新连接
  • 每接受一个新连接,就创建一个 NioSocketChannel 对象
  • 调用 pipeline.fireChannelRead(socketChannel) 触发入站事件
  • 注意:这里传递的是 SocketChannel 对象,不是 ByteBuf!
步骤 6:doReadMessages() 接受连接的底层实现
// NioServerSocketChannel.java
protected int doReadMessages(List<Object> buf) throws Exception {
    // 调用 JDK 的 accept 方法接受新连接
    SocketChannel ch = SocketUtils.accept(javaChannel());

    try {
        if (ch != null) {
            // 将新连接包装成 NioSocketChannel,添加到 buf 中
            buf.add(new NioSocketChannel(this, ch));
            return 1;  // 返回接受的连接数
        }
    } catch (Throwable t) {
        logger.warn("Failed to create a new channel from an accepted socket.", t);
        try {
            ch.close();
        } catch (Throwable t2) {
            logger.warn("Failed to close a socket.", t2);
        }
    }

    return 0;  // 没有接受到连接
}

关键点:

  • 调用 JDK 原生的 ServerSocketChannel.accept() 方法
  • 将返回的 SocketChannel 包装成 Netty 的 NioSocketChannel
  • 返回接受的连接数(1 或 0)
步骤 7:pipeline.fireChannelRead() 触发入站事件
// DefaultChannelPipeline.java
public final ChannelPipeline fireChannelRead(Object msg) {
    // 从 head 节点开始传播
    AbstractChannelHandlerContext.invokeChannelRead(head, msg);
    return this;
}

关键点:

  • 入站事件从 head 节点开始传播
  • msg 参数是新接受的 NioSocketChannel 对象

3.2 完整的调用链路图

┌─────────────────────────────────────────────────────────────┐
│ 1. 客户端发起连接                                             │
│    Socket socket = new Socket("localhost", 8080);           │
└─────────────────────────────────────────────────────────────┘
                            ↓
┌─────────────────────────────────────────────────────────────┐
│ 2. 操作系统接收连接请求                                        │
│    TCP 三次握手完成,连接进入 accept 队列                      │
└─────────────────────────────────────────────────────────────┘
                            ↓
┌─────────────────────────────────────────────────────────────┐
│ 3. Selector 检测到 OP_ACCEPT 事件                            │
│    selector.select() 返回,selectedKeys 包含就绪的 Channel    │
└─────────────────────────────────────────────────────────────┘
                            ↓
┌─────────────────────────────────────────────────────────────┐
│ 4. NioEventLoop.run()                                        │
│    for (;;) {                                                │
│        select(wakenUp.getAndSet(false));  // 阻塞等待事件     │
│        processSelectedKeys();              // 处理就绪事件     │
│        runAllTasks();                      // 执行任务队列     │
│    }                                                         │
└─────────────────────────────────────────────────────────────┘
                            ↓
┌─────────────────────────────────────────────────────────────┐
│ 5. NioEventLoop.processSelectedKeys()                        │
│    遍历所有就绪的 SelectionKey                                │
│    for (SelectionKey k : selectedKeys) {                     │
│        processSelectedKey(k, channel);                       │
│    }                                                         │
└─────────────────────────────────────────────────────────────┘
                            ↓
┌─────────────────────────────────────────────────────────────┐
│ 6. NioEventLoop.processSelectedKey(k, serverSocketChannel)   │
│    int readyOps = k.readyOps();                              │
│    if ((readyOps & OP_ACCEPT) != 0) {                        │
│        unsafe.read();  // 调用 Unsafe 的 read 方法           │
│    }                                                         │
└─────────────────────────────────────────────────────────────┘
                            ↓
┌─────────────────────────────────────────────────────────────┐
│ 7. NioServerSocketChannelUnsafe.read()                       │
│    do {                                                      │
│        int n = doReadMessages(readBuf);  // 接受新连接        │
│    } while (allocHandle.continueReading());                  │
│                                                              │
│    for (Object msg : readBuf) {                              │
│        pipeline.fireChannelRead(msg);  // 触发入站事件        │
│    }                                                         │
└─────────────────────────────────────────────────────────────┘
                            ↓
┌─────────────────────────────────────────────────────────────┐
│ 8. NioServerSocketChannel.doReadMessages(readBuf)            │
│    SocketChannel ch = serverSocketChannel.accept();          │
│    NioSocketChannel nioSocketChannel = new NioSocketChannel(this, ch); │
│    readBuf.add(nioSocketChannel);                            │
│    return 1;  // 返回接受的连接数                             │
└─────────────────────────────────────────────────────────────┘
                            ↓
┌─────────────────────────────────────────────────────────────┐
│ 9. DefaultChannelPipeline.fireChannelRead(socketChannel)     │
│    AbstractChannelHandlerContext.invokeChannelRead(head, msg);│
└─────────────────────────────────────────────────────────────┘
                            ↓
┌─────────────────────────────────────────────────────────────┐
│ 10. 事件在 Pipeline 中传播                                    │
│     head -> ServerBootstrapAcceptor -> tail                  │
│                                                              │
│     ServerBootstrapAcceptor 会将新连接注册到 workerGroup      │
└─────────────────────────────────────────────────────────────┘

3.3 关键代码位置总结

步骤类方法作用
1NioEventLooprun()事件循环,不断检测事件
2NioEventLoopselect()阻塞等待事件就绪
3NioEventLoopprocessSelectedKeys()处理所有就绪的事件
4NioEventLoopprocessSelectedKey()处理单个 Channel 的事件
5NioServerSocketChannelUnsaferead()接受新连接
6NioServerSocketChanneldoReadMessages()调用 JDK accept()
7DefaultChannelPipelinefireChannelRead()触发入站事件
8AbstractChannelHandlerContextinvokeChannelRead()开始事件传播

3.4 HeadContext 的特殊性

HeadContext 既实现了 ChannelInboundHandler,也实现了 ChannelOutboundHandler:

final class HeadContext extends AbstractChannelHandlerContext 
        implements ChannelOutboundHandler, ChannelInboundHandler {
    
    private final Unsafe unsafe;

    HeadContext(DefaultChannelPipeline pipeline) {
        super(pipeline, null, HEAD_NAME, HeadContext.class);
        this.unsafe = pipeline.channel().unsafe();
        this.setAddComplete();
    }
    
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
        // HeadContext 的 channelRead 直接传递给下一个 Handler
        ctx.fireChannelRead(msg);
    }
    
    // ... 其他方法
}

HeadContext 作为入站事件的起点,它的职责是:

  1. 接收底层传来的事件
  2. 立即将事件传递给下一个入站 Handler

3.5 时间线示例

假设服务端监听 8080 端口,客户端发起连接:

T0: 服务端启动,ServerSocketChannel 注册到 bossGroup
    └─ 开始监听 OP_ACCEPT 事件

T1: NioEventLoop.run() 开始循环
    └─ select() 阻塞等待

T2: 客户端执行 new Socket("localhost", 8080)
    └─ 发起 TCP 连接请求

T3: TCP 三次握手完成
    └─ 连接进入操作系统的 accept 队列

T4: Selector 检测到 OP_ACCEPT 事件
    └─ select() 返回

T5: processSelectedKeys() 开始处理
    └─ 遍历 selectedKeys

T6: processSelectedKey(k, serverSocketChannel)
    └─ 检测到 readyOps & OP_ACCEPT != 0

T7: unsafe.read() 被调用
    └─ NioServerSocketChannelUnsafe.read()

T8: doReadMessages(readBuf)
    └─ serverSocketChannel.accept() 返回新的 SocketChannel

T9: new NioSocketChannel(this, ch)
    └─ 包装成 Netty 的 Channel

T10: pipeline.fireChannelRead(socketChannel)
     └─ 触发入站事件

T11: HeadContext.channelRead()
     └─ ctx.fireChannelRead(msg)

T12: ServerBootstrapAcceptor.channelRead()
     └─ 将新连接注册到 workerGroup

T13: 新连接开始监听 OP_READ 事件
     └─ 等待客户端发送数据

四、入站事件的传播过程(核心)

4.1 什么是 executionMask(执行掩码)?

在深入事件传播之前,先理解 executionMask 的概念:

executionMask 的定义

executionMask 是 ChannelHandlerContext 的一个整型字段,用位掩码的方式记录当前 Handler 实现了哪些方法。

// AbstractChannelHandlerContext.java
abstract class AbstractChannelHandlerContext implements ChannelHandlerContext {
    // 执行掩码:标识这个 Handler 实现了
步骤 2:调用静态方法 invokeChannelRead
// AbstractChannelHandlerContext.java
static void invokeChannelRead(final AbstractChannelHandlerContext next, Object msg) {
    final Object m = next.pipeline.touch(ObjectUtil.checkNotNull(msg, "msg"), next);
    EventExecutor executor = next.executor();
    
    // 判断是否在 EventLoop 线程中
    if (executor.inEventLoop()) {
        // 在 EventLoop 线程中,直接调用
        next.invokeChannelRead(m);
    } else {
        // 不在 EventLoop 线程中,提交任务到 EventLoop
        executor.execute(new Runnable() {
            @Override
            public void run() {
                next.invokeChannelRead(m);
            }
        });
    }
}

关键点:确保事件处理在正确的线程中执行

步骤 3:调用实例方法 invokeChannelRead
// AbstractChannelHandlerContext.java
private void invokeChannelRead(Object msg) {
    if (invokeHandler()) {
        try {
            // 调用当前 Handler 的 channelRead 方法
            ((ChannelInboundHandler) handler()).channelRead(this, msg);
        } catch (Throwable t) {
            invokeExceptionCaught(t);
        }
    } else {
        // Handler 还未添加完成,继续传播
        fireChannelRead(msg);
    }
}

关键点:这里调用了实际的 Handler 的 channelRead 方法

步骤 4:用户 Handler 处理并传播
// 用户自定义的 Handler
public class MyInboundHandler extends ChannelInboundHandlerAdapter {
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
        // 处理业务逻辑
        System.out.println("收到消息: " + msg);
        
        // 继续传播给下一个 Handler
        ctx.fireChannelRead(msg);
    }
}
步骤 5:查找下一个入站 Handler
// AbstractChannelHandlerContext.java
public ChannelHandlerContext fireChannelRead(final Object msg) {
    // 查找下一个入站 Handler
    invokeChannelRead(findContextInbound(MASK_CHANNEL_READ), msg);
    return this;
}

这是最关键的部分! findContextInbound() 方法负责查找下一个入站 Handler。

4.2 查找下一个入站 Handler 的核心逻辑

// AbstractChannelHandlerContext.java
private AbstractChannelHandlerContext findContextInbound(int mask) {
    AbstractChannelHandlerContext ctx = this;
    EventExecutor currentExecutor = executor();
    
    do {
        // 沿着链表向后查找
        ctx = ctx.next;
    } while (skipContext(ctx, currentExecutor, mask, MASK_ONLY_INBOUND));
    
    return ctx;
}

关键方法:skipContext

private static boolean skipContext(
        AbstractChannelHandlerContext ctx,
        EventExecutor currentExecutor,
        int mask,
        int onlyMask) {
    
    // 跳过的条件:
    // 1. Handler 的执行掩码不匹配(不是我们要找的事件类型)
    // 2. Handler 不是入站类型(onlyMask 检查)
    return (ctx.executionMask & (onlyMask | mask)) == 0 ||
           (ctx.executor() == currentExecutor && (ctx.executionMask & mask) == 0);
}
执行掩码(Execution Mask)机制

Netty 使用位掩码来标识 Handler 实现了哪些方法:

// ChannelHandlerMask.java
static final int MASK_EXCEPTION_CAUGHT = 1;
static final int MASK_CHANNEL_REGISTERED = 1 << 1;
static final int MASK_CHANNEL_UNREGISTERED = 1 << 2;
static final int MASK_CHANNEL_ACTIVE = 1 << 3;
static final int MASK_CHANNEL_INACTIVE = 1 << 4;
static final int MASK_CHANNEL_READ = 1 << 5;
static final int MASK_CHANNEL_READ_COMPLETE = 1 << 6;
// ... 更多掩码

static final int MASK_ONLY_INBOUND = 
    MASK_CHANNEL_REGISTERED |
    MASK_CHANNEL_UNREGISTERED |
    MASK_CHANNEL_ACTIVE |
    MASK_CHANNEL_INACTIVE |
    MASK_CHANNEL_READ |
    MASK_CHANNEL_READ_COMPLETE |
    MASK_USER_EVENT_TRIGGERED |
    MASK_CHANNEL_WRITABILITY_CHANGED;

工作原理:

  1. 当 Handler 被添加到 Pipeline 时,Netty 会扫描 Handler 类,检查它实现了哪些方法
  2. 为每个实现的方法设置对应的位标志
  3. 在事件传播时,通过位运算快速判断 Handler 是否需要处理该事件

示例:

public class MyHandler extends ChannelInboundHandlerAdapter {
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
        // 实现了 channelRead
    }
    
    // 没有实现 channelActive
}

这个 Handler 的执行掩码会包含 MASK_CHANNEL_READ,但不包含 MASK_CHANNEL_ACTIVE。

当 channelActive 事件传播时,findContextInbound() 会跳过这个 Handler,因为:

(ctx.executionMask & MASK_CHANNEL_ACTIVE) == 0  // 返回 true,跳过

五、完整的入站事件传播示例

5.1 示例代码

// 添加多个 Handler
pipeline.addLast("handler1", new MyInboundHandler1());
pipeline.addLast("handler2", new MyInboundHandler2());
pipeline.addLast("handler3", new MyInboundHandler3());

// Handler 实现
class MyInboundHandler1 extends ChannelInboundHandlerAdapter {
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
        System.out.println("Handler1 处理消息");
        ctx.fireChannelRead(msg);  // 传播给下一个
    }
}

class MyInboundHandler2 extends ChannelInboundHandlerAdapter {
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
        System.out.println("Handler2 处理消息");
        ctx.fireChannelRead(msg);  // 传播给下一个
    }
}

class MyInboundHandler3 extends ChannelInboundHandlerAdapter {
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
        System.out.println("Handler3 处理消息");
        // 不再传播,事件在这里终止
    }
}

5.2 Pipeline 结构

head <-> handler1 <-> handler2 <-> handler3 <-> tail

5.3 事件传播流程

当底层读取到数据时:

1. pipeline.fireChannelRead(msg)
   ↓
2. head.channelRead(ctx, msg)
   ↓ ctx.fireChannelRead(msg)
3. handler1.channelRead(ctx, msg)  // 输出: Handler1 处理消息
   ↓ ctx.fireChannelRead(msg)
4. handler2.channelRead(ctx, msg)  // 输出: Handler2 处理消息
   ↓ ctx.fireChannelRead(msg)
5. handler3.channelRead(ctx, msg)  // 输出: Handler3 处理消息
   ↓ 不再传播,事件结束

5.4 如果 Handler3 继续传播

如果 Handler3 也调用了 ctx.fireChannelRead(msg):

5. handler3.channelRead(ctx, msg)
   ↓ ctx.fireChannelRead(msg)
6. tail.channelRead(ctx, msg)  // 到达 tail 节点

TailContext 的处理:

final class TailContext extends AbstractChannelHandlerContext 
        implements ChannelInboundHandler {
    
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
        // 释放消息(如果是 ByteBuf)
        onUnhandledInboundMessage(ctx, msg);
    }
    
    protected void onUnhandledInboundMessage(ChannelHandlerContext ctx, Object msg) {
        try {
            logger.debug(
                "Discarded inbound message {} that reached at the tail of the pipeline. " +
                "Please check your pipeline configuration.", msg);
        } finally {
            // 释放资源
            ReferenceCountUtil.release(msg);
        }
    }
}

重要: TailContext 会释放未处理的消息,防止内存泄漏!


六、入站事件与出站事件的对比

6.1 传播方向

事件类型起点终点传播方向
入站事件headtailhead → tail
出站事件tailheadtail → head

6.2 Handler 类型

Handler 类型处理的事件典型方法
ChannelInboundHandler入站事件channelRead, channelActive
ChannelOutboundHandler出站事件write, flush, connect

6.3 查找下一个 Handler 的方法

// 入站:向后查找(next)
private AbstractChannelHandlerContext findContextInbound(int mask) {
    AbstractChannelHandlerContext ctx = this;
    do {
        ctx = ctx.next;  // 向后
    } while (skipContext(ctx, currentExecutor, mask, MASK_ONLY_INBOUND));
    return ctx;
}

// 出站:向前查找(prev)
private AbstractChannelHandlerContext findContextOutbound(int mask) {
    AbstractChannelHandlerContext ctx = this;
    do {
        ctx = ctx.prev;  // 向前
    } while (skipContext(ctx, currentExecutor, mask, MASK_ONLY_OUTBOUND));
    return ctx;
}

七、常见问题与最佳实践

7.1 为什么我的 Handler 没有被调用?

原因 1:忘记调用 ctx.fireChannelRead(msg)

// 错误示例
public void channelRead(ChannelHandlerContext ctx, Object msg) {
    System.out.println("处理消息");
    // 忘记传播,后续 Handler 不会被调用
}

// 正确示例
public void channelRead(ChannelHandlerContext ctx, Object msg) {
    System.out.println("处理消息");
    ctx.fireChannelRead(msg);  // 必须传播
}

原因 2:Handler 类型不匹配

// 错误:使用 ChannelOutboundHandler 处理入站事件
public class MyHandler extends ChannelOutboundHandlerAdapter {
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
        // 这个方法永远不会被调用!
        // 因为 ChannelOutboundHandler 不处理入站事件
    }
}

// 正确:使用 ChannelInboundHandler
public class MyHandler extends ChannelInboundHandlerAdapter {
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
        // 正确
    }
}

7.2 内存泄漏问题

问题: 如果不传播消息,也不释放,会导致内存泄漏。

// 危险示例
public void channelRead(ChannelHandlerContext ctx, Object msg) {
    // 既不传播,也不释放
    // 内存泄漏!
}

// 正确示例 1:传播
public void channelRead(ChannelHandlerContext ctx, Object msg) {
    ctx.fireChannelRead(msg);  // 传播给下一个 Handler
}

// 正确示例 2:手动释放
public void channelRead(ChannelHandlerContext ctx, Object msg) {
    try {
        // 处理消息
    } finally {
        ReferenceCountUtil.release(msg);  // 释放资源
    }
}

7.3 ctx.fireXxx() vs ctx.channel().pipeline().fireXxx()

// 方式 1:从当前 Handler 的下一个开始传播
ctx.fireChannelRead(msg);

// 方式 2:从 head 重新开始传播
ctx.channel().pipeline().fireChannelRead(msg);

区别:

  • ctx.fireXxx() - 从当前 Handler 的下一个开始
  • pipeline().fireXxx() - 从 head 重新开始

性能建议: 优先使用 ctx.fireXxx(),避免重复处理。

7.4 异常处理

public class MyHandler extends ChannelInboundHandlerAdapter {
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
        try {
            // 处理消息
        } catch (Exception e) {
            // 触发异常事件
            ctx.fireExceptionCaught(e);
        }
    }
    
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        // 处理异常
        cause.printStackTrace();
        ctx.close();
    }
}

八、总结

8.1 入站事件处理的核心要点

  1. 传播方向:head → tail
  2. 起点:pipeline.fireXxx() 从 head 开始
  3. 查找机制:通过 findContextInbound() 查找下一个入站 Handler
  4. 执行掩码:使用位运算快速判断 Handler 是否需要处理事件
  5. 线程安全:确保事件在正确的 EventLoop 线程中执行
  6. 资源管理:必须传播或释放消息,防止内存泄漏

8.2 关键方法调用链

pipeline.fireChannelRead(msg)
  ↓
AbstractChannelHandlerContext.invokeChannelRead(head, msg)
  ↓
head.invokeChannelRead(msg)
  ↓
((ChannelInboundHandler) handler()).channelRead(this, msg)
  ↓
ctx.fireChannelRead(msg)
  ↓
findContextInbound(MASK_CHANNEL_READ)  // 查找下一个
  ↓
next.invokeChannelRead(msg)
  ↓
... 循环直到 tail 或事件被终止
LobeHub开源AI应用平台:从部署到定制的全栈实践指南 在现代AI应用开发领域,大语言模型(LLM)的集成与部署是核心技术挑战之一。其原理在于通过API或本地化部署,将预训练模型的强大能力封装为可调用的服务。这项技术的核心价值在于极大地降低了AI应用的开发门槛,使开发者能够快速构建智能对话、内容生成等场景的应用。随着模型即服务(MaaS)模式的普及,一个关键痛点是如何高效地管理多模型、设计友好的用户界面并实现功能扩展。这正是开源项目LobeHub所聚焦的领域,它通过提供可插拔的架构和配置驱动的低代码理念,解决了AI能力落地的“最后一公里”问题。本文将结合Dock 阅读详情

相关推荐

数学建模更新10(蒙特卡罗模拟)

蒙特卡罗模拟一.目录二.引例1:布丰投针实验1.投针步骤2.思路3. 历史实验者二.蒙特卡罗模拟概述1.定义2.提出3.原理4.讨论三.应用实例1.三门问题【1】问题【2】代码2.模拟排队问题【1】题目【2】引入参数【3】总结题目信息【4】过程可视化【5】一些推导公式【6】代码3.有约束的非线性规划问题【1】问题的分类【2】例子【3】推导过程【4】代码 一.目录 二.引例1:布丰投针实验 1.投针步骤 步骤: 1:取一张白纸,在上面画出许多条间距为a的平行线 2:去一根长度为l的针,随机的向纸张上投掷n次

沉晨尘宸的博客 3043

Netty 出站入站机制详解

前段时间在B站学习Netty,讲到出站入站的机制时一直没搞明白到底是怎么弄的,直到开始讲源码部分时才终于搞明白。 先来看看Netty官方有关出站入站机制的解释: 按照图片的理解,则是在通道中,每次出现读事件时,会从头至尾依次调用Inbound即入站方法处理; 而触发写事件时,则会从尾到头依次调用outbound即出站方法处理。 这里会给人一种错觉,那就是netty在内部维护了两个单向链表实现出站入站操作,而事实上,ChannelPipeline中实际上是维护了一条由ChannelHandlerCo

男儿当自强 1663

Lora无线温湿度传感器方案

综上所述,Lora无线温湿度传感器以其长距离传输、低功耗、高稳定性、精准测量、无线传输以及广泛的应用场景等特点,在环境监测领域展现出了强大的竞争力和广泛的应用前景。Lora无线温湿度传感器广泛应用于各种需要远程监控和数据采集的场景,如仓储、农业、机房、楼宇、交通、工业、矿产、电力等领域。Lora无线温湿度传感器是一种采用Lora无线通信技术进行数据传输的温湿度监测设备。四、产品参数(以某款工业级Lora温湿度传感器为例)

weixin_46632654的博客 759

10、netty中的channel、channelPipeline、channelHandler、channelHandlerContext

关系 channel —通道,一个channel中有1个channelPipeline channelPipeline—一个channelPipeline中有很多个channelHandler节点,责任链模式 channelHandler—具体的处理IO事件的处理器 channelHandlerContext—channelPipeline就是通过channelHandlerContext来管理c...

u011603018的博客 369

QFN封装芯片手工焊接实战:从焊盘处理到拖焊技巧

本文详细介绍了QFN封装芯片手工焊接的全过程,从焊盘处理到拖焊技巧,再到常见问题的现场修复方法。通过实战经验分享,帮助工程师和DIY爱好者掌握0.5mm小间距QFN芯片的焊接技术,提升焊接成功率和质量。重点讲解了垂直与平行拖焊手法的对比应用,以及焊后清理与质量检验的关键步骤。

weixin_42652674的博客 532

【Ubuntu】SMBus Host controller not enabled(虚拟机进入不了图形界面)

SMBus Host controller not enabled(虚拟机进入不了图形界面)

Cappuccino Blog 4万+

Netty源码分析之ChannelPipline

Netty源码分析之ChannelPipline

wangguodong1993的博客 281

PMF5.0计算

EPA PMF5.0 计算步骤

nuistsy的博客 4546

netty源码系列之-03_ChannelPipeline,channelhanddler,channelcontext

我读源码的思路就是先找其中一个一个的小组件看看如何设计和实现的,感觉了解的差不多了再进行串起来,今天先看看Pipeline,从使用的过程中我就一个印象双向链表,并且创建的时候 默认创建一个head和一个tail,里面存放的都是Channelcontext, ChannelConetext从名字上可以理解为上下文,里面主要包装Handler, 这些就是我读源码之前的一点点理解,接下来看看 我理解的是否有偏差,还有哪些没理解的,今天安排上…服务端accept一个新的Channel之后,需要监听这个Channel

685

MATLAB基础应用精讲-【数模应用】游程检验(附MATLAB、R语言和python代码实现)

游程检验是一种非参数性统计假设的检验方法,它是游程总数检验和最大游程检验的总称,用于两个独立样本的比较和观测结果随机性的检验测试。​游程检验亦称“连贯检验”,是根据样本标志表现排列所形成的游程的多少进行判断的检验方法。[2]例1:设某样本n=12人的标志表现为男、女,有以下三种排列。(i) 男\男,女\女\女,男,女\女,男\男\男\男;(ii) 男\男\男\男\男\男\男,女\女\女\女\女;(iii) 男,女,男,女,男,女,男,女,男,女,男\男;连续出现男或女的区段称为游程。

qq_36130719的博客 609

Vulkan入门系列0- Vulkan与OpenGL的区别

虽然Vulkan和OpenGL都是对GPU的抽象,都能达到相同的功能,但它们的设计理念不同,可以说设计差异是非常之大,由于OpenGL从1992年到现在,已经有30多年的历史,GPU功能日新月异,与当年不可同日而语,在OpenGL保持向下兼容情况下,还要在OpenGL基础上设计一套编程接口显然是不可能的,所以推出了新编程规范Vulkan。另外在一些无窗口的应用中,Vulkan可以有选择的不创建帧缓冲区,比如在数学运算,物理模拟,处理视频流的应用中并不需要窗口,所以Vulkan支持更灵活的应用场景。

GPU全栈博主 4070

一分钟理解netty.pipeline执行顺序

例如,假设我们创建了以下管道: @Override protected void initChannel(SocketChannel ch) throws Exception { ChannelPipeline p = ch.pipeline(); p.addLast("1", new InboundHandlerA()); p.addLast("2", new Inb...

ffyyhh995511的专栏 2344

串口、COM、UART、TTL、USB、RS-232、RS-485、I2C、SPI、CAN、1-WIRE

一文搞定,赶紧收藏吧

记得诚 3万+

学习笔记 1.高性能编程 2.2.2 责任链设计模式

设计模式——责任链模式:责任链模式(Chain of Responsibility Pattern)为请求创建了一个处理对象的链。发起请求和具体处理请求的过程进行解耦:职责链上的处理者负责处理请求,客户只需要将请求发送到职责链上即可,无需关心请求的处理细节和请求的传递。 实现责任链模式需要4个要素:处理器抽象类,具体的处理器实现类,保存处理器信息,处理执行 那么在netty中又是如何使...

weixin_39267363的博客 253

Outlook Express 6 (OE6) for Win7/Win8/Win10

英文版 Outlook Express6.0, 兼容Win7/Win10/WinXP,解压就可用。

锂电池充电器电路设计 里面有电路

锂电池充电器电路设计 里面有电路 3.7V升压到5V电路设计。

【Netty核心原理】篇三:ChannelPipeline——责任链模式的极致运用

责任链模式(Chain of Responsibility)是软件设计中的经典行为模式,它通过让多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。Pipeline的设计思想不仅适用于网络编程,其责任链模式和事件驱动架构对于构建复杂的异步处理系统都有很好的借鉴意义。将这一模式运用到了极致,不仅解决了网络事件处理的复杂性问题,更构建了一套高度灵活、可扩展的架构基础。Handler的执行顺序由其在Pipeline中的位置决定,理解这一点对架构设计至关重要。

持续输出Java相关知识 248

霍尼韦尔SoftMaster使用说明

霍尼韦尔SoftMaster使用说明

Netty系列-4 Pipeline和Handler

Netty将IO事件按照流向划分为两个部分,对应Inbound入站事件和Outbound出站事件:入站事件由外部触发,包括通道注册(register)、通道激活(active)、数据可读(read)、通道异常(exceptionCaught)等;出站事件由程序主动触发,如连接的建立(bind, connect)与关闭(closh),数据的写出(write、flush)等。 先整体上看, Netty的每个通道都独有一个Pipeline,通道的出站和入站事件在Pipeline中流动和处理,如下所示: 上图包含了

shengyu 1717

计算机控制技术及应用

《计算机控制技术及应用》通过总结作者的科研成果和吸收国内外的先进理论、方法和技术,以计算机控制系统的网络化、开放化、智能化和集成化发展趋势为主线索,主要介绍计算机控制系统的基本概念和计算机控制系统的典型形式,计算机控制系统的数学描述与基本分析方法,以及数字PID控制器、最少拍控制系统、达林算法、状态变量设计法等设计方法及其各种改进算法。重点突出计算机控制系统输入输出通道接口的设计方法和典型控制系统的设计案例与实现方法等。

Netty笔记05-组件Handler & Pipeline

Netty中的ChannelHandler和ChannelPipeline是其核心组件。ChannelHandler负责处理I/O事件,分为入站(Inbound)和出站(Outbound)两种类型,分别处理接收和发送数据。ChannelPipeline采用责任链模式管理多个ChannelHandler,具有有序性、双向性和灵活性特点。入站处理器处理客户端数据,出站处理器加工返回结果。通过代码示例展示了处理器链的构建过程,其中要注意数据传递和处理器调用的连续性。

weixin_46425661的博客 1042

【netty】----第 8 章 Netty 编解码器和 handler 的调用机制

本文介绍了Netty的核心组件和编解码机制。主要内容包括:1) Netty的核心组件设计,如Channel、EventLoop、ChannelHandler等;2) 编解码器的工作原理,包括入站解码和出站编码过程;3) 解码器ByteToMessageDecoder和ReplayingDecoder的实现原理及使用场景;4) Netty的handler链调用机制,强调消息类型匹配的重要性;5) 其他常用编解码器如LineBasedFrameDecoder、HttpObjectDecoder等;6) 如何将L

记录生活 1057

Netty,这次拿下核心 Pipeline

今天我们来盘一盘 Netty 中的 Pipeline 实现(没看过我 Netty 前面几篇的,建议先看看,文末有合集链接)。 再学一手有点骚的掩码操作,深入学习下 Netty 是如何处理各种事件的,看看事件是如何在 ChannelPipeline 中传播处理的。 话不多说,发车! 我们都知道,在 Netty 中业务处理逻辑都被拆分成一个个 ChannelHandler 实现,我们选装的 ChannelHandler 们又组成了一条链,叫 ChannelPipeline。 实际上 ChannelPipelin

spring3776的博客 226

Netty的使用模式

汪翰翔的Blog 1676

关于Netty Handler入站出站机制详解

关于Netty Handler入站出站机制详解 引言 由于工作需要,我开始对Netty这套框架进行了研究及使用,在学习过程中发现各论坛对Handler的入站出站机制都存在一个错误,所以写了这篇文章,方便自己及其他网友参阅。 1.Netty PipeLine机制解释 众所周知,Netty是通过Channel来建立连接的,并且这个Channel在java原生NIO Channel的基础上改装优化的。 每当一个新的连接建立后,Netty都会生成一个Channel及一个Pipeline,并互相引用; 这个Pipe

qq_26702635的博客 1158

netty ChannelPipeline的事件传输机制(入站和出站的方法)

文章目录一、Netty的事件类型1、入站事件传播方法1.1、ChannelHandlerContext#fireChannelRegistered()1.2、ChannelHandlerContext#fireChannelActive()1.3、ChannelHandlerContext#fireChannelRead(Object)1.4、ChannelHandlerContext#fireChannelReadComplete()1.5、ChannelHandlerContext#fireExcept

weixin_42030357的博客 2411

Netty技术全解析:ChannelPipeline技术详解

ChannelPipeline通过其内部的双向链表结构和责任链模式的设计,实现了网络事件的高效处理和灵活定制。它是Netty框架中处理网络事件的核心组件之一

码到三十五 1207

什么是 Netty 的 ChannelPipeline,它是如何工作的?

pipeline 翻译为管道、流水线,在 Netty 这个大工厂中,ChannelPipeline 就像一条流水线,数据流过 ChannelPipeline,被一步一步地加工,最后得到一个成熟的工艺品。在 Netty 中,ChannelPipeline 是 Netty 的核心处理链,用于实现网络时间的动态编排和有序传播。它负责组织和编排各种 ChannelHandler,使他们能够有序地组织在一起,但实际的数据加工还是由 ChannelHandler 处理。

smart_an的专栏 1047

Netty-ChannelPipeline

可以看作是一条流水线(Pipeline),其中的每一个处理单元称为。当一个 I/O 事件(如读取数据、写入数据、连接建立、连接关闭等)发生时,这个事件会在中按照顺序传播,依次被每个处理。是 Netty 中处理 I/O 事件的核心机制,它通过责任链模式将一系列组织在一起,形成一个处理链。在这个处理链中,I/O 事件被依次传递,并在中进行处理。通过,Netty 实现了极大的灵活性和可扩展性,允许开发者根据具体需求定制和管理处理器链。在实际应用中,

Flying_Fish_roe的博客 1075

08-ChannelPipeline接口

文章目录ChannelPipeline一、ChannelPipeline1.1 ChannelInboundInvoker1.2 ChannelOutboundInvoker1.3 ChannelPipeline接口二、事件2.1 入站事件2.2 出站事件三、Channel和ChannelPipeline四、小结五、参考 ChannelPipeline ChannelPipeline用于组织全...

my_momo_csdn的博客 2144
上一篇: Netty源码分析---ChannelInitializer工作原理详解
下一篇: ChannelPipeline事件传播详解-executionMask机制
不早睡不改名@
博客等级 码龄5年 536粉丝 306原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值