前言
Netty
是一个用于构建高性能、可拓展、支持异步事件驱动的Java
网络框架,基于该框架我们可以快速各种网络协议下的高性能Java
网络应用程序。本篇文章基于自编写的引导类进行Netty
服务端启动的流程进行详细分析和讲解。
注意由于Netty
服务端启动逻辑繁多,建议读者进行阅读时,可以基于源码跟随笔者的脚步一起调试,方便理解和掌握全流程。
我们使用Netty
编写服务端时需要基于ServerBootstrap
这个引导类完成服务端配置工作,配置ServerBootstrap
配置步骤大体如下:
- 创建
ServerBootstrap
引导类。 - 基于
ServerBootstrap
类完成IO模型、选项设置、属性设置、处理器设置。 - 基于
ServerBootstrap
绑定端口和异步启动服务端。
我们通过代码完成上述配置并启动Java
应用程序之后,Netty
服务端的启动整体是进行以下几个步骤:
channel
创建。channel
初始化。channel
注册。channel
绑定。
本文就会基于这4个大步骤,对Netty
服务端启动源码进行深入分析。
channel简介
在此之前,我们需要先了解一下channel
的概念,channel
用于连接字节缓冲区ByteBuf
以及对端实体。其中对端的实体可以是一个File
,也可以和服务端进行网络通信的Socket
连接。
在Netty
网络编程模型中,它对原生JDK
的ServerSocketChannel
加以拓展和封装,使之具备特性:
- 具有唯一标识身份信息的
id
。 channel
可具备父子关系,即当前channel
可以作为另一个channel
的子channel
,使其共享父通道的事件处理逻辑,并且可以通过父通道来管理和控制。- 增加
pipeline
管理业务处理流程。 - 读写可基于底层
unsafe
类实现更加高效的操作。 - 关联一个
NioEventLoop
处理各类读写请求。
而AbstractChannel
最核心的抽象类,我们可以将其理解为Channel
类的基本骨架,从它继承出了AbstractNioChannel
、AbstractOioChannel
、AbstractEpollChannel
、LocalChannel
、EmbeddedChannel
等类,每个类代表了不同的协议以及相应的IO模型。
对此,我们列出一些比较常用的 Channel
类型:
NioSocketChannel
:代表异步的客户端TCP Socket
连接。NioServerSocketChannel
:异步的服务器端TCP Socket
连接。NioDatagramChannel
:异步的UDP
连接。NioSctpChannel
:异步的客户端Sctp
连接。NioSctpServerChannel
:异步的Sctp
服务器端连接。OioSocketChannel
:同步的客户端TCP Socket
连接。OioServerSocketChannel
:同步的服务器端TCP Socket
连接。OioDatagramChannel
:同步的UDP
连接。OioSctpChannel
:同步的Sctp
服务器端连接。OioSctpServerChannel
:同步的客户端TCP Socket
连接。
Netty服务端启动源码解析
代码示例
为了更好的源码,我们给出一段可以调试的代码示例,如下所示,这就是我们上文所说的服务端配置和启动的示例代码,每一段配置的含义笔者都已给出详尽注释,读者可自行参阅:
public static void main(String[] args) throws Exception {
//Netty封装了NIO,Reactor模型,Boss,worker
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
//创建ServerBootstrap,ServerBootstrap可以理解为Nio中的ServerSocketChannel
ServerBootstrap b = new ServerBootstrap();
//开始设置各种参数
b.group(bossGroup, workerGroup)
//设置IO模型
.channel(NioServerSocketChannel.class)
//用于给每个连接都设置一些TCP参数
.childOption(ChannelOption.TCP_NODELAY, true)
//给每一个连接设置attr
.childAttr(AttributeKey.newInstance("childAttr"), "childAttrValue")
//设置IO处理器
.handler(new ServerHandler())
//定义后续每个连接的数据读写,对于业务处理逻辑
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) {
ch.pipeline().addLast(new AuthHandler());
//..
}
});
// option() 方法 用于给服务端Channel设置一些TCP参数 SO_BACKLOG 表示系统用于临时存放已完成三次握手的请求的队列的最大长度
b.option(ChannelOption.SO_BACKLOG, 1024);
// attr() 方法 用于给 NioServerSocketChannel 维护一个 Map,通常也用不到这个方法
b.attr(AttributeKey.newInstance("serverName"), "nettyServer");
ChannelFuture f = b.bind(8888).sync();
f.channel().closeFuture().sync();
} finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
可以看到在笔者在配置时用到了一个ServerHandler
来处理服务端请求,逻辑也比较简单,继承ChannelInboundHandlerAdapter
重写每一个阶段的方法,输出对应的方法名。
public class ServerHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelActive(ChannelHandlerContext ctx) {
System.out.println("channelActive");
}
@Override
public void channelRegistered(ChannelHandlerContext ctx) {
System.out.println("channelRegistered");
}
@Override
public void handlerAdded(ChannelHandlerContext ctx) {
System.out.println("handlerAdded");
}
@Override
public void channelRead(final ChannelHandlerContext ctx, Object msg) throws Exception {
System.out.println("channelRead");
}
}
完成编码工作之后,我们将Netty
服务端启动,并通过命令行输入下面这段命令尝试和服务端建立连接:
telnet 127.0.0.1 8888
最终可以在控制台看到这样3条输出,由此可知,在服务端启动后会回调这3个方法,接下来我们就可以通过调试的源码的方式了解一下服务端的启动流程。
handlerAdded
channelRegistered
channelActive
启动流程概述
我们需要在Netty
服务端启动流程的入口即下面这个bind方法,我们以这段代码为入口开始调试:
b.bind(8888).sync();
步入bind
方法可以看到其内部整体做了两件事:
- 创建一个套接字地址
InetSocketAddress
对象,该对象包含地址和端口号的信息。 - 将套接字地址对象
InetSocketAddress
作为参数传入并调用bind
方法。
public ChannelFuture bind(int inetPort) {
//1. 创建一个套接字地址
//2. 基于InetSocketAddress完成bind操作
return bind(new InetSocketAddress(inetPort));
}
继续步进bind
方法,它进行了如下几个工作:
- 调用
validate
方法对group
和channelFactory
进行判空校验,如果为空则抛出异常,反之进入步骤2。 - 判断传入的
SocketAddress
是否为空,若为空则抛出异常,反之进入步骤3。 - 调用
doBind
方法。
public ChannelFuture bind(SocketAddress localAddress) {
//对group和channelFactory进行判空校验
validate();
//SocketAddress 判空
if (localAddress == null) {
throw new NullPointerException("localAddress");
}
//调用doBind完成服务端启动核心步骤
return doBind(localAddress);
}
步入的AbstractBootstrap
的doBind
方法,即可看到笔者所说的4大步骤:
- 调用
initAndRegister
完成channel
创建、初始化、以及selector
注册。 - 调用
doBind0
完成端口号的绑定。
private ChannelFuture doBind(final SocketAddress localAddress) {
//channel创建和初始化以及selector注册
final ChannelFuture regFuture = initAndRegister();
final Channel channel = regFuture.channel();
if (regFuture.cause() != null) {
return regFuture;
}
if (regFuture.isDone()) {
// At this point we know that the registration was complete and successful.
ChannelPromise promise = channel.newPromise();
//完成端口绑定
doBind0(regFuture, channel, localAddress, promise);
return promise;
} else {
//略
return promise;
}
}
服务端Channel创建
基于反射完成服务端channel创建
上述doBind
方法我们看到Netty
服务端大体的初始化流程,接下来我们就对每一步进行深入的了解和分析,先来看看创建服务端Channel
这一步,我们步入上文的initAndRegister
方法,可以看到其调用channelFactory
的newChannel
方法,那么这个方法内部是如何完成创建的呢?
final ChannelFuture initAndRegister() {
Channel channel = null;
try {
channel = channelFactory.newChannel();
init(channel);
} catch (Throwable t) {
//略
}
//略
return regFuture;
}
步入channelFactory
后我们来到了ReflectiveChannelFactory
的newChannel
方法,可以看到它会通过JDK
的反射方法完成服务端Channel
创建。
@Override
public T newChannel() {
try {
//基于反射完成channel的创建
return clazz.newInstance();
} catch (Throwable t) {
throw new ChannelException("Unable to create Channel from class " + clazz, t);
}
}
我们在调试时基于IDEA
查看clazz
的类名,可以发现它的类类型是NioServerSocketChannel
。所以我们这里大胆才测,它是否就是我们配置阶段基于channel
方法所设置的NioServerSocketChannel.class
,然后在启动阶段,Netty
服务端通过这个类类型完成反射创建。
// 配置线程组并指定NIO模型
serverBootstrap.group(bossGroup, workerGroup)
//设置IO模型,这里为NioServerSocketChannel,建议Linux服务器使用 EpollServerSocketChannel
.channel(NioServerSocketChannel.class)
//略
为了印证这一点,我们步入channel
方法查看一下逻辑,可以看到它会将我们的channelClass
传入ReflectiveChannelFactory
中完成反射工厂的声明,然后调用channelFactory
方法。
public B channel(Class<? extends C> channelClass) {
if (channelClass == null) {
throw new NullPointerException("channelClass");
}
//基于channelClass创建一个ReflectiveChannelFactory调用channelFactory
return channelFactory(new ReflectiveChannelFactory<C>(channelClass));
}
再步入channelFactory
可以看到这个方法也仅仅是将反射工厂作为入参传入channelFactory
中,完成channelFactory
这个工厂的初始化工作。
public B channelFactory(io.netty.channel.ChannelFactory<? extends C> channelFactory) {
return channelFactory((ChannelFactory<C>) channelFactory);
}
步入channelFactory
方法,可以看到channelFactory
就是基于我们类类型所创建的工厂,这也印证了我们上文所说的channel
反射创建的来源,就是我们配置阶段所设置的NioServerSocketChannel.class
。
public B channelFactory(ChannelFactory<? extends C> channelFactory) {
//略
this.channelFactory = channelFactory;
return (B) this;
}
NioServerSocketChannel创建时做了什么
由此我们可以得出在服务端channel
初始化是基于我们的引导类通过反射的方式完成创建的。对此我们看看反射创建NioServerSocketChannel
时,它做了些什么。
源码如下,可以看到:
- 它基于
DEFAULT_SELECTOR_PROVIDER
调用了一个newSocket
方法,打开服务套接字通道。(补充:SelectorProvider
是nio
下的一个抽象类,可以选择器以及可选择通道的服务提供者。) - 调用构造方法完成
channel
的基本配置。
private static final SelectorProvider DEFAULT_SELECTOR_PROVIDER = SelectorProvider.provider();
public NioServerSocketChannel() {
this(newSocket(DEFAULT_SELECTOR_PROVIDER));
}
对上述两步进行拆解,先来看看newSocket
,它通过SelectorProvider
调用openServerSocketChannel
完成ServerSocketChannel
的创建。
private static ServerSocketChannel newSocket(SelectorProvider provider) {
try {
//调用JDK nio包下的API完成ServerSocketChannel 实例的创建
return provider.openServerSocketChannel();
} catch (IOException e) {
throw new ChannelException(
"Failed to open a server socket.", e);
}
}
再来看看得到这个ServerSocketChannel
之后,构造方法做了些什么,可以看到它基于我们创建的ServerSocketChannel
做了如下两件事:
- 调用父级构造方法完成
channel
基本设置并将感兴趣的事件设置为OP_ACCEPT
。 - 调用
NioServerSocketChannelConfig
完成当前channel
的RecvByteBufAllocator
分配器分配以及基于Java socket
基本配置。
public NioServerSocketChannel(ServerSocketChannel channel) {
//调用父级构造方法完成channel基本设置并将感兴趣的事件设置为OP_ACCEPT
super(null, channel, SelectionKey.OP_ACCEPT);
//创建NioServerSocketChannelConfig对象,内部会进行channel
config = new NioServerSocketChannelConfig(this, javaChannel().socket());
}
我们先来看看父级构造方法的调用做了什么:
- 调用父级构造方法
id
、unsafe
、pipeline
初始化。 - 它将我们创建的
channel
感兴趣的事件设置为SelectionKey.OP_ACCEPT
。 - 将阻塞模式设置为非阻塞。
protected AbstractNioChannel(Channel parent, SelectableChannel ch, int readInterestOp) {
//调用父级构造方法id 、unsafe 、pipeline 初始化。
super(parent);
this.ch = ch;
//感兴趣的事件设置为SelectionKey.OP_ACCEPT
this.readInterestOp = readInterestOp;
try {
//将阻塞模式设置为非阻塞
ch.configureBlocking(false);
} catch (IOException e) {
//略
}
}
查看上一步所说的父级构造,印证我们所说的id
、unsafe
、pipeline
初始化。
protected AbstractChannel(Channel parent) {
this.parent = parent;
id = newId();
unsafe = newUnsafe();
pipeline = newChannelPipeline();
}
再来说说第二个步骤,即config
类的创建,其构造函数不断步入我们即可看到:
- 父级构造方法完成
channel
的绑定以及对channel
进行RecvByteBufAllocator
分配。 - 设置
config
类对应的javaSocket
。
public DefaultServerSocketChannelConfig(ServerSocketChannel channel, ServerSocket javaSocket) {
//拿着我们反射创建的channel调用父级构造,最终会走到下方的构造方法完成RecvByteBufAllocator分配
super(channel);
//设置config类对应的javaSocket
if (javaSocket == null) {
throw new NullPointerException("javaSocket");
}
this.javaSocket = javaSocket;
}
protected DefaultChannelConfig(Channel channel, RecvByteBufAllocator allocator) {
//上述方法的父级构造最终会走到这里完成channel的RecvByteBufAllocator的分配
setRecvByteBufAllocator(allocator, channel.metadata());
//绑定channel
this.channel = channel;
}
初始化服务端channel
上述的流程我们完成了服务端Channel
的创建,接下来就是对服务端channel
进行初始化工作了。所以我们再次将阅读的重心回到到AbstractBootstrap
的initAndRegister
方法上,此时我们再次步入init
方法一探究竟。
final ChannelFuture initAndRegister() {
Channel channel = null;
try {
channel = channelFactory.newChannel();
//初始化服端channel
init(channel);
} catch (Throwable t) {
//略
}
//略
return regFuture;
}
步入ServerBootstrap
的init
方法可以看到一段比较长的代码,它会拿着我们创建的channel
进行如下工作:
- 设置
channelOptions
和ChannelAttrs
。 - 将
childOptions
和childAttrs
保存起来,后续有新连接来时,就给每一个连接的channel
设置这些选项和属性。 - 配置服务端
pipeline
。 - 添加
ServerBootstrapAcceptor
连接器,每次有新的连接进来时,就会基于本次设置的childOptions
和childAttrs
对这些child
(就是进来的连接的Channel
)进行配置,并注册到ChildGroup
(针对child的EventLoopGroup
)中。
整体步骤和注释如下代码所示:
@Override
void init(Channel channel) throws Exception {
//设置channelOptions
final Map<ChannelOption<?>, Object> options = options0();
synchronized (options) {
channel.config().setOptions(options);
}
//设置channelAttrs
final Map<AttributeKey<?>, Object> attrs = attrs0();
synchronized (attrs) {
for (Entry<AttributeKey<?>, Object> e: attrs.entrySet()) {
@SuppressWarnings("unchecked")
AttributeKey<Object> key = (AttributeKey<Object>) e.getKey();
channel.attr(key).set(e.getValue());
}
}
ChannelPipeline p = channel.pipeline();
final EventLoopGroup currentChildGroup = childGroup;
final ChannelHandler currentChildHandler = childHandler;
final Entry<ChannelOption<?>, Object>[] currentChildOptions;
final Entry<AttributeKey<?>, Object>[] currentChildAttrs;
//保存childOptions中的属性
synchronized (childOptions) {
currentChildOptions = childOptions.entrySet().toArray(newOptionArray(childOptions.size()));
}
//保存childAttrs的配置
synchronized (childAttrs) {
currentChildAttrs = childAttrs.entrySet().toArray(newAttrArray(childAttrs.size()));
}
//配置服务端pipeline
p.addLast(new ChannelInitializer<Channel>() {
@Override
public void initChannel(Channel ch) throws Exception {
//ChannelInitializer这个匿名内部类添加到pipeline之后会触发initChannel这个回调,下面这些代码就会将我们配置的ServerHandler添加到pipeline上
final ChannelPipeline pipeline = ch.pipeline();
ChannelHandler handler = config.handler();
if (handler != null) {
pipeline.addLast(handler);
}
ch.eventLoop().execute(new Runnable() {
@Override
public void run() {
//添加ServerBootstrapAcceptor每当有新的连接进来时就会基于currentChildOptions和currentChildAttrs为每一个新连接的channel设置选项和属性,并注册到EventLoopGroup中
pipeline.addLast(new ServerBootstrapAcceptor(
currentChildGroup, currentChildHandler, currentChildOptions, currentChildAttrs));
}
});
}
});
}
服务端以及连接参数设置
我们对init
方法的核心流程进行逐个拆解分析,先来说说设置channelOptions
这一步,它的工作流程为:
- 调用
options0
获取到对应的channelOptions
。 - 因为
setOptions
底层存储options
键值对时用的是HashMap
,在调用setOptions
时用到了synchronized
关键字确保选项设置时的线程安全。
//获取options
final Map<ChannelOption<?>, Object> options = options0();
//将options键值对存入
synchronized (options) {
channel.config().setOptions(options);
}
那么这个options
是哪里来的呢?我们步入options0()
方法,可以看到这些属性都来自一个options
的变量。
final Map<ChannelOption<?>, Object> options0() {
return options;
}
查看options
所有设置的入口,笔者定位到了这个方法,可以看到它在进行必要的校验之后,直接将传入的键值对存入options
中。
public <T> B option(ChannelOption<T> option, T value) {
//option 非空校验
if (option == null) {
throw new NullPointerException("option");
}
//value 非空校验,若为空则移除option
if (value == null) {
synchronized (options) {
options.remove(option);
}
} else {
//将options上锁后设置option和value键值对
synchronized (options) {
options.put(option, value);
}
}
return (B) this;
}
查看这个方法调用,我们来到了我们编写的代码,由此可知设置的options
都来自我们配置时用到的option
方法。
// option() 方法 用于给服务端Channel设置一些TCP参数 SO_BACKLOG 表示系统用于临时存放已完成三次握手的请求的队列的最大长度
b.option(ChannelOption.SO_BACKLOG, 1024);
同理,我们紧接着阅读下一段代码,即设置channelAttr
,原理和options
差不多,而attr
的值也是来自于笔者上文给出的配置代码 b.attr(AttributeKey.newInstance("serverName"), "nettyServer");
,这里就不多做赘述了。
//设置channel attr
final Map<AttributeKey<?>, Object> attrs = attrs0();
synchronized (attrs) {
for (Entry<AttributeKey<?>, Object> e: attrs.entrySet()) {
@SuppressWarnings("unchecked")
AttributeKey<Object> key = (AttributeKey<Object>) e.getKey();
channel.attr(key).set(e.getValue());
}
}
紧接着我们就开始设置childOptions
和childAttrs
,和上述几个选项设置也是同理,需要保证线程安全也上了锁。
//基于引导类上的配置保存childOptions和childAttrs,后续新连接建立时会用得上
synchronized (childOptions) {
currentChildOptions = childOptions.entrySet().toArray(newOptionArray(childOptions.size()));
}
synchronized (childAttrs) {
currentChildAttrs = childAttrs.entrySet().toArray(newAttrArray(childAttrs.size()));
}
依照上述代码的规律我们也可以得出childOptions
和childAttrs
也是来自于笔者的配置代码:
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
//childOptions设置处
.childOption(ChannelOption.TCP_NODELAY, true)
//childAttr设置处
.childAttr(AttributeKey.newInstance("childAttr"), "childAttrValue")
//略
pipeline配置以及handler添加
完成这些基本选项和配置的设置之后,就需要配置服务端pipeline
,配置服务端pipeline
大抵是设置这几个过程:
- 创建一个匿名内部类
ChannelInitializer
并注册到pipeline
,然后向EventLoop
提交一个任务,确保当前ChannelInitializer
添加到pipeline
之后,回调到下面代码中的initChannel
方法。 initChannel
方法会获取channel
的pipeline
,注意这个channel
就是我们上文初始化好的NioServerSocketChannel
,具体后文会分析。- 获取我们配置的
handler
即我们自行编写的ServerHandler
。 - 将
ServerHandler
添加到pipeline
中。
对应代码和注释如下:
p.addLast(new ChannelInitializer<Channel>() {
@Override
public void initChannel(Channel ch) throws Exception {
//获取channel的pipeline
final ChannelPipeline pipeline = ch.pipeline();
//获取我们配置的handler即用于为请求服务的ServerHandler
ChannelHandler handler = config.handler();
if (handler != null) {
//将ServerHandler添加到pipeline中
pipeline.addLast(handler);
}
//略
}
});
步入addLast
我们来到了DefaultChannelPipeline
的addLast
即可看到其内部调用了addLast
方法,入参为我们的ChannelInitializer
匿名内部类。
@Override
public final ChannelPipeline addLast(ChannelHandler... handlers) {
return addLast(null, handlers);
}
继续前进可以看到它会对handlers
进行非空校验,完成后则遍历ChannelHandler
并调用addLast
将当前ChannelHandler
添加到pipeline
的链表中。因为我们的handlers只有一个所以遍历一轮就直接调用addLast
。
@Override
public final ChannelPipeline addLast(EventExecutorGroup executor, ChannelHandler... handlers) {
//handlers 非空校验
if (handlers == null) {
throw new NullPointerException("handlers");
}
//将handlers添加到pipeline上
for (ChannelHandler h: handlers) {
if (h == null) {
break;
}
addLast(executor, null, h);
}
return this;
}
继续步入addLast
方法,我们来说一下大体的逻辑:
- 检测这个
handler
是否是共享的,如果是则判断是否添加过,如果添加过则抛一个异常,反之进入步骤2。 - 将这个
handler
封装成一个AbstractChannelHandlerContext
。 - 如果未注册则基于这个
handler
的上下文创建一个任务,确保后续添加完成后回调我们ServerHandler
的handlerAdded
方法,反之进入步骤4。 - 如果已注册且当前任务不在
eventLoop
中,则基于执行executor
完成handler
注册和回调handlerAdded
,反之进入步骤5。 - 如果在
eventLoop
中且注册了则直接回调ServerHandler
的handlerAdded
方法。
@Override
public final ChannelPipeline addLast(EventExecutorGroup group, String name, ChannelHandler handler) {
final AbstractChannelHandlerContext newCtx;
synchronized (this) {
//检测这个handler是否是共享的,如果是则判断是否添加过,若添加过抛出异常
checkMultiplicity(handler);
//将这个handler封装成一个AbstractChannelHandlerContext
newCtx = newContext(group, filterName(name, handler), handler);
//将AbstractChannelHandlerContext存到pipeline的链表中
addLast0(newCtx);
// 如果未注册则基于这个ServerHandler的上下文创建一个任务,确保后续添加完成后回调我们的handlerAdded方法
if (!registered) {
newCtx.setAddPending();
callHandlerCallbackLater(newCtx, true);
return this;
}
//如果已注册且当前任务不在eventLoop中,则基于执行executor 完成handler注册和回调handlerAdded
EventExecutor executor = newCtx.executor();
if (!executor.inEventLoop()) {
newCtx.setAddPending();
executor.execute(new Runnable() {
@Override
public void run() {
callHandlerAdded0(newCtx);
}
});
return this;
}
}
//如果在eventLoop中且注册了则直接回调ServerHandler的handlerAdded方法
callHandlerAdded0(newCtx);
return this;
}
先来说说checkMultiplicity
方法,逻辑比较简单:
- 判断是否是共享且添加过,如果是则抛异常。
- 反之将
added
设置为true
,代表当前handler
为已添加。
private static void checkMultiplicity(ChannelHandler handler) {
if (handler instanceof ChannelHandlerAdapter) {
ChannelHandlerAdapter h = (ChannelHandlerAdapter) handler;
//判断是否是共享且添加过,如果是则抛异常。
if (!h.isSharable() && h.added) {
throw new ChannelPipelineException(
h.getClass().getName() +
" is not a @Sharable handler, so can't be added or removed multiple times.");
}
//将当前handler的added设置为true,代表状态为已添加
h.added = true;
}
}
完成必要的channel
校验之后,我们需要基于这个handler完成:
- 创建
handler
的名称。 - 基于
handler
封装一个上下文AbstractChannelHandlerContext
。
newCtx = newContext(group, filterName(name, handler), handler);
步入其构造方法,我们可以看到它对channel
进行这些信息的记录:
- 记录
handler
属于哪个pipeline
。 - 执行器
executor
。 - 上下文名称
name
。 - 是否是入站
inBound
。 - 是否是出站
outbound
。 - 当前
handler
。
这里面仅设置属性赋值和名称初始化,笔者就不做展开,读者感兴趣可自行调试。
DefaultChannelHandlerContext(
DefaultChannelPipeline pipeline, EventExecutor executor, String name, ChannelHandler handler) {
super(pipeline, executor, name, isInbound(handler), isOutbound(handler));
if (handler == null) {
throw new NullPointerException("handler");
}
this.handler = handler;
}
完成上下文创建之后,我们需要将当前这个上下文添加到pipeline
中。
addLast0(newCtx);
步入addLast0
可以看到该操作会将我们的上下文newCtx
追加到tailContext
前面,。
private void addLast0(AbstractChannelHandlerContext newCtx) {
//获取tail前驱节点
AbstractChannelHandlerContext prev = tail.prev;
//newCtx指向这个前驱节点,并且后继节点指向tail
newCtx.prev = prev;
newCtx.next = tail;
//前驱的后继指针指向newCtx
prev.next = newCtx;
//tail的前驱指向newCtx
tail.prev = newCtx;
}
经过这一步之后,我们的handler
就会成功的添加到pipeline
的双向链表上,最终效果如下图所示:
完成handler
的添加之后,进行最后的回调通知工作:
- 判断当前
handler
是否注册过,如果registered
返回true
,则说明注册过直接调用callHandlerAdded0
当前handler
的handlerAdded
方法,反之进入步骤2。 - 如果未注册调用
setAddPending
将当前handler的状态由于初始化INIT
通过CAS
修改为ADD_PENDING
,即添加中。 - 封装一个异步任务提交到
EventLoop
中回调handlerAdded
方法。
因为我们之前并没有注册过这个handler,所以执行1、2两个步骤就直接返回了。
//如果未注册,则通过CAS方式修改handler的状态,并封装一个异步任务回调handlerAdded
if (!registered) {
newCtx.setAddPending();
callHandlerCallbackLater(newCtx, true);
return this;
}
//如果已注册则直接回调handlerAdded
EventExecutor executor = newCtx.executor();
if (!executor.inEventLoop()) {
newCtx.setAddPending();
executor.execute(new Runnable() {
@Override
public void run() {
callHandlerAdded0(newCtx);
}
});
return this;
}
所以我们先看看setAddPending
方法,它就是一个CAS
修改当前newCtx
的状态为添加中。
final void setAddPending() {
boolean updated = HANDLER_STATE_UPDATER.compareAndSet(this, INIT, ADD_PENDING);
assert updated; // This should always be true as it MUST be called before setAddComplete() or setRemoved().
}
然后来到了callHandlerCallbackLater
,它的步骤比较多了:
- 判断
added
状态,如果为true
则将上下文封装成PendingHandlerAddedTask
,反之封装成PendingHandlerRemovedTask
移除任务,从入参可知我们的added
为true
,所以这里我们会将上下文封装成添加任务。 - 判断
pendingHandlerCallbackHead
是否为空,若为空则将当前添加任务设置为pendingHandlerCallbackHead
,反之执行步骤3。 - 走到这里说明
pendingHandlerCallbackHead
不为空,我们需要将添加任务追加至pendingHandlerCallbackHead
末尾。
private void callHandlerCallbackLater(AbstractChannelHandlerContext ctx, boolean added) {
assert !registered;
//基于AbstractChannelHandlerContext 封装成一个PendingHandlerAddedTask
PendingHandlerCallback task = added ? new PendingHandlerAddedTask(ctx) : new PendingHandlerRemovedTask(ctx);
PendingHandlerCallback pending = pendingHandlerCallbackHead;
//如果pending 为空,则说明是第一个任务,直接将pendingHandlerCallbackHead 设为task
if (pending == null) {
pendingHandlerCallbackHead = task;
} else {
// 反之追加到pending的后继空位中
while (pending.next != null) {
pending = pending.next;
}
pending.next = task;
}
}
这里我们不妨步入查看一下PendingHandlerAddedTask
这个添加任务做了什么,从重写的方法不难看出它继承了Runnable
接口,这也就意味着我们将其挂到pendingHandlerCallbackHead
上之后,EventExecutor
就会执行这个任务。
private final class PendingHandlerAddedTask extends PendingHandlerCallback {
PendingHandlerAddedTask(AbstractChannelHandlerContext ctx) {
super(ctx);
}
//回调handler的handlerAdded方法
@Override
public void run() {
callHandlerAdded0(ctx);
}
//略
}
}
这里我们将callHandlerAdded0
方法内部逻辑贴出,它的逻辑为:
- 获取
ctx
的handler
,也就是我们的匿名内部类,并回调handlerAdded
方法。 - 基于
CAS
将add
状态设置为完成。
private void callHandlerAdded0(final AbstractChannelHandlerContext ctx) {
try {
ctx.handler().handlerAdded(ctx);
ctx.setAddComplete();
} catch (Throwable t) {
//略
}
}
而handlerAdded
方法,回调用一个initChannel
的方法。
@Override
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
if (ctx.channel().isRegistered()) {
initChannel(ctx);
}
}
步入initChannel
它的核心逻辑就是基于ctx
的channel
调用initChannel
。
@SuppressWarnings("unchecked")
private boolean initChannel(ChannelHandlerContext ctx) throws Exception {
if (initMap.putIfAbsent(ctx, Boolean.TRUE) == null) { // Guard against re-entrance.
try {
initChannel((C) ctx.channel());
} catch (Throwable cause) {
//略
return true;
}
return false;
}
步入initChannel
终于来到了我们匿名内部类的initChannel
,由此完成一次闭环。
来小结一下初始化服务端channel
的步骤,完成反射创建之后,addLast
在将ChannelInitializer
添加到pipeline
上之后,会向EventExecutort
提交一个PendingHandlerAddedTask
任务,该任务会回调传入的handler
的handlerAdded
方法,而ChannelInitializer
的handlerAdded
会调用内部initChannel
方法,最终走到我们匿名内部类ChannelInitializer
的内部逻辑。
查看initChannel
的逻辑:
- 它会获取
NioSocketChannel
的pipeline
- 获取配置中的
handler
即我们上文添加的ServerHandler
。 - 再次调用
addLast
方法,需要注意因为这个handler
注册过,且当前执行的就是eventLoop
线程,所以它会直接执行addLast
内部的callHandlerAdded0
方法。 - 最后添加
ServerBootstrapAcceptor
处理连接请求。
p.addLast(new ChannelInitializer<Channel>() {
@Override
public void initChannel(Channel ch) throws Exception {
//获取pipeline
final ChannelPipeline pipeline = ch.pipeline();
//拿到配置中的handler即我们的ServerHandler
ChannelHandler handler = config.handler();
//添加到pipeline中
if (handler != null) {
pipeline.addLast(handler);
}
//添加连接器ServerBootstrapAcceptor处理连接请求
ch.eventLoop().execute(new Runnable() {
@Override
public void run() {
pipeline.addLast(new ServerBootstrapAcceptor(
currentChildGroup, currentChildHandler, currentChildOptions, currentChildAttrs));
}
});
}
});
addLast
工作职责上文已经说过了,大抵上是添加handler
、更新状态、执行回调,所以它最终回调到我们的ServerHandler
的handlerAdded
,自此配置服务端的pipeline
整体流程讲解完毕。
@Override
public void handlerAdded(ChannelHandlerContext ctx) {
System.out.println("handlerAdded");
}
然后就是连接器的添加了,代码的入口就在我们提到的ServerBootstrap
回调方法的下方,可以看到设计者们对于注册Selector
的操作仍然是放到EventLoop
中执行,这一点作者也给出了原因:
We add this handler via the EventLoop as the user may have used a ChannelInitializer as handler.
In this case the initChannel(…) method will only be called after this method returns. Because
of this we need to ensure we add our handler in a delayed fashion so all the users handler are
placed in front of the ServerBootstrapAcceptor.
其大意是我们需要通过eventLoop
添加此处理程序,因为用户可能使用了ChannelInitializer
作为处理程序。在这种情况下,initChannel
方法只有在该方法返回后才会被调用。因此,我们需要将连接器添加以eventLoop
任务的方式进行提交,实现延迟的方式添加处理程序,以便将所有用户处理程序放在ServerBootstrapAcceptor
前面。
对应代码如下可以看到添加ServerBootstrapAcceptor
传入的我们上文所配置的各种ChildHandler
、ChildOptions
、ChildAttrs
等。然后仍然是通过addLast
将其封装成上下文添加到pipeline
的链表上完成handlerAdded
回调。
p.addLast(new ChannelInitializer<Channel>() {
@Override
public void initChannel(Channel ch) throws Exception {
//略
//添加连接器ServerBootstrapAcceptor
ch.eventLoop().execute(new Runnable() {
@Override
public void run() {
pipeline.addLast(new ServerBootstrapAcceptor(
currentChildGroup, currentChildHandler, currentChildOptions, currentChildAttrs));
}
});
}
});
注册channel
接下来就是对channel
的注册了,我们的代码再次回到AbstractBootstrap
的initAndRegister
上,我们在之前解读中已经完成了服务端的创建newChannel
和初始化init
,所以这里我们重点阅读register(channel)
的调用。
final ChannelFuture initAndRegister() {
//略
//注册selector
ChannelFuture regFuture = config().group().register(channel);
//略
return regFuture;
}
我们步入register
可以看到它执行以下两步:
- 调用
next
得到一个EventLoop
。 - 调用
register
完成注册。
@Override
public ChannelFuture register(Channel channel) {
return next().register(channel);
}
我们直接步入register
方法,该方法无论以何种方式都会是对register0
的调用,整体逻辑为:
- 判断当前线程是否是
eventLoop
如果是直接调用register0
,反之执行步骤2。 - 将
register0
封装成一个任务并提交到eventLoop
中。
因为我们的线程不是eventLoop
所以需要走第二个分支。
@Override
public final void register(EventLoop eventLoop, final ChannelPromise promise) {
//略
AbstractChannel.this.eventLoop = eventLoop;
//如果在eventLoop中直接调用register0
if (eventLoop.inEventLoop()) {
register0(promise);
} else {
try {
//若不在eventLoop则封装成一个线程任务提交到eventLoop中执行
eventLoop.execute(new Runnable() {
@Override
public void run() {
register0(promise);
}
});
} catch (Throwable t) {
//略
}
}
}
我们继续查看register0
,代码很长,整体的步骤为:
- 调用
doRegister
完成注册。 - 如果是第一次注册,则通过
pipeline
传播机制,触发handlerAdded
回调。 - 通过
pipeline
传播机制,触发channelRegistered
回调。 - 如果已连接且是第一次注册则回调
channelActive
方法。
这里因为我们连接还未建立,所以代码只会执行步骤1、2、3。
private void register0(ChannelPromise promise) {
try {
//略
//调用doRegister完成实际注册逻辑
doRegister();
neverRegistered = false;
registered = true;
//如果是第一次注册则调用pipeline上处理器的handlerAdded方法
pipeline.invokeHandlerAddedIfNeeded();
safeSetSuccess(promise);
//调用pipeline上处理器的channelRegistered方法
pipeline.fireChannelRegistered();
//如果连接激活且是第一次注册则回调channelActive方法
if (isActive()) {
if (firstRegistration) {
pipeline.fireChannelActive();
} else if (config().isAutoRead()) {
//略
}
}
} catch (Throwable t) {
//略
}
}
doRegister完成注册
我们先来看看doRegister
方法内部逻辑,可以看到它调用javaChannel
方法获取到JDK
的ServerSocketChannelImpl
并调用register
完成注册,并返回SelectionKey
。
@Override
protected void doRegister() throws Exception {
boolean selected = false;
for (;;) {
try {
selectionKey = javaChannel().register(eventLoop().selector, 0, this);
return;
} catch (CancelledKeyException e) {
//略
}
}
}
register
方法逻辑比较简单:
- 上
regLock
锁,确保操作线程安全。 - 判断是否当前
channel
是否open
,如果不是则抛异常,反之进入步骤3。 - 通过位运算判断
ops
是否是0,如果不是0则抛出异常,反之进入步骤4。 - 判断是否阻塞,如果阻塞则抛出异常,反之进入步骤5。
- 通过
Selector
得到SelectionKey
。 - 判断
key
是否为空,若不为空,则将ops
绑定到SelectionKey
上,注意这里的ops
为0,代表不关心任何事件,仅仅完成注册操作。 - 并将
att
添加到SelectionKey
上确保收到这个事件会及时通知att
,需要说明一下这里的att
即我们的NioSocketChannel
,反之进入步骤7。 - 如果为空,则调用
nio
包下的register
方法,创建一个SelectionKey
并将SelectionKey
感兴趣的值设置为0,并将att
绑定到SelectionKey
上,后续如果有读写事件则会及时通知我们的NioSocketChannel
进行处理。 - 将生成的
SelectionKey
保存到SelectionKey
数组上。
public final SelectionKey register(Selector sel, int ops,
Object att)
throws ClosedChannelException
{
//上锁
synchronized (regLock) {
//如果连接为打开则直接抛异常
if (!isOpen())
throw new ClosedChannelException();
//如果是无效ops则抛异常
if ((ops & ~validOps()) != 0)
throw new IllegalArgumentException();
//如果阻塞则抛阻塞异常
if (blocking)
throw new IllegalBlockingModeException();
//拿到当前的SelectionKey
SelectionKey k = findKey(sel);
if (k != null) {
//设置感兴趣的事件,以及收到事件后要通知的对象
k.interestOps(ops);
k.attach(att);
}
//如果key为空则上锁创建一个,并基于nio包下的SelectorImpl完成SelectionKey创建和事件设置和通知对象设置,
if (k == null) {
// New registration
synchronized (keyLock) {
if (!isOpen())
throw new ClosedChannelException();
k = ((AbstractSelector)sel).register(this, ops, att);
//将key设置到SelectionKey数组上
addKey(k);
}
}
return k;
}
}
invokeHandlerAddedIfNeeded进行handlerAdded回调
完成register
操作之后,invokeHandlerAddedIfNeeded
,它会判断是否是第一次注册,如果是则会调用callHandlerAddedForAllHandlers
回调pipeline
上handler
的handlerAdded
方法。
final void invokeHandlerAddedIfNeeded() {
assert channel.eventLoop().inEventLoop();
if (firstRegistration) {
firstRegistration = false;
//如果是第一次注册则调用callHandlerAddedForAllHandlers
callHandlerAddedForAllHandlers();
}
}
还记得我们上文中提到的pendingHandlerCallbackHead
,即我们添加handler时封装的一个任务,它会调用任务对应的hander
的handlerAdded
方法。我们步入callHandlerAddedForAllHandlers
就会看到pendingHandlerCallbackHead
。
private void callHandlerAddedForAllHandlers() {
final PendingHandlerCallback pendingHandlerCallbackHead;
synchronized (this) {
//略
//拿到pendingHandlerCallbackHead ,并将this的pendingHandlerCallbackHead 置空辅助gc
pendingHandlerCallbackHead = this.pendingHandlerCallbackHead;
this.pendingHandlerCallbackHead = null;
}
//从pendingHandlerCallbackHead开始遍历,逐个调用每个task 中的handler的handlerAdded
PendingHandlerCallback task = pendingHandlerCallbackHead;
while (task != null) {
task.execute();
task = task.next;
}
}
基于fireChannelRegistered回调channelRegistered
最后就是fireChannelRegistered
,我们步入fireChannelRegistered
即可看到invokeChannelRegistered
这个方法的调用。
可以看到入参为head
,即从HeadContext
作为入参开始invokeChannelRegistered
这个方法的调用。
@Override
public final ChannelPipeline fireChannelRegistered() {
AbstractChannelHandlerContext.invokeChannelRegistered(head);
return this;
}
步入invokeChannelRegistered
方法,可以看到它会调用一个channelRegistered
方法,其核心步骤很简单:
- 通过
handler
方法获取head
上下文对应的handler
。 - 调用
channelRegistered
方法。
private void invokeChannelRegistered() {
//判断handler状态是否被调用
if (invokeHandler()) {
try {
//通过handler得到上下文中的handler调用channelRegistered
((ChannelInboundHandler) handler()).channelRegistered(this);
} catch (Throwable t) {
notifyHandlerException(t);
}
} else {
fireChannelRegistered();
}
}
继续步进channelRegistered
,它会进行如下步骤:
- 会判断是否是第一次注册,如果是则
callHandlerAddedForAllHandlers
,反之进入步骤2。 - 通过上下文调用
fireChannelRegistered
。
@Override
public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
invokeHandlerAddedIfNeeded();
ctx.fireChannelRegistered();
}
来到fireChannelRegistered
可以看到它的核心即通过findContextInbound
遍历找到下一个inbound的handler
并调用了invokeChannelRegistered
方法。
@Override
public ChannelHandlerContext fireChannelRegistered() {
invokeChannelRegistered(findContextInbound());
return this;
}
步入invokeChannelRegistered
可以看到,因为当前工作线程在eventloop
上,所以直接调用invokeChannelRegistered
static void invokeChannelRegistered(final AbstractChannelHandlerContext next) {
EventExecutor executor = next.executor();
if (executor.inEventLoop()) {
next.invokeChannelRegistered();
} else {
executor.execute(new Runnable() {
@Override
public void run() {
next.invokeChannelRegistered();
}
});
}
}
于是代码来到了invokeChannelRegistered
,最终调用handler
获取到我们绑定的ServerHandler
,回调channelRegistered
方法。
private void invokeChannelRegistered() {
if (invokeHandler()) {
try {
((ChannelInboundHandler) handler()).channelRegistered(this);
} catch (Throwable t) {
notifyHandlerException(t);
}
} else {
fireChannelRegistered();
}
}
自此来到我们的channelRegistered
由此完成一次闭环。
@Override
public void channelRegistered(ChannelHandlerContext ctx) {
System.out.println("channelRegistered");
}
来小结一下注册selector
的整体步骤:
- 调用
doRegister
完成事件注册和以及事件通知对象NioSocketChannel
设置。 - 如果是第一次注册则回调
pipeline
上的handlerAdded
方法。 - 基于
pipeline
从HeadContext
开始进行对handler
的channelRegistered
方法回调。
完成端口绑定
最后一步就是完成端口绑定,代码再次回到AbstractBootstrap
上,我们完成initAndRegister
方法之后就来到了doBind0
方法
private ChannelFuture doBind(final SocketAddress localAddress) {
final ChannelFuture regFuture = initAndRegister();
final Channel channel = regFuture.channel();
if (regFuture.cause() != null) {
return regFuture;
}
//完成channel创建和初始化之后,这个regFuture会返回true
if (regFuture.isDone()) {
//调用doBind进行端口绑定
ChannelPromise promise = channel.newPromise();
doBind0(regFuture, channel, localAddress, promise);
return promise;
} else {
//略
}
}
步入doBind0
方法,它会提交一个异步任务,任务即调用NioSocketChannel
完成地址和端口绑定。
private static void doBind0(
final ChannelFuture regFuture, final Channel channel,
final SocketAddress localAddress, final ChannelPromise promise) {
//提交一个绑定端口号的任务
channel.eventLoop().execute(new Runnable() {
@Override
public void run() {
if (regFuture.isSuccess()) {
channel.bind(localAddress, promise).addListener(ChannelFutureListener.CLOSE_ON_FAILURE);
} else {
promise.setFailure(regFuture.cause());
}
}
});
}
步入bind
方法可以看到它仅仅是调用pipeline
的bind
方法。
@Override
public ChannelFuture bind(SocketAddress localAddress, ChannelPromise promise) {
return pipeline.bind(localAddress, promise);
}
我们再次步进,代码来到了DefaultChannelPipeline
的bind
方法,可以看到它通过tail
调用bind
方法。
@Override
public final ChannelFuture bind(SocketAddress localAddress, ChannelPromise promise) {
return tail.bind(localAddress, promise);
}
最终结果不断步进终于来到了NioServerSocketChannel
的doBind
内部仅仅获取channel
后调用bind
方法,我们继续步入。
@Override
protected void doBind(SocketAddress localAddress) throws Exception {
if (PlatformDependent.javaVersion() >= 7) {
javaChannel().bind(localAddress, config.getBacklog());
} else {
javaChannel().socket().bind(localAddress, config.getBacklog());
}
}
于是来到tail
的bind
方法,它会执行下面这段逻辑:
- 基于
findContextOutbound
从tail
开始自后向前找到第一个outbound
的handler
的上下文。 - 拿到这个
handler
的executor
。 - 如果这个
executor
在eventLoop
中则直接调用invokeBind
。 - 反之提交到
eventLoop
中进行无锁串行化执行。
@Override
public ChannelFuture bind(final SocketAddress localAddress, final ChannelPromise promise) {
//从tail开始自后向前拿到第一个handler的AbstractChannelHandlerContext
final AbstractChannelHandlerContext next = findContextOutbound();
//拿到这个AbstractChannelHandlerContext 的executor
EventExecutor executor = next.executor();
//如果executor在eventLoop中直接调用invokeBind,反之提交到executor中异步执行
if (executor.inEventLoop()) {
next.invokeBind(localAddress, promise);
} else {
safeExecute(executor, new Runnable() {
@Override
public void run() {
next.invokeBind(localAddress, promise);
}
}, promise, null);
}
return promise;
}
于是拿着我们找到的AbstractChannelHandlerContext
,找到对应的handler
,调用bind
方法。
private void invokeBind(SocketAddress localAddress, ChannelPromise promise) {
if (invokeHandler()) {
try {
//通过上下文找到handler并调用bind方法
((ChannelOutboundHandler) handler()).bind(this, localAddress, promise);
} catch (Throwable t) {
notifyOutboundHandlerException(t, promise);
}
} else {
bind(localAddress, promise);
}
}
查看bind
方法会发现,它会调用unsafe
类的bind
方法
@Override
public void bind(
ChannelHandlerContext ctx, SocketAddress localAddress, ChannelPromise promise)
throws Exception {
unsafe.bind(localAddress, promise);
}
步入unsafe
的bind
,我们可以看到两个核心的逻辑:
- 基于
JDK
原生API
获取ServerSocketChannelImpl
完成端口绑定。 - 如果之前
channel
未绑定(wasActive
为false
)且本次绑定成功(调用isActice
方法返回true
),则提交一个调用pipeline
上所有handler
的channelActive
的回调
@Override
public final void bind(final SocketAddress localAddress, final ChannelPromise promise) {
//略
boolean wasActive = isActive();
try {
//基于jdk原生API获取ServerSocketChannelImpl完成端口绑定
doBind(localAddress);
} catch (Throwable t) {
safeSetFailure(promise, t);
closeIfClosed();
return;
}
//如果之前未绑定且本次绑定完成则提交一个调用pipeline上所有handler的channelActive的回调
if (!wasActive && isActive()) {
invokeLater(new Runnable() {
@Override
public void run() {
pipeline.fireChannelActive();
}
});
}
safeSetSuccess(promise);
}
查看 pipeline.fireChannelActive();
具体执行可以看到它最终会来到HeadContext
的handler
中的channelActive
方法其内部逻辑为:
- 基于
pipeline
完成channelActive
传播。 - 调用
readIfIsAutoRead
对SelectionKey
的兴趣集上增加OP_ACCEPT
,处理后续新的连接接入。
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
ctx.fireChannelActive();
readIfIsAutoRead();
}
fireChannelActive
的传播和上述各种事件传播类似,不多赘述,这里我们查看readIfIsAutoRead
内部逻辑,它会判断是否是autoRead
如果是则直接调用read
方法。
private void readIfIsAutoRead() {
if (channel.config().isAutoRead()) {
channel.read();
}
}
而read
方法内部也是对pipeline
上read
的调用。
@Override
public Channel read() {
pipeline.read();
return this;
}
可以看到pipeline
对于read
的调用则是直接调用tail
的read
。
@Override
public final ChannelPipeline read() {
tail.read();
return this;
}
于是通过findContextOutbound
还是找到HeadContext
,并拿到其executor
直接调用invokeRead
。
@Override
public ChannelHandlerContext read() {
//拿到headContext获取其executor ,完成invokeRead调用
final AbstractChannelHandlerContext next = findContextOutbound();
EventExecutor executor = next.executor();
if (executor.inEventLoop()) {
next.invokeRead();
} else {
//略
}
return this;
}
invokeRead
内部则是直接拿着HeadContext
中的handler
调用read
。
private void invokeRead() {
if (invokeHandler()) {
try {
((ChannelOutboundHandler) handler()).read(this);
} catch (Throwable t) {
notifyHandlerException(t);
}
} else {
read();
}
}
于是走到了unsafe的beginRead方法。
@Override
public void read(ChannelHandlerContext ctx) {
unsafe.beginRead();
}
其内部也仅仅是调用doBeginRead
@Override
public final void beginRead() {
assertEventLoop();
if (!isActive()) {
return;
}
try {
doBeginRead();
} catch (final Exception e) {
invokeLater(new Runnable() {
@Override
public void run() {
pipeline.fireExceptionCaught(e);
}
});
close(voidPromise());
}
}
不断步入后,最终来到doBeginRead,可以看到该方法内部逻辑为:
- 校验
SelectionKey
有效性。 - 获取
selectionKey
判断selectionKey
感兴趣的事件是否为0,即不关心任何事件,如果是则基于异或运算符添加readInterestOp
(readInterestOp
即我们上文channel
创建的时候,设置值为SelectionKey.OP_ACCEPT
)。
@Override
protected void doBeginRead() throws Exception {
// Channel.read() or ChannelHandlerContext.read() was called
final SelectionKey selectionKey = this.selectionKey;
if (!selectionKey.isValid()) {
return;
}
readPending = true;
final int interestOps = selectionKey.interestOps();
if ((interestOps & readInterestOp) == 0) {
selectionKey.interestOps(interestOps | readInterestOp);
}
}
自此我们将端口会绑定的源码也讲解完成了,小结一下端口绑定的流程:
- 基于
JDK
的API
完成channel
端口绑定。 - 基于
pipeline
传播channelActive
事件。 selectionKey
上设置SelectionKey.OP_ACCEPT
,使之可以处理后续各种新连接。
小结
自此我们将Netty
服务端启动的源码讲解完成了,下面我们来小结一下Netty
服务端启动的整体步骤:
- 基于反射创建
channel
。 - 基于引导类的配置信息完成
NioServerSocketChannel
的选项和属性绑定,以及child
的选项和属性记录。 - 将引导类配置的
handler
添加到pipeline
上。 - 添加连接器
ServerBootstrapAcceptor
,用于处理后续的连接请求。 - 注册
channel
,完成后基于pipeline
完成handlerAdd
、channelRegister
方法回调。 - 完成端口绑定,绑定完成之后触发
channelActive
方法回调并在selectionKey
上设置SelectionKey.OP_ACCEPT
,使之可以处理后续各种新连接。
常见面试题
请解释一下Netty服务端启动的主要步骤是什么?
整体来说是以下4个步骤:
- 基于反射创建
channel
。 - 完成
channel
初始化即选项和属性设置以及引导类配置的handler添加。 - 注册channel,将
selector
注册到Java NIO Channel
上。 - 完成端口绑定,绑定完成之后触发
channelActive
方法回调并在selectionKey
上设置SelectionKey.OP_ACCEPT
,使之可以处理后续各种新连接。
Netty服务端启动的过程中,主要有哪些重要的组件和概念?
Netty
启动的过程中,会涉及下面这些组件:
ServerBootstrap
:用于配置和启动Netty
服务端的引导类。EventLoopGroup
:EventLoopGroup
是由一组EventLoop
构成,用于处理客户端连接的I/O操作。Channel
:可以理解为一个网络连接,可以进行数据的读写操作。ChannelHandler
:实际处理请求和响应的业务逻辑。ChannelPipeline
:管理ChannelHandler
的容器,负责处理入站和出站的事件和操作。
ChannelPipeline和ChannelHandler它们在Netty服务端启动中的作用是什么?
ChannelPipeline
是一个拦截和处理事件的管道,由一些列ChannelHandler
组成,可以理解为构建请求和响应处理的流水线,将ChannelHandler
添加到ChannelPipeline
上,即可实现对特定事件的编码、解码、业务逻辑处理、数据读写等操作。
参考文献
Netty服务端创建源码流程解析:https://juejin.cn/post/7050739087724003358#heading-24
netty源码解析01-netty服务端启动过程:https://www.modb.pro/db/175020
Netty源码------Channel的创建到底干了什么?:https://blog.csdn.net/qqq3117004957/article/details/106440866