手把手写C++服务器(31):服务器性能提升关键——IO复用技能【两

闲聊 闲聊 1532 人阅读 | 0 人回复

<
   本系列文章导航:脚把脚写C++效劳器(0):专栏文章-汇总导航【更新中】 
  媒介: Linux中素有“万物皆文件,统统皆IO”的道法。前里几讲脚撕了CGI网闭效劳器、echo回隐效劳器、discard效劳的代码,可是那几个一次只能监听一个文件形貌符,因而机能十分本初、低下。IO复用能使效劳器同时监听多个文件形貌符,是效劳器机能提拔的枢纽。固然IO复用自己是壅闭的,可是战并收手艺分离起去,再减上一面设想形式,一个下机能效劳器的基石便底子拆建完成了。
目次
1、准备常识
(1)文件形貌符
(2)历程壅闭
(3)缓存IO
(4)甚么是IO多路复用?
2、Linux五年夜IO模子
(1)壅闭IO
(2)非壅闭IO
(3)IO多路复用
(4)旌旗灯号驱动IO
(5)同步IO
3、select
函数返回
参数详解
主要构造体详解
利用流程
代码真例
4、poll
函数本型
主要构造体详解
变乱范例
利用流程
代码真例
5、epoll
函数本型
函数返回
LT程度触收形式战ET边缘触收形式
代码真例
6、三组IO复用函数比照
1. 用户态将文件形貌符传进内乱核的方法
2. 内乱核态检测文件形貌符读写形态的方法
3. 找到伏贴的文件形貌符并通报给用户态的方法
4. 反复监听的处理惩罚方法
7、典范面试题:epoll更下效的缘故原由
写正在最初
参考

1、准备常识

(1)文件形貌符

激烈举荐看一下本系列的第25讲《脚把脚写C++效劳器(25):万物皆可文件之socket fd
文件形貌符(File descriptor)是计较机科教中的一个术语,是一个用于表述指背文件的援用的笼统化观点。 文件形貌符正在情势上是一个非背整数。实践上,它是一个索引值,指背内乱核为每个历程所保护的该历程翻开文件的记载表。当法式翻开一个现有文件大概创立一个新文件时,内乱核背历程返回一个文件形貌符。正在法式设想中,一些触及底层的法式编写常常会环绕着文件形貌符睁开。可是文件形貌符那一观点常常只合用于UNIX、Linux如许的操纵系统。
(2)历程壅闭

正正在施行的历程,因为等候的某些变乱已发作,如恳求系统资本失利、等候某种操纵的完成、新数据还没有抵达或无新事情做等,则由系统主动施行壅闭本语(Block),使本人由运转形态变成壅闭形态。可睹,历程的壅闭是历程本身的一种自动举动,也因而只要处于运转态的历程(得到了CPU资本),才大要将其转为壅闭形态。当历程进进壅闭形态,是没有占用CPU资本的。
(3)缓存IO

缓存I/O又称为标准I/O,年夜大都文件系统的默许I/O操纵皆是缓存I/O。正在Linux的缓存I/O机造中,操纵系统会将I/O的数据缓存正在文件系统的页缓存中,即数据会先被拷贝到操纵系统内乱核的缓冲区中,然后才会从操纵系统内乱核的缓冲区拷贝到使用法式的地点空间。
缓存 I/O 的缺陷:
数据正在传输过程当中需求正在使用法式地点空间战内乱核停止屡次数据拷贝操纵,那些数据拷贝操纵所带去的 CPU 和内乱存开消口角常年夜的。
(4)甚么是IO多路复用?

IO 多路复用是一种同步IO模子,完成一个线程能够监视多个文件句柄;一旦某个文件句柄伏贴,就可以够照顾使用法式停止响应的读写操纵;出有文件句柄伏贴便会壅闭使用法式,交出CPU。
2、Linux五年夜IO模子

(1)壅闭IO

那是最经常使用的简朴的IO模子。壅闭IO意味着当我们倡议一次IO操纵后不断等候胜利或失利以后才返回,正在那时期法式不克不及做别的的事情。壅闭IO操纵只能对单个文件形貌符停止操纵,详睹readwrite
(2)非壅闭IO

