Socket網絡編程--epoll小結

  之前使用的用於I/O多路複用爲了方便就使用select函數,但select這個函數是有缺陷的。由於它所支持的併發鏈接數是有限的(通常小於1024),由於用戶處理的數組是使用硬編碼的。這個最大值爲FD_SETSIZE,這是在<sys/select.h>中的一個常量,它說明了最大的描述符數。可是對於大多數應用程序而言,這個數是夠用的,並且有可能仍是太大的,多數應用程序只使用3~10個描述符。而現在的網絡服務器小小的都有幾萬的鏈接,雖然可使用多線程多進程(也就有N*1024個)。可是這樣處理起來既不方面,性能又低。html

  同時期有I/O多路複用的還有一個poll函數,這個函數相似於select,可是其應用程序接口有所不用。原型以下linux

  #include <poll.h>
  int poll(struct pollfd fdarray[], nfds_t nfds, int timeout);數組

  //返回值: 準備就緒的描述符數,若超時則返回0,出錯返回-1服務器

  若是隻是考慮性能的話,poll()也是不合適的,儘管它能夠支持較高的TCP併發鏈接數,可是因爲其採用「輪詢」機制(遍歷數組而已),但併發數較高時,其運行效率至關低(若是有10k個鏈接,單用於輪詢的時間就須要1~10ms了),同時還可能存在I/O事件分配不均,致使部分TCP鏈接上的I/O出現「飢餓」現象。基於種種緣由在Linux 2.5.44版本後poll被epoll取代。
  支持一個進程打開最大數目的 socket 描述符(FD)。select 最不能忍受的是一個進程所打開的FD 是有必定限制的,由 FD_SETSIZE 設置,默認值是 2048。對於那些須要支持的上萬鏈接數目的 IM 服務器來講顯然太少了。這時候你一是能夠選擇修改這個宏而後從新編譯內核,不過資料也同時指出這樣會帶來網絡效率的降低,二是能夠選擇多進程的解決方案(傳統的 Apache 方案Process Per Connection,TPC方案 Thread Per Connection),不過雖然 linux 上面建立進程的代價比較小,但仍舊是不可忽視的,加上進程間數據同步遠比不上線程間同步的高效,因此也不是一種完美的方案。不過 epoll 則沒有這個限制,它所支持的 FD 上限是最大能夠打開文件的數目,這個數字通常遠大於 2048,舉個例子,在 1GB 內存的機器上大約是 10 萬左右,具體數目能夠 cat /proc/sys/fs/file-max 察看,通常來講這個數目和系統內存關係很大。
因爲epoll這個函數是後增長上的,形成如今不多有資料說起到,我看了APUE,UNPv1等書都沒有找到相關的函數原型。因此我只能從網絡上抄一些函數原型過來了。網絡

   epoll用到的全部函數都是在頭文件sys/epoll.h中聲明,有什麼地方不明白或函數忘記了能夠去看一下或者man epoll。epoll和select相比,最大不一樣在於:數據結構

  epoll返回時已經明確的知道哪一個sokcet fd發生了事件,不用再一個個比對(輪詢)。這樣就提升了效率。select的FD_SETSIZE是有限制的,而epoll是沒有限制的只與系統資源有關。多線程

  epoll_create函數併發

1 /* Creates an epoll instance.  Returns an fd for the new instance.
2    The "size" parameter is a hint specifying the number of file
3    descriptors to be associated with the new instance.  The fd
4    returned by epoll_create() should be closed with close().  */
5 extern int epoll_create (int __size) __THROW;

  該函數生成一個epoll專用的文件描述符。它實際上是在內核申請空間,用來存放你想關注的socket fd上是否發生以及發生了什麼事件。size就是你在這個epoll fd上能關注的最大socket fd數。這個數沒有select的1024約束。socket

  epoll_ctl函數ide

 1 /* Manipulate an epoll instance "epfd". Returns 0 in case of success,
 2    -1 in case of error ( the "errno" variable will contain the
 3    specific error code ) The "op" parameter is one of the EPOLL_CTL_*
 4    constants defined above. The "fd" parameter is the target of the
 5    operation. The "event" parameter describes which events the caller
 6    is interested in and any associated user data.  */
 7 extern int epoll_ctl (int __epfd, int __op, int __fd,
 8                       struct epoll_event *__event) __THROW;
 9 /* Valid opcodes ( "op" parameter ) to issue to epoll_ctl(). */
