如下分析只讲NIOjava
使用java nio作网络编程大体流程以下react
这个流程有哪些能够优化的空间?git
java nio使用简介
java nio 启动源码分析github
Netty是对java网络框架的包装,它自己确定也会有相似的处理流程。一定在这个方面作了本身的优化处理编程
使用Netty的时候都会用到对应的EventLoopGroup,它实际上就完成了Selector的初始化过程网络
Netty自定义了SelectionKey的集合,作了层包装,实际将Selector只有1个SelectorKey的集合换成了默认的两个集合多线程
使用Netty时会执行channel的类型,而后在执行bind方法时,此处就会对channel实行初始化框架
构建的方式为
class.newInstance()
,以NioServerSocketChannel为例,它执行的就是对应的无参构造函数。socket
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和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循环处理的流程以下
值得注意的是,这是单个线程在运行,并且非本线程的任务一律不处理
在启动的过程当中,有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的注册发生在boss的线程执行中,此刻一定不是同一个线程,于是开始启动worker的线程,并在内部完成注册事件,等待读消息的到来
链接创建后的请求则是交由NioSocketChannel
来处理,它将读到的消息封装成ByteBuf,经过InBound
处理器fireChannelRead
依次传给其它的地方消费,一直到tailContext消息处理完毕
此处也能够得知管道的 in 表示数据传入netty,回写则是经过 out 一直到Head而后写入channel
从上述分析能够获得,Netty的处理流程以下
mainReactor 多线程配置 ,对于多个端口监听是有益的,固然1个也能够处理多端口
CPU的处理速度快于IO处理速度,在处理事情时,最佳状况是CPU不会因为IO处理而遭到阻塞,形成CPU的”浪费“,固然能够用多线程去处理IO请求,可是这会增长线程的上下文切换,切换过去可能IO操做也尚未完成,这也存在浪费的状况。
另外一种方式是:当IO操做完成以后,再通知CPU进行处理。那谁来知晓IO操做完成?并将事件讲给CPU处理呢?在Reactor模式中,这就是Reactor的做用,它启动一个不断执行的线程来等待IO发生,并按照事件类型,分发给不一样的事先注册好的事件处理器来处理
Reactor模式抽象以下