EPOLL使用總結

來源:互聯網
上載者:User

2010-10-27 kejieleung

 

 


epoll的介面非常簡單,一共就三個函數:
1. int epoll_create(int
size);

建立一個epoll的控制代碼,size用來告訴核心這個監聽的數目一共有多大。這個參數不同於select()中的第一個參數,給出最大監聽的fd+1的值。需要注意的是,當建立好epoll控制代碼後,它就是會佔用一個fd值,在linux下如果查看/proc/進程id/fd/,是能夠看到這個fd的,所以在使用完epoll後,必須調用close()關閉,否則可能導致fd被耗盡。

2. int epoll_ctl(int epfd, int
op, int fd, struct epoll_event
*event);

epoll的事件註冊函數,它不同與select()是在監聽事件時告訴核心要監聽什麼類型的事件,而是在這裡先註冊要監聽的事件類型。第一個參數是epoll_create()的傳回值,第二個參數表示動作,用三個宏來表示:
EPOLL_CTL_ADD:註冊新的fd到epfd中;
EPOLL_CTL_MOD:修改已經註冊的fd的監聽事件;
EPOLL_CTL_DEL:從epfd中刪除一個fd;

第三個參數是需要監聽的fd,第四個參數是告訴核心需要監聽什麼事,struct
epoll_event結構如下:

struct
epoll_event {
  __uint32_t events;  /* Epoll events
*/
  epoll_data_t data;  /* User data variable
*/
};

events可以是以下幾個宏的集合:
EPOLLIN
:表示對應的檔案描述符可以讀(包括對端SOCKET正常關閉);
EPOLLOUT:表示對應的檔案描述符可以寫;
EPOLLPRI:表示對應的檔案描述符有緊急的資料可讀(這裡應該表示有帶外資料到來);
EPOLLERR:表示對應的檔案描述符發生錯誤;
EPOLLHUP:表示對應的檔案描述符被掛斷;
EPOLLET:
將EPOLL設為邊緣觸發(Edge Triggered)模式,這是相對於水平觸發(Level
Triggered)來說的。
EPOLLONESHOT:只監聽一次事件,當監聽完這次事件之後,如果還需要繼續監聽這個socket的話,需要再次把這個socket加入到EPOLL隊列裡

3. int epoll_wait(int epfd,
struct epoll_event * events, int maxevents, int
timeout);

等待事件的產生,類似於select()調用。參數events用來從核心得到事件的集合,maxevents告之核心這個events有多大,這個maxevents的值不能大於建立epoll_create()時的size,參數timeout是逾時時間(毫秒,0會立即返回,-1將不確定,也有說法說是永久阻塞)。該函數返回需要處理的事件數目,如返回0表示已逾時。

EPOLL事件有兩種模型:
Edge Triggered
(ET) 
    只有在監視的檔案控制代碼上發生了某個事件的時候 ET
工作模式才會彙報事件。必須使用非阻塞套介面,以避免由於一個檔案控制代碼的阻塞讀/阻塞寫操作把處理多個檔案描述符的任務餓死。
    i    基於非阻塞檔案控制代碼
    ii  
只有當read(2)或者write(2)返回EAGAIN時才需要掛起,等待。
    但這並不是說每次read()時都需要迴圈讀,直到讀到產生一個EAGAIN才認為此次事件處理完成,當read()返回的讀到的資料長度小於請求的資料長度時,就可以確定此時緩衝中已沒有資料了,也就可以認為此事讀事件已處理完成。
    ET(edge-triggered)是高速工作方式,只支援no-block
socket。在這種模式下,當描述符從未就緒變為就緒時,核心通過epoll告訴你。然後它會假設你知道檔案描述符已經就緒,並且不會再為那個檔案描述符發送更多的就緒通知,直到你做了某些操作導致那個檔案描述符不再為就緒狀態了(比如,你在發送,接收或者接收請求,或者發送接收的資料少於一定量時導致了一個EWOULDBLOCK
錯誤)。但是請注意,如果一直不對這個fd作IO操作(從而導致它再次變成未就緒),核心不會發送更多的通知(only once),不過在TCP協議中,ET模式的加速效用仍需要更多的benchmark確認(這句話不理解)。