我们正在倡议IO时,经由过程对文件形貌符设置O_NONBLOCK flag去指定该文件形貌符的IO操纵为非壅闭。非壅闭IO凡是发作正在一个for轮回傍边,由于每次停止IO操纵时要末IO操纵胜利,要末当IO操纵会壅闭时返回毛病EWOULDBLOCK/EAGAIN,然后再按照需求停止下一次的for轮回操纵,这类相似轮询的方法会华侈许多没有需要的CPU资本,是一种蹩脚的设想。战壅闭IO一样,非壅闭IO也是经由过程挪用readwrite去停止操纵的,也只能对单个形貌符停止操纵。
(3)IO多路复用

IO多路复用正在Linux下包罗了三种,selectpollepoll,笼统去看,他们功用是相似的,但详细细节各有差别:尾先城市对一组文件形貌符停止相干变乱的注册,然后壅闭等候某些变乱的发作或等候超时。IO多路复用皆能够存眷多个文件形貌符,但关于那三种机造而行,差别数目级文件形貌符对机能的影响是差别的,上面会详细引见。
(4)旌旗灯号驱动IO

旌旗灯号驱动IO是操纵旌旗灯号机造,让内乱核见告使用法式文件形貌符的相干变乱。
但旌旗灯号驱动IO正在收集编程的时分凡是很罕用到,由于正在收集情况中,战socket相干的读写变乱太多了,好比上面的变乱城市招致SIGIO旌旗灯号的发生:

  • TCP毗连创立
  • 一圆断开TCP毗连恳求
  • 断开TCP毗连恳求完成
  • TCP毗连半封闭
  • 数据抵达TCP socket
  • 数据曾经收收进来(如:写buffer有空余空间)
上里一切的那些城市发生SIGIO旌旗灯号,但我们出法子正在SIGIO对应的旌旗灯号处理惩罚函数中辨别上述差别的变乱,SIGIO只该当正在IO变乱单一状况下利用,好比道用去监听端心的socket,由于只要客户端倡议新毗连的时分才会发生SIGIO旌旗灯号。
(5)同步IO

同步IO战旌旗灯号驱动IO好未几,但它比旌旗灯号驱动IO能够多做一步:比拟旌旗灯号驱动IO需求正在法式中完成数据从用户态到内乱核态(或反标的目的)的拷贝,同步IO能够把拷贝那一步也帮我们完成以后才照顾使用法式。我们利用 aio_read 去读,aio_write 写。
同步IO vs 同步IO
1. 同步IO指的是法式会不断壅闭到IO操纵如read、write完成
2. 同步IO指的是IO操纵没有会壅闭当出息序的持续施行
所以按照那个界说,上里壅闭IO当然算是同步的IO,非壅闭IO也是同步IO,由于当文件操纵符可用时我们依旧需求壅闭的读或写,同理IO多路复用战旌旗灯号驱动IO也是同步IO,只要同步IO是完整完成了数据的拷贝以后才照顾法式停止处理惩罚,出有壅闭的数据读写历程。
3、select

select的感化是正在一段指定的工夫内乱,监听用户感爱好的文件形貌符上的可读、可写、非常等变乱。函数本型以下:
  1. #include <sys/select.h>
  2. int select(int nfds, fd_set *readfds, fd_set *writefds,
  3.                 fd_set *exceptfds, struct timeval *timeout);
复造代码
函数返回



  • select胜利时返回伏贴文件形貌符的总数;
  • 假如正在超不时间内乱出有任何文件形貌符伏贴,select将返回0;
  • select失利时返回-1并设置errno。;
  • 假如正在select等候时期,法式领受到旌旗灯号,select立刻返回-1,并将errno设置为EINTR。
参数详解



  • nfds:指定被监听文件形貌符总数。凡是被设置为select监听一切文件形貌符中的最年夜值+1。
  • readfds:可读变乱对应文件形貌符汇合。
  • writefds:可写变乱对应文件形貌符汇合。
  • exceptfds:非常变乱对应文件形貌符汇合。
  • timeout:设置select超不时间。
主要构造体详解

