2 Netty 服务端创建

     当我们直接使用 JDK NIO 的类库开发基于 NIO 的异步服务端时,需要使用到 多 路 复 用 器 Selector、ServerSocketChannel、SocketChannel、ByteBuffer、 SelectionKey 等等,相比于传统的 BIO 开发,NIO 的开发要复杂很多,开发出稳 定、高性能的异步通信框架,一直是个难题。 Netty 为了向使用者屏蔽 NIO 通信的底层细节,在和用户交互的边界做了封 装,目的就是为了减少用户开发工作量,降低开发难度。ServerBootstrap 是 Socket 服务端的启动辅助类,用户通过 ServerBootstrap 可以方便的创建 Netty 的服务端。

Netty服务端创建代码示例:

package netty5.server;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;

/**
 * netty5服务端
 * @author -琴兽-
 *
 */
public class Server {

	public static void main(String[] args) {
		//服务类
		ServerBootstrap bootstrap = new ServerBootstrap();
		
		//boss和worker
		EventLoopGroup boss = new NioEventLoopGroup();
		EventLoopGroup worker = new NioEventLoopGroup();
		
		try {
			//设置线程池
			bootstrap.group(boss, worker);
			
			//设置socket工厂、
			bootstrap.channel(NioServerSocketChannel.class);
			
			//设置管道工厂
			bootstrap.childHandler(new ChannelInitializer<Channel>() {

				@Override
				protected void initChannel(Channel ch) throws Exception {
					ch.pipeline().addLast(new StringDecoder());
					ch.pipeline().addLast(new StringEncoder());
					ch.pipeline().addLast(new ServerHandler());
				}
			});
			
			//netty3中对应设置如下
			//bootstrap.setOption("backlog", 1024);
			//bootstrap.setOption("tcpNoDelay", true);
			//bootstrap.setOption("keepAlive", true);
			//netty5设置参数,TCP参数
			bootstrap.option(ChannelOption.SO_BACKLOG, 2048);//serverSocketchannel的设置,链接缓冲池的大小
			bootstrap.childOption(ChannelOption.SO_KEEPALIVE, true);//socketchannel的设置,维持链接的活跃,清除死链接
			bootstrap.childOption(ChannelOption.TCP_NODELAY, true);//socketchannel的设置,关闭延迟发送
			
			//绑定端口
			ChannelFuture future = bootstrap.bind(10101);
			
			System.out.println("start");
			
			//等待服务端关闭
			future.channel().closeFuture().sync();
			
		} catch (Exception e) {
			e.printStackTrace();
		} finally{
			//释放资源
			boss.shutdownGracefully();
			worker.shutdownGracefully();
		}
	}
}
package netty5.server;

import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
/**
 * 服务端消息处理
 * @author -琴兽-
 *
 */
public class ServerHandler extends SimpleChannelInboundHandler<String> {

	@Override
	protected void messageReceived(ChannelHandlerContext ctx, String msg) throws Exception {

		System.out.println(msg);
		
		ctx.channel().writeAndFlush("hi");
		ctx.writeAndFlush("hi");
	}

	/**
	 * 新客户端接入
	 */
	@Override
	public void channelActive(ChannelHandlerContext ctx) throws Exception {
		System.out.println("channelActive");
	}

	/**
	 * 客户端断开
	 */
	@Override
	public void channelInactive(ChannelHandlerContext ctx) throws Exception {
		System.out.println("channelInactive");
	}

	/**
	 * 异常
	 */
	@Override
	public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
		cause.printStackTrace();
	}
	
	
}

2.1 Netty服务端创建时序图

步骤 1:创建 ServerBootstrap 实例。ServerBootstrap 是 Netty 服务端的 启动辅助类,它提供了一系列的方法用于设置服务端启动相关的参数。底层通过 门面模式对各种能力进行抽象和封装,尽量不需要用户跟过多的底层 API 打交道, 降低用户的开发难度。
我们在创建 ServerBootstrap 实例时,会惊讶的发现 ServerBootstrap 只有一个无参的构造函数,作为启动辅助类这让人不可思议,因为它需要与多个其它组件或者类交互。ServerBootstrap 构造函数没有参数的根本原因是因为它的参数太多了,而且未来也可能会发生变化,为了解决这个问题,就需要引入Builder 模式。《Effective Java》第二版第 2 条建议遇到多个构造器参数时要考虑用构建器,关于多个参数构造函数的缺点和使用构建器的优点大家可以查阅《Effective Java》, 在此不再详述。
//服务类
ServerBootstrap bootstrap = new ServerBootstrap();

