代码改变世界

Netty学习笔记——Server端初始化

2019-11-03 21:08  CeddieCoding  阅读(499)  评论(0)    收藏  举报

这段时间一直在学习Netty,在通过《Netty实战》一书学习了一些Netty的基础知识之后,便开始写一些简单的Server-Client交互Demo。一个Server端的代码大致如下:

 1 EventLoopGroup bossGroup = new NioEventLoopGroup();
 2 EventLoopGroup workerGroup = new NioEventLoopGroup();
 3 try {
 4     ServerBootstrap b = new ServerBootstrap();
 5     b.group(bossGroup, workerGroup)
 6      .channel(NioServerSocketChannel.class)
 7      .childHandler(new SimpleChatServerInitializer())
 8      .option(ChannelOption.SO_BACKLOG, 128)
 9      .childOption(ChannelOption.SO_KEEPALIVE, true);
10 
11     ChannelFuture f = b.bind(port).sync();
12     f.channel().closeFuture().sync();
13 } finally {
14     workerGroup.shutdownGracefully();
15     bossGroup.shutdownGracefully();
16 }     

其中ServerBootStrap相当于Server端的引导,由其group方法指定了事件处理的线程组,每个group中都存在多个EventLoop,每个EventLoop都和某个Thread绑定,处理多个Channel(可以看做是Socket)。一旦一个Channel被分配给一个EventLoop,它将在它的整个生命周期中都是用这个EventLoop,这是Netty的基本线程模型。

理解了以上概念后,我在网上找了一些例子进行学习,发现大多数示例代码都通过重写io.netty.channel.ChannelHandlerAdapter.handlerAdded(ChannelHandlerContext)方法,来对连接Server的Client进行注册。从方法名字上看,似乎是当handler添加时被调用的回调,与Client的Channel毫不相干,所以它是如何生效的呢?带着这个疑问,我开始了源码的学习:

首先,从ChannelInitializer开始看,这个抽象类继承了ChannelInboundHandlerAdapter这个适配器类,所以它也算是一个Handler,它有一个需要被实现的抽象方法,initChannel,从调用栈上可以看到,当handlerAdded被调用时,此方法也同时被调用:

1 public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
2     if (ctx.channel().isRegistered()) {
3         initChannel(ctx);
4     }
5 }

handlerAdded方法是在ChannelPipeline调用其addFirst/addLast方法时被调用(也有许多其他调用,但这两个方法最常用)。回过头来看,在引导Server端时调用了bind方法,进而调用doBind方法:

1 private ChannelFuture doBind(final SocketAddress localAddress) {
2         final ChannelFuture regFuture = initAndRegister();
3         ...
4 }

doBind方法中的第一行调用了initAndRegister方法,在这个方法中又调用了init方法:

 1 void init(Channel channel) throws Exception {
 2     ...
 3     ChannelPipeline p = channel.pipeline();
 4 
 5     final ChannelHandler currentChildHandler = childHandler;
 6     ...
 7     p.addLast(new ChannelInitializer<Channel>() {
 8         @Override
 9         public void initChannel(Channel ch) throws Exception {
10             final ChannelPipeline pipeline = ch.pipeline();
11             ChannelHandler handler = config.handler();
12             if (handler != null) {
13                 pipeline.addLast(handler);
14             }
15             ch.eventLoop().execute(new Runnable() {
16                 @Override
17                 public void run() {
18                     pipeline.addLast(new ServerBootstrapAcceptor(
19                         currentChildGroup, currentChildHandler, currentChildOptions, currentChildAttrs));
20                 }
21             });
22         }
23     });
24 }

其中,childHandler就是ServerBootStrap传入childHandler方法中的参数,p代表了与NioServerSocketChannel绑定的ChannelPipeLine。init方法向p中添加了一个ChannelInitializer,我在上面提到,当ChannelPipeline调用其addFirst/addLast方法时,handlerAdded会被调用,从而initChannel方法会被调用(此处不会被直接调用,而是延迟调用,但最终总会被调用)。在initChannel方法中,用NioServerSocketChannel的eventLoop(线程)执行了任务:向该pipeline中添加ServerBootstrapAcceptor,而此对象也是一个handler,重写了channelRead方法:

1 public void channelRead(ChannelHandlerContext ctx, Object msg) {
2     final Channel child = (Channel) msg;
3     child.pipeline().addLast(childHandler);
4     ...
5 }

由此可以看到,当有Client连接到Server时,Client的channel会调用其绑定pipeline的addLast方法,添加childHandler,而childHandler在前面提到过,就是ServerBootStrap传入childHandler方法中的参数,而此handler(或者说initializer)通过ChannelHandlerContext获取到pipeline,调用其addLast方法添加了自定义的handler,此pipeline就是Client的channel绑定的pipeline,当addLast方法被调用时,自定义handler的handlerAdded被调用,所以我们可以通过重写该方法来对连接Server的Client进行注册