ServerSocketChannel是如何初始化和绑定到EventLoop的?
ServerBootstrap 启动时需要初始化 ServerSocketChannel 并将其绑定到 EventLoop 上,用于处理该 channel 上产生的各种事件。那么 ServerSocketChannel 是如何完成创建和初始化的?又是如何绑定到 EventLoop 上的?
一个简单的Echo Server
要搞清楚这些问题,我们先要知道一个基本的 netty server 代码是如何编写的。
如下是一个简单的 Echo Server,通过它可以调试和跟踪 ServerBootstrap 的启动流程。
1 | EventLoopGroup eventLoopGroup = new NioEventLoopGroup(); |
这段代码相当简单,它只进行了一些必要的设置,注册了一个 EchoServerHandler 用于处理请求,绑定到本地端口并启动。因此可以分成 3 部分来看:
首先是 ServerBootstrap 的配置
- 配置使用的 EventLoopGroup
- 指定 channel 类型为 NioServerSocketChannel
- 设置 childHandler,往 SocketChannel 的 pipeline 上添加了一个 EchoServerHandler
接下来是绑定端口
1 | ChannelFuture future = bootstrap.bind(port).sync(); |
最后是阻塞等待服务关闭
1 | future.channel().closeFuture().sync(); |
再来看下 EchoServerHandler
1 | public class EchoServerHandler extends ChannelInboundHandlerAdapter { |
它继承了 ChannelInboundHandlerAdapter 类,用于对入站(Inbound)信息进行处理,对于 EchoServer 来说,也就是处理客户端的请求信息。
channelRead 是在读取到请求数据时调用的,它只是简单地将读取到的信息输出到 ChannelHandlerContext,最终会传递到责任链的末端并发送出去。
从ServerBootstrap的启动说起
绑定端口的同时会触发 server 的启动,因此从 bootstrap.bind(port)
看起。它的内部逻辑如下:先创建并初始化 channel,然后将其注册到 EventLoop 上,最后为 socket 绑定本地地址及端口
1 | private ChannelFuture doBind(final SocketAddress localAddress) { |
注意:为了方便阅读删除了部分代码
initAndRegister 会完成 channel 的创建和注册工作,因此需要看下它的内部是如何实现的
1 | final ChannelFuture initAndRegister() { |
创建channel
首先是通过 channelFactory 创建 channel。channelFactory 是在配置 ServerBootstrap 时生成的
1 | # AbstractBootStrap |
ReflectiveChannelFactory 是一个工厂类,它可以通过反射生成指定类型的实例
1 | public class ReflectiveChannelFactory<T extends Channel> implements ChannelFactory<T> { |
回到开头的 Echo Server,因为我们配置的 channel 是 NioServerSocketChannel.class
1 | bootstrap.channel(NioServerSocketChannel.class) |
所以 channel = channelFactory.newChannel()
创建的是一个 NioServerSocketChannel
类型的实例
初始化channel
channel 的初始化是在 AbstractBootstrap 的子类中完成的, ServerBootstrap 中的实现如下:
1 | void init(Channel channel) { |
它主要是设置 ServerSocketChannel 的 option、attribute,然后往pipeline上添加一个channelhandler,用于添加 Acceptor
注册channel到EventLoop
1 | ChannelFuture regFuture = config().group().register(channel); |
获取到 EventLoopGroup,调用其 register 方法注册 channel
1 | # MultithreadEventLoopGroup |
其内部是先选择一个 EventLoop,然后调用 EventLoop 的 register 方法。EventLoop的选择策略可参考 netty的EventLoop选择策略
1 | # SingleThreadEventLoop |
本质上是调用 unsafe 属性进行注册
1 | # AbstractChannel$AbstractUnsafe |
这里会将 EventLoop 绑定到 channel 上。每个channel 都会对应一个 EventLoop,用于处理其生命周期中的所有事件,而每个 EventLoop 可以对应多个 channel。
注册channel到selector
接着看 register0 的代码,它会继续完成 channel 到 selector 的注册
1 | private void register0(ChannelPromise promise) { |
doRegister 用于完成注册,它是在子类中实现的
1 | # AbstractNioChannel |
unwrappedSelector 是具体的实现类,它依赖于底层操作系统,例如在 mac 下的实现类为 KQueueSelectorImpl
javaChannel 则是 jdk 原生的 ServerSocketChannel 实现,这里是 sun.nio.ch.ServerSocketChannelImpl
为了方便取出 NioServerSocketChannel,还会将其以 attatchment绑定到selectionKey
到这里就完成了 channel 的注册过程
- 2022-01-30
在上一篇文章中,我们了解了如何通过 netty 实现一个echo server。你应该还记得
ServerBootstrap
启动类,它负责 server 的启动管理,在启动前我们需要为其配置EventLoopGroup
。EventLoopGroup
有配套的ServerSocketChannel
,比如通常使用最多的是NioEventLoopGroup
,它就需要和NioServerSocketChannel
搭配起来工作。 - 2021-02-05
netty 是一个基于异步事件驱动实现的网络编程框架,它的内部使用了大量的异步编程方法,这是它性能高效的一个原因,但同时也使得代码阅读起来更加困难,本文就尝试分析下它的启动过程
- 2021-02-26
对于一个 Netty client 来说,在配置好
Bootstrap
之后,通过调用其connect
方法来连接到远程服务端,如下所示1
2
3
4Bootstrap b = new Bootstrap();
b.group(group)
...
ChannelFuture f = b.connect(HOST, PORT).sync(); - 2021-01-02
netty 中的 channel 在完成创建和初始化之后,需要注册到 EventLoopGroup 上,这本质上是交给 EventLoop 管理 channel 的各种事件。一个 EventLoopGroup 管理了多个 EventLoop,那么在注册 channel 时,EventLoopGroup 就需要选择一个 EventLoop,然后将其和 channel 关联起来。选择 EventLoop 是一个很高频的操作,该操作是否高效会直接影响 netty 的性能,本文就来聊下 EventLoop 的选择策略。
- 2021-04-14
Netty server 在启动过程中会触发一系列的 Inbound 事件,它的流程是怎样的呢?