步 骤 2: 设 置 并 绑 定 Reactor 线 程 池。Netty 的 Reactor 线 程 池 是EventLoopGroup,它实际就是 EventLoop 的数组。EventLoop 的职责是处理所有注册到本线程多路复用器 Selector 上的 Channel,Selector 的轮询操作由绑定的 EventLoop 线程 run 方法驱动,在一个循环体内循环执行。值得说明的是,EventLoop 的职责不仅仅是处理网络 I/O 事件,用户自定义的 Task 和定时任务Task 也统一由 EventLoop 负责处理,这样线程模型就实现了统一。从调度层面看,也不存在在 EventLoop 线程中再启动其它类型的线程用于异步执行其它的任务,这样就避免了多线程并发操作和锁竞争,提升了 I/O 线程的处理和调度性能。

    public ServerBootstrap group(EventLoopGroup parentGroup, EventLoopGroup childGroup) {
        super.group(parentGroup);
        if (childGroup == null) {
            throw new NullPointerException("childGroup");
        } else if (this.childGroup != null) {
            throw new IllegalStateException("childGroup set already");
        } else {
            this.childGroup = childGroup;
            return this;
        }
    }

步 骤 3: 设 置 并 绑 定 服 务 端 Channel。 作 为 NIO 服 务 端, 需 要 创 建ServerSocketChannel,Netty 对 原 生 的 NIO 类 库 进 行 了 封 装, 对 应 实 现 是NioServerSocketChannel。对于用户而言,不需要关心服务端 Channel 的底层实现细节和工作原理,只需要指定具体使用哪种服务端 Channel 即可。因此,Netty 的 ServerBootstrap 方法提供了 channel 方法用于指定服务端 Channel 的类型。Netty 通过工厂类,利用反射创建 NioServerSocketChannel 对象。由于服务端监听端口往往只需要在系统启动时才会调用,因此反射对性能的影响并不大。相关代码如下所示:

    public B channel(Class<? extends C> channelClass) {
        if (channelClass == null) {
            throw new NullPointerException("channelClass");
        } else {
            return this.channelFactory((io.netty.channel.ChannelFactory)(new ReflectiveChannelFactory(channelClass)));
        }
    }

步骤 4:链路建立的时候创建并初始化 ChannelPipeline。ChannelPipeline并不是 NIO 服务端必需的,它本质就是一个负责处理网络事件的职责链,负责管理和执行 ChannelHandler。网络事件以事件流的形式在 ChannelPipeline中流转,由 ChannelPipeline 根据ChannelHandler 的执行策略调度 ChannelHandler 的执行。典型的网络事件如下:
1. 链路注册;
2. 链路激活;
3. 链路断开;
4. 接收到请求消息;
5. 请求消息接收并处理完毕;
6. 发送应答消息;
7. 链路发生异常;
8. 发生用户自定义事件。

步骤 5:初始化 ChannelPipeline 完成之后,添加并设置 ChannelHandler。ChannelHandler 是 Netty 提 供 给 用 户 定 制 和 扩 展 的 关 键 接 口。 利 用ChannelHandler 用户可以完成大多数的功能定制,例如消息编解码、心跳、安全认证、TSL/SSL 认证、流量控制和流量整形等。Netty 同时也提供了大量的系统 ChannelHandler 供用户使用,比较实用的系统 ChannelHandler 总结如下:
1. 系统编解码框架-ByteToMessageCodec;
2. 通用基于长度的半包解码器-LengthFieldBasedFrameDecoder;
3. 码流日志打印Handler-LoggingHandler;
4. SSL安全认证Handler-SslHandler;
5. 链路空闲检测Handler-IdleStateHandler;
6. 流量整形Handler-ChannelTrafficShapingHandler;
7. Base64编解码-Base64Decoder和Base64Encoder。
创建和添加 ChannelHandler 的代码示例如下:
 

			//设置管道工厂
			bootstrap.childHandler(new ChannelInitializer<Channel>() {

				@Override
				protected void initChannel(Channel ch) throws Exception {
					ch.pipeline().addLast(new StringDecoder());
					ch.pipeline().addLast(new StringEncoder());
					ch.pipeline().addLast(new ServerHandler());
				}
			});

