网络IO-探究poll和epoll在内核层面的不同

代码、资料来自于马士兵MAC课程。

本文通过 strace 命令来监控使用 poll 和 epoll 不同模型的同一代码在内核方法调用上的不同。

阅读本文,你将切实体会到 poll 和 epoll 的不同实现方式。对于两种模型的介绍推荐阅读:网络IO-IO模型的演变

前置:如何用同一段代码使用 poll 和 epoll 两种不同模型

代码 Selector selector = Selector.open(); 在 poll 和 epoll 模型都支持的情况下优先选择 epoll。

可以通过 JVM 参数 -Djava.nio.channels.spi.SelectorProvider=sun.nio.ch.PollSelectorProvider 手动选择 poll 模型。

代码 - SocketMultiplexingSingleThread

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
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
public class SocketMultiplexingSingleThread {

private ServerSocketChannel server;
private Selector selector;
private int port = 9090;

/**
* 建立监听,并注册进 selector
*/
private void initServer() {
try {
server = ServerSocketChannel.open();
server.configureBlocking(false);
server.bind(new InetSocketAddress(port));

selector = Selector.open();
server.register(selector, SelectionKey.OP_ACCEPT);
} catch (IOException e) {
e.printStackTrace();
}
}

public void start() {
initServer();
System.out.println("服务器初始化完成...");

try {
while (true) {
Set<SelectionKey> keys = selector.keys();
System.out.println(keys.size() + " size");

while (selector.select() > 0) {
Set<SelectionKey> selectionKeys = selector.selectedKeys();
Iterator<SelectionKey> iterator = selectionKeys.iterator();

while (iterator.hasNext()) {
SelectionKey key = iterator.next();
iterator.remove();
if (key.isAcceptable()) {
acceptHandler(key);
} else if (key.isReadable()) {
readHandler(key);
}
}
}
}

} catch (Exception e) {
e.printStackTrace();
}
}

/**
* 1、建立 socket
* 2、将指向这个 socket 注册进 select
*/
private void acceptHandler(SelectionKey key) {
try {
ServerSocketChannel serverSocketChannel = (ServerSocketChannel) key.channel();
SocketChannel socketChannel = serverSocketChannel.accept();
socketChannel.configureBlocking(false);

ByteBuffer buffer = ByteBuffer.allocate(8192);
socketChannel.register(selector, SelectionKey.OP_READ, buffer);

System.out.println("-------------------------------------------");
System.out.println("新客户端:" + socketChannel.getRemoteAddress());
System.out.println("-------------------------------------------");

} catch (IOException e) {
e.printStackTrace();
}

}

private void readHandler(SelectionKey key) {
System.out.println("read handler...");
SocketChannel client = (SocketChannel) key.channel();
ByteBuffer buffer = (ByteBuffer) key.attachment();
buffer.clear();
int read = 0;
try {
while (true) {
read = client.read(buffer);
if (read > 0) {
buffer.flip();
while (buffer.hasRemaining()) {
client.write(buffer);
}
buffer.clear();
} else if (read == 0) {
break;
} else {
client.close();
break;
}
}
} catch (IOException e) {
e.printStackTrace();
}
}

public static void main(String[] args) {
SocketMultiplexingSingleThread socketMultiplexingSingleThread = new SocketMultiplexingSingleThread();
socketMultiplexingSingleThread.start();
}
}

用 strace 追踪两种模型

1
2
3
4
5
6
strace -ff -o poll java -Djava.nio.channels.spi.SelectorProvider=sun.nio.ch.PollSelectorProvider SocketMultiplexingSingleThread

strace -ff -o epoll java SocketMultiplexingSingleThread

# 通过 nc 命令连接 java 代码并发送一条数据
nc 192.168.203.133 9090

