系列文章目录
这是本周期内系列打卡文章的所有文章的目录
- 《Go 并发数据结构和算法实践》学习笔记 Day 1
- 《Go 并发数据结构和算法实践》学习笔记 Day 2
- 《说透芯片》学习笔记 Day 3
- 《深入浅出计算机组成原理》学习笔记 Day 4
- 《编程高手必学的内存知识》学习笔记 Day 5
- NUMA内存知识 学习笔记 Day6
- 《C++并发编程》 学习笔记 Day7
- 《职场求生攻略》学习笔记 Day8
- 《网络编程实战》学习笔记 Day9
文章目录
- 系列文章目录
- 前言
- 一、poll是什么?(What)
- 二、(Why)
- 对比select认识
- 三、如何使用,demo实验show me the code?(How)
- 总结
前言
提示:这里可以添加本文要记录的大概内容:
学习内容:https://time.geekbang.org/column/article/140520
与知识建立主客体之间的联系:
其实内核poll的实现与select的实现类似,但在select之上做了改进,传递的数据结构不同,突破了文件描述符上限的限制。
提示:以下是本篇文章正文内容,下面案例可供参考
一、poll是什么?(What)
背景: select 有一个缺点,那就是所支持的文件描述符的个数是有限的,在 Linux 系统中,select 的默认最大值为 1024。
这就出现了 poll 函数。该 I/O 多路复用技术,突破了文件描述符个数限制。关键是传递给内核的数据结构不同。
poll 函数的原型:
// man poll
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
#include <poll.h>
第一个参数是一个 pollfd 的数组。注意这里的 events 可以表示多个不同的事件,具体的实现可以通过使用二进制掩码位操作来完成。
关键数据结构pollfd
struct pollfd {
int fd; /* file descriptor */
short events; /* requested events */
short revents; /* returned events */
};
events 类型的事件可以分为两大类:
- 第一类是可读事件
- 第二类是可写事件
如何查看poll函数代码的实现?TODO
二、(Why)
对比select认识
- 性能相近
有张I/O多路复用的图可以看出select和poll的性能
- 使用限制
和 select 函数对比一下,我们发现 poll 函数和 select 不一样的地方就是,在 select 里面,文件描述符的个数已经随着 fd_set 的实现而固定,没有办法对此进行配置;而在 poll 函数里,我们可以控制 pollfd 结构的数组大小,这意味着我们可以突破原来 select 函数最大描述符的限制。
- 其他对比摘录
和 select 非常不同的地方在于,poll 每次检测之后的结果不会修改原来的传入值,而是将结果保留在 revents 字段中,这样就不需要每次检测完都得重置待检测的描述字和感兴趣的事件。我们可以把 revents 理解成“returned events”。
poll 函数有一点非常好,如果我们不想对某个 pollfd 结构进行事件检测,可以把它对应的 pollfd 结构的 fd 成员设置成一个负值。
三、如何使用,demo实验show me the code?(How)
demo:
详见:https://gitee.com/jahentao/experiments/pulls/2/files
TODO 实验还要进行改进
./bin/pollserver
#define INIT_SIZE 128
int main(int argc, char **argv) {
int listen_fd, connected_fd;
int ready_number;
ssize_t n;
char buf[MAXLINE];
struct sockaddr_in client_addr;
listen_fd = tcp_server_listen(SERV_PORT);
//初始化pollfd数组,这个数组的第一个元素是listen_fd,其余的用来记录将要连接的connect_fd
struct pollfd event_set[INIT_SIZE];
event_set[0].fd = listen_fd;
event_set[0].events = POLLRDNORM;
// 用-1表示这个数组位置还没有被占用
int i;
for (i = 1; i < INIT_SIZE; i++) {
event_set[i].fd = -1;
}
for (;;) {
if ((ready_number = poll(event_set, INIT_SIZE, -1)) < 0) {
error(1, errno, "poll failed ");
}
if (event_set[0].revents & POLLRDNORM) {
socklen_t client_len = sizeof(client_addr);
connected_fd = accept(listen_fd, (struct sockaddr *) &client_addr, &client_len);
//找到一个可以记录该连接套接字的位置
for (i = 1; i < INIT_SIZE; i++) {
if (event_set[i].fd < 0) {
event_set[i].fd = connected_fd;
event_set[i].events = POLLRDNORM;
break;
}
}
if (i == INIT_SIZE) {
error(1, errno, "can not hold so many clients");
}
if (--ready_number <= 0)
continue;
}
for (i = 1; i < INIT_SIZE; i++) {
int socket_fd;
if ((socket_fd = event_set[i].fd) < 0)
continue;
if (event_set[i].revents & (POLLRDNORM | POLLERR)) {
if ((n = read(socket_fd, buf, MAXLINE)) > 0) {
if (write(socket_fd, buf, n) < 0) {
error(1, errno, "write error");
}
} else if (n == 0 || errno == ECONNRESET) {
close(socket_fd);
event_set[i].fd = -1;
} else {
error(1, errno, "read error");
}
if (--ready_number <= 0)
break;
}
}
}
}
客户端 1:
$telnet 127.0.0.1 43211
Trying 127.0.0.1...
Connected to 127.0.0.1.
Escape character is '^]'.
a
a
aaaaaaaaaaa
aaaaaaaaaaa
afafasfa
afafasfa
fbaa
fbaa
^]
telnet> quit
Connection closed.
客户端 2:
telnet 127.0.0.1 43211
Trying 127.0.0.1...
Connected to 127.0.0.1.
Escape character is '^]'.
b
b
bbbbbbb
bbbbbbb
bbbbbbb
bbbbbbb
^]
telnet> quit
Connection closed.
总结
提示:这里对文章进行总结:
今天的学习,我了解了poll。
poll 是另一种在各种 UNIX 系统上被广泛支持的 I/O 多路复用技术,虽然名声没有 select 那么响,能力一点不比 select 差,而且因为可以突破 select 文件描述符的个数限制,在高并发的场景下尤其占优势。
内容来源:
极客时间:21 | poll:另一种I/O多路复用