步骤 6:绑定并启动监听端口。在绑定监听端口之前系统会做一系列的初始化和检测工作,完成之后,会启动监听端口,并将 ServerSocketChannel 注册到Selector 上监听客户端连接,相关代码如下

    public ChannelFuture bind(int inetPort) {
        return this.bind(new InetSocketAddress(inetPort));
    }

步骤 7:Selector 轮询。由 Reactor 线程 NioEventLoop 负责调度和执行Selector 轮询操作,选择准备就绪的 Channel 集合
步 骤 8: 当 轮 询 到 准 备 就 绪 的 Channel 之 后, 就 由 Reactor 线程 NioEventLoop 执 行 ChannelPipeline 的 相 应 方 法, 最 终 调 度 并 执 行ChannelHandler
步 骤 9: 执 行 Netty 系 统 ChannelHandler 和 用 户 添 加 定 制 的ChannelHandler。ChannelPipeline 根 据 网 络 事 件 的 类 型, 调 度 并 执 行ChannelHandler
 

2.2 Netty服务端创建源码分析

首先通过构造函数创建 ServerBootstrap 实例,随后,通常会创建两个EventLoopGroup(并不是必须要创建两个不同的 EventLoopGroup,也可以只创建一个并共享),代码如下所示:
 

		//服务类
		ServerBootstrap bootstrap = new ServerBootstrap();
		
		//boss和worker
		EventLoopGroup boss = new NioEventLoopGroup();
		EventLoopGroup worker = new NioEventLoopGroup();
		
		//设置线程池
		bootstrap.group(boss, worker);
NioEventLoopGroup 实际就是 Reactor 线程池,负责调度和执行客户端、的接入、网络读写事件的处理、用户自定义任务和定时任务的执行。通过ServerBootstrap 的 group 方法将两个 EventLoopGroup 实例传入,代码如下
    public ServerBootstrap group(EventLoopGroup parentGroup, EventLoopGroup childGroup) {
        super.group(parentGroup);
        if (childGroup == null) {
            throw new NullPointerException("childGroup");
        } else if (this.childGroup != null) {
            throw new IllegalStateException("childGroup set already");
        } else {
            this.childGroup = childGroup;
            return this;
        }
    }

其中父 NioEventLoopGroup 被传入了父类构造函数中:

    public B group(EventLoopGroup group) {
        if (group == null) {
            throw new NullPointerException("group");
        } else if (this.group != null) {
            throw new IllegalStateException("group set already");
        } else {
            this.group = group;
            return this;
        }
    }

该方法会被客户端和服务端重用,用于执行和调度网络事件的读写。线 程 组 和 线 程 类 型 设 置 完 成 后, 需 要 设 置 服 务 端 Channel,Netty 通过 Channel 工 厂 类 来 创 建 不 同 类 型 的 Channel, 对 于 服 务 端, 需 要 创 建NioServerSocketChannel, 所以,通过指定 Channel 类型的方式创建 Channel 工厂。ServerBootstrapChannelFactory 是 ServerBootstrap 的 内 部 静 态 类, 职责是根据 Channel 的类型通过反射创建 Channel 的实例,服务端需要创建的是NioServerSocketChannel 实例,代码如下

    public ReflectiveChannelFactory(Class<? extends T> clazz) {
        if (clazz == null) {
            throw new NullPointerException("clazz");
        } else {
            this.clazz = clazz;
        }
    }

    public T newChannel() {
        try {
            return (Channel)this.clazz.newInstance();
        } catch (Throwable var2) {
            throw new ChannelException("Unable to create Channel from class " + this.clazz, var2);
        }
    }