readfds、writefds、exceptfds皆是fd_set构造体,timeout是timeval构造体,那里详解一下那两个构造体。
1、fd_set
fd_set构造体界说比力庞大,触及到位操纵,比力庞大。所以凡是用宏去会见fd_set中的位。
  1. #include <sys/select.h>
  2. FD_ZERO(fd_set* fdset);    // 肃清fdset中的一切位
  3. FD_SET(int fd, fd_set* fdset); // 设置fdset中的位
  4. FD_CLR(int fd, fd_set* fdset); // 肃清fdset中的位
  5. int FD_ISSET(int fd, fd_set* fdset);  // 测试fdset的位fd能否被设置
复造代码


  • FD_ZERO用去浑空文件形貌符组。每次挪用select前皆需求浑空一次。
  • FD_SET增加一个文件形貌符到组中,FD_CLR对应将一个文件形貌符移出组中。
  • FD_ISSET检测一个文件形貌符能否正在组中,我们用那个去检测一次select挪用以后有哪些文件形貌符能够停止IO操纵。
2、timeval
  1. struct timeval {
  2.     long tv_sec; // 秒数
  3.     long tv_usec; // 奇妙数
  4. };
复造代码
利用流程

综上所述,我们普通的利用流程是:

  • 筹办事情——界说readfds、timeval等
  • 利用FD_ZERO浑整,利用FD_SET设置文件形貌符。由于变乱发作后,文件形貌符汇合皆将被内乱核修正。
  • 挪用select
  • 利用FD_ISSET检测文件形貌符能否正在组中
代码真例

按照利用流程,给出一个代码示例:
  1. #include <stdio.h>
  2. #include <sys/time.h>
  3. #include <sys/types.h>
  4. #include <unistd.h>
  5. #define TIMEOUT 5 /* select timeout in seconds */
  6. #define BUF_LEN 1024 /* read buffer in bytes */
  7. int main (void) {
  8.   struct timeval tv;
  9.   fd_set readfds;
  10.   int ret;
  11.   
  12.   /* Wait on stdin for input. */
  13.   FD_ZERO(&readfds);
  14.   FD_SET(STDIN_FILENO, &readfds);
  15.   /* Wait up to five seconds. */
  16.   tv.tv_sec = TIMEOUT;
  17.   tv.tv_usec = 0;
  18.   
  19.   /* All right, now block! */
  20.   ret = select (STDIN_FILENO + 1, &readfds,
  21.                 NULL,
  22.                 NULL,
  23.                 &tv);
  24.   if (ret == −1) {
  25.     perror ("select");
  26.     return 1;
  27.   } else if (!ret) {
  28.     printf ("%d seconds elapsed.\n", TIMEOUT);
  29.     return 0;
  30.   }
  31.   /*
  32.   * Is our file descriptor ready to read?
  33.   * (It must be, as it was the only fd that
  34.   * we provided and the call returned
  35.   * nonzero, but we will humor ourselves.)
  36.   */
  37.   if (FD_ISSET(STDIN_FILENO, &readfds)) {
  38.     char buf[BUF_LEN+1];
  39.     int len;
  40.     /* guaranteed to not block */
  41.     len = read (STDIN_FILENO, buf, BUF_LEN);
  42.     if (len == −1) {
  43.       perror ("read");
  44.       return 1;
  45.     }
  46.     if (len) {
  47.       buf[len] = &#39;\0&#39;;
  48.       printf ("read: %s\n", buf);
  49.     }
  50.     return 0;
  51.   }
  52.   fprintf (stderr, "This should not happen!\n");
  53.   return 1;
  54. }
复造代码
前面一讲会给出一些适用的例子,有了select以后我们能够同时监听许多个恳求,系统的处理惩罚才能年夜年夜加强了。
4、poll

战select相似,正在必然工夫内乱轮询必然数目的文件形貌符。
函数本型

  1. #include <poll.h>
  2. int poll(struct pollfd* fds, nfds_t nfds, int timeout);
复造代码
可是战select差别的是,select需求用三组文件形貌符,poll只要一个pollfd文件数组,数组中的每一个元素皆表现一个需求监听IO操纵变乱的文件形貌符。并且我们只需求体贴数组中events参数,revents由内乱核主动加添。
主要构造体详解

  1.     struct pollfd {
  2.         int fd;    // 文件形貌符
  3.         short events;    // 注册的变乱
  4.         short revents;   // 实践发作的变乱,由内乱核添补
  5.     };
复造代码
变乱范例

