1. Overview
先上一个Echo
的例子:
上述例子中,左侧是服务端,右侧是客户端。
可以看到,代码中涉及到6部分:
-
Bootstrap
:启动服务端/客户端的引导类,服务端需要使用ServerBootstrap
-
EventLoopGroup
:事件处理线程池,服务端需要boss
和worker
2个线程池(Netty线程模型,本质为多路复用),而客户端只需要1个 -
Channel
:两端全双工的通信通道,可支持多种协议,这里分别是NioServerSocketChannel
和NioSocketChannel
-
Handler
:用于处理每个新请求InboundHandler
:用于处理请求的接收OutboundHandler
:用于处理响应的发送
服务端上:
-
handler
:用于处理新连接 -
childHandler
:用于处理连接后的请求
-
ChannelPipeline
:当多个handler
配置后,会组成一个pipeline
,用于决定handler
的执行顺序 -
ChannelFuture
:类似于Java的Future
首先我们将上面几个组件,然后过一遍connect
和bind
的流程。
2. Netty Channel
这里只讨论最常用了TCP的Channel
,它和JDK NIO的Channel
拥有相同的含义。
Netty中,和Java NIO一样,服务端和客户端使用自己的Channel
:
- 服务端:
NioServerSocketChannel
,对应NIO的ServerSocketChannel
- 客户端:
NioSocketChannel
,对应NIO的SocketChannel
在创建服务端/客户端时,通过channel()
配置,它会创建一个工厂,连接创建时,对应的Channel
也会被创建(通过反射):
- 服务端:
bind()
时 - 客户端:
connect()
时
2.1. 客户端Channel
它在connect()
时创建:
1
2
3
4
5
6
7
8
public ChannelFuture connect(SocketAddress remoteAddress) {
if (remoteAddress == null) {
throw new NullPointerException("remoteAddress");
}
validate();
// 这一步
return doResolveAndConnect(remoteAddress, config.localAddress());
}
最后调入doResolveAndConnect
方法:
1
2
3
4
5
6
7
private ChannelFuture doResolveAndConnect(final SocketAddress remoteAddress, final SocketAddress localAddress) {
// Channel创建主要是这一步: initAndRegister
final ChannelFuture regFuture = initAndRegister();
final Channel channel = regFuture.channel();
// 后面之后再讲
// ...
}
这里进入initAndRegister
:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
final ChannelFuture initAndRegister() {
Channel channel = null;
try {
// 1. channelFactory通过反射创建Channel
channel = channelFactory.newChannel();
// 2. 涉及channelPipeline的配置初始化
init(channel);
} catch(Throwable t) {
// ...
}
// ...
// 3. 将channel注册到event group中
ChannelFuture regFuture = group().register(channel);
// ...
return regFuture
}
这里只讲第一步,后两步之后讲。
channelFactory
反射调用的NioSocketChannel
的构造方法:
-
首先,创建
socket
句柄:调用NioSocketChannel
的newSocket
方法,它最后调用参数中的SelectorProvider#openSocketChannel()
方法,创建了一个SocketChannelImpl
实例,实例初始化时,做了下面几件事:SocketChannelImpl
是SocketChannel
的实现- 调用
Net#socket0
创建socket
句柄 - 记录
socket
句柄
- 调用
-
然后,初始化
channel
,步骤如下:- 在
AbstractChannel
,创建channelId
(ChannelId
),unsafe
(Unsafe
),pipeline
(Pipeline
),这些东西以后说 - 在
AbstractNioChannel
中,设置channel
为非阻塞,且设置监听事件SelectionKey.OP_READ
(即客户端只监听接收数据的事件) - 在
NioSocketChannel
中创建配置,这步无关紧要
- 在
2.2. 服务端Channel
它在bind()
时创建,会进入doBind(addr)
方法:
1
2
3
4
5
private ChannelFuture doBind(final SocketAddress localAddress) {
final ChannelFuture regFuture = initAndRegister();
final Channel channel = regFuture.channel();
// ...
}
依旧和客户端类似,会通过工厂创建(反射),这里是创建NioServerSocketChannel
:
-
首先,和客户端类似,创建
socket
句柄:调用NioServerSocketChannel
的newSocket
方法,它最后调用参数中的SelectorProvider#openServerSocketChannel()
方法,创建了一个ServerSocketChannelImpl
实例,实例初始化时,做了下面几件事:ServerSocketChannelImpl
是ServerSocketChannel
的实现- 调用
Net#socket0
创建socket
句柄(和客户端的不同,这里reuse
为true
) - 记录
socket
句柄
- 调用
-
然后,也和客户端类似,初始化
channel
:- 在
AbstractChannel
,创建channelId
(ChannelId
),unsafe
(Unsafe
),pipeline
(Pipeline
),这些东西以后说 - 在
AbstractNioChannel
中,设置channel
为非阻塞,且设置监听事件SelectionKey.OP_ACCEPT
(即服务端关心连接事件) - 在
NioServerSocketChannel
中创建配置
- 在
2.3. 总结
从上面可知,很简单,Netty中,和Java NIO一样,服务端和客户端使用自己的Channel
,但是它们只是包装了JDK NIO的Channel
实现:
- 服务端:
NioServerSocketChannel
,对应NIO的ServerSocketChannel
,监听ACCEPT
- 客户端:
NioSocketChannel
,对应NIO的SocketChannel
,监听READ
2.4. 下一篇文章
上述创建中涉及到2个很重要的组件:
EventGroup
ChannelPipeline
但在此之前,先说明一下Netty的Future
和Promise
,代表性的就是ChannelFuture
。