指定 NioServerSocketChannel 后,需要设置 TCP 的一些参数,作为服务端,主要是要设置 TCP 的 backlog 参数,底层 C 的对应接口定义如下:

int listen(int fd, int backlog);

backlog 指定了内核为此套接口排队的最大连接个数,对于给定的监听套接口,内核要维护两个队列,未链接队列和已连接队列,根据 TCP 三路握手过程中三个分节来分隔这两个队列。backlog 被规定为两个队列总和的最大值,大多数实现默认值为 5,但在高并发 web 服务器中此值显然不够,lighttpd 中此值达到 128*8 。需要设置此值更大一些的原因是未完成连接队列的长度可能因为客户端 SYN 的到达及等待三路握手第三个分节的到达延时而增大。Netty 默认的backlog 为 100,当然,用户可以修改默认值,用户需要根据实际场景和网络状
况进行灵活设置。TCP 参数设置完成后,用户可以为启动辅助类和其父类分别指定 Handler,两 类 Handler 的 用 途 不 同, 子 类 中 的 Hanlder 是 NioServerSocketChannel 对应的 ChannelPipeline 的 Handler,父类中的 Hanlder 是客户端新接入的连接SocketChannel 对应的 ChannelPipeline 的 Handler。两者的区别可以通过下图来展示:

本质区别就是:ServerBootstrap 中的 Handler 是 NioServerSocketChannel使用的,所有连接该监听端口的客户端都会执行它,父类 AbstractBootstrap 中的 Handler 是个工厂类,它为每个新接入的客户端都创建一个新的 Handler。服务端启动的最后一步,就是绑定本地端口,启动服务,下面我们来分析下这部分代码:
 
    private ChannelFuture doBind(final SocketAddress localAddress) {
        final ChannelFuture regFuture = this.initAndRegister();
        final Channel channel = regFuture.channel();
        if (regFuture.cause() != null) {
            return regFuture;
        } else if (regFuture.isDone()) {
            ChannelPromise promise = channel.newPromise();
            doBind0(regFuture, channel, localAddress, promise);
            return promise;
        } else {
            final AbstractBootstrap.PendingRegistrationPromise promise = new AbstractBootstrap.PendingRegistrationPromise(channel);
            regFuture.addListener(new ChannelFutureListener() {
                public void operationComplete(ChannelFuture future) throws Exception {
                    Throwable cause = future.cause();
                    if (cause != null) {
                        promise.setFailure(cause);
                    } else {
                        promise.executor = channel.eventLoop();
                    }

                    AbstractBootstrap.doBind0(regFuture, channel, localAddress, promise);
                }
            });
            return promise;
        }
    }

先看下 NO.1, 首先创建 Channel,createChannel 由子类 ServerBootstrap实现,创建新的 NioServerSocketChannel,它有两个参数,参数 1 是从父类的NIO 线程池中顺序获取一个 NioEventLoop,它就是服务端用于监听和接收客户端连接的 Reactor 线程。第二个参数就是所谓的 workerGroup 线程池,它就是处理IO 读写的 Reactor 线程组 , 相关代码如下:
 

    final ChannelFuture initAndRegister() {
        Channel channel = this.channelFactory().newChannel();

        try {
            this.init(channel);
        } catch (Throwable var3) {
            channel.unsafe().closeForcibly();
            return (new DefaultChannelPromise(channel, GlobalEventExecutor.INSTANCE)).setFailure(var3);
        }

        ChannelFuture regFuture = this.group().register(channel);
        if (regFuture.cause() != null) {
            if (channel.isRegistered()) {
                channel.close();
            } else {
                channel.unsafe().closeForcibly();
            }
        }

        return regFuture;
    }

NioServerSocketChannel 创建成功后对它进行初始化,初始化工作主要有三点。

