Netty源码分析之IO事件

本文详细分析了Netty中的IO事件,包括读、写、接收和连接事件。在服务端,接收事件通过NioEventLoop的processSelectedKey方法处理,使用NioMessageUnsafe读取客户端连接。客户端连接后,服务端读事件处理与接收事件类似,但使用NioByteUnsafe处理。读事件流程中, AdaptiveRecvByteBufAllocator动态调整接收缓冲区大小。写事件涉及ChannelHandlerContext的writeAndFlush方法,数据先写入缓存,然后在合适时机通过flush0方法发送。
摘要由CSDN通过智能技术生成

IO事件概述

在上节我们知道Netty启动后会动起一个selector线程监听IO事件,IO事件包括以下几个:

  • SelectionKey.OP_READ 读操作位触发读事件
  • SelectionKey.OP_WRITE 写操作位触发写事件
  • SelectionKey.OP_ACCEPT 接收操作位触发接收事件
  • SelectionKey.OP_CONNECT 连接操作位触发连接事件

读事件

读事件即可以发生在客户端也可会发生在服务端,当客户端或服务端注册读事件并接受到远端发送的数据就会触发读事件。

写事件

写事件即可以发生在客户端也可会发生在服务端,写事件可以由外部直接调用触发,当出现写半包时(出现在TCP缓存满的情况),Netty会注册写操作位,待TCP缓存消耗后也会触发写事件。

接收事件

只发生在服务端,服务端启动的时候会注册接收操作位监听客户端的连接。

连接事件

只发生在客户端,客户端启动时会尝试连接服务端,连接是异步的不一定马上成功不成功则需要注册连接操作位监听客户端的连接成功。

下面从服务端的角度介绍Netty启动后,接收客户端连接的流程,以及客户端连接上后服务端的读和写的流程。

接收事件流程

当Selector轮询到接收事件会在NioEventLoop类中的processSelectedKey方法中进行处理,源码如下:

 private static void processSelectedKey(SelectionKey k, AbstractNioChannel ch) {
 
...

            int readyOps = k.readyOps();
            if ((readyOps & (SelectionKey.OP_READ | SelectionKey.OP_ACCEPT)) != 0 || readyOps == 0) {
                unsafe.read();
                if (!ch.isOpen()) {
                    return;
                }
            }
...

    }

服务端的接收逻辑直接委托给unsafe.read()处理,unsafe中有2个实现类NioMessageUnsafe和NioByteUnsafe,由于服务端启动初始化的Channel用的是NioServerSocketChannel,所以unsafe的实现类是NioMessageUnsafe,下面看下unsafe.read()的实现:


public void read() {
	 ...
        try {
        for (;;) {
            //获取接收结果
            int localRead = doReadMessages(readBuf);
            //如果接收结果为空直接推出
            if (localRead == 0) {
                break;
            }
            //异常情况下返回,tcp协议未用到
            if (localRead < 0) {
                closed = true;
                break;
            }
            //非自动读,退出并去掉监听客户端的连接事件,变成手工注册,一般不用
            if (!config.isAutoRead()) {
                break;
            }
            //每波的最大处理连接请求数默认为16
            if (readBuf.size() >= maxMessagesPerRead) {
                break;
            }
        }
    } catch (Throwable t) {
        exception = t;
    }
    setReadPending(false);
    int size = readBuf.size();
    //调用pipeline链处理客户端连接事件
    for (int i = 0; i < size; i ++) {
        pipeline.fireChannelRead(readBuf.get(i));
    }
    //清理接收对象
    readBuf.clear();
    //调用pipeline链处理接收完成事件
    pipeline.fireChannelReadComplete();
	...
}

接收客户端的处理逻辑主要流程如下:

  • 调用doReadMessages方法获取客户端连接,doReadMessages的实现其实就是调用了JDK原生的accept()方法获取与客户端通讯的通道SocketChannel(系统包装成NioSocketChannel类)。
  • 获取到SocketChannel后交给pipeline.fireChannelRead方法做进一步处理。

SocketChannel是服务端和客户端通讯的核心操作类,pipeline.fireChannelRead方法在之前讲过是一个调用链,调用用户的配置ChannelHandler,这里系统会调用初始化channel时系统自动注册的ServerBootstrapAcceptor里的channelRead方法(初始化channel流程可以阅读上节内容

channelRead是Netty的核心代码主要对SocketChannel进一步封装使剥离AcceptorSelector线程,独立出跟客户端通讯IOSelector线程。我们来看下它的实现:

        public void channelRead(ChannelHandlerContext ctx, Object msg) {
           //获取与客户端通讯的通道SocketChannel(下面叫childChannel)
            final Channel child = (Channel) msg;
            //将用户配置的处理器childChannel设置到childChannel
            child.pipeline().addLast(childHandler);
            //将用户配置的系统参数设置到childChannel
            for (Entry<ChannelOption<?>, Object> e: childOptions) {
                try {
                    if (!child.config().setOption((ChannelOption<Object>) e.getKey(), e.getValue())) {
                        logger.warn("Unknown channel option: " + e);
                    }
                } catch (Throwable t) {
                    logger.warn("Failed to set a channel option: " + child, t);
                }
            }
            //将用户配置的属性设置到childChannel
            for (Entry<AttributeKey<?>, Object> e: childAttrs) {
                child.attr((AttributeKey<Object>) e.getKey()).set(e.getValue());
            }

            try {
                //在从线程池里注册childChannel
                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);
            }
        }

channelRead流程比较简单就是在从线程池注册childChannel,然后从线程池起相应的selector线程处理服务端和客户端的读事件和写事件。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值