这时在文件架下将会出现以下文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
-rw-r--r--. 1 root root  13893 6月   3 13:27 epoll.8306
-rw-r--r--. 1 root root 227300 6月 3 13:27 epoll.8307
-rw-r--r--. 1 root root 18207 6月 3 13:27 epoll.8308
-rw-r--r--. 1 root root 1005 6月 3 13:27 epoll.8309
-rw-r--r--. 1 root root 1128 6月 3 13:27 epoll.8310
-rw-r--r--. 1 root root 2166 6月 3 13:27 epoll.8311
-rw-r--r--. 1 root root 11073 6月 3 13:27 epoll.8312
-rw-r--r--. 1 root root 8025 6月 3 13:27 epoll.8313
-rw-r--r--. 1 root root 1004 6月 3 13:27 epoll.8314
-rw-r--r--. 1 root root 182136 6月 3 13:27 epoll.8315
-rw-r--r--. 1 root root 1925 6月 3 13:27 epoll.8317
-rw-r--r--. 1 root root 13932 6月 3 13:26 poll.8292
-rw-r--r--. 1 root root 213812 6月 3 13:26 poll.8293
-rw-r--r--. 1 root root 11598 6月 3 13:26 poll.8294
-rw-r--r--. 1 root root 1005 6月 3 13:26 poll.8295
-rw-r--r--. 1 root root 1128 6月 3 13:26 poll.8296
-rw-r--r--. 1 root root 2166 6月 3 13:26 poll.8297
-rw-r--r--. 1 root root 7734 6月 3 13:26 poll.8298
-rw-r--r--. 1 root root 4650 6月 3 13:26 poll.8299
-rw-r--r--. 1 root root 1004 6月 3 13:26 poll.8300
-rw-r--r--. 1 root root 76215 6月 3 13:26 poll.8301
-rw-r--r--. 1 root root 1925 6月 3 13:26 poll.8303
-rw-r--r--. 1 root root 3574 6月 3 12:48 SocketMultiplexingSingleThread.class
-rw-r--r--. 1 root root 3773 6月 3 11:43 SocketMultiplexingSingleThread.java

我们观察 poll.8293epoll.8307 这两个文件。

poll.8293 的内核方法调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# ServerSocketChannel.open()
socket(AF_INET6, SOCK_STREAM, IPPROTO_IP) = 5
# server.configureBlocking(false)
fcntl(5, F_SETFL, O_RDWR|O_NONBLOCK) = 0
bind(5, {sa_family=AF_INET6, sin6_port=htons(9090), inet_pton(AF_INET6, "::", &sin6_addr), sin6_flowinfo=htonl(0), sin6_scope_id=0}, 28) = 0
listen(5, 50)

# selector.select() 这里就是把想要监听的 fds 全传给内核,让内核进行遍历返回可用的 fd
poll([{fd=6, events=POLLIN}, {fd=5, events=POLLIN}], 2, -1) = 1 ([{fd=5, revents=POLLIN}])
# SocketChannel socketChannel = serverSocketChannel.accept()
accept(5, {sa_family=AF_INET6, sin6_port=htons(35202), inet_pton(AF_INET6, "::ffff:192.168.203.133", &sin6_addr), sin6_flowinfo=htonl(0), sin6_scope_id=0}, [28]) = 8
# socketChannel.configureBlocking(false)
fcntl(8, F_SETFL, O_RDWR|O_NONBLOCK) = 0

# 再次进行 selector.select() 时也就会把 fd8 也放入参数当中监听是否有数据
poll([{fd=6, events=POLLIN}, {fd=5, events=POLLIN}, {fd=8, events=POLLIN}], 3, -1) = 1 ([{fd=8, revents=POLLIN}])

epoll.8307 的内核方法调用

1
2
3
4
5
6
7
8
9
10
11
12
13
# ServerSocketChannel.open()
socket(AF_INET6, SOCK_STREAM, IPPROTO_IP) = 5
# server.configureBlocking(false)
fcntl(5, F_SETFL, O_RDWR|O_NONBLOCK) = 0
bind(5, {sa_family=AF_INET6, sin6_port=htons(9090), inet_pton(AF_INET6, "::", &sin6_addr), sin6_flowinfo=htonl(0), sin6_scope_id=0}, 28) = 0
listen(5, 50)

# selector = Selector.open();
epoll_create(256) = 8
# server.register(selector, SelectionKey.OP_ACCEPT);
epoll_ctl(8, EPOLL_CTL_ADD, 5, {EPOLLIN, {u32=5, u64=9512930623052840965}}) = 0
# selector.select()
epoll_wait(8, [{EPOLLIN, {u32=5, u64=9512930623052840965}}], 4096, -1) = 1