void init(Channel channel) throws Exception {
        //设置 Socket 参数和 NioServerSocketChannel 的附加属性,
        Map<ChannelOption<?>, Object> options = this.options();
        synchronized(options) {
            channel.config().setOptions(options);
        }

        Map<AttributeKey<?>, Object> attrs = this.attrs();
        synchronized(attrs) {
            Iterator i$ = attrs.entrySet().iterator();

            while(true) {
                if (!i$.hasNext()) {
                    break;
                }

                Entry<AttributeKey<?>, Object> e = (Entry)i$.next();
                AttributeKey<Object> key = (AttributeKey)e.getKey();
                channel.attr(key).set(e.getValue());
            }
        }

        //将 AbstractBootstrap 的 Handler 添 加 到 NioServerSocketChannel ChannelPipeline中
        ChannelPipeline p = channel.pipeline();
        if (this.handler() != null) {
            p.addLast(new ChannelHandler[]{this.handler()});
        }

        final EventLoopGroup currentChildGroup = this.childGroup;
        final ChannelHandler currentChildHandler = this.childHandler;
        final Entry[] currentChildOptions;
        synchronized(this.childOptions) {
            currentChildOptions = (Entry[])this.childOptions.entrySet().toArray(newOptionArray(this.childOptions.size()));
        }

        final Entry[] currentChildAttrs;
        synchronized(this.childAttrs) {
            currentChildAttrs = (Entry[])this.childAttrs.entrySet().toArray(newAttrArray(this.childAttrs.size()));
        }
        
        //将 用 于 服 务 端 注 册 的 Handler ServerBootstrapAcceptor 添 加 到ChannelPipeline 中
        p.addLast(new ChannelHandler[]{new ChannelInitializer<Channel>() {
            public void initChannel(Channel ch) throws Exception {
                ch.pipeline().addLast(new ChannelHandler[]{new ServerBootstrap.ServerBootstrapAcceptor(currentChildGroup, currentChildHandler, currentChildOptions, currentChildAttrs)});
            }
        }});
    }

到此处,Netty 服务端监听的相关资源已经初始化完毕,就剩下最后一 步 - 注 册 NioServerSocketChannel 到 Reactor 线 程 的 多 路 复 用 器 上, 然后轮询客户端连接事件。在分析注册代码之前,我们先通过下图看看目前NioServerSocketChannel 的 ChannelPipeline 的组成:

最后,我们看下 NioServerSocketChannel 的注册。当 NioServerSocketChannel初始化完成之后,需要将它注册到 Reactor 线程的多路复用器上监听新客户端的接入,
首先判断是否是NioEventLoop自身发起的操作,如果是,则不存在并发操作,直接执行 Channel 注册;如果由其它线程发起,则封装成一个 Task 放入消息队列中异步执行。此处,由于是由 ServerBootstrap 所在线程执行的注册操作,所以会将其封装成 Task 投递到 NioEventLoop 中执行,代码如下:
        @Override
        public final void register(EventLoop eventLoop, final ChannelPromise promise) {
            if (eventLoop == null) {
                throw new NullPointerException("eventLoop");
            }
            if (promise == null) {
                throw new NullPointerException("promise");
            }
            if (isRegistered()) {
                promise.setFailure(new IllegalStateException("registered to an event loop already"));
                return;
            }
            if (!isCompatible(eventLoop)) {
                promise.setFailure(
                        new IllegalStateException("incompatible event loop type: " + eventLoop.getClass().getName()));
                return;
            }

            // It's necessary to reuse the wrapped eventloop object. Otherwise the user will end up with multiple
            // objects that do not share a common state.
            if (AbstractChannel.this.eventLoop == null) {
                AbstractChannel.this.eventLoop = new PausableChannelEventLoop(eventLoop);
            } else {
                AbstractChannel.this.eventLoop.unwrapped = eventLoop;
            }

            if (eventLoop.inEventLoop()) {
                register0(promise);
            } else {
                try {
                    eventLoop.execute(new OneTimeTask() {
                        @Override
                        public void run() {
                            register0(promise);
                        }
                    });
                } catch (Throwable t) {
                    logger.warn(
                            "Force-closing a channel whose registration task was not accepted by an event loop: {}",
                            AbstractChannel.this, t);
                    closeForcibly();
                    closeFuture.setClosed();
                    safeSetFailure(promise, t);
                }
            }
        }
 
        private void register0(ChannelPromise promise) {
            try {
                // check if the channel is still open as it could be closed in the mean time when the register
                // call was outside of the eventLoop
                if (!promise.setUncancellable() || !ensureOpen(promise)) {
                    return;
                }
                boolean firstRegistration = neverRegistered;
                doRegister();
                neverRegistered = false;
                registered = true;
                eventLoop.acceptNewTasks();
                safeSetSuccess(promise);
                pipeline.fireChannelRegistered();
                // Only fire a channelActive if the channel has never been registered. This prevents firing
                // multiple channel actives if the channel is deregistered and re-registered.
                if (firstRegistration && isActive()) {
                    pipeline.fireChannelActive();
                }
            } catch (Throwable t) {
                // Close the channel directly to avoid FD leak.
                closeForcibly();
                closeFuture.setClosed();
                safeSetFailure(promise, t);
            }
        }
