回顾上文 Netty 服务 如何 接收新的连接

总结规律:
上一节,我们一起学习了服务接收新连接过程的源码剖析,发现一个很有趣的现象,其实, Netty 底层还是使用的Java 原生的 NIO 来操作的。那么,接收新数据也是一样吗?如果是,那么数据如何转化成 Netty 的 ByteBuf 呢?

Java 原生 NIO 从 SocketChannel 中取出来的数据是存储在 ByteBuffer 里面的,也就是下面这样的代码:

if (selectionKey.isReadable()) {
    // 强制转换为SocketChannel
    SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
    // 创建Buffer用于读取数据
    ByteBuffer buffer = ByteBuffer.allocate(1024);
    // 将数据读入到buffer中(第二阶段阻塞)
    int length = socketChannel.read(buffer);
    // 处理数据。。。
}

那么,今天的问题是:
1 如果 Netty 底层使用的是 Java 原生的 SocketChannel,那么她是如何处理数据的?
2 数据又是如何在 ChannelPipeline 中传递的?
3 如何在控制台中打印出客户端传递过来的数据?

上一节,我们说接收新连接的处理主要是在 NioEventLoop 的 run () 方法中,里面有个方法叫做 processSelectedKey() ,它里面对于四种事件都有相应的判断并交给 Channel 的 Unsafe 属性来处理,代码如下:

private void processSelectedKey(SelectionKey k, AbstractNioChannel ch) {
final AbstractNioChannel.NioUnsafe unsafe = ch.unsafe();
	//  省略异常处理等其他代码
try {
	int readyOps = k.readyOps();
	//  如果是 Connect 事件
	if ((readyOps & SelectionKey.OP_CONNECT) != 0) {
		int ops = k.interestOps();
		ops &= ~SelectionKey.OP_CONNECT;
		k.interestOps(ops);
		unsafe.finishConnect();
	}
	//  如果是 Write 事件
	if ((readyOps & SelectionKey.OP_WRITE) != 0) {
		ch.unsafe().forceFlush();
	}
	//  如果是 Read 事件或者 Accept 事件
	if ((readyOps & (SelectionKey.OP_READ | SelectionKey.OP_ACCEPT)) != 0 || 	readyOps == 0) {
	unsafe.read();
	}
} catch (CancelledKeyException ignored) {
		unsafe.close(unsafe.voidPromise());
	}
}

让我们跟踪到这个 unsafe 内部
// io.netty.channel.nio.AbstractNioByteChannel.NioByteUnsafe#read

@Override
        public final void read() {
            final ChannelConfig config = config();
            if (shouldBreakReadReady(config)) {
                clearReadPending();
                return;
            }
            final ChannelPipeline pipeline = pipeline();

            // ByteBuf的分配器
            final ByteBufAllocator allocator = config.getAllocator();
            final RecvByteBufAllocator.Handle allocHandle = recvBufAllocHandle();
            allocHandle.reset(config);

            ByteBuf byteBuf = null;
            boolean close = false;
            try {
                do {
                    // key1,通过allocator创建一个ByteBuf,如何创建的
                    byteBuf = allocHandle.allocate(allocator);

                    // key2,读取数据到ByteBuf中,如何读取?
                    allocHandle.lastBytesRead(doReadBytes(byteBuf));

                    if (allocHandle.lastBytesRead() <= 0) {
                        // nothing was read. release the buffer.
                        byteBuf.release();
                        byteBuf = null;
                        close = allocHandle.lastBytesRead() < 0;
                        if (close) {
                            // There is nothing left to read as we received an EOF.
                            readPending = false;
                        }
                        break;
                    }
                    // key3,触发channelRead()
                    allocHandle.incMessagesRead(1);
                    readPending = false;
                    pipeline.fireChannelRead(byteBuf);
                    byteBuf = null;
                } while (allocHandle.continueReading());
                
                // key4,触发channelReadComplete()
                allocHandle.readComplete();
                pipeline.fireChannelReadComplete();

                if (close) {
                    closeOnRead(pipeline);
                }
            } catch (Throwable t) {
                handleReadException(pipeline, byteBuf, t, close, allocHandle);
            } finally {
                // Check if there is a readPending which was not processed yet.
                // This could be for two reasons:
                // * The user called Channel.read() or ChannelHandlerContext.read() in channelRead(...) method
                // * The user called Channel.read() or ChannelHandlerContext.read() in channelReadComplete(...) method
                //
                // See https://github.com/netty/netty/issues/2254
                if (!readPending && !config.isAutoRead()) {
                    removeReadOp();
                }
            }
        }
    }