Level Triggered
(LT)
    LT(level triggered)是預設的工作方式,並且同時支援block和no-block
socket.在這種做法中,核心告訴你一個檔案描述符是否就緒了,然後你可以對這個就緒的fd進行IO操作。如果你不作任何操作,核心還是會繼續通知你的,所以,這種模式編程出錯誤可能性要小一點。傳統的select/poll都是這種模型的代表.

    
    在許多測試中我們會看到如果沒有大量的idle
-connection或者dead-connection,epoll的效率並不會比select/poll高很多,但是當我們遇到大量的idle-
connection(例如WAN環境中存在大量的慢速連線),就會發現epoll的效率大大高於select/poll。

EPOLLOUT事件的處理策略就是,
先嘗試直接發送.如果發送不完整,就buffer住等下一輪再發


 

使用總結

1.EPOLL 預設是水平觸發模式LT,
如果要使用邊緣觸發模式,要將listen的socket事件加上EPOLLET


  ev.data.fd = listen_fd;
  ev.events = EPOLLIN | EPOLLET | EPOLLOUT | EPOLLHUP | EPOLLERR;
  //add server socket fd
in to event set
  epoll_ctl( epfd, EPOLL_CTL_ADD, listen_fd, &ev ); 

 

 

2.需要設定非阻塞


     int opts;
    opts = fcntl( sock_fd, F_GETFL );
     if( opts < 0 )
    {
        printf("err:fcntl F_GETFL");
        exit( 1 );
    }
    opts = opts
| O_NONBLOCK ;
     if( fcntl( sock_fd, F_SETFL, opts ) < 0
)
    {
        printf("err:fcntl
F_SETFL");
        exit( 1
);
    } 

 

3.如果是以LT方式處理,只要資料未處理完(send、recv核心緩衝有資料)就會不停的收到
EPOLLIN/EPOLLOU訊息(已設定監聽)。所以在recv時可以先處理一個MAX_LEN, 之後等待下次觸發再處理

處理:

1)不將EPOLLOUT事件加入監聽

2)在收到EPOLLIN後,需要寫時再加入EPOLLIN | EPOLLOUT

3)寫完後再設為 EPOLLIN

4)或者改為ET模式

    如果是以ET方式處理,事件只會通知一次,如資料到達可接收,需要使用while迴圈來接入資料。同樣可發送也只會通常一次。send/recv都以收到EAGAIN為標識,如果還沒處理完,才會現收到事件通知。

    注意EPOLLOUT事件,如果是LT模式,只要send緩衝區空閑就會一直有這個事件通知,程式需要判斷是否有資料可發送。如果是ET模式,只有在accept一個socket時觸發一次,除非發送,緩衝區滿了,再變為空白閑時,才會再通知一次

 

4.處理socket recv/send時返回-1, 串連已斷開

 

5.關於accept有個小細節,一定要指定sockaddr_in長度,不然會莫名accept不了


  clnLen = sizeof( sockaddr_in );
  memset( &clnAddr, 0 , sizeof( sockaddr_in ) );
  conn_fd = accept( listen_fd, (sockaddr*)&clnAddr, &clnLen ); 

 

 

6.設定連接埠複用,可以多個socket綁定同一連接埠


     int flags = 1;
     if(setsockopt( sock_fd , SOL_SOCKET, SO_REUSEADDR,
&flags, sizeof(flags)) == -1)
    {
         return false;
    }

 

 

 

 

 

 

 

範例程式碼如下:

 