10 #define EPOLL_CTL_ADD 1 /* Add a file descriptor to the interface. */
11 #define EPOLL_CTL_DEL 2 /* Remove a file descriptor from the interface. */
12 #define EPOLL_CTL_MOD 3 /* Change file descriptor epoll_event structure. */

  該函數用於控制某個epoll文件描述符上的事件,能夠註冊事件,修改事件,刪除事件。 

  參數: epfd:由 epoll_create 生成的epoll專用的文件描述符;
      op:要進行的操做例如註冊事件,可能的取值EPOLL_CTL_ADD 註冊、EPOLL_CTL_MOD 修 改、EPOLL_CTL_DEL 刪除

      fd:關聯的文件描述符;
      event:指向epoll_event的指針;
      返回值:若是調用成功返回0,不成功返回-1

  用到的數據結構

 1 typedef union epoll_data
 2 {
 3   void *ptr;
 4   int fd;
 5   uint32_t u32;
 6   uint64_t u64;
 7 } epoll_data_t;
 8 
 9 struct epoll_event
10 {
11   uint32_t events;      /* Epoll events */
12   epoll_data_t data;    /* User data variable */
13 };

  設置實例

 1 struct epoll_event ev;
 2 //設置與要處理的事件相關的文件描述符
 3 ev.data.fd=listenfd;
 4 //設置要處理的事件類型
 5 ev.events=EPOLLIN|EPOLLET;
 6 //註冊epoll事件
 7 epoll_ctl(epfd,EPOLL_CTL_ADD,listenfd,&ev);
 8 //經常使用的事件類型:
 9 EPOLLIN :表示對應的文件描述符能夠讀;
10 EPOLLOUT:表示對應的文件描述符能夠寫;
11 EPOLLPRI:表示對應的文件描述符有緊急的數據可讀
12 EPOLLERR:表示對應的文件描述符發生錯誤;
13 EPOLLHUP:表示對應的文件描述符被掛斷;
14 EPOLLET:表示對應的文件描述符有事件發生;
15 //具體能夠看<sys/epoll.h>

  epoll_wait函數

 1 /* Wait for events on an epoll instance "epfd". Returns the number of
 2    triggered events returned in "events" buffer. Or -1 in case of
 3    error with the "errno" variable set to the specific error code. The
 4    "events" parameter is a buffer that will contain triggered
 5    events. The "maxevents" is the maximum number of events to be
 6    returned ( usually size of "events" ). The "timeout" parameter
 7    specifies the maximum wait time in milliseconds (-1 == infinite).
 8    This function is a cancellation point and therefore not marked with
 9    __THROW.  */
10 extern int epoll_wait (int __epfd, struct epoll_event *__events,
11                        int __maxevents, int __timeout);
12 
13 
14 /* Same as epoll_wait, but the thread's signal mask is temporarily
15    and atomically replaced with the one provided as parameter.
16    This function is a cancellation point and therefore not marked with
17    __THROW.  */
18 extern int epoll_pwait (int __epfd, struct epoll_event *__events,
19                         int __maxevents, int __timeout,
20                         __const __sigset_t *__ss);

  該函數用於輪詢I/O事件的發生;
  參數: epfd:由epoll_create 生成的epoll專用的文件描述符;
      epoll_event:用於回傳代處理事件的數組;
      maxevents:每次能處理的事件數;
      timeout:等待I/O事件發生的超時值(單位應該是ms);-1至關於阻塞,0至關於非阻塞。通常用-1便可

      返回值:返回發生事件數。如出錯則返回-1。

   下面給一個man手冊裏面的例子

 1 #define MAX_EVENTS 10
 2 struct epoll_event ev, events[MAX_EVENTS];
 3 int listen_sock, conn_sock, nfds, epollfd;
 4 
 5 /* Set up listening socket, 'listen_sock' (socket(),
 6    bind(), listen()) */
 7 
 8 epollfd = epoll_create(10);
 9 if (epollfd == -1) {
10     perror("epoll_create");
11     exit(EXIT_FAILURE);
12 }
13 
14 ev.events = EPOLLIN;
15 ev.data.fd = listen_sock;
16 if (epoll_ctl(epollfd, EPOLL_CTL_ADD, listen_sock, &ev) == -1) {
17     perror("epoll_ctl: listen_sock");
18     exit(EXIT_FAILURE);
19 }
20 
21 for (;;) {
22     nfds = epoll_wait(epollfd, events, MAX_EVENTS, -1);
23     if (nfds == -1) {
24         perror("epoll_pwait");
25         exit(EXIT_FAILURE);
26     }
27 
28     for (n = 0; n < nfds; ++n) {
29         if (events[n].data.fd == listen_sock) {
30             conn_sock = accept(listen_sock,
31                     (struct sockaddr *) &local, &addrlen);
32             if (conn_sock == -1) {
33                 perror("accept");
34                 exit(EXIT_FAILURE);
35             }
36             setnonblocking(conn_sock);
37             ev.events = EPOLLIN | EPOLLET;
38             ev.data.fd = conn_sock;
39             if (epoll_ctl(epollfd, EPOLL_CTL_ADD, conn_sock,
40                         &ev) == -1) {
41                 perror("epoll_ctl: conn_sock");
42                 exit(EXIT_FAILURE);
43             }
44         } else {
45             do_use_fd(events[n].data.fd);
46         }
47     }
48 }

  下面這個是引用mmz_xiaokong