详细的变乱范例参看脚册:https://man7.org/linux/man-pages/man2/poll.2.html
  1.        <strong>POLLIN </strong>There is data to read.
  2.        <strong>POLLPRI</strong>
  3.               There is some exceptional condition on the file
  4.               descriptor.  Possibilities include:
  5.               • There is out-of-band data on a TCP socket (see <a target="_blank" href="https://man7.org/linux/man-pages/man7/tcp.7.html">tcp(7)</a>).
  6.               • A pseudoterminal master in packet mode has seen a state
  7.                 change on the slave (see <a target="_blank" href="https://man7.org/linux/man-pages/man2/ioctl_tty.2.html">ioctl_tty(2)</a>).
  8.               • A <em>cgroup.events</em> file has been modified (see <a target="_blank" href="https://man7.org/linux/man-pages/man7/cgroups.7.html">cgroups(7)</a>).
  9.        <strong>POLLOUT</strong>
  10.               Writing is now possible, though a write larger than the
  11.               available space in a socket or pipe will still block
  12.               (unless <strong>O_NONBLOCK </strong>is set).
  13.        <strong>POLLRDHUP </strong>(since Linux 2.6.17)
  14.               Stream socket peer closed connection, or shut down writing
  15.               half of connection.  The <strong>_GNU_SOURCE </strong>feature test macro
  16.               must be defined (before including <em>any</em> header files) in
  17.               order to obtain this definition.
  18.        <strong>POLLERR</strong>
  19.               Error condition (only returned in <em>revents</em>; ignored in
  20.               <em>events</em>).  This bit is also set for a file descriptor
  21.               referring to the write end of a pipe when the read end has
  22.               been closed.
  23.        <strong>POLLHUP</strong>
  24.               Hang up (only returned in <em>revents</em>; ignored in <em>events</em>).
  25.               Note that when reading from a channel such as a pipe or a
  26.               stream socket, this event merely indicates that the peer
  27.               closed its end of the channel.  Subsequent reads from the
  28.               channel will return 0 (end of file) only after all
  29.               outstanding data in the channel has been consumed.
  30.        <strong>POLLNVAL</strong>
  31.               Invalid request: <em>fd</em> not open (only returned in <em>revents</em>;
  32.               ignored in <em>events</em>).
  33.        When compiling with <strong>_XOPEN_SOURCE </strong>defined, one also has the
  34.        following, which convey no further information beyond the bits
  35.        listed above:
  36.        <strong>POLLRDNORM</strong>
  37.               Equivalent to <strong>POLLIN</strong>.
  38.        <strong>POLLRDBAND</strong>
  39.               Priority band data can be read (generally unused on
  40.               Linux).
  41.        <strong>POLLWRNORM</strong>
  42.               Equivalent to <strong>POLLOUT</strong>.
  43.        <strong>POLLWRBAND</strong>
  44.               Priority data may be written.
复造代码
利用流程

综上所述,我们普通的利用流程是:

  • 界说pollfd数组,并设置poll数组相干参数。
  • 设置超不时间
  • 挪用poll
代码真例

按照利用流程,给出一个代码示例:
  1. #include <stdio.h>
  2. #include <unistd.h>
  3. #include <poll.h>
  4. #define TIMEOUT 5 /* poll timeout, in seconds */
  5. int main (void) {
  6.   struct pollfd fds[2];
  7.   int ret;
  8.   /* watch stdin for input */
  9.   fds[0].fd = STDIN_FILENO;
  10.   fds[0].events = POLLIN;
  11.   /* watch stdout for ability to write (almost always true) */
  12.   fds[1].fd = STDOUT_FILENO;
  13.   fds[1].events = POLLOUT;
  14.   /* All set, block! */
  15.   ret = poll (fds, 2, TIMEOUT * 1000);
  16.   if (ret == −1) {
  17.     perror ("poll");
  18.     return 1;
  19.   }
  20.   if (!ret) {
  21.     printf ("%d seconds elapsed.\n", TIMEOUT);
  22.     return 0;
  23.   }
  24.   if (fds[0].revents & POLLIN)
  25.     printf ("stdin is readable\n");
  26.   if (fds[1].revents & POLLOUT)
  27.     printf ("stdout is writable\n");
  28.   return 0;
  29. }
