之前看过不少文章都是从bootstrap讲起来的,这样的好处显而易见,可以较快的对Netty的整体结构,各种功能间的协作关系有一个认知。但是当时自己对selector,channel等都不甚理解,所以看的也是云里雾里。其实这次也是我阅读源码的一个顺序吧,把BootStrap放在了这样一个位置再来学习。
实际上分析完了基础组件再回头看启动过程,其实会发现这个过程也是蛮简单的,我们先来看AbstractBootStrap, 主要成员变量如下
volatile EventLoopGroup group; //线程池 @SuppressWarnings("deprecation") private volatile ChannelFactory<? extends C> channelFactory; // channel工厂 private volatile SocketAddress localAddress; private final Map<ChannelOption<?>, Object> options = new LinkedHashMap<ChannelOption<?>, Object>(); //channel选项 private final Map<AttributeKey<?>, Object> attrs = new LinkedHashMap<AttributeKey<?>, Object>(); //channel属性
那么这里要说明一下的是options和attrs,其中options是关于channel的一些选项,我们可以进入到ChannelOptions里面大致看一下,里面实际上已经给我们定义好了许多的常量,
public static final ChannelOption<Boolean> SO_BROADCAST = valueOf("SO_BROADCAST"); public static final ChannelOption<Boolean> SO_KEEPALIVE = valueOf("SO_KEEPALIVE"); public static final ChannelOption<Integer> SO_SNDBUF = valueOf("SO_SNDBUF"); public static final ChannelOption<Integer> SO_RCVBUF = valueOf("SO_RCVBUF"); public static final ChannelOption<Boolean> SO_REUSEADDR = valueOf("SO_REUSEADDR"); public static final ChannelOption<Integer> SO_LINGER = valueOf("SO_LINGER"); public static final ChannelOption<Integer> SO_BACKLOG = valueOf("SO_BACKLOG"); public static final ChannelOption<Integer> SO_TIMEOUT = valueOf("SO_TIMEOUT");
实际上它决定了channel的一些工作方式,比如说SO_KEEPALIVE便是tcp连接的存活时间。
而attrs是用户自定义的一些属性,可以在挂在在上面随着ctx一起传播。
AbstractBootStrap最重要的功能是提供了绑定地址的方法,首先在
final ChannelFuture initAndRegister() { Channel channel = null; try { channel = channelFactory.newChannel(); init(channel); } catch (Throwable t) { if (channel != null) { channel.unsafe().closeForcibly(); return new DefaultChannelPromise(channel, GlobalEventExecutor.INSTANCE).setFailure(t); } return new DefaultChannelPromise(new FailedChannel(), GlobalEventExecutor.INSTANCE).setFailure(t); } ChannelFuture regFuture = config().group().register(channel); if (regFuture.cause() != null) { if (channel.isRegistered()) { channel.close(); } else { channel.unsafe().closeForcibly(); } } return regFuture; }
方法中,通过channelFactory new出来一个channel,init(channel)为抽象方法交给子类去实现,接下来将channel注册到eventLoopGroup中去,最后返回一个future,源码注释中也给出了为何要使用future而不是直接返回一个channel,原因是:接下来要进行的bind或者connect的动作,必须要在channel已经注册到eventloop以后才能进行,但是register过程并不一定在同一个线程内进行,所以用future来确保进行下一步动作的时候,register动作已经做完。在接下来的代码中,就能体现出future的作用:
// 如果注册动作已经完成,则执行绑定 if (regFuture.isDone()) { ChannelPromise promise = channel.newPromise(); doBind0(regFuture, channel, localAddress, promise); return promise; } else { //否则,监听future绑定动作的完成 final PendingRegistrationPromise promise = new PendingRegistrationPromise(channel); regFuture.addListener(new ChannelFutureListener() { @Override public void operationComplete(ChannelFuture future) throws Exception { Throwable cause = future.cause(); if (cause != null) { promise.setFailure(cause); } else { promise.registered(); doBind0(regFuture, channel, localAddress, promise); } } }); return promise; }
接下来我们来看服务端启动器ServerBootstrap的实现,ServerBootstrap比较的一个特色就是它支持传入两个线程池BossGroup和WorkerGroup,Bossgroup只负责处理connect事件,当新的连接建立好之后,就交给WorkerGroup来处理具体的读写等事件。那么这个过程是怎么实现的,实际上当NioSocket接收到accept事件时,它会调用对应channel的read()方法,首先执行AbstractNioMessageChannel.Unsafe的read()方法,随后执行到子类NioServerSocketChannel的doReadMessages(List<Object> buf)的方法,这里获取到的是一个channel,随后通过fireChannelRead传播事件。而在ServerBootstrap中,它自己实现了一个handler。
public void channelRead(ChannelHandlerContext ctx, Object msg) { final Channel child = (Channel) msg; child.pipeline().addLast(childHandler); setChannelOptions(child, childOptions, logger); 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); } }
其中的read方法将读到的channel注册到child上,即注册到了workerGroup上,所以bossGroup上注册的channel只有服务端channel一个,所以它只要处理此channel上发生的事件就可以了,也就是注册事件。而每一个服务端与客户端之间的连接channel都在workerGroup上注册,所以workerGroup要处理的事情就要多得多了,最后上张图。
大概就是这样子,反正我是懂了。
那么刚才提到AbstractBossgroup会调用子类的init()方法,那么ServerBootstrap中init做的处理也比较简单了,主要就是把刚才我们讲到的handler添加到channel的pipeline上,这里的channel就是tcp服务端绑定的那个端口了。
服务端的启动器Bootstrap相对而言,就更简单了,主要功能便是connect,而connect的过程和Server端一样。首先通过initAndRegister()初始化channel并把它注册到eventLoopGroup上,随后在init中设置channel的options,加上attrs,在pipeline上添加handlers。最后调用对应的unsafe的connect()方法。