前面我们分析了netty内存池、线程模型,比较难的两个点已经被一一消化,接下来我们开始进入大家最关心的环节,总体流程分析。 这里我选了io.netty.example.http.snoop来作为分析的入口,分别从server端、client端的维度来看看netty是如果设计的。这里你将了解比较详细的netty处理流程,让你在今后的应用中不再感到疑惑 。 如果还有不清楚的地方,可以直接交流,通过交流发现问题,并不断完善这系列文章。 本文假设你对netty已有大致了解,需要更深入的了解它的运作流程。如果是初学者,可能会发现头晕、眼花、脑抽经等症状。
配置篇
首先看看snoop包的server端启动代码。 在netty中,不管是server还是client,都是由引导类进行启动。在启动之前需要先做好各种参数的配置。可以配置的参数如下:
字段 | 类型 | 说明 | server模式 | client模式 |
options | Map | channel的配置项 | 作用于ServerChannel | |
childOptions | Map | channel的配置项 | 作用于Channel | |
attrs | Map | 自定义的channel属性 | 作用于ServerChannel | 作用于Channel |
childAttrs | Map | 自定义的channel属性 | 作用于Channel | |
handler | ChannelHandler | 连接处理器 | 作用于ServerChannel | 作用于Channel |
childHandler | ChannelHandler | 连接处理器 | 作用于Channel | |
group | EventLoopGroup | 注册并处理连接 | 作用于ServerChannel | 作用于Channel |
childGroup | EventLoopGroup | 注册并处理连接 | 作用于Channel | |
channelFactory | ChannelFactory | 生成连接对象的工厂类 | 生成ServerChannel | 生成Channel |
我们知道netty采用了reactor的设计模式,其中mainReactor主要负责连接的建立,连接建立后交由subReactor处理,而subReactor则主要负责处理读写等具体的事件。这里mainReactor的实际执行者是bossGroup,而subReactor的实际执行者则是workerGroup。 下面是HttpSnoopServer类中main方法的主要代码(去掉了一部分)
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.handler(new LoggingHandler(LogLevel.INFO))
.childHandler(new HttpSnoopServerInitializer(sslCtx));
Channel ch = b.bind(PORT).sync().channel();
ch.closeFuture().sync();
} finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
这里bossGroup只启用了一个线程,因为一个端口只能创建一个ServerChannel,该ServerChannel的整个生命周期都在bossGroup中。如果你想用同一个ServerBootstrap启动多个端口,则bossGroup的大小需要根据启动的端口数调整。handler设置为LogginHandler,表示在ServerChannel的处理链中加入了日志记录(这个与客户端连接无关,即它只记录ServerChannel的注册、注销、关闭等,而不会记录客户端连接的相应事件。之前有同学加了LoggingHandler而没看到客户端的相应日志,就是这样了。需要的话要在childHandler的Initializer中加入LoggingHandler)。 childHandler设置为HttpSnoopServerInitializer,即用户连接使用HttpSnoopServerInitializer进行处理。
初始化完成开始调用bind(port)方法,bind首先会对各个参数进行验证,如channelFactory是否设置,group、childGroup是否设置,端口是否设置等,验证通过后,最终调用doBind方法(AbstractBootstrap中)。
private ChannelFuture doBind(final SocketAddress localAddress) {
// 初始化并注册Channel(此时是ServerChannel)
final ChannelFuture regFuture = initAndRegister();
final Channel channel = regFuture.channel();
// 如果注册出错则直接返回
if (regFuture.cause() != null) {
return regFuture;
}
// 注册完成调用doBind0,否则添加一个注册事件的监听器,该监听器在监听到注册完成后也会触发doBind0操作
if (regFuture.isDone()) {
// At this point we know that the registration was complete and successful.
ChannelPromise promise = channel.newPro