将 NioServerSocketChannel注册到 NioEventLoop的 Selector上,代码如下
 
    @Override
    protected void doRegister() throws Exception {
        boolean selected = false;
        for (;;) {
            try {
                selectionKey = javaChannel().register(((NioEventLoop) eventLoop().unwrap()).selector, 0, this);
                return;
            } catch (CancelledKeyException e) {
                if (!selected) {
                    // Force the Selector to select now as the "canceled" SelectionKey may still be
                    // cached and not removed because no Select.select(..) operation was called yet.
                    ((NioEventLoop) eventLoop().unwrap()).selectNow();
                    selected = true;
                } else {
                    // We forced a select operation on the selector before but the SelectionKey is still cached
                    // for whatever reason. JDK bug ?
                    throw e;
                }
            }
        }
    }

大伙儿可能会很诧异,应该注册 OP_ACCEPT(16)到多路复用器上,怎么注册 0 呢? 0 表示只注册,不监听任何网络操作。这样做的原因如下:注册方法是多态的,它既可以被 NioServerSocketChannel 用来监听客户端的连接接入,也可以用来注册 SocketChannel,用来监听网络读或者写操作;通过 SelectionKey 的 interestOps(int ops) 方法可以方便的修改监听操作位。所以,此处注册需要获取 SelectionKey 并给 AbstractNioChannel 的成员变量 selectionKey 赋值。
注册成功之后,触发 ChannelRegistered 事件,方法如下:

doRegister();
neverRegistered = false;
registered = true;
eventLoop.acceptNewTasks();
safeSetSuccess(promise);
pipeline.fireChannelRegistered();

Netty 的 HeadHandler 不需要处理 ChannelRegistered 事件,所以,直接调用下一个 Handler, 代码如下:

    @Override
    public ChannelHandlerContext fireChannelRegistered() {
        AbstractChannelHandlerContext next = findContextInbound();
        next.invoker().invokeChannelRegistered(next);
        return this;
    }

当 ChannelRegistered 事件传递到 TailHandler 后结束,TailHandler 也不关心 ChannelRegistered 事件,因此是空实现,代码如下:

        @Override
        public void channelRegistered(ChannelHandlerContext ctx) throws Exception { }

ChannelRegistered 事件传递完成后,判断 ServerSocketChannel 监听是否成功,如果成功,需要出发 NioServerSocketChannel 的 ChannelActive 事件,代码如下
 

// Only fire a channelActive if the channel has never been registered. This prevents firing
// multiple channel actives if the channel is deregistered and re-registered.
if (firstRegistration && isActive()) {
    pipeline.fireChannelActive();
}

isActive() 也是个多态方法,如果是服务端,判断监听是否启动,如果是客户端,判断 TCP连接是否完成。ChannelActive事件在 ChannelPipeline中传递,完成之后根据配置决定是否自动触发 Channel 的读操作,代码如下:

    @Override
    public ChannelPipeline fireChannelActive() {
        head.fireChannelActive();

        if (channel.config().isAutoRead()) {
            channel.read();
        }

        return this;
    }

AbstractChannel 的读操作触发 ChannelPipeline 的读操作,最终调用到HeadHandler 的读方法,代码如下:

        @Override
        public void read(ChannelHandlerContext ctx) {
            unsafe.beginRead();
        }

继续看 AbstractUnsafe 的 beginRead 方法,代码如下:

        @Override
        public final void beginRead() {
            if (!isActive()) {
                return;
            }

            try {
                doBeginRead();
            } catch (final Exception e) {
                invokeLater(new OneTimeTask() {
                    @Override
                    public void run() {
                        pipeline.fireExceptionCaught(e);
                    }
                });
                close(voidPromise());
            }
        }