复造代码
5、epoll

epoll是Linux独有的IO复用函数,利用一组函数去完成使命,而没有是单个函数。
epoll把用户体贴的文件形貌符上的变乱放正在内乱核的一个变乱表中,没有需求像select、poll那样每次挪用皆要反复传进文件形貌符散或变乱散。
epoll需求利用一个分外的文件形貌符,去独一标识内乱核中的工夫表,由epoll_create创立。
函数本型

  1.     #include <sys/epoll.h>
  2.     int epoll_create(int size);
  3.     int epoll_create1(int flags);
  4.     int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
  5.     int epoll_wait(int epfd, struct epoll_event *events,
  6.                 int maxevents, int timeout);
  7.     int epoll_pwait(int epfd, struct epoll_event *events,
  8.                 int maxevents, int timeout,
  9.                 const sigset_t *sigmask);
复造代码


  • epoll_create:创立一个epoll真例,size参数给内乱核一个提醒,标识变乱表的巨细。函数返回的文件形貌符将感化其他一切epoll系统挪用的第一个参数,以指定要会见的内乱核变乱表。
  • epoll_ctl:操纵文件形貌符。fd表现要操纵的文件形貌符,op指定操纵范例,event指定变乱。
  • epoll_wait:正在一段超不时间内乱等候一组文件形貌符上的变乱。假如监测到变乱,便将一切伏贴的变乱从内乱核变乱表(epfd参数指定)中复造到第两个参数events指背的数组中。由于events数组只用于输出epoll_wait监测到的伏贴变乱,而没有像select、poll那样便用于传进用户注册的变乱,又用于输出内乱核检测到的伏贴变乱。如许极年夜进步了使用法式索引伏贴文件形貌符的服从。
函数返回

出格留意epoll_wait函数胜利时返回伏贴的文件形貌符总数。select战poll返回文件形貌符总数。
以寻觅曾经伏贴的文件形貌符,举个例子以下:
epoll_wait只需求遍历返回的文件形貌符,可是poll战select需求遍历一切文件形貌符
  1. //  poll
  2. int ret = poll(fds, MAX_EVENT_NUMBER, -1);
  3. // 必需遍历一切已注册的文件形貌符
  4. for (int i = 0; i < MAX_EVENT_NUMBER; i++) {
  5.     if (fds[i].revents & POLLIN) {
  6.         int sockfd = fds[i].fd;
  7.     }
  8. }
  9. // epoll_wait
  10. int ret = epoll_wait(epollfd, events, MAX_EVENT_NUMBER, -1);
  11. // 仅需求遍历停当的ret个文件形貌符
  12. for (int i = 0; i < ret; i++) {
  13.     int sockfd = events[i].data.fd;
  14. }
复造代码
LT程度触收形式战ET边缘触收形式

epoll监控多个文件形貌符的I/O变乱。epoll撑持边沿触收(edge trigger,ET)或程度触收(level trigger,LT),经由过程epoll_wait等候I/O变乱,假如当前出有可用的变乱则壅闭挪用线程。
select战poll只撑持LT事情形式,epoll的默许的事情形式是LT形式。
程度触收:


  • 当epoll_wait检测到其上有变乱发作并将此变乱照顾使用法式后,使用法式能够没有立刻处理惩罚此变乱。如许使用法式下一次挪用epoll_wait的时分,epoll_wait借会再次背使用法式布告此变乱,曲到变乱被处理惩罚。
边缘触收:


  • 当epoll_wait检测到其上有变乱发作并将此变乱照顾使用法式后,使用法式必需立刻处理惩罚此变乱,后绝的epoll_wait挪用将没有再背使用法式照顾那一变乱。
