本文介绍netty server的启动流程,以NioServerSocket.class为例
记录自己遇到的一个问题:我们通过bootstrap.handler里通过initializer handler给serverChannel添加了一个NettyTmpHandler,然后这个handler实现了channelRead,初始化完成后initializer handler会从pipeline中去除,然后当客户端连接到来时会触发read事件,然后流程会进入NettyTmpHandler.channelRead函数,但是我们的NettyTmpHandler没有调用super.channelRead,super.channelRead函数会把事件继续往下传,但是我们没有调用就意味着read流程在我们这里就终止了,因为建立连接是其他handler实现的(我猜的,这一块还不太懂),这样就意味着连接得不到建立,既然连接得不到建立,那么ChildHandler中的initializer Handler就不会被调用,既然不会被调用,那么我们在childhandler里面打断点就没用,且client发过来的消息server也收不到。之所在这里卡住,以为连接建立后就和serverchannel的handler无关了,因为client对应的clientchannel是由workgroup管理的。。谁知道这个问题的原因是在连接建立前。。。。术业不精害死人。。。。!!!还有,在workgoup中创建clientChannel的流程和serverchannel的创建一模一样,也要initAndRegister也要doBegin,区别只是eventloop从bossgroup换到了workgroup,handler换到了childhandler
对象总结:netty NioServerSocketChannel是对java nio 的channel的一个封装,java nio channel是对socket的一个封装,一个NioServerSocketChannel内部含有一个pipeline,pipeline是一条双向链表,链表中每一个节点是一个ChannelHandlerContext,ChannelHandlerContext是对handler的一个封装
本文介绍netty server的启动流程,以NioServerSocket.class为例
刚接触java netty,本文为此系列第一篇,所以还有一些遗漏,以及一些不确定的地方,比如pipeline的inbound和outbound以及pipeline的head和tail,这一块还有些疑问,后续会补充完善。
netty源码版本为4.1
<dependencies>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-transport</artifactId>
<version>4.1.110.Final</version>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.108.Final</version>
</dependency>
</dependencies>
我们自己写的客户端代码:
EventLoopGroup bossGroup = new NioEventLoopGroup(10);
EventLoopGroup workGroup = new NioEventLoopGroup(10);
ServerBootstrap bootstrap = new ServerBootstrap(); //配置对象
try {
bootstrap.group(bossGroup, workGroup)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 1024)
//childHandler的含义是来了一个socketChannel后,给这个socketChannel添加一个handler
//如果是handler,则直接处理socket事件
.handler(new ChannelInitializer<NioServerSocketChannel>() {
@Override
protected void initChannel(NioServerSocketChannel socketChannel) throws Exception {
socketChannel.pipeline().addLast(new NettyTmpHandler());
}
})
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
socketChannel.pipeline().addLast(new NettyServerHandler_simple_msg_b());
}
});
ChannelFuture cf2 = bootstrap.bind(port);
ChannelFuture cf = cf2.sync();
cf.channel().closeFuture().sync();
代码流程:入口点:bind。
这里以NioServerSocket为例即bootstrap.group(bossGroup, workGroup).channel(NioServerSocketChannel.class)即这里传递NioServerSocketChannel给bootstrap
AbstractBootStrap.bind #绑定端口并启动监听:1、监听客户端连接并建立连接;2、监听客户端连接,并处理各种事件如读写
AbstractBootStrap.doBind #分两个步骤:1:create channel、init channel、register channel;2:bind(channel,port,OP_ACCEPT)、发送channelActive事件
AbstractBootStrap.initAndRegister #1:创建一个NioserverSocketChannel对象(这个类由bootstrap.channel方法指定,我们这里以NioServerSocektChannel为例)
2:初始化该channel。
3:把它注册到到一个eventloop线程的selector中,这个eventLoop线程是bossgroup中的一个线程
ReflectiveChannelFactory.newChannel #1:创建channel,netty的channel是对java channel的一个封装,java channel又是对socket的一个封装
#bootstrap.channel函数接受一个class参数,不同的class对应不同的输入流,这里通过反射来创建对应类型的serverSocketChannel
#不同的输入流有tcp流、文件流、udp流,NioServerSocketChannel对应的是tcp流
new NioServerSocketChannel() #NioServerSocketChannel的默认构造函数,这里继续调用其他构造函数,selectorProvider最终是java nio 框架的selectorProvider
#SelectorProvider就是用来返回一个selector,selector的具体实现肯定是依赖于平台的,比如linux平台可以nio框架可以选择select、poll、epoll
......
NioServerSocketChannel.newChannel
SelectorProviderImpl.openServerSocketChannel #这个SelectorProver是java nio包里自带的一个selectorProver,会自动根据平台选择合适的底层SelectorProver
#也就是说NioServerSocketChannel内部包含了一个java nio框架提供的socket对象,
#也就是说netty是对java nio的一个封装,所有的操作最终都会转发给底层的java nio框架对象
new ServerSocketChannelImpl() #创建一个java channel,java channel本质上就是对socket的一个包装
if (family == UNIX) { #unix协议簇
this.fd = UnixDomainSockets.socket();
} else { #其他协议簇
this.fd = Net.serverSocket(family, true);
}
NioServerSocketChannel #netty里一个构造调用另一个构造
AbstractNioMessageChannel(channel,SelectionKey.OP_ACCEPT) #!!这里只是把OP_ACCEPT保存到channel 内部,并没有注册到selectionKey
#此时只是创建channel对象
#netty里面channel对象和selector对象是两个单独的对象,selector对象保存在eventloop内部
#所以说后续我们还需要一个步骤把channel对象register到eventloop中
#register的意思就是说把这个channel注册到这个eventloop的selector中
#register的时候并不会为这个channel添加感兴趣的事件,那是绑定阶段的事情
AbstractNioChannel() #父类构造函数
ch.configureBlocking(false) #默认channel是非阻塞的
new NioServerSocketChannelConfig(channel.serverSocket) #创建一个NioServerSocketChanelConfig对象,并把刚才创建的java channel内部的socket传递给cfg
#还会设置autoread=true
DefaultServerSocketChannelConfig(socket,ServerChannelRecvByteBufAllocator) #父类构造函数,把刚创建的channel对象和一个bytebufallocator对象用cfg对象绑定起来
#这个byteallocator负责recv bytebuf缓冲区的管理(目前不太懂)
#即netty的NioServerSocketChannel内部有一个java channel
#NioServerSocketChannel内部还有一个readbuf
#注意readbuf不是这个bytebufallocator,不知道这个allocator干嘛的
#readBuf是一个List<Object>
NioServerSocketChannel.init #2:初始化该channel
#初始化包括3个操作:1、初始化一些参数;2、给channel内部的pipeline添加handler(注意,这个是handler不是childhandler);
#3:netty扩展机制(默认不启用)
#因为bossGroup有多个线程,每个线程可以对应一个serversocketchannel(即一个selector和eventLoop),
#这个handler就是每当serverSocketChanel上发生事件的时候就为这个channel对象调用handler集合中的对应函数
#这些事件包括新建serverSocketChannel、registered、active、收到客户端连接(会触发read)等。。。。。。
#也就是说bossGroup里面的channel有一条pipeline,然后workgroup里面的channel也有一条pipeline
#添加的是一个channelInitializer handler,这个handler做两件事:
#1:把bootStrap.handler函数中我们自己添加的handler添加到socket对应的pipeline中;
#2:异步给这个channel添加一个acceptor
#笔记:
#1:channelInitializer handler只会执行一次,执行完之后就会把自己从pipeline中删除
#2:addLast函数在添加handler之后还会自动调用该handler的handlerAdded方法,我们实现自己的handler时可以重写这个方法实现一些自定义逻辑
...初始化一些变量...略......
p.addLast(new ChannelInitializer<Channel>() { #2:通过addLast添加一个handler,这里就表明ChannelInitializer其实也是一个handler
#这里只是保存,并没有执行,当在bind阶段把NioServerSocketChannel添加到eventLoop的selector后
#执行DefaultChannelPipeline.invokeHandlerAddedIfNeeded时
#才会调用这个ChannelInitializer handler的handlerAdded方法,这个added方法最终会调用这个initChannel方法
@Override
public void initChannel(final Channel ch) {
final ChannelPipeline pipeline = ch.pipeline();
ChannelHandler handler = config.handler(); #cfg.handler就是我们通过bootstrap.handler保存的handler
if (handler != null) {
pipeline.addLast(handler);
}
ch.eventLoop().execute(new Runnable() {
@Override
public void run() {
pipeline.addLast(new ServerBootstrapAcceptor) #往pipeline添加一个acceptor
#这个acceptor负责负责把serverchannel获取到的NioSocketServer丢到child也就是workGroup
#我们通过bootstrap.handler添加的handler都会在acceptor之前执行
}
});
}
});
ChannelInitializerExtension.postInitializeServerListenerChannel #3:netty扩展机制,默认不启用,这里略
future=MutilthreadEventLoopGroups.register #把channel和group中的某一个线程绑定起来(就是添加到该线程的selector中)
#register是一个异步的操作,这里返回一个registerFuture
MutilthreadEventLoopGroups.next #next().register(channel) ,netxt找到mutilthreadEventLoopGroup的下一个线程(即一个EventLoop线程),默认是轮询
#然后register把NioServerSocketChannel添加到该eventloop线程内部的selector中
#mutilthreadEventLoopGroup(就是bossgroup)内部有一个或多个线程,每个线程是一个singleThreadEventLoop
#一个channel只需要一个线程,所以需要从group中选择一个线程,默认是轮询
#笔记:
#1:一个netty bootstrap只会创建唯一的一个用来接收客户端连接的channel,
#即使bossgroup有多个线程,也只会把这个唯一的channel和其中一个线程绑定起来,其他的线程都不会用到
#2:boosgroup线程数大于1,网上说是多开端口用的,也就是创建多个bootstrap,也就是相当于创建多个Netty Server,
#然后创建时传递同一个bossGroup对象,这样就可用一个bossGroup来统一管理多个nettyServer
#3:bossGroup中的一个eventloop线程中的selector可以添加多个NioServerSocketChannel,
#bossGroup就相当于用一个线程池来管理多个NioServerSocketChannel,这些channel都是用来接受客户端请求的
#所以在这种情况下bossGroup就相当方便了:创建多个nettyServer,绑定到不同的端口,然后这些nettyServer实际对应的都是同一个程序
#这样多开端口就增强了程序的健壮性吧或者不同的端口对应同一程序的不同的功能。
SingleThreadEventLoop.register #把channel添加到本线程内部的selector中,2:绑定端口;3:并进行一些绑定以后的操作
AbstractChannel.unsafe.register
AbstractChannel.unsafe.this.eventLoop = eventLoop #一个singleThreadEventLoop对应一个eventloop,
#这里就是把前面创建的nioServerSocketChannel和bossgroup中的一个eventLoop对应起来
#一个eventLoop可以理解为一个事件循环+一个selector
AbstractChannel.unsafe.register0 #会把register0这个操作封装成一个task丢到channel对应的eventLoop中去异步执行,
#!!eventLoop在第一次执行execute的时候会调用 startThread()来启动整个eventloop,我们这里略
#当把eventloop启动后,eventloop就处于for-select死循环中,
#然后把serverchannel添加到eventloop的selector并注册OP_ACCEPT事件,
#那么下一次eventloop循环时就可以接收客户端连接了,
#所以server实际就是一个eventloop,server的启动就是往这个eventloop里添加serverChannel和OP_ACCEPT事件
#最开始channel还没有和eventloop线程对应起来,所以此时是异步执行,但是当进入eventloop执行后,
#后续的任务就直接在eventloop线程执行了,此时inEvenloop就会返回true,此时就无需再封装成异步任务
#然后通过promise等待结果,这里直接跳到register0
AbstractNioChannel.doRegister #这里就是把nioserverSocketChannel注册到eventloop对应的selector中
NioEventLoop.unwrappedSelector #获取eventloop对应的selector
AbstractSelectableChannel.register #前面创建的NioServerSocketChannel是AbstractSelectableChannel的一个子类
AbstractSelector.register #把channel注册到selector中。注意:注册前会检查是否存在
#!!!!此时仅仅是把channel添加到selector,并没有添加任何channel感兴趣的事件即OP_ACCEPT事件
#也就是说此时的channel是不会被激活的
registered = true #标记channel已经注册到了handler
DefaultChannelPipeline.invokeHandlerAddedIfNeeded #把channel添加到selector后就差不多可以开始了,但是接下来还需要先做两件事:
#1:调用pipeline中handler的handlerAdded方法来初始化一个handler
#2:调用pipeline的handler的channelRegistered方法
#invokeHandlerAddedIfNeed就是做第一件事即调用挂起的handler的handlerAdded方法
#前面netty源码在在NioServerSocketChannel.init中调用了pipeline.addLast方法
#addLast添加一个handler后会自动调用handler的handlerAdded方法
#但是此时还没有register,所以会把这些包装成一个任务丢到一个pending队列
#等register之后再调用
#这个addLast方法添加了一个ChannelInitializer handler,
#ChannelInitializer的handleradded函数会调用initChannel函数
#initChannel函数又会通过addLast把我们通过bootstrap.handler添加的handler添加到pipeline中
#添加完后又会调用handler的handlerAdded方法,而我们自己添加的handler又是一个ChannelInitializer
#所以他添加完后又会调用ChannelInitializer的handlerAdded方法,
#handlerAdded方法最终会调用initChannel方法,而我们实现的initChannel方法里又
#通过addLast添加了一个NettyTmpHandler,所有他又会调用NettyTmpHandler的handlerAdded方法
#就像无限套娃一样。。。调用完后ChannelInitializer的handlerAdded方法最后会把自身
#从pipeline中删除,因为ChannelInitializer只需要执行一次。
DefaultChannelPipeline.callHandlerAddedForAllHandlers #执行先前所有挂起的handler的Added方法,一个added对应一个pendinghandlercallback任务
PendingHandlerCallback.execute #netty handler的相关方法的执行都是这样一个逻辑:
#判断当前调用线程是不是handler对应的io线程?如果是,直接执行
#如果不是,则封装成一个任务,丢到handler对应的io线程中去
#暂不清楚为啥一定要丢到对应的io线程
if (executor.inEventLoop()) {
callHandlerAdded0(ctx);
} else {
executor.execute(this);
}
AbstractUnsafe.safeSetSuccess #MutilthreadEventLoopGroups.register是一个异步操作,这里表示register操作已经完成
#所以这里通知对应的future register事件已经完成
#future收到完成的消息后就会异步去把channel和一个端口绑定起来
DefaultChannelPipeline.fireChannelRegistered #fireXXX,xxx表示各种各样的事件,fire xxxx的意思是把这个xxxx事件丢到pipeline,
#然后pipeline中的handler都会调用一次xxxx方法
#这里是ChannelRegistered事件,所以就会调用一次所有的handler的ChannelRegistered事件
#fireXXX事件的处理流程都一样:
#1:从当前位置开始,找到第一个对XXX事件感兴趣的in或者out handler
#2:执行XXX事件对应的方法
#3:根据自己需要来决定执行完当前handler后要不要执行在这之后的其他handler
#如果需要,则必须再次调用fireXXX方法,就是java doChain一样的思路
#笔记:我们自己实现handler的时候,如果不会拦截事件,那么就必须再次调用super.xxx方法
#比如对于registered事件,他会调用我们自己实现的handler的channelRegistered方法
#然后我们处理完后调用super.channelRegistered方法继续往后传递事件,否则就到此为止
AbstractChannelHandlerContext.findContextInbound(MASK_CHANNEL_REGISTERED) #找到第一个可以处理registered事件的handler
AbstractChannelHandlerContext.invokeChannelRegistered #pipeline就是一条链表,然后这里就是从头开始调用所有handler的Registered方法
#outbound就是从tail开始调用
EventExecutor executor = next.executor(); #默认为serverChannel对应的eventloop线程
if (executor.inEventLoop()) { #一样的,如果当前线程是handler对应的io线程就直接在本线程调用,反之丢到io线程去异步执行
next.invokeChannelRegistered();
} else {
executor.execute(new Runnable() {
@Override
public void run() {
next.invokeChannelRegistered();
}
});
}
regFuture.addListener(new ChannelFutureListener() {
public void operationComplete(ChannelFuture future){ #当register完成后,AbstractUnsafe.safeSetSuccess函数就通知此处开始异步绑定port和channel
AbstractBootStrap.doBind0(regFuture, channel, localAddress, promise);
}
}
});
AbstractBootStrap.doBind0 #这里是doBind0的展开,做两件事:
#1:绑定端口;2:绑定端口后通知channel做一些事情
AbstractBootStrap.bind(InetAddress) #默认是0.0.0.0:xxxx
AbstractChannel.bind #channel.bind实际就是pipeline.bind
DefaultChannelPipeline.bind #暂不知道这里为啥是tail.bind
AbstractChannelHandlerContext.bind
AbstractChannelHandlerContext.invokeBind #这里同样会丢到对应的io线程去执行
HeadContext.bind
AbstractUnsafe.bind
NioServerSocketChannel.doBind #把netty创建的接受用户请求的channel和一个地址绑定起来,地址包括(ip,port)
ServerSocketChannel.bind #NioServerSocketChannel的bind实际就是NioServerSocketChannel内部的java nio框架的ServerSocketChannel的bind操作
NioServerSocketChannel.isActive #判断是不是激活状态
AbstractUnsafe.invodeLater #如果是第一次激活,则需要进行一些处理,这里是封装成一个任务丢到io线程去异步处理
this.eventloop().execute() #默认为channel对应的eventloop线程
#一个eventloop包括一个selector、一个eventloop
DefaultChannelPipeline.fireChannelActive() #第一次激活后需要进行一些处理,这里把事件发送到pipeline
#pipeline的fireChannelActive就是直接调用第一个handler的invokeChannelActive,
#即invokeChannelActive(head)
AbstractChannelHandlerContext.invokeChannelActive(head) #把这个事件丢到pipeline的head节点,然后会依次调用所有的handler
AbstractChannelHandlerContext.invokeChannelActive #重载函数
HeadContext.channelActive #headContext的处理会特殊一点
#pipeline中head和tail是特殊的节点,xxx事件直接从head.next/tail.pre开始执行
AbstractChannelHandlerContext.fireChannelActive #headContext直接把事件丢到他的下一个节点。
AbstractChannelHandlerContext.findContextInbound(MASK_CHANNEL_ACTIVE) #找到当前节点之后的第一个对active感兴趣的handler
AbstractChannelHandlerContext.invokeChannelActive #调用找到的handler对应的active方法
HeadContext.readIfIsAutoRead #headContext要做的就是这个readIfAutoRead方法
#他是先发送事件,然后再处理即等到所有handler处理完后再readIfIsAutoRead
#这个readIfAutoRead实际就是开始接受请求,所以在此之前的handler的对应事件都要处理完
#笔记:即使是发送到其他线程,那么这个readIfAutoRead肯定也是发送到其他eventloop线程
#而eventloop线程的异步队列也是fifo的(应该是的,还未确认),
#所以还是可以保证先执行完其他handler的方法,然后再执行readIfAutoRead开放连接
#个人猜测:应该数据的读取也是pipeline来处理而不是先读取数据然后再丢到pipeline
#所以这里的headContext就直接调用channel.read来读取
#chanel.read就是调用pipeline.read,
#pipeline的read就是从第一个outbound开始read
AbstractChannel.read #开放channel,即在selector中为serverchannel添加accept事件
#一旦添加accept事件,channel就可以accept连接了
#个人猜测:在开放accept之前的连接应该是都放在back队列的
DefaultChannelPipeline.read
AbstractChannelHandlerContext.findContextOutbound(MASK_READ) # 从tail开始找到第一个读取的out handler
#笔记:注意findContext InBound和OutBound
HeadContext.read #最终会来到headContext,headContext的处理照样是最后一步
AbstractUnsafe.beginRead
AbstractNioMessageChannel.doBeginRead
AbstractNioChannel.doBeginRead
selectionKey.interestOps(interestOps | readInterestOp) #给channel添加OP_ACCEPT事件
#op_accept就是我们创建NioServerSocketChannel时保存的
#此后就可以接受accept事件了,自此server启动完毕
#启动完毕后就进入到eventloop了