这个方法中有四个关键的信息

1 通过 allocator 创建一个 ByteBuf,如何创建的呢?默认的又是什么呢?这个比较简单,默认地,通过各种参数判断当前操作系统是否允许池化、Unsafe、堆外这三个指标,当然,这些参数也可以通过启动参数来控制。我的系统默认创建的是 PooledUnsafeDirectByteBuf,你也可以看看你的是哪个。

2 读取数据到 ByteBuf 中,我们知道,Netty 底层包装了 Java 原生的 SocketChannel,那这里是如何读取的呢?

3 触发 ChannelPipeline 中的 ChannelHandler 的 channelRead () 方法。

4 触发 ChannelPipeline 中的 ChannelHandler 的 channelReadComplete () 方法,与上述同理,本文不详细展开。

首先,让我们看看 doReadBytes(byteBuf) 这个方法内部

// io.netty.channel.socket.nio.NioSocketChannel#doReadBytes
@Override
protected int doReadBytes(ByteBuf byteBuf) throws Exception {
    final RecvByteBufAllocator.Handle allocHandle = unsafe().recvBufAllocHandle();
    // 设置可读取的长度
    allocHandle.attemptedBytesRead(byteBuf.writableBytes());
    // key,调用ByteBuf的writeBytes()方法
    // 第一个参数是Java原生的SocketChannel,第二个参数是可读取的长度
    return byteBuf.writeBytes(javaChannel(), allocHandle.attemptedBytesRead());
}

// io.netty.buffer.AbstractByteBuf#writeBytes(java.nio.channels.ScatteringByteChannel, int)
@Override
public int writeBytes(ScatteringByteChannel in, int length) throws IOException {
    // 保证容量足够,里面有扩容的逻辑
    ensureWritable(length);
    // key,调用setBytes()方法
    // 第一个参数是写入的位置,第二参数是SocketChannel,第三个参数可写入的长度
    int writtenBytes = setBytes(writerIndex, in, length);
    if (writtenBytes > 0) {
        writerIndex += writtenBytes;
    }
    return writtenBytes;
}
// io.netty.buffer.PooledByteBuf#setBytes(int, java.nio.channels.ScatteringByteChannel, int)
@Override
public final int setBytes(int index, ScatteringByteChannel in, int length) throws IOException {
    try {
        // key,调用Java原生SocketChannel的read()方法
        // read()方法的参数是Java原生的ByteBuffer
        return in.read(internalNioBuffer(index, length));
    } catch (ClosedChannelException ignored) {
        return -1;
    }
}

果然不出我们所料, Netty 底层在读取数据的时候依然是调用的 Java 原生的 SocketChannel 的 read() 方法,将数据读取到了 ByteBuffer 中,也就是这里的 internalNioBuffer(index, length) 返回的是一个 Java 原生的ByteBuffer ,所以,此时,我们是不是可以大胆猜测, Netty 的 ByteBuf 是对 Java 原生 ByteBuffer 的包装呢?

为了验证我们的猜想,让我们再前进一步,深入到 internalNioBuffer(index, length) 内部。

// io.netty.buffer.PooledByteBuf#internalNioBuffer(int, int)
@Override
public final ByteBuffer internalNioBuffer(int index, int length) {
	checkIndex(index, length);
	return _internalNioBuffer(index, length, false);
}
final ByteBuffer _internalNioBuffer(int index, int length, boolean duplicate) {
	index = idx(index);
	// duplicate 为 false ,所以使用的是第二个
	ByteBuffer buffer = duplicate ? newInternalNioBuffer(memory) : 		 internalNioBuffer();
    buffer.limit(index + length).position(index);
   return buffer;
}
protected final ByteBuffer internalNioBuffer() {
     // this.tmpNioBuf
      ByteBuffer tmpNioBuf = this.tmpNioBuf;
   if (tmpNioBuf == null) {
    this.tmpNioBuf = tmpNioBuf = newInternalNioBuffer(memory);
   }
	return tmpNioBuf;
}

可以看到,最后返回的实际上是 this.tmpNioBuf,它的类型的是 ByteBuffer,果然,Netty 的 ByteBuf 底层果然是包装了 Java 原生的 ByteBuffer。

好了,让我们再来看看第二个问题,数据如何在 ChannelPipeline 中传递的?

