Netty 是一个高性能、异步事件驱动的网络应用程序框架,它使用了 NIO 技术来提供高性能的网络通信。在 NIO 中,有三种常用的多路复用器(Multiplexor):Selector、Poll 和 Epoll。这些多路复用器可以同时监听多个网络连接的 IO 事件,从而提高网络通信的效率。
- Selector
Selector 是 Java NIO 中的一种多路复用器,它可以监听多个网络连接的 IO 事件,如读、写、连接等。Selector 通过 selectionKey 来注册和监听网络连接的 IO 事件,当某个连接的 IO 事件就绪时,Selector 会通知应用程序进行相应的处理。Selector 的实现是基于操作系统的选择器(select)来实现的,因此它的性能比传统的阻塞 IO 模型要高得多,但是在高并发情况下,它的性能比 Epoll 要低。
- Poll
Poll 是 Linux 操作系统中的一种多路复用器,它可以监听多个网络连接的 IO 事件。Poll 的工作原理类似于 Selector,但是它的实现是基于轮询(poll)来实现的。Poll 的性能比 Selector 要高,但是在高并发情况下,它的性能比 Epoll 要低。
- Epoll
Epoll 是 Linux 操作系统中的一种高性能多路复用器,它可以监听多个网络连接的 IO 事件。Epoll 的工作原理类似于 Selector 和 Poll,但是它的实现是基于事件驱动(event-driven)来实现的。Epoll 的特点是它可以在高并发情况下维持很高的性能,因为它不需要像 Selector 和 Poll 那样不断地轮询所有的网络连接,而是只需要监听那些真正需要处理的连接。
在 Netty 中,默认使用的是 Epoll 多路复用器,因为它的性能最高。但是,在某些情况下,Netty 也支持使用 Selector 和 Poll 多路复用器。可以通过配置 Netty 的 Transport 来选择使用哪种多路复用器。
Selector、Poll 和 Epoll 都是多路复用器,它们可以提高网络通信的效率。在选择哪种多路复用器时,需要根据实际情况来选择,通常在高并发情况下,使用 Epoll 是最好的选择。
源码解读
Epoll
由于 Netty 的代码比较庞大,因此这里我只能选择一小部分代码进行讲解。以下是 Netty 中 Epoll 多路复用器的部分代码,并添加了中文注释:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
|
/**
* Epoll implementation which supports edge-triggered mode.
*/
public final class EpollEventLoop extends SingleThreadEventLoop {
// Epoll 实例
private static final Epoll EPOLL = Epoll.newInstance();
// Epoll 多路复用器的文件描述符
private final int epollFd;
// 用于存储就绪的 IO 事件的数组
private final EpollEvent[] events;
public EpollEventLoop() throws IOException {
// 创建 Epoll 多路复用器
epollFd = EPOLL.epollCreate();
// 计算需要监听的 IO 事件的数量
int size = config().getIoRatio() * 2;
if (size < 64) {
size = 64;
}
// 创建用于存储就绪的 IO 事件的数组
events = new EpollEvent[size];
}
@Override
protected void run() {
int eventCnt;
try {
// 不断地轮询 Epoll 多路复用器,等待 IO 事件的发生
for (;;) {
// 计算需要等待的时间
long curDeadlineNanos = nextScheduledTaskDeadlineNanos();
if (curDeadlineNanos == -1L) {
curDeadlineNanos = NONE; // nothing on the calendar
}
// 调用 epoll_wait 函数,等待 IO 事件的发生
eventCnt = EPOLL.epollWait(epollFd, events, -1, curDeadlineNanos);
// 处理 IO 事件
for (int i = 0; i < eventCnt; i ++) {
EpollEvent event = events[i];
processReady(event);
}
}
} catch (Throwable t) {
handleLoopException(t);
} finally {
// 关闭 Epoll 多路复用器
EPOLL.close(epollFd);
}
}
// 处理就绪的 IO 事件
private void processReady(EpollEvent event) {
// 获取就绪的 IO 事件的类型
int events = event.events();
// 获取就绪的 IO 事件对应的 Channel
AbstractEpollChannel ch = (AbstractEpollChannel) event.attachment();
// 处理 IO 事件
if ((events & EPOLLHUP) != 0) {
// 处理连接关闭事件
ch.unsafe().close(ch.unsafe().voidPromise());
}
if ((events & EPOLLRDHUP) != 0) {
// 处理对端关闭事件
ch.unsafe().close(ch.unsafe().voidPromise());
}
if ((events & EPOLLERR) != 0) {
// 处理 IO 错误事件
ch.unsafe().close(ch.unsafe().voidPromise());
}
if ((events & EPOLLIN) != 0) {
// 处理可读事件
ch.read();
}
if ((events & EPOLLOUT) != 0) {
// 处理可写事件
ch.write();
}
}
}
|
以上代码是 Netty 中 Epoll 多路复用器的部分实现,主要包括以下几个部分:
- 创建 Epoll 多路复用器:在构造函数中,通过调用
Epoll.newInstance()
函数创建了一个 Epoll 实例,并通过调用 EPOLL.epollCreate()
函数创建了一个 Epoll 多路复用器的文件描述符。
- 创建用于存储就绪的 IO 事件的数组:在构造函数中,根据配置的 IO 比例计算出需要监听的 IO 事件的数量,并创建了一个用于存储就绪的 IO 事件的数组。
- 不断地轮询 Epoll 多路复用器:在
run()
函数中,通过调用 EPOLL.epollWait()
函数不断地轮询 Epoll 多路复用器,等待 IO 事件的发生。
- 处理 IO 事件:在
run()
函数中,当 IO 事件发生时,通过调用 processReady()
函数处理就绪的 IO 事件。
- 关闭 Epoll 多路复用器:在
run()
函数中,通过调用 EPOLL.close()
函数关闭 Epoll 多路复用器。
可以看出,Epoll 多路复用器的工作原理是通过不断地轮询 Epoll 多路复用器来等待 IO 事件的发生,当 IO 事件发生时,通过调用 processReady()
函数处理就绪的 IO 事件。这种工作模式可以有效地提高网络通信的效率,特别是在高并发情况下。
selector
Selector 是 Java NIO 中提供的一种多路复用器,它可以同时监听多个网络连接的 IO 事件,从而提高网络通信的效率。在 Netty 中,Selector 的实现是基于 Java NIO 的 Selector 类来实现的。以下是 Netty 中 Selector 多路复用器的部分代码,并添加了中文注释:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
|
/**
* Selector implementation which supports edge-triggered mode.
*/
public final class NioEventLoop extends SingleThreadEventLoop {
// Selector 实例
private final Selector selector;
// 用于存储就绪的 IO 事件的数组
private final SelectedSelectionKeySet selectedKeys;
public NioEventLoop() throws IOException {
// 创建 Selector 实例
selector = SelectorProvider.provider().openSelector();
// 创建用于存储就绪的 IO 事件的数组
selectedKeys = new SelectedSelectionKeySet();
}
@Override
protected void run() {
int selectedKeysCnt;
try {
// 不断地轮询 Selector,等待 IO 事件的发生
for (;;) {
// 计算需要等待的时间
long curDeadlineNanos = nextScheduledTaskDeadlineNanos();
if (curDeadlineNanos == -1L) {
curDeadlineNanos = NONE; // nothing on the calendar
}
// 调用 select() 函数,等待 IO 事件的发生
selectedKeysCnt = selector.select(curDeadlineNanos);
// 处理 IO 事件
processSelectedKeys(selectedKeysCnt);
}
} catch (Throwable t) {
handleLoopException(t);
} finally {
// 关闭 Selector
closeSelector();
}
}
// 处理就绪的 IO 事件
private void processSelectedKeys(int selectedKeysCnt) {
Set<SelectionKey> selectedKeys = this.selectedKeys;
// 遍历就绪的 IO 事件
for (SelectionKey k: selectedKeys) {
// 获取就绪的 IO 事件的类型
int readyOps = k.readyOps();
// 获取就绪的 IO 事件对应的 Channel
AbstractNioChannel ch = (AbstractNioChannel) k.attachment();
// 处理 IO 事件
if ((readyOps & SelectionKey.OP_CONNECT) != 0) {
// 处理连接事件
ch.doFinishConnect();
}
if ((readyOps & SelectionKey.OP_READ) != 0) {
// 处理可读事件
ch.doReadMessages(readBuffer);
}
if ((readyOps & SelectionKey.OP_WRITE) != 0) {
// 处理可写事件
ch.doWrite(writeBuffer);
}
if ((readyOps & SelectionKey.OP_ACCEPT) != 0) {
// 处理接收事件
ch.doAccept();
}
}
// 移除已处理的 IO 事件
selectedKeys.clear();
}
// 关闭 Selector
private void closeSelector() {
try {
selector.close();
} catch (IOException e) {
logger.warn("Failed to close a selector.", e);
}
}
}
|
以上代码是 Netty 中 Selector 多路复用器的部分实现,主要包括以下几个部分:
- 创建 Selector 实例:在构造函数中,通过调用
SelectorProvider.provider().openSelector()
函数创建了一个 Selector 实例。
- 创建用于存储就绪的 IO 事件的数组:在构造函数中,创建了一个用于存储就绪的 IO 事件的数组。
- 不断地轮询 Selector:在
run()
函数中,通过调用 selector.select()
函数不断地轮询 Selector,等待 IO 事件的发生。
- 处理 IO 事件:在
run()
函数中,当 IO 事件发生时,通过调用 processSelectedKeys()
函数处理就绪的 IO 事件。
- 关闭 Selector:在
run()
函数中,通过调用 closeSelector()
函数关闭 Selector。
可以看出,Selector 多路复用器的工作原理是通过不断地轮询 Selector 来等待 IO 事件的发生,当 IO 事件发生时,通过调用 processSelectedKeys()
函数处理就绪的 IO 事件。这种工作模式可以有效地提高网络通信的效率,特别是在高并发情况下。但是,Selector 的性能比 Epoll 要低,因为 Selector 需要不断地轮询所有的网络连接,而 Epoll 只需要监听那些真正需要处理的连接。