由于不同类型的 Channel 对读操作的准备工作不同,因此,beginRead 也是个多态方法,对于 NIO 通信,无论是客户端还是服务端,都是要修改网络监听操作位为自身感兴趣的,对于 NioServerSocketChannel 感兴趣的操作是 OP_ACCEPT(16),于是重新修改注册的操作位为 OP_ACCEPT,代码如下:在某些场景下,当前监听的操作类型和 Chanel 关心的网络事件是一致的,不需要重复注册,所以增加了 & 操作的判断,只有两者不一致,才需要重新注册操作位。

    @Override
    protected void doBeginRead() throws Exception {
        // Channel.read() or ChannelHandlerContext.read() was called
        if (inputShutdown) {
            return;
        }

        final SelectionKey selectionKey = this.selectionKey;
        if (!selectionKey.isValid()) {
            return;
        }

        readPending = true;

        final int interestOps = selectionKey.interestOps();
        if ((interestOps & readInterestOp) == 0) {
            selectionKey.interestOps(interestOps | readInterestOp);
        }
    }

JDK SelectionKey 有四种操作类型,分别为:
• OP_READ = 1 << 0;
• OP_WRITE = 1 << 2;
• OP_CONNECT = 1 << 3;
• OP_ACCEPT = 1 << 4。
由于只有四种网络操作类型,所以用 4 bit 就可以表示所有的网络操作位,由于 JAVA 语言没有 bit 类型,所以使用了整形来表示,每个操作位代表一种网络操作类型,分别为:0001、0010、0100、1000, 这样做的好处是可以非常方便的通过位操作来进行网络操作位的状态判断和状态修改,提升操作性能。

由 于 创 建 NioServerSocketChannel 将 readInterestOp 设 置 成 了 OP_ACCEPT,所以,在服务端链路注册成功之后重新将操作位设置为监听客户端的网络连接操作,初始化 NioServerSocketChannel 的代码如下:

    /**
     * Create a new instance using the given {@link ServerSocketChannel}.
     */
    public NioServerSocketChannel(ServerSocketChannel channel) {
        super(null, channel, SelectionKey.OP_ACCEPT);
        config = new NioServerSocketChannelConfig(this, javaChannel().socket());
    }

到此,服务端监听启动部分源码已经分析完成,接下来,让我们继续分析一个新的客户端是如何接入的。

2.3 客户端接入源码分析

负 责 处 理 网 络 读 写、 连 接 和 客 户 端 请 求 接 入 的 Reactor 线 程 就 是NioEventLoop, 下 面 我 们 分 析 下 NioEventLoop 是 如 何 处 理 新 的 客 户 端 连接 接 入 的。 当 多 路 复 用 器 检 测 到 新 的 准 备 就 绪 的 Channel 时, 默 认 执 行processSelectedKeysOptimized 方法,代码如下:

    private void processSelectedKeys() {
        if (selectedKeys != null) {
            processSelectedKeysOptimized(selectedKeys.flip());
        } else {
            processSelectedKeysPlain(selector.selectedKeys());
        }
    }
