Netty系列-6 Netty消息处理流程

背景

前文介绍了Netty服务端的启动流程,服务端启动后可以处理客户端发送的请求,包括连接请求和普通消息。

1.处理连接

当客户端有连接请求到达时,服务器会创建通道并将通道注册到选择器上,处理逻辑与NIO中实现完全一致。
详细流程如下所示:
在这里插入图片描述
本章节将分小节对上图进行详细介绍。

1.1 Server阻塞监听

Netty系列-5 Netty启动流程中介绍过,当Netty服务端启动时,将NioServerSocketChannel作为attachment:

public abstract class AbstractNioChannel extends AbstractChannel {
    @Override
    protected void doRegister() throws Exception {
        boolean selected = false;
        for (;;) {
            try {
                selectionKey = javaChannel().register(eventLoop().unwrappedSelector(), 0, this);
                return;
            } catch (CancelledKeyException e) {
				//... 异常处理逻辑
            }
        }
    }
}

将this(NioServerSocketChannel对象)作为attachment传递给register方法,当选择器selector被事件唤醒时,可以通过selectionKey.attachment获取NioServerSocketChannel对象。
注意:1.2 将用到这部分内容。
将ServerSocketChannel注册到选择器后,关联的NioEventLoop将陷入阻塞等待状态(调用选择器的选择方法阻塞监听连接请求)。

1.2 server接受连接

当连接请求到达Netty服务器时,NioEventLoop线程从select阻塞中唤醒, 并执行processSelectedKeys方法处理已就绪的事件:

private void processSelectedKeys() {
	// ...
   processSelectedKeysOptimized();
}
	
private void processSelectedKeysOptimized() {
	for (int i = 0; i < selectedKeys.size; ++i) {
		final SelectionKey k = selectedKeys.keys[i];
		selectedKeys.keys[i] = null;

		final Object a = k.attachment();
		processSelectedKey(k, (AbstractNioChannel) a);
		//...
	}
}

依次遍历就绪的SelectionKey,从SelectionKey中取出attachment,即取出ServerSocketChannel对象(1.1中介绍过),然后调用processSelectedKey方法实际处理每个SelectedKey:

private void processSelectedKey(SelectionKey k, AbstractNioChannel ch) {
	final AbstractNioChannel.NioUnsafe unsafe = ch.unsafe();
	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();
		}
		// 处理可读、连接事件
		if ((readyOps & (SelectionKey.OP_READ | SelectionKey.OP_ACCEPT)) != 0 || readyOps == 0) {
			unsafe.read();
		}
	} catch (CancelledKeyException ignored) {
		unsafe.close(unsafe.voidPromise());
	}
}

此时readyOps是Accept事件,因此表达式
(readyOps & (SelectionKey.OP_READ |SelectionKey.OP_ACCEPT)) != 0 || readyOps == 0为true
进入unsafe.read()方法:

private final List<Object> readBuf = new ArrayList<Object>();
 
public void read() {
	
	do {
		// ... 
		
		int localRead = doReadMessages(readBuf);
		if (localRead == 0) {
			break;
		}
		if (localRead < 0) {
			closed = true;
			break;
		}
		allocHandle.incMessagesRead(localRead);
	} while (allocHandle.continueReading());


	int size = readBuf.size();
	for (int i = 0; i < size; i ++) {
		readPending = false;
		pipeline.fireChannelRead(readBuf.get(i));
	}
	readBuf.clear();
	allocHandle.readComplete();
	pipeline.fireChannelReadComplete();
	//...省略try-catch异常处理分支
}

逻辑较为清晰:持续调用doReadMessages读取数据并添加到readBuf列表中,直到数据完全读完;然后遍历readBuf列表,将每个消息(元素)以channelRead事件触发到pipeline,消息全部处理完成后,向pipeline提交ChannelReadComplete事件。
这里有个细节需要关注一下,doReadMessages内部将NIO的通道封装为netty的通道:

