襄阳好风日,留醉与山翁。大家好,我是Y,上篇文章主要给大家简单介绍了下Netty的一些概念和原理性的东西,今天就带着大家一起过下服务端的启动核心流程。
一、服务端Demo
public void start() {
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workGroup = new NioEventLoopGroup();
ServerBootstrap server = new ServerBootstrap()
.group(bossGroup, workGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new ServerChannelInitializer());
try {
ChannelFuture future = server.bind(this.port).sync();
future.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
bossGroup.shutdownGracefully();
workGroup.shutdownGracefully();
}
}
public class ServerChannelInitializer extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
ChannelPipeline pipeline = socketChannel.pipeline();
pipeline.addLast(new IdleStateHandler(10, 0, 0, TimeUnit.SECONDS));
pipeline.addLast(new StringDecoder());
pipeline.addLast(new StringEncoder());
// 自己的业务handler
pipeline.addLast(new MyServerHandler());
}
}
以上就是Netty server端的简单demo,核心步骤如下:
- 分别创建了boss线程组和worker线程组。boss线程组一般来说设置一个线程即可,worker线程如果不传任何参数则默认创建的个数为NettyRuntime.availableProcessors() * 2。
- 初始化NioServerSocketChannel同时会开启selector多路复用器,并将handler添加到NioServerSocketChannel的管道中去。这些handler包含用来处理心跳的IdleStateHandler,用来编解码的StringDecoder/Encoder(同样可以选择其他的编解码器)以及业务自定义handler。
- 最后调用server.bind方法,绑定对应的端口,等待客户端的请求到来。
这里要注意一点,由于Netty服务启动后会阻塞线程,因此实际应用中一般都是在异步线程中去启动Netty。
二、ServerBootStrap-bind分析
老规矩,有图当然先看图了,以上就是ServerBootStrap-bind方法时序图。涉及的类比较多,如果看不动的同学可以优先看下最后一个类ServerSocketChannelImpl,熟悉java socket编程的同学可能对这个类很熟悉,该类就是java原生的用来进行网络通信的类。也就是说,bind方法执行到最后,其实是把ServerSocketChannel封装成了NioServerSocketChannel,底层通信还是依赖的java原生类ServerSocketChannel。
2.1. 步骤2-doBind分析
private ChannelFuture doBind(final SocketAddress localAddress) {
// 初始化channel并注册到NioEventLoop线程上
final ChannelFuture regFuture = initAndRegister();
final Channel channel = regFuture.channel();
// 注册是否异常
if (regFuture.cause() != null) {
return regFuture;
}
if (regFuture.isDone()) {
// 组册完成后要进行端口绑定
// 又NioEventLoop线程去异步执行
ChannelPromise promise = channel.newPromise();
doBind0(regFuture, channel, localAddress, promise);
return promise;
} else {
// 注册操作是异步的,可能会没完成,先给主线程返回PendingRegistrationPromise
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;
}
}
通过上述源码可以看到,doBind首会把channel注册到NioEventLoop上并开启selector,此时还未进行OP_ACCEPT事件绑定。由于注册的过程是由NioEventLoop线程去执行的,是异步的,因此要等注册动作完成后,才能进行端口的绑定。可以看到,做了几次状态的判断,如果注册动作未完成,则会注册一个监听,等注册动作完成后,会调用operationComplete方法,执行后续的端口绑定动作。
2.2. 步骤三-initAndRegister分析
final ChannelFuture initAndRegister() {
Channel channel = null;
try {
// 通过反射创建NioServerSocketChannel实例
channel = channelFactory.newChannel();
init(channel);
} catch (Throwable t) {
... ...
}
// 底层调用的就是AbstractChannel.AbstractUnsafe-register方法
// 此处不展开,等分析AbstractChannel时候在讲解下调用链路
// 此处.group获取的是boss group,也就是把serverSocketChannel交给了boss group去处理
ChannelFuture regFuture = config().group().register(channel);
if (regFuture.cause() != null) {
if (channel.isRegistered()) {
channel.close();
} else {
channel.unsafe().closeForcibly();
}
}
return regFuture;
}
debug可以看到,此处的channelFactory实际类型为ReflectiveChannelFactory,反射创建的channel对象为 NioServerSocketChannel。
channelFactory类型的确定其实是在new ServerBootstrap()
.channel(NioServerSocketChannel.class)时候,查看源码如下:
public B channel(Class<? extends C> channelClass) {
return channelFactory(new ReflectiveChannelFactory<C>(
ObjectUtil.checkNotNull(channelClass, "channelClass")
));
}
初始化NioServerSocketChannel实例主要为了初始化ServerSocketChannel对象,查看源码如下:
public NioServerSocketChannel() {
this(newSocket(DEFAULT_SELECTOR_PROVIDER));
}
private static ServerSocketChannel newSocket(SelectorProvider provider) {
return provider.openServerSocketChannel();
}
// SelectorProviderImpl-openServerSocketChannel实现
public ServerSocketChannel openServerSocketChannel() throws IOException {
return new ServerSocketChannelImpl(this);
}
初始化channel后,则要进行selector的绑定和注册,通过上面的时序图可以看到,最终调用的是EventLoop的register方法,把channel注册到对应的selector上,使得select可以监听channel的各种事件,EventLoop等后面再分析。
2.3. 步骤七-doBind0分析
通过上面时序图也可以看到,bind的过程涉及了多个类,最终执行的是AbstractChannel的内部类AbstractUnsafe的bind(SocketAddress localAddress, ChannelPromise promise),查看其源码实现如下:
public final void bind(SocketAddress localAddress, ChannelPromise promise) {
this.assertEventLoop();
if (promise.setUncancellable() && this.ensureOpen(promise)) {
... ...
boolean wasActive = AbstractChannel.this.isActive();
try {
AbstractChannel.this.doBind(localAddress);
} catch (Throwable var5) {
this.safeSetFailure(promise, var5);
this.closeIfClosed();
return;
}
if (!wasActive && AbstractChannel.this.isActive()) {
this.invokeLater(new Runnable() {
public void run() {
AbstractChannel.this.pipeline.fireChannelActive();
}
});
}
this.safeSetSuccess(promise);
}
}
- 调用抽象方法doBind
查看其实现发现,调用的是NioServerSocketChannel-doBind发下,源码如下:
protected void doBind(SocketAddress localAddress) throws Exception {
if (PlatformDependent.javaVersion() >= 7) {
javaChannel().bind(localAddress, config.getBacklog());
} else {
javaChannel().socket().bind(localAddress, config.getBacklog());
}
}
最终调用的java nio的ServerSocketChannelImpl bind方法进行端口绑定:
@Override
public ServerSocketChannel bind(SocketAddress local, int backlog) throws IOException {
synchronized (lock) {
if (!isOpen())
throw new ClosedChannelException();
if (isBound())
throw new AlreadyBoundException();
InetSocketAddress isa = (local == null) ? new InetSocketAddress(0) :
Net.checkAddress(local);
SecurityManager sm = System.getSecurityManager();
if (sm != null)
sm.checkListen(isa.getPort());
NetHooks.beforeTcpBind(fd, isa.getAddress(), isa.getPort());
Net.bind(fd, isa.getAddress(), isa.getPort());
Net.listen(fd, backlog < 1 ? 50 : backlog);
synchronized (stateLock) {
localAddress = Net.localAddress(fd);
}
}
return this;
}
- 调用invokeLater 绑定端口成功后,会触发active事件,调用invokeLater方法,用来给注册到Selector上的socket channel加上OP_ACCEPPT事件。
invokeLater方法调用链路有点深,可以看到最终调用的是AbstractNioChannel-doBeginRead方法实现事件注册,查看源码如下:
底层其实调用的还是java nio的原生类SelectionKey,这里的readInterestOp=16表示的当前是OP_ACCEPT事件,实现OP_ACCEPT事件注册,具体的事件类型都定义在SelectionKey里
public static final int OP_READ = 1 << 0;
public static final int OP_WRITE = 1 << 2;
public static final int OP_CONNECT = 1 << 3;
public static final int OP_ACCEPT = 1 << 4;
三、小结
本文主要为大家简单介绍了Netty ServerBootStrap相关源码实现,主要做了几件事
- 初始化NioServerSocketChannel,并包装jdk nio实现类ServerSocketChannel,将handler添加到NioServerSocketChannel的pipline管道中去。
- 初始化EventLoop并开启Selector,此时还未注册OP_ACCEPT
- 调用底层java nio相关接口绑定端口后,会触发active事件,底层通过调用java nio原生类SelectionKeyImpl将事件OP_ACCEPT(具体值为16)注册到对应的selector上。
这里还要分线程讲下,netty启动的过程中主要设计三个线程,分别为主线程、boss线程、worker线程。
- main线程职责
- 创建select
- 创建server socket channel并初始化
- 从boss group中选择一个NioEventLoop交给server socket channel
- boss线程职责
- 将server socket channel注册到选择的NioEventLoop的selector上
- 绑定地址启动
- 注册接受连接事件(OP_ACCEPT到selecor上
- worker线程职责
- 将socket channel注册到选择的NioEventLoop的selector
- 注册读事件(OP_READ)到selector上,并处理