所以,边缘触收形式很年夜水平上低落了统一个epoll变乱被反复触收的次数,所以服从更下
代码真例

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <string.h>
  4. #include <sys/types.h>
  5. #include <sys/socket.h>
  6. #include <netdb.h>
  7. #include <unistd.h>
  8. #include <fcntl.h>
  9. #include <sys/epoll.h>
  10. #include <errno.h>
  11. #define MAXEVENTS 64
  12. static int make_socket_non_blocking (int sfd)
  13. {
  14.   int flags, s;
  15.   flags = fcntl (sfd, F_GETFL, 0);
  16.   if (flags == -1)
  17.     {
  18.       perror ("fcntl");
  19.       return -1;
  20.     }
  21.   flags |= O_NONBLOCK;
  22.   s = fcntl (sfd, F_SETFL, flags);
  23.   if (s == -1)
  24.     {
  25.       perror ("fcntl");
  26.       return -1;
  27.     }
  28.   return 0;
  29. }
  30. static int create_and_bind (char *port)
  31. {
  32.   struct addrinfo hints;
  33.   struct addrinfo *result, *rp;
  34.   int s, sfd;
  35.   memset (&hints, 0, sizeof (struct addrinfo));
  36.   hints.ai_family = AF_UNSPEC;     /* Return IPv4 and IPv6 choices */
  37.   hints.ai_socktype = SOCK_STREAM; /* We want a TCP socket */
  38.   hints.ai_flags = AI_PASSIVE;     /* All interfaces */
  39.   s = getaddrinfo (NULL, port, &hints, &result);
  40.   if (s != 0)
  41.     {
  42.       fprintf (stderr, "getaddrinfo: %s\n", gai_strerror (s));
  43.       return -1;
  44.     }
  45.   for (rp = result; rp != NULL; rp = rp->ai_next)
  46.     {
  47.       sfd = socket (rp->ai_family, rp->ai_socktype, rp->ai_protocol);
  48.       if (sfd == -1)
  49.         continue;
  50.       s = bind (sfd, rp->ai_addr, rp->ai_addrlen);
  51.       if (s == 0)
  52.         {
  53.           /* We managed to bind successfully! */
  54.           break;
  55.         }
  56.       close (sfd);
  57.     }
  58.   if (rp == NULL)
  59.     {
  60.       fprintf (stderr, "Could not bind\n");
  61.       return -1;
  62.     }
  63.   freeaddrinfo (result);
  64.   return sfd;
  65. }
  66. int main (int argc, char *argv[])
  67. {
  68.   int sfd, s;
  69.   int efd;
  70.   struct epoll_event event;
  71.   struct epoll_event *events;
  72.   if (argc != 2)
  73.     {
  74.       fprintf (stderr, "Usage: %s [port]\n", argv[0]);
  75.       exit (EXIT_FAILURE);
  76.     }
  77.   sfd = create_and_bind (argv[1]);
  78.   if (sfd == -1)
  79.     abort ();
  80.   s = make_socket_non_blocking (sfd);
  81.   if (s == -1)
  82.     abort ();
  83.   s = listen (sfd, SOMAXCONN);
  84.   if (s == -1)
  85.     {
  86.       perror ("listen");
  87.       abort ();
  88.     }
  89.   efd = epoll_create1 (0);
  90.   if (efd == -1)
  91.     {
  92.       perror ("epoll_create");
  93.       abort ();
  94.     }
  95.   event.data.fd = sfd;
  96.   event.events = EPOLLIN | EPOLLET;
  97.   s = epoll_ctl (efd, EPOLL_CTL_ADD, sfd, &event);
  98.   if (s == -1)
  99.     {
  100.       perror ("epoll_ctl");
  101.       abort ();
  102.     }
  103.   /* Buffer where events are returned */
  104.   events = calloc (MAXEVENTS, sizeof event);
  105.   /* The event loop */
  106.   while (1)
  107.     {
  108.       int n, i;
  109.       n = epoll_wait (efd, events, MAXEVENTS, -1);
  110.       for (i = 0; i < n; i++)
  111.         {
  112.           if ((events[i].events & EPOLLERR) ||
  113.               (events[i].events & EPOLLHUP) ||
  114.               (!(events[i].events & EPOLLIN)))
  115.             {
  116.               /* An error has occured on this fd, or the socket is not
  117.                  ready for reading (why were we notified then?) */
  118.               fprintf (stderr, "epoll error\n");
  119.               close (events[i].data.fd);
  120.               continue;
  121.             }
  122.           else if (sfd == events[i].data.fd)
  123.             {
  124.               /* We have a notification on the listening socket, which
  125.                  means one or more incoming connections. */
  126.               while (1)
  127.                 {
  128.                   struct sockaddr in_addr;
  129.                   socklen_t in_len;
  130.                   int infd;
  131.                   char hbuf[NI_MAXHOST], sbuf[NI_MAXSERV];
  132.                   in_len = sizeof in_addr;
  133.                   infd = accept (sfd, &in_addr, &in_len);
  134.                   if (infd == -1)
  135.                     {
  136.                       if ((errno == EAGAIN) ||
  137.                           (errno == EWOULDBLOCK))
  138.                         {
  139.                           /* We have processed all incoming
  140.                              connections. */
  141.                           break;
  142.                         }
  143.                       else
  144.                         {
  145.                           perror ("accept");
  146.                           break;
  147.                         }
  148.                     }
  149.                   s = getnameinfo (&in_addr, in_len,
  150.                                    hbuf, sizeof hbuf,
  151.                                    sbuf, sizeof sbuf,
  152.                                    NI_NUMERICHOST | NI_NUMERICSERV);
  153.                   if (s == 0)
  154.                     {
  155.                       printf("Accepted connection on descriptor %d "
  156.                              "(host=%s, port=%s)\n", infd, hbuf, sbuf);
  157.                     }
  158.                   /* Make the incoming socket non-blocking and add it to the
  159.                      list of fds to monitor. */
  160.                   s = make_socket_non_blocking (infd);
  161.                   if (s == -1)
  162.                     abort ();
  163.                   event.data.fd = infd;
  164.                   event.events = EPOLLIN | EPOLLET;
  165.                   s = epoll_ctl (efd, EPOLL_CTL_ADD, infd, &event);
  166.                   if (s == -1)
  167.                     {
  168.                       perror ("epoll_ctl");
  169.                       abort ();
  170.                     }
  171.                 }
  172.               continue;
  173.             }
  174.           else
  175.             {
  176.               /* We have data on the fd waiting to be read. Read and
  177.                  display it. We must read whatever data is available
  178.                  completely, as we are running in edge-triggered mode
  179.                  and won&#39;t get a notification again for the same
  180.                  data. */
  181.               int done = 0;
  182.               while (1)
  183.                 {
  184.                   ssize_t count;
  185.                   char buf[512];
  186.                   count = read (events[i].data.fd, buf, sizeof buf);
  187.                   if (count == -1)
  188.                     {
  189.                       /* If errno == EAGAIN, that means we have read all
  190.                          data. So go back to the main loop. */
  191.                       if (errno != EAGAIN)
  192.                         {
  193.                           perror ("read");
  194.                           done = 1;
  195.                         }
  196.                       break;
  197.                     }
  198.                   else if (count == 0)
  199.                     {
  200.                       /* End of file. The remote has closed the
  201.                          connection. */
  202.                       done = 1;
  203.                       break;
  204.                     }
  205.                   /* Write the buffer to standard output */
  206.                   s = write (1, buf, count);
  207.                   if (s == -1)
  208.                     {
  209.                       perror ("write");
  210.                       abort ();
  211.                     }
  212.                 }
  213.               if (done)
  214.                 {
  215.                   printf ("Closed connection on descriptor %d\n",
  216.                           events[i].data.fd);
  217.                   /* Closing the descriptor will make epoll remove it
  218.                      from the set of descriptors which are monitored. */
  219.                   close (events[i].data.fd);
  220.                 }
  221.             }
  222.         }
  223.     }
  224.   free (events);
  225.   close (sfd);
  226.   return EXIT_SUCCESS;
  227. }
