I/O复用之 epoll
epoll 系統(tǒng)調(diào)用
1. 內(nèi)核事件表
epoll使用一系列函數(shù)來完成任務(wù),把用戶關(guān)心的文件描述符中的事件放到內(nèi)核里的一個(gè)事件表中,因此不用像select、poll那樣每次調(diào)用都要重復(fù)傳入文件描述符集或事件表。epoll需要一個(gè)文件描述符來唯一標(biāo)識(shí)該事件表,該文件描述符使用epoll_create函數(shù)創(chuàng)建:
#include <sys/epoll.h>int epoll_create( int size );size 標(biāo)記事件表大小。該函數(shù)返回的文件描述符將用作其他所有epoll系統(tǒng)調(diào)用的第一個(gè)參數(shù),以指定要訪問的內(nèi)核事件表。
epoll_ctl函數(shù)用于操作epoll的內(nèi)核事件表:
#include <sys/epoll.h>int epoll_ctl( int epfd, int op, int fd, struct epoll_event * event )fd參數(shù)是要操作的文件描述符,op參數(shù)指定操作類型(如下):
- EPOLL_CTL_ADD,往事件表中注冊(cè)fd上的事件
- EPOLL_CTL_DEL,刪除fd上的注冊(cè)事件
- EPOLL_CTL_MOD,修改fd上的注冊(cè)事件
event參數(shù)指定被操作的事件,它是epoll_event結(jié)構(gòu)類型指針。epoll_event的定義如下:
struct epoll_event{__uint32_t events; //epoll事件epoll_data_t data; //用戶數(shù)據(jù)}其中events成員描述事件類型(如 EPOLLIN表示數(shù)據(jù)可讀事件)。epoll_data_t 定義如下:
typedef union epoll_data{void * ptr;int fd;uint32_t u32;uint64_t u64;} epoll_data_t;epoll_data_t 是一個(gè)共用體,其4個(gè)成員使用最多的是fd,它指定事件所從屬的目標(biāo)文件描述符。各成員不能同時(shí)使用。
2. epoll_wait 函數(shù)
epoll_wait 函數(shù)在一段超時(shí)時(shí)間內(nèi)等待一組文件描述符上的事件,其原型如下:
#include <sys/epoll.h> int epoll_wait( int epfd, struct epoll_event * events, int maxevents, int timeout )該函數(shù)成功時(shí)返回就緒的文件描述符的個(gè)數(shù),失敗時(shí)返回-1并設(shè)置errno。timeout參數(shù)為延時(shí)時(shí)間。maxevents參數(shù)指定最多監(jiān)聽多少個(gè)事件,它必須大于0。
epoll_wait函數(shù)如果檢測到事件,就將所有就緒的事件從內(nèi)核事件表(由epfd指定)中復(fù)制到它的第二個(gè)參數(shù)events指向的數(shù)組中,該數(shù)組只用于輸出epoll_wait檢測到的就緒事件,而不像select和poll那樣既傳入用戶注冊(cè)事件,又輸出內(nèi)核檢測到的就緒事件。這極大提高了應(yīng)用程序索引就緒文件描述符的效率。
3. LT和ET模式
LT : 電平觸發(fā)(默認(rèn)工作方式)
ET :邊沿觸發(fā)(高效工作方式)
對(duì)于采用LT工作模式的文件描述符,當(dāng)epoll_wait檢測到其上有事件發(fā)生并將此事件通知應(yīng)用程序后,應(yīng)用程序可以不立即處理該事件。這樣,當(dāng)下一次調(diào)用epoll_wait()時(shí),epoll_wait()還會(huì)再次向應(yīng)用程序通知此事件,直至該事件被處理。
對(duì)于采用ET工作模式的文件描述符,當(dāng)epoll_wait檢測到其上有事件發(fā)生并將此事件通知應(yīng)用程序后,應(yīng)用程序必須立即處理該事件,因?yàn)楹罄m(xù)的epoll_wait()將不再向應(yīng)用程序通知該事件。
可見,ET模式在很大程度上降低了同一個(gè)epoll事件被重復(fù)觸發(fā)的次數(shù),因此效率會(huì)比LT模式高。
4. code
/* 該程序以 “ ./a.out ip_number port_number ” 方式使用 ** 通過telnet連接到該運(yùn)行程序 */#include <iostream> #include <cstdio> #include <sys/socket.h> #include <sys/types.h> #include <sys/stat.h> #include <sys/sendfile.h> #include <unistd.h> #include <signal.h> #include <netinet/in.h> #include <arpa/inet.h> #include <cstdlib> #include <cstring> #include <fcntl.h> #include <sys/epoll.h> using namespace std;const int MAX_EVENT_NUMBER = 1024; const int BUFFER_SIZE = 10;void err( int ); void addfd( int , int , bool ); int setnonblocking( int ); void lt( epoll_event * , int , int , int ); void et( epoll_event * , int , int , int );int main(int argc, char * argv[]) {if ( argc < 3 ) {cout << "usage:\n";return 1;}const char *ip = argv[1];const int port = atoi( argv[2] );struct sockaddr_in address;memset( &address, 0, sizeof( address ) );address.sin_family = AF_INET;address.sin_port = htons( port );inet_pton( AF_INET, ip, &address.sin_addr );int sock_fd = socket( AF_INET, SOCK_STREAM, 0 );if ( sock_fd < 0) {err( __LINE__ );}int ret = bind( sock_fd, ( struct sockaddr * )&address, sizeof( address ) );if ( ret < 0 ) {err( __LINE__ );}ret = listen( sock_fd, 5 );if ( ret < 0 ) {err( __LINE__ );}epoll_event events[MAX_EVENT_NUMBER]; //內(nèi)核事件表,通過epoll_wait()函數(shù)發(fā)生作用int epoll_fd = epoll_create( 5 );if ( epoll_fd < 0 ) {err( __LINE__ );}addfd( epoll_fd, sock_fd, true );while( true ) {ret = epoll_wait( epoll_fd, events, MAX_EVENT_NUMBER, -1 );if ( ret < 0 ) {cout << "epoll failure\n";break;}lt( events, ret, epoll_fd, sock_fd ); //使用LT模式// et( events, ret, epoll_fd, sock_fd ); //使用ET模式}close( sock_fd );return 0; }void err(int line) {cout << "error: line " << line << endl; }void addfd( int epoll_fd, int fd, bool enable_et ) {epoll_event event;event.data.fd = fd;event.events = EPOLLIN;if ( enable_et ) {event.events |= EPOLLET;}epoll_ctl( epoll_fd, EPOLL_CTL_ADD, fd, &event );setnonblocking( fd ); }int setnonblocking( int fd ) {int old_option = fcntl( fd, F_GETFL );int new_option = old_option | O_NONBLOCK;fcntl( fd, F_SETFL, new_option );return old_option; }void lt( epoll_event * events, int number, int epoll_fd, int listen_fd) {char buf[ BUFFER_SIZE ];for( int i = 0; i < number; i++ ) {int sock_fd = events[i].data.fd;if ( sock_fd == listen_fd ) { //有新的連接請(qǐng)求struct sockaddr_in client;socklen_t client_length = sizeof( client );int conn_fd = accept( sock_fd, ( struct sockaddr * )&client,&client_length );addfd( epoll_fd, conn_fd, false );} else if ( events[i].events & EPOLLIN ) { //還存在未讀數(shù)據(jù)cout << "event trigger once\n";memset( buf, 0, sizeof( buf ) );int ret = recv( sock_fd, buf, BUFFER_SIZE - 1, 0 );if ( ret <= 0 ) {close( sock_fd );}printf( "get %d bytes of content: -%s-\n", ret, buf );} else {cout << "something else happened\n";}} }void et( epoll_event * events, int number, int epoll_fd, int listen_fd) {char buf[ BUFFER_SIZE ];for ( int i = 0; i < number; i++ ) {int sock_fd = events[i].data.fd;if ( sock_fd == listen_fd ) { //有新的連接請(qǐng)求struct sockaddr_in client;socklen_t client_length = sizeof( client );int conn_fd = accept( sock_fd, ( struct sockaddr * )&client,&client_length );addfd( epoll_fd, conn_fd, true );} else if ( events[i].events & EPOLLIN ) { //還存在未讀數(shù)據(jù)printf("event trigger once\n");while( true ) {memset( buf, 0, sizeof( buf ) );int ret = recv( sock_fd, buf, BUFFER_SIZE - 1, 0 );if ( ret < 0 ) {/* 數(shù)據(jù)已全部讀取完畢 */if ( errno == EAGAIN || errno == EWOULDBLOCK ) {cout << "read later\n";break;}close( sock_fd );break;} else if ( !ret ) {close( sock_fd );} else {printf("got %d bytes of content: -%s-\n", ret, buf);}}}} }總結(jié)
以上是生活随笔為你收集整理的I/O复用之 epoll的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 请问如下效果的Multiple Sele
- 下一篇: DNF中的生锈的小太刀加13的值多少钱?