Netty 处理连接那些事

 戳蓝字「TopCoder」关注我们哦!

Netty 处理连接那些事_第1张图片

编者注: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的channelHandlerchannelPipeline可以理解成就是责任链模式,通过动态增加channelHandler可达到复用和高扩展性目的。

了解netty连接处理机制之前需要了解下NioEventLoop模型,其中处理连接事件的架构图如下:

Netty 处理连接那些事_第2张图片

对应的处理逻辑源码为:

 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部分展开:

ACCEPT事件

 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) e.getKey()).set(e.getValue());
11    }
12
13    try {
14        // 将channel注册到childGroup中的Selector上
15        childGroup.register(child).addListener(new ChannelFutureListener() {
16            @Override
17            public void operationComplete(ChannelFuture future) throws Exception {
18                if (!future.isSuccess()) {
19                    forceClose(child, future.cause());
20                }
21            }
22        });
23    } catch (Throwable t) {
24        forceClose(child, t);
25    }
26}

READ事件

 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}

CONNECT事件

该事件是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 深入浅出


欢迎小伙伴 关注【TopCoder】 阅读更多精彩好文。

你可能感兴趣的:(Netty 处理连接那些事)