// 1. io.netty.channel.DefaultChannelPipeline#fireChannelRead
@Override
public final ChannelPipeline fireChannelRead(Object msg) {
    // 从HeadContext开始调用
    AbstractChannelHandlerContext.invokeChannelRead(head, msg);
    return this;
}
// 2. io.netty.channel.AbstractChannelHandlerContext#invokeChannelRead
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()) {
        next.invokeChannelRead(m);
    } else {
        executor.execute(new Runnable() {
            @Override
            public void run() {
                next.invokeChannelRead(m);
            }
        });
    }
}
// 3. io.netty.channel.AbstractChannelHandlerContext#invokeChannelRead
private void invokeChannelRead(Object msg) {
    if (invokeHandler()) {
        try {
            // 调用里面的ChannelHandler的channelRead()方法
            ((ChannelInboundHandler) handler()).channelRead(this, msg);
        } catch (Throwable t) {
            notifyHandlerException(t);
        }
    } else {
        fireChannelRead(msg);
    }
}
// 4. io.netty.channel.DefaultChannelPipeline.HeadContext#channelRead
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
    // 调用ChannelHandlerContext的fireChannelRead()方法
    // 触发下一个Context中Handler的调用
    ctx.fireChannelRead(msg);
}
// 5. io.netty.channel.AbstractChannelHandlerContext#fireChannelRead
@Override
public ChannelHandlerContext fireChannelRead(final Object msg) {
    // 寻找下一个可用的ChannelHandlerContext
    invokeChannelRead(findContextInbound(MASK_CHANNEL_READ), msg);
    return this;
}
// 5.1 io.netty.channel.AbstractChannelHandlerContext#findContextInbound
private AbstractChannelHandlerContext findContextInbound(int mask) {
    AbstractChannelHandlerContext ctx = this;
    do {
        ctx = ctx.next;
    } while ((ctx.executionMask & mask) == 0);
    return ctx;
}
// 6. io.netty.channel.AbstractChannelHandlerContext#invokeChannelRead
// 又回到了上面第2步
static void invokeChannelRead(final AbstractChannelHandlerContext next, Object msg) {
    final Object m = next.pipeline.touch(ObjectUtil.checkNotNull(msg, "msg"), next);
    EventExecutor executor = next.executor();
    if (executor.inEventLoop()) {
        next.invokeChannelRead(m);
    } else {
        executor.execute(new Runnable() {
            @Override
            public void run() {
                next.invokeChannelRead(m);
            }
        });
    }
}

从这段代码中我们可以得出以下几点重要内容(对于入站消息):
1 ChannelPipeline 是从 HeadContext 开始执行的;
2 同一个 Channel 的所有 ChannelHandler 的执行都会放在 EventLoop 中执行,所以它们是线程安全的;
3 调用 ctx.fireChannelRead(msg); 即可触发下一个 ChannelHandler 的执行;
对于我们的 EchoServer ,它里面有四个 Handler ,即head<=>LoggingHandler<=>EchoServerHandler<=>tail , EchoServerHandler 中 channelRead () 方法为:

public class EchoServerHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
	//  读取数据后写回客户端
		ctx.write(msg);
	}
}

java netty 客户端给 设备发送 接收 指令 netty客户端接收数据_Java

HeadContext ,不仅是一个 ChannelHandlerContext ,也是一个 ChannelInboundHandler ,同时也是一个ChannelOutboundHandler 。
TailContext ,不仅是一 ChannelHandlerContext ,同时也是一个 ChannelInboundHandler 。

本节,我们一起学习了 Netty 接收新数据过程的源码剖析,通过不断的探寻底层的源码和原理,我们发现,揭开了 Netty 的神秘面纱之后,其实,她也很简单,底层都是对 Java 原生 NIO 的包装和优化,不断地提升用户体验,创造更美好的东西供大家使用。

好了,对于如何读取新数据以及数据如何在 ChannelPipeline 中传递,我们都弄明白了,让我们再看看第三个问题,如何在控制台中打印出客户端传递过来的消息呢?

netty服务接收新数据源码剖析总结

如何接收:

1 NioEventLoop的processSelectedKey()方法

2 Allocator创建ByteBuf – 累加器累加

3 读取数据 3.1 ByteBuf 实际封装了 ByteBuffer 3.2 通过该java原生SocketChannel读取数据

如何传递:

1 pipeline.fireChannelRead() 2.ctx.invokeChannelRead() 3.handler.channelRead() 4 ctx.fireChannelRead()

java netty 客户端给 设备发送 接收 指令 netty客户端接收数据_.net_02


这里有一些比较有意思的故事,稍后分享:

TODO。。。。。。