开篇
Netty的启动类分为客户端启动类和服务端启动类,分别是BootStrap和ServerBootStrap。它们都是AbstractBootStrap的子类,总的来说它们都是Netty中的辅助类,提供了链式配置方法,方便了Channel的引导和启动。
- BootStrap的举例用法:
// Configure the client.
EventLoopGroup group = new NioEventLoopGroup();
try {
Bootstrap b = new Bootstrap();
b.group(group)
.channel(NioSocketChannel.class)
.handler(new HttpSnoopClientInitializer(sslCtx));
// Make the connection attempt.
Channel ch = b.connect(host, port).sync().channel();
// Prepare the HTTP request.
HttpRequest request = new DefaultFullHttpRequest(
HttpVersion.HTTP_1_1, HttpMethod.GET, uri.getRawPath(), Unpooled.EMPTY_BUFFER);
request.headers().set(HttpHeaderNames.HOST, host);
request.headers().set(HttpHeaderNames.CONNECTION, HttpHeaderValues.CLOSE);
request.headers().set(HttpHeaderNames.ACCEPT_ENCODING, HttpHeaderValues.GZIP);
// Set some example cookies.
request.headers().set(
HttpHeaderNames.COOKIE,
ClientCookieEncoder.STRICT.encode(
new DefaultCookie("my-cookie", "foo"),
new DefaultCookie("another-cookie", "bar")));
// Send the HTTP request.
ch.writeAndFlush(request);
// Wait for the server to close the connection.
ch.closeFuture().sync();
} finally {
// Shut down executor threads to exit.
group.shutdownGracefully();
}
- ServerBootStrap的举例用法:
// Configure the server.
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();
System.err.println("Open your web browser and navigate to " +
(SSL? "https" : "http") + "://127.0.0.1:" + PORT + '/');
ch.closeFuture().sync();
} finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
-
BootStrap相关类在Netty源码中的工程结构和类结构图
Netty作为客户端的引导启动分析
引导启动代码如下:
Bootstrap b = new Bootstrap();
b.group(group)
.channel(NioSocketChannel.class)
.option(ChannelOption.SO_KEEPALIVE, true);
.handler(new HttpSnoopClientInitializer(sslCtx));
// Make the connection attempt.
Channel ch = b.connect(host, port).sync().channel();
-
group()
方法是设置处理channel事件的线程池。
public B group(EventLoopGroup group) {
ObjectUtil.checkNotNull(group, "group");
if (this.group != null) {
throw new IllegalStateException("group set already");
}
this.group = group;
return self();
}
-
channel()
方法就是设置引导启动的Channel类型,参数为类型Class,作为客户端时的Channel类型为NioSocketChannel,后续在调用connect()方法时会根据这个类型通过反射获取到Channel实例。 -
option()
方法是设置channel的属性,比如常见的属性有
// 链路是否保活
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");
关于其他属性的说明,后面会专门写一篇文章说明。
-
handler()
方法是设置处理请求使用的channelHandler。一般在这里会设置成一个特殊的handler,即ChannelInitializer
,下面来看一下这个handler
public abstract class ChannelInitializer<C extends Channel> extends ChannelInboundHandlerAdapter
通过类的定义可以看出来它确实是一个handler,用于处理异步事件。当一个handler加入到ChannelHandlerContext
中,准备好开始处理事件时,会回调到handlerAdded
方法
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
if (ctx.channel().isRegistered()) {
if (initChannel(ctx)) {
removeState(ctx);
}
}
}
在这个方法中,会调用initChannel
private boolean initChannel(ChannelHandlerContext ctx) throws Exception {
if (initMap.add(ctx)) { // Guard against re-entrance.
try {
// 子类实现这个方法,用于向pipeline中添加handler
initChannel((C) ctx.channel());
} catch (Throwable cause) {
exceptionCaught(ctx, cause);
} finally {
ChannelPipeline pipeline = ctx.pipeline();
if (pipeline.context(this) != null) {
// 执行完成后,将自己从pipeline中移除,因为这个handler的使命已完成
pipeline.remove(this);
}
}
return true;
}
return false;
}
可以看到,在调用子类实现的initChannel后,这个handler会主动调pipeline的remove方法将自己从pipeline中移除,因为它的使命已完成。
-
connect()
方法即真正的开始同对端建立连接。前面所说的通过反射创建Channel、设置channel属性、将Channel注册到EventLoop线程等都是在connect时执行的。connect会调用doResolveAndConnect方法
private ChannelFuture doResolveAndConnect(final SocketAddress remoteAddress, final SocketAddress localAddress) {
final ChannelFuture regFuture = initAndRegister();
final Channel channel = regFuture.channel();
看下这里的initAndRegister
final ChannelFuture initAndRegister() {
Channel channel = null;
try {
// 创建channel
channel = channelFactory.newChannel();
// 设置channel属性
init(channel);
} catch (Throwable t) {
// 次要分支代码省略
}
// 将channel注册到EventLoop线程
ChannelFuture regFuture = config().group().register(channel);
if (regFuture.cause() != null) {
if (channel.isRegistered()) {
channel.close();
} else {
channel.unsafe().closeForcibly();
}
}
主要的处理逻辑已经在代码中注释,不再赘述。回到doResolveAndConnect方法,接下来会调用doResolveAndConnect0和doConnect方法真正执行建链操作。
Netty作为服务端的引导启动分析
引导启动代码如下:
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 1024)
.childOption(ChannelOption.SO_RCVBUF, 1024 * 256)
.childOption(ChannelOption.SO_SNDBUF, 1024 * 256)
.handler(new LoggingHandler(LogLevel.INFO))
.childHandler(new HttpSnoopServerInitializer(sslCtx));
Channel ch = b.bind(PORT).sync().channel();
服务端的链式配置方法和客户端基本是一样的,来看下差异点
-
group()
方法有两个参数,分别叫parentGroup和childGroup,一个是处理建链请求的EventLoop线程池和一个是处理建链后的读写请求的EventLoop线程池。服务端和客户端不同,服务端是需要接受客户端的建链请求的,所以通常在服务端会建立两个线程池将两种操作独立开。 -
channel()
方法指定的Channel类型为NioServerSocketChannel,这类channel专门用来接收建链请求。 -
option和childOption()
都是设置Channel的属性,但是与客户端不同的时,服务端有专门的childOption方法,用于设置建链完成后创建的Channel(比如NioSocketChannel)的属性。 -
handler()和childHandler()
都是设置Channel的handler,但是与客户端不同的是,服务端有专门的childHandler方法,用于指定建链完成后创建的Channel(比如NioSocketChannel)的channelPipeline中的handler。 -
bind()
方法用于监听本地地址和端口。通过反射创建NioServerSocketChannel对象,设置NioServerSocketChannel的属性以及将NioServerSocketChannel注册到parentGroup都是在bind方法中真正执行的(注意,将NioSocketChannel注册到childGroup是在accept新的链接后进行的)。
private ChannelFuture doBind(final SocketAddress localAddress) {
final ChannelFuture regFuture = initAndRegister();
final Channel channel = regFuture.channel();
可以看到,与客户端的connect方法一样,也会调到initAndRegister
final ChannelFuture initAndRegister() {
Channel channel = null;
try {
// 创建NioServerSocketChannel
channel = channelFactory.newChannel();
// 初始化channel
init(channel);
} catch (Throwable t) {
if (channel != null) {
// 省略
}
// 将channel注册到EventLoop的Selector
ChannelFuture regFuture = config().group().register(channel);
if (regFuture.cause() != null) {
if (channel.isRegistered()) {
channel.close();
} else {
channel.unsafe().closeForcibly();
}
}
return regFuture;
}
initAndRegister返回后,会调用doBind0方法绑定本地地址和端口。
总结
本文讲解了Netty作为客户端和服务端的启动流程,其中会涉及到Netty的一些核心概念,包括Channel、ChannelHandler、ChannelPipeline、EventLoop等,这些会在后面的文章中逐个进行解读。另外,对于更加深入具体的初始化和建链流程请看//www.greatytc.com/p/12e01aa1b338