系列文章目录
这是本周期内系列打卡文章的所有文章的目录
- 《Go 并发数据结构和算法实践》学习笔记 Day 1
- 《Go 并发数据结构和算法实践》学习笔记 Day 2
- 《说透芯片》学习笔记 Day 3
- 《深入浅出计算机组成原理》学习笔记 Day 4
- 《编程高手必学的内存知识》学习笔记 Day 5
- NUMA内存知识 学习笔记 Day6
- 《C++并发编程》 学习笔记 Day7
- 《职场求生攻略》学习笔记 Day8
文章目录
- 系列文章目录
- 前言
- 一、select I/O多路复用模型是什么?(What)
- 二、select 内核是如何实现的?(Why)
- 三、如何使用select,demo实验show me the code?(How)
- 总结
前言
提示:这里可以添加本文要记录的大概内容:
学习内容:https://time.geekbang.org/column/article/138948
与知识建立主客体之间的联系:
其实我之前对高性能网络编程接触的少,除了计算机网络基础课TCP协议(三次握手、四次分手)、应用程序对Socket编程+多线程编程的实践理解外,没有真实需求推动,深究的就更少了。八股文背了,可能当时有些概念,过后又忘了。
但随着中间件的设计,团队有时在高并发改造场景,会谈到“poll”类比该I/O多路复用模型。这次真发现了这个网络编程实践专栏高性能篇,将阻塞/非阻塞/异步、I/O多路复用(select、poll、epoll)、多进程/多线程编程模型,三个维度循序渐进的组合起来,一步步揭开、对比、明晰,初看3篇文章概念+实践,和后续目录安排,方觉大快朵颐。
提示:以下是本篇文章正文内容,下面案例可供参考
一、select I/O多路复用模型是什么?(What)
问题场景:
I/O多路复用最初设计解决的场景是:标准输入、套接字等都看做I/O的一路。多路复用的意思是在任何一路有“事件”发生的情况下,通知应用程序去处理相应的I/O事件。而在没有I/O事件触发时,应用程序进程是挂起(阻塞,时间片调度执行其他进程),还是继续做其他事情(在剩余的时间片内)这就是I/O阻塞、非阻塞的维度了。
select之所以大名鼎鼎,可能跟golang通过协程实现高效的I/O多路复用有关。其关键字:select的使用和传播,让该I/O多路复用大名鼎鼎。
从专栏几篇文章看下来,要理解I/O多路复用,除了有开发者应用进程的视角,得理解到要从操作系统内核这个角度看(在序列图中,表示出这个实体)。从而,对比select、poll、epoll多种I/O多路复用模型。
使用select函数,通知内核挂起进程,当一个或多个I/O事件发生后,控制权返还给应用程序,由应用程序进行I/O事件的处理。
草图
二、select 内核是如何实现的?(Why)
select 函数调用传递给内核的数据结构,概念上是:文件描述符的集合 fd_set
。
在实现上,是通过INT型的bit位表示集合中的元素,因而也受实现的限制,select支持的I/O多路复用有1024的限制。
int select(int maxfd, fd_set *readset, fd_set *writeset, fd_set *exceptset, const struct timeval *timeout);
返回:若有就绪描述符则为其数目,若超时则为0,若出错则为-1
这块我没翻内核源码,之后补上 TODO。
集合数据结构fd_set *readset, fd_set *writeset, fd_set *exceptset
传递给内核,其实也暗示了内核的实现,内核根据I/O事件给读文件描述符、写文件描述符、异常文件描述符集合填充数据。
三、如何使用select,demo实验show me the code?(How)
demo:
详见:https://gitee.com/jahentao/experiments/pulls/1/files
调用select
函数,传参。
操作宏:
FD_ISSET
对向量进行检测,判断出对应套接字的元素 a[fd]是 0 还是 1
根据readmask
返会的文件描述符判断那一路I/O可读,进而返回控制权处理。
int main(int argc, char **argv) {
if (argc != 2) {
error(1, 0, "usage: select01 <IPaddress>");
}
int socket_fd = tcp_client(argv[1], SERV_PORT);
char recv_line[MAXLINE], send_line[MAXLINE];
int n;
fd_set readmask;
fd_set allreads;
FD_ZERO(&allreads);
FD_SET(0, &allreads);
FD_SET(socket_fd, &allreads);
for (;;) {
readmask = allreads;
int rc = select(socket_fd + 1, &readmask, NULL, NULL, NULL);
if (rc <= 0) {
error(1, errno, "select failed");
}
if (FD_ISSET(socket_fd, &readmask)) {
n = read(socket_fd, recv_line, MAXLINE);
if (n < 0) {
error(1, errno, "read error");
} else if (n == 0) {
error(1, 0, "server terminated \n");
}
recv_line[n] = 0;
fputs(recv_line, stdout);
fputs("\n", stdout);
}
if (FD_ISSET(STDIN_FILENO, &readmask)) {
if (fgets(send_line, MAXLINE, stdin) != NULL) {
int i = strlen(send_line);
if (send_line[i - 1] == '\n') {
send_line[i - 1] = 0;
}
printf("now sending %s\n", send_line);
size_t rt = write(socket_fd, send_line, strlen(send_line));
if (rt < 0) {
error(1, errno, "write failed ");
}
printf("send bytes: %zu \n", rt);
}
}
}
}
我想进一步在GEM5/QEMU仿真中,进行I/O多路复用实验的性能工程对比。
总结
提示:这里对文章进行总结:
今天的学习,我了解了select 函数的使用。select 函数提供了最基本的 I/O 多路复用方法,在使用 select 时,我们需要建立两个重要的认识:
- 描述符基数是当前最大描述符 +1;
- 每次 select 调用完成之后,记得要重置待测试集合。
内容来源:
极客时间:20 | 大名⿍⿍的select:看我如何同时感知多个I/O事件