#include <sys/socket.h><br />#include <sys/epoll.h><br />#include <netinet/in.h><br />#include <arpa/inet.h><br />#include <fcntl.h><br />#include <unistd.h><br />#include <stdio.h><br />#include <stdlib.h><br />#include <memory.h><br />#include <pthread.h><br />#include <errno.h><br />#define MAX_LINE_LEN 1024<br />#define OPEN_MAX 1024<br />#define SVR_PORT 17143<br />struct STask<br />{<br /> int fd;<br /> struct STask * next;<br />};<br />struct SData<br />{<br /> int fd;<br /> int size;<br /> char data[ MAX_LINE_LEN ];<br />};<br />struct epoll_event ev, events[20];<br />int epfd;<br />pthread_mutex_t g_mutex;<br />pthread_cond_t g_cond;<br />volatile bool bSend = false;<br />struct SData *readHead = NULL, *readTail = NULL, *writeHead = NULL;<br />void setNonBlock( int sock_fd )<br />{<br /> int opts;<br /> opts = fcntl( sock_fd, F_GETFL );<br /> if( opts < 0 )<br /> {<br /> printf("err:fcntl F_GETFL");<br /> exit( 1 );<br /> }<br /> opts = opts | O_NONBLOCK ;<br /> if( fcntl( sock_fd, F_SETFL, opts ) < 0 )<br /> {<br /> printf("err:fcntl F_SETFL");<br /> exit( 1 );<br /> }<br />}<br />bool setSockReuse( int sock_fd )<br />{<br /> int flags = 1;<br /> if(setsockopt( sock_fd , SOL_SOCKET, SO_REUSEADDR, &flags, sizeof(flags)) == -1)<br /> {<br /> return false;<br /> }<br /> printf("set sock reuse ok./n");<br /> return true;<br />}<br />void OnAccept( int conn_fd, sockaddr_in * cln_addr )<br />{<br /> setNonBlock( conn_fd );<br /> char * str = inet_ntoa( cln_addr->sin_addr );<br /> printf("connect_from:%s/n", str);<br /> //add to epoll event for reading event<br /> ev.data.fd = conn_fd;<br /> ev.events = EPOLLIN | EPOLLOUT | EPOLLET;<br /> epoll_ctl( epfd, EPOLL_CTL_ADD, conn_fd, &ev );<br />}<br />void OnConnect()<br />{<br />}<br />void OnWrite( epoll_event * aEv );<br />void OnRead( epoll_event * aEv )<br />{<br /> int n;<br /> struct SData * newData = (struct SData *)malloc( sizeof(struct SData ) );<br /> newData->fd = aEv->data.fd;</p><p> int bRead = 1;<br /> int recv_len = 0;<br /> memset( newData->data, 0, MAX_LINE_LEN );<br /> while( bRead )<br /> {<br /> n = read( aEv->data.fd, newData->data + recv_len, 5 );<br /> printf("read return n:%d/n",n);<br /> recv_len += n;<br /> if( n < 0 )<br /> {<br /> if( errno == ECONNRESET )<br /> {<br /> close( aEv->data.fd );<br /> aEv->data.fd = -1;<br /> break;<br /> }<br /> else if( errno == EAGAIN ) //read interupt(no data in buf)<br /> {<br /> printf("EAGAIN/n");<br /> break;<br /> }<br /> else if( errno == EWOULDBLOCK )<br /> {<br /> perror("recv not over.../n");<br /> }<br /> else<br /> {<br /> printf("read err");<br /> break;<br /> }<br /> }<br /> else if( n == 0 )<br /> {<br /> close( aEv->data.fd );<br /> aEv->data.fd = -1;<br /> break;<br /> }<br /> }<br /> newData->data[ recv_len ] = '/n';<br /> printf("read: %s", newData->data );<br /> ev.data.fd = aEv->data.fd;<br /> ev.events = EPOLLIN | EPOLLOUT;<br /> //ev.events = EPOLLOUT | EPOLLET;<br /> //epoll_ctl(epfd,EPOLL_CTL_MOD, aEv->data.fd ,&ev);<br /> bSend = true;<br /> OnWrite( &ev );<br /> free( newData );<br />}<br />void OnWrite( epoll_event * aEv )<br />{<br /> struct SData * newData = (struct SData *)malloc( sizeof(struct SData ) );<br /> newData->fd = aEv->data.fd;<br /> //if( !bSend )<br /> // return;<br /> newData->size = snprintf( newData->data,MAX_LINE_LEN, "hello! fd=%d/n", aEv->data.fd );<br /> if( 0 != newData->size )<br /> {<br /> printf("send back: %s/n", newData->data );<br /> write( aEv->data.fd , newData->data, newData->size );<br /> }<br /> ev.data.fd = aEv->data.fd;<br /> //ev.events = EPOLLIN ;<br /> ev.events = EPOLLIN | EPOLLET;<br /> //epoll_ctl( epfd, EPOLL_CTL_MOD, aEv->data.fd, &ev );<br /> free( newData );<br /> bSend = false;<br />}<br />int main()<br />{<br /> int i;<br /> int listen_fd, conn_fd,sock_fd, nfds;<br /> //pthread_t rdTrhead_id, wtThread_id;<br /> //struct SData * newReadData = NULL;<br /> //struct SData * newWriteData = NULL;<br /> pthread_mutex_init( &g_mutex, NULL );<br /> pthread_cond_init( &g_cond, NULL );<br /> epfd = epoll_create( 256 );</p><p> struct sockaddr_in svrAddr;<br /> struct sockaddr_in clnAddr;<br /> socklen_t clnLen;<br /> listen_fd = socket( AF_INET, SOCK_STREAM, 0);<br /> setNonBlock( listen_fd );<br /> setSockReuse( listen_fd );<br /> ev.data.fd = listen_fd;<br /> ev.events = EPOLLIN | EPOLLET | EPOLLOUT | EPOLLHUP | EPOLLERR;<br /> //ev.events = EPOLLIN | EPOLLET;<br /> //add server socket fd in to event set<br /> epoll_ctl( epfd, EPOLL_CTL_ADD, listen_fd, &ev );<br /> memset( &svrAddr, 0 , sizeof( sockaddr_in ) );<br /> inet_aton( "127.0.0.1", &svrAddr.sin_addr );<br /> svrAddr.sin_family = AF_INET;<br /> svrAddr.sin_port = htons( SVR_PORT );<br /> int ret;<br /> ret = bind( listen_fd, (sockaddr*)&svrAddr, sizeof( sockaddr_in ) );<br /> if( ret != 0 )<br /> {<br /> perror( "bind fail" );<br /> exit(-3);<br /> }<br /> listen( listen_fd, 20 );<br /> for(;;)<br /> {<br /> nfds = epoll_wait( epfd, events, 20, 500 );<br /> //maybe serval event arrive<br /> for(i=0; i<nfds;++i)<br /> {<br /> if( events[i].data.fd == listen_fd )<br /> {<br /> printf("event accept./n");<br /> clnLen = sizeof( sockaddr_in );<br /> memset( &clnAddr, 0 , sizeof( sockaddr_in ) );<br /> conn_fd = accept( listen_fd, (sockaddr*)&clnAddr, &clnLen );<br /> if( conn_fd < 0 )<br /> {<br /> if( errno == EWOULDBLOCK )<br /> printf("EWOULDBLOCK/n");<br /> perror("conn_fd < 0");<br /> break;<br /> }<br /> OnAccept( conn_fd, &clnAddr );<br /> }<br /> else if( events[i].events & EPOLLIN )<br /> {<br /> printf("event EPOLLIN./n");<br /> if( ( sock_fd = events[i].data.fd ) < 0 )<br /> continue;<br /> OnRead( &events[i] );<br /> }<br /> else if( events[i].events & EPOLLOUT )<br /> {<br /> printf("event EPOLLOUT./n");<br /> if( ( sock_fd = events[i].data.fd ) < 0 )<br /> continue;<br /> OnWrite( &events[i] );<br /> }<br /> else if( events[i].events & EPOLLHUP )<br /> {<br /> printf("event EPOLLHUP. fd=%d./n", events[i].data.fd );<br /> }<br /> else if( events[i].events & EPOLLERR )<br /> {<br /> printf("event EPOLLERR. fd=%d./n", events[i].data.fd );<br /> }<br /> }<br /> }<br /> pthread_mutex_destroy( &g_mutex );<br /> pthread_cond_destroy( &g_cond);<br /> return 0;<br />}<br /> 

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.