内容简介:以下分析只讲NIO使用java nio做网络编程大致流程如下这个流程有哪些可以优化的空间?
以下分析只讲NIO
使用java nio做网络编程大致流程如下
这个流程有哪些可以优化的空间?
Netty是对 java 网络框架的包装,它本身肯定也会有类似的处理流程。必定在这个方面做了自己的优化处理
获得Selector
使用Netty的时候都会用到对应的EventLoopGroup,它实际上就完成了Selector的初始化过程
Netty自定义了SelectionKey的集合,做了层包装,实际将Selector只有1个SelectorKey的集合换成了默认的两个集合
获得Channel
使用Netty时会执行channel的类型,然后在执行bind方法时,此处就会对channel实行初始化
构建的方式为 class.newInstance()
,以NioServerSocketChannel为例,它执行的就是对应的无参构造函数。
public NioServerSocketChannel() { //newSocket即返回java的ServerSocketChannel this(newSocket(DEFAULT_SELECTOR_PROVIDER)); } public NioServerSocketChannel(ServerSocketChannel channel) { //指定当前channel用来接收连接请求,并在父类中指定为非阻塞 super(null, channel, SelectionKey.OP_ACCEPT); //javaChannel()即这里的参数channel config = new NioServerSocketChannelConfig(this, javaChannel().socket()); } 复制代码
紧接着Netty开始channel的初始化,在NioServerSocketChannel的pipeline最后添加了一个 ChannelInboundHandlerAdapter
即 ServerBootstrapAcceptor
,它会执有 childGroup
和 childHandler
,childHandler即用户自定义的channelHandler,而childGroup则是处理请求所用的EventLoop,此时整个pipeline的结构为
childGroup为源码中字段的命名,对应为group中传递的worker线程池
channel的注册与监听端口地址关联
注册即建立channel和Selector的关系,值得注意的是,注册使用的线程池为 group
,对应用户传入的线程池即boss线程池,注册和端口、地址关联则顺着Netty的启动流程进行
至此可以看到,java nio所需要的准备工作都已经准备好了,剩下的就是等待事件发生以及处理发生的事件。与普通java nio的不同之处在于
- Netty准备了两个线程池,channel注册、端口绑定监听的只用到了其中同一个线程池
等待事件发生
NioEventLoop实现了Executor,意味着它接受其它地方提交任务给它执行,execute的大致结构如下
//判断当前正在执行的线程是否是Netty自己的eventLoop中保存的线程 boolean inEventLoop = inEventLoop(); if (inEventLoop) { //往队列里添加任务 addTask(task); } else { //这里即运行NioEventLoop自身的run方法 startThread(); addTask(task); } 复制代码
NioEventLoop启动线程执行run方法,整体结构如下
for (;;) { if (hasTasks()) { selectNow(); } else { select(oldWakenUp); } processSelectedKeys(); runAllTasks(); } 复制代码
run循环处理的流程如下
值得注意的是,这是 单个线程在运行,而且非本线程的任务一概不处理
boss线程的启动时机
在启动的过程中,有ServerBootstrap来串起整个流程,它的执行线程为主线程,而注册事件都是交由线程池自己来执行的,用程序表达来讲,就是执行了eventLoop自己的execute,此时执行线程必定不是EventLoop自己的线程,从而boss中的线程启动,在队列任务中完成注册
新连接请求的到来
当NioServerSocketChannel绑定了端口之后,NioServerSocketChannel对应的NioEventLoop会等待channel发生事件。整个处理流程如下
-
读取消息的内容,发生在NioServerSocketChannel,对于这个新的连接事件,则包装成一个客户端的请求channel作为后续处理
protected int doReadMessages(List<Object> buf) throws Exception { //1:获取请求的channel SocketChannel ch = javaChannel().accept(); try { if (ch != null) { //2:包装成一个请求,Socket channel返回 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; } 复制代码
-
返回的NioSocketChannel则完成自身channel的初始化,注册感兴趣的事件
protected AbstractNioByteChannel(Channel parent, SelectableChannel ch) { super(parent, ch, SelectionKey.OP_READ); } 复制代码
回想到boss中的下一环即 ServerBootstrapAcceptor
,而它读取消息的处理则是添加用户自己的handler,并继续完成注册事件
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); } } 复制代码
worker线程的启动时机
worker的注册发生在boss的线程执行中,此刻必定不是同一个线程,因而开始启动worker的线程,并在内部完成注册事件,等待读消息的到来
OP_read消息处理
连接建立后的请求则是交由 NioSocketChannel
来处理,它将读到的消息封装成ByteBuf,通过 InBound
处理器 fireChannelRead
依次传给其它的地方消费,一直到tailContext消息处理完毕
此处也可以得知管道的 in 表示数据传入netty,回写则是通过 out 一直到Head然后写入channel
Netty中Nio的处理流程
从上述分析可以得到,Netty的处理流程如下
boss是否需要多个线程
mainReactor 多线程配置 ,对于多个端口监听是有益的,当然1个也可以处理多端口
Reactor模式
CPU的处理速度快于IO处理速度,在处理事情时,最佳情况是CPU不会由于IO处理而遭到阻塞,造成CPU的”浪费“,当然可以用多线程去处理IO请求,但是这会增加线程的上下文切换,切换过去可能IO操作也还没有完成,这也存在浪费的情况。
另一种方式是:当IO操作完成之后,再通知CPU进行处理。那谁来知晓IO操作完成?并将事件讲给CPU处理呢?在Reactor模式中,这就是Reactor的作用,它启动一个不断执行的线程来等待IO发生,并按照事件类型,分发给不同的事先注册好的事件处理器来处理
Reactor模式抽象如下
以上所述就是小编给大家介绍的《Netty源码分析之一次请求是如何到达channelRead的?》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- “地震波还有61秒到达”
- 秋招还有 1 个月到达战场,请做好准备 !
- 终于来了!Istio 1.0 还有 5 天到达战场!
- 老王,Laravel 的请求怎么一步步到达控制器的?
- 哦屋~如此优化能使你项目的速度到达一个逼格!
- Safe.js 3.1.0 发布,8点准时到达!增加双向绑定!
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。