国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

手把手寫C++服務器(31):服務器性能提升關鍵——IO復用技術【兩萬字長文】

big_cat / 3611人閱讀

摘要:前面幾講手撕了網關服務器回顯服務器服務的代碼,但是這幾個一次只能監聽一個文件描述符,因此性能非常原始低下。復用能使服務器同時監聽多個文件描述符,是服務器性能提升的關鍵。表示要操作的文件描述符,指定操作類型,指定事件。

?本系列文章導航: 手把手寫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信號的產生:

  1. TCP連接建立
  2. 一方斷開TCP連接請求
  3. 斷開TCP連接請求完成
  4. TCP連接半關閉
  5. 數據到達TCP socket
  6. 數據已經發送出去(如:寫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的作用是在一段指定的時間內,監聽用戶感興趣的文件描述符上的可讀、可寫、異常等事件。函數原型如下:

#include int select(int nfds, fd_set *readfds, fd_set *writefds,                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中的位。

#include FD_ZERO(fd_set* fdset);    // 清除fdset中的所有位FD_SET(int fd, fd_set* fdset); // 設置fdset中的位FD_CLR(int fd, fd_set* fdset); // 清除fdset中的位int FD_ISSET(int fd, fd_set* fdset);  // 測試fdset的位fd是否被設置
  • FD_ZERO用來清空文件描述符組。每次調用select前都需要清空一次。
  • FD_SET添加一個文件描述符到組中,FD_CLR對應將一個文件描述符移出組中。
  • FD_ISSET檢測一個文件描述符是否在組中,我們用這個來檢測一次select調用之后有哪些文件描述符可以進行IO操作。

2、timeval

struct timeval {    long tv_sec; // 秒數    long tv_usec; // 微妙數};

使用流程

綜上所述,我們一般的使用流程是:

  1. 準備工作——定義readfds、timeval等
  2. 使用FD_ZERO清零,使用FD_SET設置文件描述符。因為事件發生后,文件描述符集合都將被內核修改。
  3. 調用select
  4. 使用FD_ISSET檢測文件描述符是否在組中

代碼實例

根據使用流程,給出一個代碼示例:

#include #include #include #include #define TIMEOUT 5 /* select timeout in seconds */#define BUF_LEN 1024 /* read buffer in bytes */int main (void) {  struct timeval tv;  fd_set readfds;  int ret;    /* Wait on stdin for input. */  FD_ZERO(&readfds);  FD_SET(STDIN_FILENO, &readfds);  /* Wait up to five seconds. */  tv.tv_sec = TIMEOUT;  tv.tv_usec = 0;    /* All right, now block! */  ret = select (STDIN_FILENO + 1, &readfds,                NULL,                NULL,                 &tv);  if (ret == ?1) {    perror ("select");    return 1;   } else if (!ret) {    printf ("%d seconds elapsed./n", TIMEOUT);    return 0;   }  /*  * Is our file descriptor ready to read?  * (It must be, as it was the only fd that  * we provided and the call returned  * nonzero, but we will humor ourselves.)  */  if (FD_ISSET(STDIN_FILENO, &readfds)) {    char buf[BUF_LEN+1];    int len;    /* guaranteed to not block */    len = read (STDIN_FILENO, buf, BUF_LEN);    if (len == ?1) {      perror ("read");      return 1;     }    if (len) {      buf[len] = "/0";      printf ("read: %s/n", buf);    }    return 0;   }  fprintf (stderr, "This should not happen!/n");  return 1; }

后面一講會給出一些實用的例子,有了select之后我們可以同時監聽很多個請求,系統的處理能力大大增強了。

4、poll

和select類似,在一定時間內輪詢一定數量的文件描述符。

函數原型

#include int poll(struct pollfd* fds, nfds_t nfds, int timeout);

但是和select不同的是,select需要用三組文件描述符,poll只有一個pollfd文件數組,數組中的每個元素都表示一個需要監聽IO操作事件的文件描述符。而且我們只需要關心數組中events參數,revents由內核自動填充。

重要結構體詳解

    struct pollfd {        int fd;    // 文件描述符        short events;    // 注冊的事件         short revents;   // 實際發生的事件,由內核填充    };

事件類型

具體的事件類型參看手冊:https://man7.org/linux/man-pages/man2/poll.2.html

       POLLIN There is data to read.       POLLPRI              There is some exceptional condition on the file              descriptor.  Possibilities include:              ? There is out-of-band data on a TCP socket (see tcp(7)).              ? A pseudoterminal master in packet mode has seen a state                change on the slave (see ioctl_tty(2)).              ? A cgroup.events file has been modified (see cgroups(7)).       POLLOUT              Writing is now possible, though a write larger than the              available space in a socket or pipe will still block              (unless O_NONBLOCK is set).       POLLRDHUP (since Linux 2.6.17)              Stream socket peer closed connection, or shut down writing              half of connection.  The _GNU_SOURCE feature test macro              must be defined (before including any header files) in              order to obtain this definition.       POLLERR              Error condition (only returned in revents; ignored in              events).  This bit is also set for a file descriptor              referring to the write end of a pipe when the read end has              been closed.       POLLHUP              Hang up (only returned in revents; ignored in events).              Note that when reading from a channel such as a pipe or a              stream socket, this event merely indicates that the peer              closed its end of the channel.  Subsequent reads from the              channel will return 0 (end of file) only after all              outstanding data in the channel has been consumed.       POLLNVAL              Invalid request: fd not open (only returned in revents;              ignored in events).       When compiling with _XOPEN_SOURCE defined, one also has the       following, which convey no further information beyond the bits       listed above:       POLLRDNORM              Equivalent to POLLIN.       POLLRDBAND              Priority band data can be read (generally unused on              Linux).       POLLWRNORM              Equivalent to POLLOUT.       POLLWRBAND              Priority data may be written.

使用流程

綜上所述,我們一般的使用流程是:

  1. 定義pollfd數組,并設置poll數組相關參數。
  2. 設置超時時間
  3. 調用poll

代碼實例

根據使用流程,給出一個代碼示例:

#include #include #include #define TIMEOUT 5 /* poll timeout, in seconds */int main (void) {  struct pollfd fds[2];  int ret;  /* watch stdin for input */  fds[0].fd = STDIN_FILENO;  fds[0].events = POLLIN;  /* watch stdout for ability to write (almost always true) */  fds[1].fd = STDOUT_FILENO;  fds[1].events = POLLOUT;  /* All set, block! */  ret = poll (fds, 2, TIMEOUT * 1000);  if (ret == ?1) {    perror ("poll");    return 1;   }  if (!ret) {    printf ("%d seconds elapsed./n", TIMEOUT);    return 0;   }  if (fds[0].revents & POLLIN)    printf ("stdin is readable/n");  if (fds[1].revents & POLLOUT)    printf ("stdout is writable/n");  return 0; }

5、epoll

epoll是Linux特有的IO復用函數,使用一組函數來完成任務,而不是單個函數。

epoll把用戶關心的文件描述符上的事件放在內核的一個事件表中,不需要像select、poll那樣每次調用都要重復傳入文件描述符集或事件集。

epoll需要使用一個額外的文件描述符,來唯一標識內核中的時間表,由epoll_create創建。

函數原型

    #include     int epoll_create(int size);    int epoll_create1(int flags);    int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);    int epoll_wait(int epfd, struct epoll_event *events,                int maxevents, int timeout);    int epoll_pwait(int epfd, struct epoll_event *events,                int maxevents, int timeout,                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需要遍歷所有文件描述符

//  pollint ret = poll(fds, MAX_EVENT_NUMBER, -1);// 必須遍歷所有已注冊的文件描述符for (int i = 0; i < MAX_EVENT_NUMBER; i++) {    if (fds[i].revents & POLLIN) {        int sockfd = fds[i].fd;    }}// epoll_waitint ret = epoll_wait(epollfd, events, MAX_EVENT_NUMBER, -1);// 僅需要遍歷就緒的ret個文件描述符for (int i = 0; i < ret; i++) {    int sockfd = events[i].data.fd;}

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事件被重復觸發的次數,所以效率更高

代碼實例

#include #include #include #include #include #include #include #include #include #include #define MAXEVENTS 64static int make_socket_non_blocking (int sfd){  int flags, s;  flags = fcntl (sfd, F_GETFL, 0);  if (flags == -1)    {      perror ("fcntl");      return -1;    }  flags |= O_NONBLOCK;  s = fcntl (sfd, F_SETFL, flags);  if (s == -1)    {      perror ("fcntl");      return -1;    }  return 0;}static int create_and_bind (char *port){  struct addrinfo hints;  struct addrinfo *result, *rp;  int s, sfd;  memset (&hints, 0, sizeof (struct addrinfo));  hints.ai_family = AF_UNSPEC;     /* Return IPv4 and IPv6 choices */  hints.ai_socktype = SOCK_STREAM; /* We want a TCP socket */  hints.ai_flags = AI_PASSIVE;     /* All interfaces */  s = getaddrinfo (NULL, port, &hints, &result);  if (s != 0)    {      fprintf (stderr, "getaddrinfo: %s/n", gai_strerror (s));      return -1;    }  for (rp = result; rp != NULL; rp = rp->ai_next)    {      sfd = socket (rp->ai_family, rp->ai_socktype, rp->ai_protocol);      if (sfd == -1)        continue;      s = bind (sfd, rp->ai_addr, rp->ai_addrlen);      if (s == 0)        {          /* We managed to bind successfully! */          break;        }      close (sfd);    }  if (rp == NULL)    {      fprintf (stderr, "Could not bind/n");      return -1;    }  freeaddrinfo (result);  return sfd;}int main (int argc, char *argv[]){  int sfd, s;  int efd;  struct epoll_event event;  struct epoll_event *events;  if (argc != 2)    {      fprintf (stderr, "Usage: %s [port]/n", argv[0]);      exit (EXIT_FAILURE);    }  sfd = create_and_bind (argv[1]);  if (sfd == -1)    abort ();  s = make_socket_non_blocking (sfd);  if (s == -1)    abort ();  s = listen (sfd, SOMAXCONN);  if (s == -1)    {      perror ("listen");      abort ();    }  efd = epoll_create1 (0);  if (efd == -1)    {      perror ("epoll_create");      abort ();    }  event.data.fd = sfd;  event.events = EPOLLIN | EPOLLET;  s = epoll_ctl (efd, EPOLL_CTL_ADD, sfd, &event);  if (s == -1)    {      perror ("epoll_ctl");      abort ();    }  /* Buffer where events are returned */  events = calloc (MAXEVENTS, sizeof event);  /* The event loop */  while (1)    {      int n, i;      n = epoll_wait (efd, events, MAXEVENTS, -1);      for (i = 0; i < n; i++)	{	  if ((events[i].events & EPOLLERR) ||              (events[i].events & EPOLLHUP) ||              (!(events[i].events & EPOLLIN)))	    {              /* An error has occured on this fd, or the socket is not                 ready for reading (why were we notified then?) */	      fprintf (stderr, "epoll error/n");	      close (events[i].data.fd);	      continue;	    }	  else if (sfd == events[i].data.fd)	    {              /* We have a notification on the listening socket, which                 means one or more incoming connections. */              while (1)                {                  struct sockaddr in_addr;                  socklen_t in_len;                  int infd;                  char hbuf[NI_MAXHOST], sbuf[NI_MAXSERV];                  in_len = sizeof in_addr;                  infd = accept (sfd, &in_addr, &in_len);                  if (infd == -1)                    {                      if ((errno == EAGAIN) ||                          (errno == EWOULDBLOCK))                        {                          /* We have processed all incoming                             connections. */                          break;                        }                      else                        {                          perror ("accept");                          break;                        }                    }                  s = getnameinfo (&in_addr, in_len,                                   hbuf, sizeof hbuf,                                   sbuf, sizeof sbuf,                                   NI_NUMERICHOST | NI_NUMERICSERV);                  if (s == 0)                    {                      printf("Accepted connection on descriptor %d "                             "(host=%s, port=%s)/n", infd, hbuf, sbuf);                    }                  /* Make the incoming socket non-blocking and add it to the                     list of fds to monitor. */                  s = make_socket_non_blocking (infd);                  if (s == -1)                    abort ();                  event.data.fd = infd;                  event.events = EPOLLIN | EPOLLET;                  s = epoll_ctl (efd, EPOLL_CTL_ADD, infd, &event);                  if (s == -1)                    {                      perror ("epoll_ctl");                      abort ();                    }                }              continue;            }          else            {              /* We have data on the fd waiting to be read. Read and                 display it. We must read whatever data is available                 completely, as we are running in edge-triggered mode                 and won"t get a notification again for the same                 data. */              int done = 0;              while (1)                {                  ssize_t count;                  char buf[512];                  count = read (events[i].data.fd, buf, sizeof buf);                  if (count == -1)                    {                      /* If errno == EAGAIN, that means we have read all                         data. So go back to the main loop. */                      if (errno != EAGAIN)                        {                          perror ("read");                          done = 1;                        }                      break;                    }                  else if (count == 0)                    {                      /* End of file. The remote has closed the                         connection. */                      done = 1;                      break;                    }                  /* Write the buffer to standard output */                  s = write (1, buf, count);                  if (s == -1)                    {                      perror ("write");                      abort ();                    }                }              if (done)                {                  printf ("Closed connection on descriptor %d/n",                          events[i].data.fd);                  /* Closing the descriptor will make epoll remove it                     from the set of descriptors which are monitored. */                  close (events[i].data.fd);                }            }        }    }  free (events);  close (sfd);  return EXIT_SUCCESS;}

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,敬請期待。

參考

文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。

轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/118798.html

相關文章

  • 【Webpack 性能優化系列(3) - oneOf】

    摘要:當一個文件要被多個處理,那么一定要指定執行的先后順序先執行在執行參考 webpack系列文章: 【Webpack 性能優化系列(2) - source-map】【W...

    myshell 評論0 收藏0
  • ES6指北【3】——5000長文帶你徹底搞懂ES6模塊

    摘要:模塊什么是模塊什么是模塊化玩過游戲的朋友應該知道,一把裝配完整的步槍,一般是槍身消音器倍鏡握把槍托。更重要的是,其它大部分語言都支持模塊化。這一點與規范完全不同。模塊輸出的是值的緩存,不存在動態更新。 1.模塊 1.1 什么是模塊?什么是模塊化? 玩過FPS游戲的朋友應該知道,一把裝配完整的M4步槍,一般是槍身+消音器+倍鏡+握把+槍托。 如果把M4步槍看成是一個頁面的話,那么我們可以...

    ygyooo 評論0 收藏0
  • 從小白程序員一路晉升為大廠高級技術專家我看過哪些書籍?(建議收藏)

    摘要:大家好,我是冰河有句話叫做投資啥都不如投資自己的回報率高。馬上就十一國慶假期了,給小伙伴們分享下,從小白程序員到大廠高級技術專家我看過哪些技術類書籍。 大家好,我是...

    sf_wangchong 評論0 收藏0

發表評論

0條評論

最新活動
閱讀需要支付1元查看
<