由 于 Channel 的 Attachment 是 NioServerSocketChannel, 所 以 执 行processSelectedKey 方法,根据就绪的操作位,执行不同的操作,此处,由于监听的是连接操作,所以执行 unsafe.read() 方法,由于不同的 Channel执行不同的操作,所以 NioUnsafe 被设计成接口,由不同的 Channel 内部的NioUnsafe 实现类负责具体实现,我们发现 read() 方法的实现有两个,分别是NioByteUnsafe 和 NioMessageUnsafe, 对于 NioServerSocketChannel,它使用的是 NioMessageUnsafe,它的 read 方法代码如下:
        @Override
        public void read() {
            assert eventLoop().inEventLoop();
            final ChannelConfig config = config();
            if (!config.isAutoRead() && !isReadPending()) {
                // ChannelConfig.setAutoRead(false) was called in the meantime
                removeReadOp();
                return;
            }

            final int maxMessagesPerRead = config.getMaxMessagesPerRead();
            final ChannelPipeline pipeline = pipeline();
            boolean closed = false;
            Throwable exception = null;
            try {
                try {
                    for (;;) {
                        int localRead = doReadMessages(readBuf);
                        if (localRead == 0) {
                            break;
                        }
                        if (localRead < 0) {
                            closed = true;
                            break;
                        }

                        // stop reading and remove op
                        if (!config.isAutoRead()) {
                            break;
                        }

                        if (readBuf.size() >= maxMessagesPerRead) {
                            break;
                        }
                    }
                } catch (Throwable t) {
                    exception = t;
                }
                setReadPending(false);
                int size = readBuf.size();
                for (int i = 0; i < size; i ++) {
                    pipeline.fireChannelRead(readBuf.get(i));
                }

对 doReadMessages 方法进行分析,发现它实际就是接收新的客户端连接并创建 NioSocketChannel 代码如下:

    @Override
    protected int doReadMessages(List<Object> buf) throws Exception {
        SocketChannel ch = javaChannel().accept();

        try {
            if (ch != null) {
                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;
    }

接收到新的客户端连接后,触发 ChannelPipeline 的 ChannelRead 方法,代码如下:

                int size = readBuf.size();
                for (int i = 0; i < size; i ++) {
                    pipeline.fireChannelRead(readBuf.get(i));
                }

执 行 headChannelHandlerContext 的 fireChannelRead 方 法, 事 件 在ChannelPipeline 中传递,执行 ServerBootstrapAcceptor 的 channelRead 方法,代码如下。该方法包含三个主要步骤:
第一步:将启动时传入的 childHandler 加入到客户端 SocketChannel 的ChannelPipeline 中;
第二步:设置客户端 SocketChannel 的 TCP 参数;
第三步:注册 SocketChannel 到多路复用器。channelRead 主要执行如上图所示的三个方法,

        @Override
        @SuppressWarnings("unchecked")
        public void channelRead(ChannelHandlerContext ctx, Object msg) {
            final Channel child = (Channel) msg;

            child.pipeline().addLast(childHandler);

            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);
                }
            }

            for (Entry<AttributeKey<?>, Object> e: childAttrs) {
                child.attr((AttributeKey<Object>) e.getKey()).set(e.getValue());
            }

            try {
                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);
            }
        }

下面我们展开看下 NioSocketChannel 的 register 方法,代码如下所示。

NioSocketChannel 的注册方法与 ServerSocketChannel 的一致,也是将Channel 注册到 Reactor 线程的多路复用器上,由于注册的操作位是 0,所以,此时 NioSocketChannel 还不能读取客户端发送的消息,那什么时候修改监听操作位为 OP_READ 呢,别着急,继续看代码。执行完注册操作之后,紧接着会触发 ChannelReadComplete 事件,我们继 续 分 析 ChannelReadComplete 在 ChannelPipeline 中 的 处 理 流 程:Netty的 Header 和 Tail 本身不关注 ChannelReadComplete 事件就直接透传,执行完 ChannelReadComplete 后,接着执行 PipeLine 的 read()方法,最终执行HeadHandler 的 read()方法,代码如下:

        @Override
        public void read(ChannelHandlerContext ctx) {
            unsafe.beginRead();
        }

后面的代码已经在之前的小节已经介绍过,用来修改网络操作位为读操作,创建 NioSocketChannel 的时候已经将 AbstractNioChannel 的 readInterestOp设 置 为 OP_READ, 这 样, 执 行 selectionKey.interestOps(interestOps |readInterestOp) 操作时就会把操作位设置为 OP_READ。代码如下:

    /**
     * Create a new instance
     *
     * @param parent            the parent {@link Channel} by which this instance was created. May be {@code null}
     * @param ch                the underlying {@link SelectableChannel} on which it operates
     */
    protected AbstractNioByteChannel(Channel parent, SelectableChannel ch) {
        super(parent, ch, SelectionKey.OP_READ);
    }

到此,新接入的客户端连接处理完成,可以进行网络读写等 I/O 操作。

 
备注:本文参考《深入浅出Netty》,作者:李林锋
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值