这是本周期内系列打卡文章的所有文章的目录
提示:这里可以添加本文要记录的大概内容:
学习内容:https://time.geekbang.org/column/article/138948
与知识建立主客体之间的联系:
其实我之前对高性能网络编程接触的少,除了计算机网络基础课TCP协议(三次握手、四次分手)、应用程序对Socket编程+多线程编程的实践理解外,没有真实需求推动,深究的就更少了。八股文背了,可能当时有些概念,过后又忘了。
但随着中间件的设计,团队有时在高并发改造场景,会谈到“poll”类比该I/O多路复用模型。这次真发现了这个网络编程实践专栏高性能篇,将阻塞/非阻塞/异步、I/O多路复用(select、poll、epoll)、多进程/多线程编程模型,三个维度循序渐进的组合起来,一步步揭开、对比、明晰,初看3篇文章概念+实践,和后续目录安排,方觉大快朵颐。
提示:以下是本篇文章正文内容,下面案例可供参考
问题场景:
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 函数调用传递给内核的数据结构,概念上是:文件描述符的集合 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事件给读文件描述符、写文件描述符、异常文件描述符集合填充数据。
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 ");}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多路复用实验的性能工程对比,TODO。
提示:这里对文章进行总结:
今天的学习,我了解了select 函数的使用。select 函数提供了最基本的 I/O 多路复用方法,在使用 select 时,我们需要建立两个重要的认识:
内容来源:
极客时间:20 | 大名⿍⿍的select:看我如何同时感知多个I/O事件