6. Netty中ChannelHandler读取、写入消息

之前我们分析Netty服务端的启动和连接的过程,接下来我们分析下Netty中Server端消息的读取和写入流程。
我们首先看看Server端消息读取过程。
通过前几节的分析,Netty中Server端通过worker线程组的线程,在不断监听轮询读取事件,主要的处理在processSelectedKey中,

private void processSelectedKey(SelectionKey k, AbstractNioChannel ch) {
        final AbstractNioChannel.NioUnsafe unsafe = ch.unsafe();
        if (!k.isValid()) {
            final EventLoop eventLoop;
            try {
                eventLoop = ch.eventLoop();
            } catch (Throwable ignored) {
                return;
            }
            if (eventLoop == this) {
                // close the channel if the key is not valid anymore
                unsafe.close(unsafe.voidPromise());
            }
            return;
        }

        try {
            int readyOps = k.readyOps();
            if ((readyOps & SelectionKey.OP_CONNECT) != 0) {
                int ops = k.interestOps();
                ops &= ~SelectionKey.OP_CONNECT;
                k.interestOps(ops);
                unsafe.finishConnect();
            }
            if ((readyOps & SelectionKey.OP_WRITE) != 0) {
                ch.unsafe().forceFlush();
            }
            // to a spin loop
            if ((readyOps & (SelectionKey.OP_READ | SelectionKey.OP_ACCEPT)) != 0 || readyOps == 0) {
                unsafe.read();
            }
        } catch (CancelledKeyException ignored) {
            unsafe.close(unsafe.voidPromise());
        }
    }

可以看到这里最后通过unsafe.read()来实现:

    public interface NioUnsafe extends Unsafe {
        SelectableChannel ch();
        void finishConnect();
        void read();
        void forceFlush();
    }

在这里插入图片描述
主要有这两个实现,在之前也说过NioByteUnsafe是用来读取SocketChannel的数据,NioMessageUnsafe用来读取ServerSocketChannel数据,这里读取客户端数据就是通过NioByteUnsafe来进行读取:

// NioByteUnsafe
public final void read() {
            final ChannelConfig config = config();
            if (shouldBreakReadReady(config)) {
                clearReadPending();
                return;
            }
            final ChannelPipeline pipeline = pipeline();
            final ByteBufAllocator allocator = config.getAllocator();
            final RecvByteBufAllocator.Handle allocHandle = recvBufAllocHandle();
            allocHandle.reset(config);
            ByteBuf byteBuf = null;
            boolean close = false;
            try {
                do {
                    byteBuf = allocHandle.allocate(allocator);
                    allocHandle.lastBytesRead(doReadBytes(byteBuf));
                    if (allocHandle.lastBytesRead() <= 0) {
                        byteBuf.release();
                        byteBuf = null;
                        close = allocHandle.lastBytesRead() < 0;
                        if (close) {
                            readPending = false;
                        }
                        break;
                    }

                    allocHandle.incMessagesRead(1);
                    readPending = false;
                    pipeline.fireChannelRead(byteBuf);
                    byteBuf = null;
                } while (allocHandle.continueReading());
                allocHandle.readComplete();
                pipeline.fireChannelReadComplete();
                if (close) {
                    closeOnRead(pipeline);
                }
            } catch (Throwable t) {
                handleReadException(pipeline, byteBuf, t, close, allocHandle);
            } finally {
                if (!readPending && !config.isAutoRead()) {
                    removeReadOp();
                }
            }
        }
    }

而真正读取底层网络数据是通过

// NioSocketChannel.java
protected int doReadBytes(ByteBuf byteBuf) throws Exception {
        final RecvByteBufAllocator.Handle allocHandle = unsafe().recvBufAllocHandle();
        allocHandle.attemptedBytesRead(byteBuf.writableBytes());
        return byteBuf.writeBytes(javaChannel(), allocHandle.attemptedBytesRead());
    }

这里在NioSocketChannel调用持有的底层网络Socket将网络数据读取到ByteBuf 中,这样就将底层网络数据读取到Netty中,读取完数据之后,就会触发ChannelHandler事件。在unsafe中读取完网路数据之后就会触发pipeline.fireChannelRead。然后依次向后执行ChannelHandler相关事件,到这里数据就读取完成。

当我们在Netty中需要写入相关消息,一般都是通过Channel写入,典型的是在Handler中处理,代码如下:

