戳蓝字「TopCoder」关注我们哦!
编者注:Netty是Java领域有名的开源网络库,特点是高性能和高扩展性,因此很多流行的框架都是基于它来构建的,比如我们熟知的Dubbo、Rocketmq、Hadoop等,针对高性能RPC,一般都是基于Netty来构建,比如soft-bolt。总之一句话,Java小伙伴们需要且有必要学会使用Netty并理解其实现原理。
关于Netty的入门讲解可参考:Netty 入门,这一篇文章就够了
Netty的连接处理就是IO事件的处理,IO事件包括读事件、ACCEPT事件、写事件和OP_CONNECT事件。
IO事件的处理是结合ChanelPipeline来做的,一个IO事件到来,首先进行数据的读写操作,然后交给ChannelPipeline进行后续处理,ChannelPipeline中包含了channelHandler链(head + 自定义channelHandler + tail)。
使用channelPipeline和channelHandler机制,起到了解耦和可扩展的作用。一个IO事件的处理,包含了多个处理流程,这些处理流程正好对应channelPipeline中的channelHandler。如果对数据处理有新的需求,那么就新增channelHandler添加到channelPipeline中,这样实现很6,以后自己写代码可以参考。
说到这里,一般为了满足扩展性要求,常用2种模式:
方法模板模式:模板中定义了各个主流程,并且留下对应hook方法,便于扩展。
责任链模式:串行模式,可以动态添加链数量和对应回调方法。
netty的channelHandler
的channelPipeline
可以理解成就是责任链模式,通过动态增加channelHandler可达到复用和高扩展性目的。
了解netty连接处理机制之前需要了解下NioEventLoop模型,其中处理连接事件的架构图如下:
对应的处理逻辑源码为:
1// 处理各种IO事件
2private void processSelectedKey(SelectionKey k, AbstractNioChannel ch) {
3 final AbstractNioChannel.NioUnsafe unsafe = ch.unsafe();
4
5 try {
6 int readyOps = k.readyOps();
7 if ((readyOps & SelectionKey.OP_CONNECT) != 0) {
8 // OP_CONNECT事件,client连接上客户端时触发的事件
9 int ops = k.interestOps();
10 ops &= ~SelectionKey.OP_CONNECT;
11 k.interestOps(ops);
12 unsafe.finishConnect();
13 }
14
15 if ((readyOps & SelectionKey.OP_WRITE) != 0) {
16 ch.unsafe().forceFlush();
17 }
18
19 if ((readyOps & (SelectionKey.OP_READ | SelectionKey.OP_ACCEPT)) != 0 || readyOps == 0) {
20 // 注意,这里读事件和ACCEPT事件对应的unsafe实例是不一样的
21 // 读事件 -> NioByteUnsafe, ACCEPT事件 -> NioMessageUnsafe
22 unsafe.read();
23 }
24 } catch (CancelledKeyException ignored) {
25 unsafe.close(unsafe.voidPromise());
26 }
27}
从上面代码来看,事件主要分为3种,分别是OP_CONNECT事件、写事件和读事件(也包括ACCEPT事件)。下面分为3部分展开:
1// NioMessageUnsafe
2public void read() {
3 assert eventLoop().inEventLoop();
4 final ChannelConfig config = config();
5 final ChannelPipeline pipeline = pipeline();
6 final RecvByteBufAllocator.Handle allocHandle = unsafe().recvBufAllocHandle();
7 allocHandle.reset(config);
8
9 boolean closed = false;
10 Throwable exception = null;
11 try {
12 do {
13 // 调用java socket的accept方法,接收请求
14 int localRead = doReadMessages(readBuf);
15 // 增加统计计数
16 allocHandle.incMessagesRead(localRead);
17 } while (allocHandle.continueReading());
18 } catch (Throwable t) {
19 exception = t;
20 }
21
22 // readBuf中存的是NioChannel
23 int size = readBuf.size();
24 for (int i = 0; i < size; i ++) {
25 readPending = false;
26 // 触发fireChannelRead
27 pipeline.fireChannelRead(readBuf.get(i));
28 }
29 readBuf.clear();
30 allocHandle.readComplete();
31 pipeline.fireChannelReadComplete();
32}
连接建立好之后就该连接的channel注册到workGroup中某个NIOEventLoop的selector中,注册操作是在fireChannelRead中完成的,这一块逻辑就在ServerBootstrapAcceptor.channelRead中。
1// ServerBootstrapAcceptor
2public void channelRead(ChannelHandlerContext ctx, Object msg) {
3 final Channel child = (Channel) msg;
4
5 // 设置channel的pipeline handler,及channel属性
6 child.pipeline().addLast(childHandler);
7 setChannelOptions(child, childOptions, logger);
8
9 for (Entry, Object> e: childAttrs) {
10 child.attr((AttributeKey
1// NioByteUnsafe
2public final void read() {
3 final ChannelConfig config = config();
4 final ChannelPipeline pipeline = pipeline();
5 final ByteBufAllocator allocator = config.getAllocator();
6 final RecvByteBufAllocator.Handle allocHandle = recvBufAllocHandle();
7 allocHandle.reset(config);
8
9 ByteBuf byteBuf = null;
10 boolean close = false;
11 try {
12 do {
13 byteBuf = allocHandle.allocate(allocator);
14 // 从channel中读取数据,存放到byteBuf中
15 allocHandle.lastBytesRead(doReadBytes(byteBuf));
16
17 allocHandle.incMessagesRead(1);
18 readPending = false;
19
20 // 触发fireChannelRead
21 pipeline.fireChannelRead(byteBuf);
22 byteBuf = null;
23 } while (allocHandle.continueReading());
24
25 // 触发fireChannelReadComplete,如果在fireChannelReadComplete中执行了ChannelHandlerContext.flush,则响应结果返回给客户端
26 allocHandle.readComplete();
27 // 触发fireChannelReadComplete
28 pipeline.fireChannelReadComplete();
29
30 if (close) {
31 closeOnRead(pipeline);
32 }
33 } catch (Throwable t) {
34 if (!readPending && !config.isAutoRead()) {
35 removeReadOp();
36 }
37 }
38}
正常情况下一般是不会注册写事件的,如果Socket发送缓冲区中没有空闲内存时,再写入会导致阻塞,此时可以注册写事件,当有空闲内存(或者可用字节数大于等于其低水位标记)时,再响应写事件,并触发对应回调。
1if ((readyOps & SelectionKey.OP_WRITE) != 0) {
2 // 写事件,从flush操作来看,虽然之前没有向socket缓冲区写数据,但是已经写入到
3 // 了chnanel的outboundBuffer中,flush操作是将数据从outboundBuffer写入到
4 // socket缓冲区
5 ch.unsafe().forceFlush();
6}
该事件是client触发的,由主动建立连接这一侧触发的。
1if ((readyOps & SelectionKey.OP_CONNECT) != 0) {
2 // OP_CONNECT事件,client连接上客户端时触发的事件
3 int ops = k.interestOps();
4 ops &= ~SelectionKey.OP_CONNECT;
5 k.interestOps(ops);
6
7 // 触发finishConnect事件,其中就包括fireChannelActive事件,如果有自定义的handler有channelActive方法,则会触发
8 unsafe.finishConnect();
9}
推荐阅读
Netty 入门,这一篇文章就够了
Java常见几种动态代理的对比
程序员必看| mockito原理浅析
Eureka 原理分析
MQ初窥门径【面试必看的Kafka和RocketMQ存储区别】
java lambda 深入浅出