epoll函數
  經常使用模型的缺點
    若是不擺出來其餘模型的缺點,怎麼能對比出 Epoll 的優勢呢。
  PPC/TPC 模型
  這兩種模型思想相似,就是讓每個到來的鏈接一邊本身作事去,別再來煩我 。只是 PPC 是爲它開了一個進程,而 TPC 開了一個線程。但是別煩我是有代價的,它要時間和空間啊,鏈接多了以後,那麼多的進程 / 線程切換,這開銷就上來了;所以這類模型能接受的最大鏈接數都不會高,通常在幾百個左右。
  select 模型
  1. 最大併發數限制,由於一個進程所打開的 FD (文件描述符)是有限制的,由 FD_SETSIZE 設置,默認值是 1024/2048 ,所以 Select 模型的最大併發數就被相應限制了。本身改改這個 FD_SETSIZE ?想法雖好,但是先看看下面吧 …
  2. 效率問題, select 每次調用都會線性掃描所有的 FD 集合,這樣效率就會呈現線性降低,把 FD_SETSIZE 改大的後果就是,你們都慢慢來,什麼?都超時了??!!
  3. 內核 / 用戶空間 內存拷貝問題,如何讓內核把 FD 消息通知給用戶空間呢?在這個問題上 select 採起了內存拷貝方法。
  poll 模型
基本上效率和 select 是相同的, select 缺點的 2 和 3 它都沒有改掉。
  Epoll 的提高
  把其餘模型逐個批判了一下,再來看看 Epoll 的改進之處吧,其實把 select 的缺點反過來那就是 Epoll 的優勢了。
  1. Epoll 沒有最大併發鏈接的限制,上限是最大能夠打開文件的數目,這個數字通常遠大於 2048, 通常來講這個數目和系統內存關係很大 ,具體數目能夠 cat /proc/sys/fs/file-max 察看。
  2. 效率提高, Epoll 最大的優勢就在於它只管你「活躍」的鏈接 ,而跟鏈接總數無關,所以在實際的網絡環境中, Epoll 的效率就會遠遠高於 select 和 poll 。
  3. 內存拷貝, Epoll 在這點上使用了「共享內存 」,這個內存拷貝也省略了。
  Epoll 爲何高效
  Epoll 的高效和其數據結構的設計是密不可分的(以空間換時間),這個下面就會提到。
  首先回憶一下 select 模型,當有 I/O 事件到來時, select 通知應用程序有事件到了快去處理,而應用程序必須輪詢全部的 FD 集合,測試每一個 FD 是否有事件發生,並處理事件;代碼像下面這樣:

 1 int res = select(maxfd+1, &readfds, NULL, NULL, 120);
 2 if (res > 0)
 3 {
 4     for (int i = 0; i < MAX_CONNECTION; i++)
 5     {
 6         if (FD_ISSET(allConnection[i], &readfds))
 7         {
 8             handleEvent(allConnection[i]);
 9         }
10     }
11 }
12 // if(res == 0) handle timeout, res < 0 handle error    

  Epoll 不只會告訴應用程序有I/0 事件到來,還會告訴應用程序相關的信息,這些信息是應用程序填充的,所以根據這些信息應用程序就能直接定位到事件,而沒必要遍歷整個FD 集合。

1 int res = epoll_wait(epfd, events, 20, 120);
2 for (int i = 0; i < res;i++)
3 {
4      handleEvent(events[n]);
5 }

   下面用一個實例來講明

  client.cpp

 1 #include <stdio.h>
 2 #include <stdlib.h>
 3 #include <string.h>
 4 #include <netinet/in.h>
 5 #include <sys/types.h>
 6 #include <sys/socket.h>
 7 #include <netdb.h>
 8 #include <unistd.h>
 9 