class HelloWorldHandler extends ChannelInboundHandlerAdapter
    {
        public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
            String resp = "Hello world";
            ctx.channel().writeAndFlush(resp);

        }
    }

当我们使用channel进行写入的时候,底层是怎么处理的呢 ? 我们随着channel.writeAndFlush进入:

//调用实际上是AbstractChannel中的writeAndFlush,可以明显的看到,调用的是pipline的的写入方法
public ChannelFuture writeAndFlush(Object msg) {
        return pipeline.writeAndFlush(msg);
    }

在pipline中的是调用tail节点的writeAndFlush实际处理逻辑如下:

 public final ChannelFuture writeAndFlush(Object msg, ChannelPromise promise) {
        return tail.writeAndFlush(msg, promise);
    }

实际调用是在: AbstractChannelHandlerContext

private void write(Object msg, boolean flush, ChannelPromise promise) {
        ObjectUtil.checkNotNull(msg, "msg");
        try {
            if (isNotValidPromise(promise, true)) {
                ReferenceCountUtil.release(msg);//方便后面内存释放,如果ByteBuf相关会进行释放计数,普通消息不会
                return;
            }
        } catch (RuntimeException e) {
            ReferenceCountUtil.release(msg);//方便后面内存释放
            throw e;
        }

        final AbstractChannelHandlerContext next = findContextOutbound(flush ?
                (MASK_WRITE | MASK_FLUSH) : MASK_WRITE);//找到下一个outoundhandler
        final Object m = pipeline.touch(msg, next); // 以上面例子来说,这里返回的就是resp本身
        EventExecutor executor = next.executor();
        if (executor.inEventLoop()) {//是否在IO线程中
            if (flush) {
                next.invokeWriteAndFlush(m, promise); //在IO线程中执行后面handler的write逻辑
            } else {
                next.invokeWrite(m, promise);
            }
        } else {//不在IO线程中提交任务给IO线程去处理
            final WriteTask task = WriteTask.newInstance(next, m, promise, flush);
            if (!safeExecute(executor, task, promise, m, !flush)) {
                task.cancel();
            }
        }
    }

正常情况,最后分别调用HeadContext的writeflush方法,我们跟中在DefaultPipline中的HeadContext

public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) {
            unsafe.write(msg, promise);
        }
public void flush(ChannelHandlerContext ctx) {
            unsafe.flush();
        }

底层调用了unsafe的write方法,进一步跟踪代码:

 public final void write(Object msg, ChannelPromise promise) {
            assertEventLoop(); // 写入一定是在IO线程中
            ChannelOutboundBuffer outboundBuffer = this.outboundBuffer;
            if (outboundBuffer == null) {
                ReferenceCountUtil.release(msg); //如果是可回收的类型消息,如ByteBuffer,这里计算减一,方便后面回收内存资源
                return;
            }
            int size;
            try {
                msg = filterOutboundMessage(msg); 
                size = pipeline.estimatorHandle().size(msg);
                if (size < 0) {
                    size = 0;
                }
            } catch (Throwable t) {
                safeSetFailure(promise, t);
                ReferenceCountUtil.release(msg);
                return;
            }
            outboundBuffer.addMessage(msg, size, promise);
        }

        @Override
        public final void flush() {
            assertEventLoop();
            ChannelOutboundBuffer outboundBuffer = this.outboundBuffer;
            if (outboundBuffer == null) {
                return;
            }
            outboundBuffer.addFlush();
            flush0();
        }

filterOutboundMessage中对msg进行了封装,完成堆外内存的转换:

 protected final Object filterOutboundMessage(Object msg) {
        if (msg instanceof ByteBuf) {
            ByteBuf buf = (ByteBuf) msg;
            if (buf.isDirect()) {
                return msg;
            }
            return newDirectBuffer(buf);
        }
        if (msg instanceof FileRegion) {
            return msg;
        }
        throw new UnsupportedOperationException(
                "unsupported message type: " + StringUtil.simpleClassName(msg) + EXPECTED_TYPES);
    }

从上面可以看出,如果不是ByteBuf类型,发送会抛出异常,也就是我们经常会在服务端handler里面一般会加入编码器和解码器