复造代码
6、三组IO复用函数比照

1. 用户态将文件形貌符传进内乱核的方法



  • select:创立3个文件形貌符散并拷贝到内乱核中,别离监听读、写、非常行动。那里遭到单个历程能够翻开的fd数目限定,默许是1024。
  • poll:将传进的struct pollfd构造体数组拷贝到内乱核中停止监听。
  • epoll:施行epoll_create会正在内乱核的下速cache区中创立一颗白乌树和伏贴链表(该链表存储曾经伏贴的文件形貌符)。接着用户施行的epoll_ctl函数增加文件形貌符会正在白乌树上增长响应的结面。
2. 内乱核态检测文件形貌符读写形态的方法



  • select:采取轮询方法,遍历一切fd,最初返回一个形貌符读写操纵能否伏贴的mask掩码,按照那个掩码给fd_set赋值。
  • poll:一样采取轮询方法,查询每一个fd的形态,假如伏贴则正在等候行列中参加一项并持续遍历。
  • epoll:采取回调机造。正在施行epoll_ctl的add操纵时,不只将文件形貌符放到白乌树上,并且也注册了回调函数,内乱核正在检测到某文件形貌符可读/可写时会挪用回调函数,该回调函数将文件形貌符放正在伏贴链表中。
3. 找到伏贴的文件形貌符并通报给用户态的方法



  • select:将之前传进的fd_set拷贝传出到用户态并返回伏贴的文件形貌符总数。用户态其实不明白是哪些文件形貌符处于伏贴态,需求遍向来判定。
  • poll:将之前传进的fd数组拷贝传出用户态并返回伏贴的文件形貌符总数。用户态其实不明白是哪些文件形貌符处于伏贴态,需求遍向来判定。
  • epoll:epoll_wait只用察看伏贴链表中有没有数据便可,最初将链表的数据返回给数组并返回伏贴的数目。内乱核将伏贴的文件形貌符放正在传进的数组中,所以只用遍历顺次处理惩罚便可。