10 #define MAX_DATA_SIZE 4096
11 #define SERVER_PORT 12138
12 
13 
14 int main(int argc,char *argv[])
15 {
16     int sockfd;
17     struct hostent * host;
18     struct sockaddr_in servAddr;
19     int pid;
20     char sendBuf[MAX_DATA_SIZE],recvBuf[MAX_DATA_SIZE];
21     int sendSize,recvSize;
22 
23     host=gethostbyname(argv[1]);
24     if(host==NULL)
25     {
26         perror("get host error");
27         exit(-1);
28     }
29 
30     sockfd=socket(AF_INET,SOCK_STREAM,0);
31     if(sockfd==-1)
32     {
33         perror("建立socket失敗");
34         exit(-1);
35     }
36 
37     servAddr.sin_family=AF_INET;
38     servAddr.sin_port=htons(SERVER_PORT);
39     servAddr.sin_addr=*((struct in_addr *)host->h_addr);
40     bzero(&(servAddr.sin_zero),8);
41 
42     if(connect(sockfd,(struct sockaddr *)&servAddr,sizeof(struct sockaddr_in))==-1)
43     {
44         perror("connect 失敗");
45         exit(-1);
46     }
47 
48     if((pid=fork())<0)
49     {
50         perror("fork error");
51     }
52     else if(pid>0)
53     {
54         while(1)
55         {
56             fgets(sendBuf,MAX_DATA_SIZE,stdin);
57             sendSize=send(sockfd,sendBuf,MAX_DATA_SIZE,0);
58             if(sendSize<0)
59                 perror("send error");
60             memset(sendBuf,0,sizeof(sendBuf));
61         }
62     }
63     else
64     {
65         while(1)
66         {
67             recvSize=recv(sockfd,recvBuf,MAX_DATA_SIZE,0);
68             if(recvSize<0)
69                 perror("recv error");
70             printf("接收到的信息:%s",recvBuf);
71             memset(recvBuf,0,sizeof(recvBuf));
72         }
73     }
74     return 0;
75 }

  server.cpp

  1 #include <stdio.h>
  2 #include <stdlib.h>
  3 #include <sys/socket.h>
  4 #include <sys/epoll.h>
  5 #include <string.h>
  6 #include <netinet/in.h>
  7 #include <string.h>
  8 #include <netdb.h>
  9 #include <arpa/inet.h>
 10 #include <unistd.h>
 11 
 12 #define SERVER_PORT 12138
 13 #define CON_QUEUE 20
 14 #define MAX_DATA_SIZE 4096
 15 #define MAX_EVENTS 500
 16 
 17 void AcceptConn(int sockfd,int epollfd);
 18 void Handle(int clientfd);
 19 
 20 int main(int argc,char *argv[])
 21 {
 22     struct sockaddr_in serverSockaddr;
 23     int sockfd;
 24 
 25     //建立socket
 26     if((sockfd=socket(AF_INET,SOCK_STREAM,0))==-1)
 27     {
 28         perror("建立socket失敗");
 29         exit(-1);
 30     }
 31     serverSockaddr.sin_family=AF_INET;
 32     serverSockaddr.sin_port=htons(SERVER_PORT);
 33     serverSockaddr.sin_addr.s_addr=htonl(INADDR_ANY);
 34     bzero(&(serverSockaddr.sin_zero),8);
 35 
 36     int on=0;
 37     setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on));
 38 
 39     if(bind(sockfd,(struct sockaddr *)&serverSockaddr,sizeof(struct sockaddr))==-1)
 40     {
 41         perror("綁定失敗");
 42         exit(-1);
 43     }
 44 
 45     if(listen(sockfd,CON_QUEUE)==-1)
 46     {
 47         perror("監聽失敗");
 48         exit(-1);
 49     }
 50 
 51     //epoll初始化
 52     int epollfd;//epoll描述符
 53     struct epoll_event eventList[MAX_EVENTS];
 54     epollfd=epoll_create(MAX_EVENTS);
 55     struct epoll_event event;
 56     event.events=EPOLLIN|EPOLLET;
 57     event.data.fd=sockfd;//把server socket fd封裝進events裏面
 58 
 59     //epoll_ctl設置屬性,註冊事件
 60     if(epoll_ctl(epollfd,EPOLL_CTL_ADD,sockfd,&event)<0)
 61     {
 62         printf("epoll 加入失敗 fd:%d\n",sockfd);
 63         exit(-1);
 64     }
 65 
 66     while(1)
 67     {
 68         int timeout=300;//設置超時;在select中使用的是timeval結構體
 69         //epoll_wait epoll處理
 70         //ret會返回在規定的時間內獲取到IO數據的個數,並把獲取到的event保存在eventList中,注意在每次執行該函數時eventList都會清空,由epoll_wait函數填寫。
 71         //而不清除已經EPOLL_CTL_ADD到epollfd描述符的其餘加入的文件描述符。這一點與select不一樣,select每次都要進行FD_SET,具體可看個人select講解。
 72         //epoll裏面的文件描述符要手動經過EPOLL_CTL_DEL進行刪除。
 73         int ret=epoll_wait(epollfd,eventList,MAX_EVENTS,timeout);
 74 
 75         if(ret<0)
 76         {
 77             perror("epoll error\n");
 78             break;
 79         }
 80         else if(ret==0)
 81         {
 82             //超時
 83             continue;
 84         }
 85 
 86         //直接獲取了事件數量,給出了活動的流,這裏就是跟selec,poll區別的關鍵 //select要用遍歷整個數組才知道是那個文件描述符有事件。而epoll直接就把有事件的文件描述符按順序保存在eventList中
 87         for(int i=0;i<ret;i++)
 88         {
 89             //錯誤輸出
 90             if((eventList[i].events & EPOLLERR) || (eventList[i].events & EPOLLHUP) || !(eventList[i].events & EPOLLIN))
 91             {
 92                 printf("epoll error\n");
 93                 close(eventList[i].data.fd);
 94                 exit(-1);
 95             }
 96 
 97             if(eventList[i].data.fd==sockfd)
 98             {
 99                 //這個是判斷sockfd的,主要是用於接收客戶端的鏈接accept
100                 AcceptConn(sockfd,epollfd);
101             }
102             else //裏面能夠經過判斷eventList[i].events&EPOLLIN 或者 eventList[i].events&EPOLLOUT 來區分當前描述符的鏈接是對應recv仍是send
103             {
104                 //其餘全部與客戶端鏈接的clientfd文件描述符
105                 //獲取數據等操做
106                 //如需不接收客戶端發來的數據,可是不關閉鏈接。
107                 //epoll_ctl(epollfd, EPOLL_CTL_DEL,eventList[i].data.fd,eventList[i]);
108                 //Handle對各個客戶端發送的數據進行處理
109                 Handle(eventList[i].data.fd);
110             }
111         }
112     }
113 
114     close(epollfd);
115     close(sockfd);
116     return 0;
117 }
118 
119 void AcceptConn(int sockfd,int epollfd)
120 {
121     struct sockaddr_in sin;
122     socklen_t len=sizeof(struct sockaddr_in);
123     bzero(&sin,len);
124 
125     int confd=accept(sockfd,(struct sockaddr *)&sin,&len);
126 
127     if(confd<0)
128     {
129         perror("connect error\n");
130         exit(-1);
131     }
132 
133     //把客戶端新創建的鏈接添加到EPOLL的監聽中
134     struct epoll_event event;
135     event.data.fd=confd;
136     event.events=EPOLLIN|EPOLLET;
137     epoll_ctl(epollfd,EPOLL_CTL_ADD,confd,&event);
138     return ;
139 }
140 
141 void Handle(int clientfd)
142 {
143     int recvLen=0;
144     char recvBuf[MAX_DATA_SIZE];
145     memset(recvBuf,0,sizeof(recvBuf));
146     recvLen=recv(clientfd,(char *)recvBuf,MAX_DATA_SIZE,0);
147     if(recvLen==0)
148         return ;
149     else if(recvLen<0)
150     {
151         perror("recv Error");
152         exit(-1);
153     }
154     //各類處理
155     printf("接收到的數據:%s \n",recvBuf);
156     return ;
157 }

 


  epoll參考資料

  http://blog.csdn.net/mmz_xiaokong/article/details/8704988
  http://blog.csdn.net/mmz_xiaokong/article/details/8704455
  http://www.cppblog.com/converse/archive/2008/10/13/63928.html
  http://blog.csdn.net/haoahua/article/details/2037704
  https://banu.com/blog/2/how-to-use-epoll-a-complete-example-in-c/

  epoll爲何這麼快: http://www.cppblog.com/converse/archive/2008/10/12/63836.html

 

  本文地址: http://www.cnblogs.com/wunaozai/p/3895860.html

相關文章
相關標籤/搜索