bootstrap.group(boss,worker)
                .channel(NioServerSocketChannel.class)
                .childHandler(new ChannelInitializer<SocketChannel>()
                {
                    @Override
                    protected void initChannel(SocketChannel socketChannel) throws Exception
                    {
                        socketChannel.pipeline()
                                .addLast("encoder",new StringEncoder())
                                .addLast("decoder",new StringDecoder())
                                .addLast("hello world hanlder",new HelloWorldHandler());
                    }
                });

如果不加入encoder和decoder,则发送消息:

     bootstrap.group(boss,worker)
                .channel(NioServerSocketChannel.class)
                .childHandler(new ChannelInitializer<SocketChannel>()
                {
                    @Override
                    protected void initChannel(SocketChannel socketChannel) throws Exception
                    {
                        socketChannel.pipeline()
                                //.addLast("encoder",new StringEncoder())
                                //.addLast("decoder",new StringDecoder())
                                .addLast("hello world hanlder",new HelloWorldHandler());

                    }
                });

		// ------------华丽的分割线----------------------------------------
        public void channelActive(ChannelHandlerContext ctx) throws Exception
        {
            System.out.println("channel Active and write back......");
            String resp = "Hello world";
            ChannelFuture future = ctx.channel().writeAndFlush(resp);
            future.sync();

            System.out.println("success:"+future.isSuccess());
        }

在这里插入图片描述
因此必须在handler中添加编解码器,将发送的消息缓缓为ByteBuf类型。例如在StringEncoderencode方法:

 protected void encode(ChannelHandlerContext ctx, CharSequence msg, List<Object> out) throws Exception {
        if (msg.length() == 0) {
            return;
        }
        out.add(ByteBufUtil.encodeString(ctx.alloc(), CharBuffer.wrap(msg), charset)); // 将msg转换为ByteBuf类型
    }

回到之前的处理,

outboundBuffer.addMessage(msg, size, promise);

这里实际上是完成了将消息加入到了buffer缓冲里面:

//形成了一个链表结构
 public void addMessage(Object msg, int size, ChannelPromise promise) {
        Entry entry = Entry.newInstance(msg, size, total(msg), promise);
        if (tailEntry == null) {
            flushedEntry = null;
        } else {
            Entry tail = tailEntry;
            tail.next = entry;
        }
        tailEntry = entry;
        if (unflushedEntry == null) {
            unflushedEntry = entry;
        }
        incrementPendingOutboundBytes(entry.pendingSize, false);
    }

然后调用 flush方法,完成实际写入,最终在NioSocketChanneldoWrite完成实际的写入, 而最终是关联到channel里面的socket完成了实际的写入。
我们看下在Netty中ChannelHandler的类型,主要分为Inbound和Outbound两种:
在这里插入图片描述

在这里插入图片描述
可以看到,这里不管Inbound和Outbound都继承了ChannelHandlerAdapter,那这个ChannelHandlerAdapter具体是干嘛的呢 ?说白了,这个ChannelHandlerAdapter默认实现了ChannelHandler中一些不重要的方法(都是空实现),这样我们去实现的时候,不用在去空实现一些无关紧要的方法。
我们看下Netty中一些常见的Handler:

SimpleChannelInboundHandler

实现SimpleChannelInboundHandlerchannelRead0后,不用编程释放内存,在SimpleChannelInboundHandler.channelRead实现了内存的释放:

public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        boolean release = true;
        try {
            if (acceptInboundMessage(msg)) {
                I imsg = (I) msg;
                channelRead0(ctx, imsg);
            } else {
                release = false;
                ctx.fireChannelRead(msg);
            }
        } finally {
            if (autoRelease && release) {
                ReferenceCountUtil.release(msg);
            }
        }
    }
ChannelDuplexHandler

这是一个双通道的ChannelHandler,同时继承Inbound和Outbound:

public class ChannelDuplexHandler extends ChannelInboundHandlerAdapter implements ChannelOutboundHandler {
...
}
AbstractTrafficShapingHandler

AbstractTrafficShapingHandler在Netty中用来进行流量整形,有三个实现:
在这里插入图片描述
在Netty中可以通过高低水位和流量整形来进行读取写入速度的控制。
上面介绍的就是介于流量整形来进行读写速度的控制。在ChannelConfig中提供了setWriteBufferLowWaterMarksetWriteBufferHighWaterMark来设置高低水位线,我们可以在启动时候自定义实现的ChannelInitializer增加这个逻辑处理

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值