4. 反复监听的处理惩罚方法



  • select:将新的监听文件形貌符汇合拷贝传进内乱核中,持续以上步伐。
  • poll:将新的struct pollfd构造体数组拷贝传进内乱核中,持续以上步伐。
  • epoll:无需从头构建白乌树,间接相沿已存正在的便可。
7、典范面试题:epoll更下效的缘故原由?

select战poll的行动底子分歧,只是poll采取链表去停止文件形貌符的存储,而select采取fd标注位去寄存,所以select会遭到最年夜毗连数的限定,而poll没有会。
select、poll、epoll固然城市返回伏贴的文件形貌符数目。可是select战poll其实不会明白指出是哪些文件形貌符伏贴,而epoll会。酿成的区分便是,系统挪用返回后,挪用select战poll的法式需求遍历监听的全部文件形貌符找到是谁处于伏贴,而epoll则间接处理惩罚便可。
select、poll皆需求将有闭文件形貌符的数据构造拷贝进内乱核,最初再拷贝出去。而epoll创立的有闭文件形貌符的数据构造自己便存于内乱核态中。
select、poll采取轮询的方法去检查文件形貌符能否处于伏贴态,而epoll采取回调机造。酿成的结果便是,跟着fd的增长,select战poll的服从会线性低落,而epoll没有会遭到太年夜影响,除非活泼的socket许多。
epoll的边沿触收形式服从下,系统没有会充溢大批没有体贴的伏贴文件形貌符。
固然epoll的机能最好,可是正在毗连数少并且毗连皆十分活泼的状况下,select战poll的机能大要比epoll好,终究epoll的照顾机造需求许多函数回调。
写正在最初

那一讲偏偏实际,次要讲了Linux中三种IO复用。前面几讲会正在那一讲的根底上,环绕IO写一些风趣的真战demo,敬请等候。
  参考

  

免责声明:假如进犯了您的权益,请联络站少,我们会实时删除侵权内乱容,感谢协作!
1、本网站属于个人的非赢利性网站,转载的文章遵循原作者的版权声明,如果原文没有版权声明,按照目前互联网开放的原则,我们将在不通知作者的情况下,转载文章;如果原文明确注明“禁止转载”,我们一定不会转载。如果我们转载的文章不符合作者的版权声明或者作者不想让我们转载您的文章的话,请您发送邮箱:Cdnjson@163.com提供相关证明,我们将积极配合您!
2、本网站转载文章仅为传播更多信息之目的,凡在本网站出现的信息,均仅供参考。本网站将尽力确保所提供信息的准确性及可靠性,但不保证信息的正确性和完整性,且不对因信息的不正确或遗漏导致的任何损失或损害承担责任。
3、任何透过本网站网页而链接及得到的资讯、产品及服务,本网站概不负责,亦不负任何法律责任。
4、本网站所刊发、转载的文章,其版权均归原作者所有,如其他媒体、网站或个人从本网下载使用,请在转载有关文章时务必尊重该文章的著作权,保留本网注明的“稿件来源”,并自负版权等法律责任。
回复 关闭延时

使用道具 举报

 
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则