protected int doReadMessages(List<Object> buf) throws Exception {
    // 接收客户端连接,得到SocketChannel对象:serverSocketChannel.accept()得到SocketChannel
	SocketChannel ch = SocketUtils.accept(javaChannel());

	try {
		if (ch != null) {
            // 封装SocketChannel对象为NioSocketChannel类型
			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;
}

这里的buf集合存储的是NioSocketChannel类型的元素(消息)。
因此,进入Pipeline的channelRead事件的对象是NioSocketChannel对象。消息在pipeline中传递的顺序由左往右,如下所示:
在这里插入图片描述
当消息经过ServerBootstrapAcceptor时被处理:取出消息对象NioSocketChannel,配置NioSocketChannel对象,将NioSocketChannel对象注册到选择器上。

1.3 ServerBootstrapAcceptor作用

ServerBootstrapAcceptor是ChannelInboundHandlerAdapter的子类,属于入站事件处理器。ServerBootstrapAcceptor重写了channelRead和exceptionCaught方法,核心逻辑在channelRead中:

private static class ServerBootstrapAcceptor extends ChannelInboundHandlerAdapter {
	// ...
	public void channelRead(ChannelHandlerContext ctx, Object msg) {
        // 步骤1:从消息中提取通道对象,为NioSocketChannel类型
		final Channel child = (Channel) msg;
        // 步骤2:向NioSocketChannel通道设置handler、配置options和attrs
		child.pipeline().addLast(childHandler);
		setChannelOptions(child, childOptions, logger);
		setAttributes(child, childAttrs);
		try {
            // 步骤3: 注册到childGroup(NioEventLoopGroup)上
			childGroup.register(child).addListener(new ChannelFutureListener() {
				@Override
				public void operationComplete(ChannelFuture future) throws Exception {
					if (!future.isSuccess()) {
						forceClose(child, future.cause());
					}
				}
			});
		} catch (Throwable t) {
			forceClose(child, t);
		}
	}
}

服务器接收连接请求得到NIO的SocketChannel,netty框架封装为NioSocketChannel对象,传递给Pipeline. 当消息沿着Pipeline传递到ServerBootstrapAcceptor的channelRead方法时,将进行以下处理:
[1] 从消息中提取通道对象(即NioSocketChannel对象),消息本身就是通道对象;
[2] 向NioSocketChannel通道设置handler、配置options和attrs,这里的配置来源于ServerBootstrap启动时配置的childHandler, childOptions和childAttrs.
[3] 将NioSocketChannel通道注册到childGroup(NioEventLoopGroup)上,对应传递给ServerBootstrap的workerGroup线程池.
其中:步骤[3]中向NioEventLoopGroup注册NioSocketChannel,即从NioEventLoopGroup选择出一个NioEventLoop,使用NioEventLoop注册NioSocketChannel,这一部分已在Netty系列-2 NioServerSocketChannel和NioSocketChannel介绍中介绍过,不再赘述。有个细节需要注意:向选择器注册NioServerSocketChannel时,attachment是NioServerSocketChannel对象;而向选择器注册NioSocketChannel时,attachment是NioSocketChannel对象。

2.处理消息

当通道有消息可读时,NioEventLoop线程从阻塞中唤醒并处理SelectionKey, 流程与NIO相似。详细流程如下所示:
在这里插入图片描述

2.1 可读事件

有可读事件到达时,workerGroup中的某个NioEventLoop将(从select阻塞中)被唤醒,调用processSelectedKeys方法处理就绪的事件。

private void processSelectedKeys() {
	//...
	processSelectedKeysOptimized();
}

private void processSelectedKeysOptimized() {
	for (int i = 0; i < selectedKeys.size; ++i) {
		final SelectionKey k = selectedKeys.keys[i];
		selectedKeys.keys[i] = null;
		final Object a = k.attachment();
		//...
		processSelectedKey(k, (AbstractNioChannel) a);
		//...

	}
}

遍历已就绪的事件,调用processSelectedKey处理。
注意:这里从selectedKey取出的attachment对象是NioSocketChannel通道。
继续跟进processSelectedKey方法:

private void processSelectedKey(SelectionKey k, AbstractNioChannel ch) {
	final AbstractNioChannel.NioUnsafe unsafe = ch.unsafe();

	try {
		int readyOps = k.readyOps();
        //... 省略其他分支:SelectionKey.OP_CONNECT和readyOps & SelectionKey.OP_WRITE

		if ((readyOps & (SelectionKey.OP_READ | SelectionKey.OP_ACCEPT)) != 0 || readyOps == 0) {
			unsafe.read();
		}
}

此时,readyOps为SelectionKey.OP_READ,调用unsafe.read()处理消息。

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;
	//...
	do {
		byteBuf = allocHandle.allocate(allocator);
		// 读取数据,存入ByteBuf对象
		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触发ChannelRead事件
		pipeline.fireChannelRead(byteBuf);
		byteBuf = null;
	} while (allocHandle.continueReading());
	//...
	// 消息处理完,向pipeline触发ChannelReadComplete事件
	pipeline.fireChannelReadComplete();
}

逻辑较为清晰:持续调用doReadBytes读取数据至ByteBuf对象中,并将每个读取的ByteBuf以channelRead事件提交到pipeline,全部消息处理完成后,向pipeline提交ChannelReadComplete事件。
这里有个细节需要关注一下,doReadBytes内部使用NIO中的通道将数据流写入到ByteBuf中:

public final int setBytes(int index, ScatteringByteChannel in, int length) throws IOException {
    try {
        return in.read(internalNioBuffer(index, length));
    } catch (ClosedChannelException ignored) {
        return -1;
    }
}

2.2 pipeline处理数据

当数据沿着NioSocketChannel通道的Pipeline传输时,从左到右顺序如下:
在这里插入图片描述
Bytebuf类型的消息将沿着解码器Handler->业务Handler->编码器Handler->…->TailContext的顺序处理。
其中HeadContext和TailContext由框架携带,其他Handler由用户根据业务需要开发和引入。
因此,业务Handler中未定义加码器时,第一个处理可读消息的Handler的消息为Bytebuf类型。另外,消息外发时,也需要将业务对象转为Bytebuf类型,才可以正常发出(编码器)。

2.4 扩展

编解码器内容将在Netty系列-7 Netty编解码器中详细介绍。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值