I/O复用之 EPOLLONESHOT 事件
EPOLLONESHOT 事件
- EPOLLONESHOT 事件
- 1. 使用EPOLLONESHOT的原因及優點
- 2. recv返回值 及 與errno的配合使用
- 3. 示例程序
1. 使用EPOLLONESHOT的原因及優點
即使使用ET模式,一個socket上的某個事件還是可能被觸發多次。比如:一個線程在讀取完某個socket上的數據后開始處理這些數據,而在數據的處理過程中該socket上又有新數據可讀(EPOLLIN再次被觸發),此時另外一個線程被喚醒用來讀取這些新的數據。于是就出現了兩個線程同時操作一個socket的局面。而我們希望一個socket連接在任一時刻都只能被一個線程處理,這就可以通過EPOLLONESHOT事件實現。
對于注冊了EPOLLONESHOT事件的文件描述符,操作系統最多觸發其上注冊的一個事件,且只觸發一次,除非我們使用epoll_ctl函數重置該文件描述符上注冊的EPOLLONESHOT事件。這樣,在一個線程使用socket時,其他線程無法操作socket。同樣,只有在該socket被處理完后,須立即重置該socket的EPOLLONESHOT事件,以確保這個socket在下次可讀時,其EPOLLIN事件能夠被觸發,進而讓其他線程有機會操作這個socket。
2. recv返回值 及 與errno的配合使用
在編寫程序時,發現對recv函數返回值意義仍然不清,導致部分代碼含義沒看懂,在此特別記錄一下,以下用ret表示recv函數的返回值。
(1) ret == 0
表示對方斷開連接。當客戶端不與服務端交互數據好長時間之后,服務端程序會自動斷開連接(當客戶端recv時),同時客戶端的連接狀態變成了 CLOSE_WAIT,如果客戶端再向服務端發送數據,然后recv服務端的反饋時,就會造成recv返回0。
(2) ret > 0
表示接收到數據。ret值為接收數據的字節數。
(3) ret < 0
表示recv函數出錯。此時分為多種情況,僅介紹以下情況:
- errno == EAGAIN 表示數據未讀完。數據量太大,一次發送導致緩沖區滿,需要再次檢查是否還有未讀取數據。
在此引用其他大佬的思考:
當對側沒有send,即本側的套接字s的接收緩沖區無數據,返回值是?(EAGAIN,原因為超時,待測)
3. 示例程序
/* EPOLLONESHOT 事件的使用 ** 運行命令: g++ filename.cpp -lpthread; ./a.out 127.0.0.1 6666 ** 可以使用telnet連接該服務器(telnet 127.0.0.1 6666) */#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> #include <pthread.h> using namespace std;const int MAX_EVENT_NUMBER = 1024; const int BUF_SIZE = 1024;struct fds{int epoll_fd;int sock_fd; };void err( int line ) {cout << "error_line: " << line << endl; }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 addfd( int epoll_fd, int fd, bool oneshot ) {epoll_event event;event.data.fd = fd;event.events = EPOLLIN | EPOLLET;if ( oneshot ) {event.events |= EPOLLONESHOT;}epoll_ctl( epoll_fd, EPOLL_CTL_ADD, fd, &event );setnonblocking( fd ); }void reset_oneshot( int epoll_fd, int fd ) {epoll_event event;event.data.fd = fd;event.events = EPOLLIN | EPOLLET | EPOLLONESHOT;epoll_ctl( epoll_fd, EPOLL_CTL_MOD, fd, &event ); }void * worker( void * arg ) {int sock_fd = ( (fds *)arg )->sock_fd;int epoll_fd = ( (fds *)arg )->epoll_fd;printf("start new thread to receive data on fd: %d\n", sock_fd);char buf[BUF_SIZE];memset( buf, 0, sizeof( buf ) );while( true ) {int ret = recv( sock_fd, buf, BUF_SIZE - 1, 0 );if ( !ret ) {close( sock_fd );cout << "foreiner closed the connection\n";break;} else if ( ret < 0 ) {if ( errno == EAGAIN ) { //數據未讀完,需要再次讀取reset_oneshot( epoll_fd, sock_fd );cout << "read later\n";break;}} else {printf( "got %d bytes of data: %s\n", ret, buf );sleep(5);}}printf("end thread receiving data on fd: %d\n", sock_fd); }int main( int argc, char * argv[] ) {if ( argc < 3 ) {printf( "usage: ./file ip_number port_number\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 ( sock_fd < 0 ) {err( __LINE__ );}ret = listen( sock_fd, 5 );if ( sock_fd < 0 ) {err( __LINE__ );}epoll_event events[MAX_EVENT_NUMBER];int epoll_fd = epoll_create( 5 );if ( epoll_fd < 0 ) {err( __LINE__ );}/* 監聽socket的 sock_fd 不能注冊 EPOLLONESHOT 事件,否則程序只能處理一個客戶連接** 因為后續的客戶連接請求將不再觸發sock_fd的 EPOLLIN 事件*/addfd( epoll_fd, sock_fd, false );while( true ) {ret = epoll_wait( epoll_fd, events, MAX_EVENT_NUMBER, -1 ); //等待事件發生if ( ret < 0 ) {printf( "epoll failure\n" );break;}for ( int i = 0; i < ret; i++ ) {int fd = events[i].data.fd;if ( fd == sock_fd ) { //有新的連接請求struct sockaddr_in client;socklen_t client_length = sizeof( client );int conn_fd = accept( sock_fd, ( struct sockaddr * )&client,&client_length );//對每個非監聽文件描述符都注冊 EPOLLONESHOT 事件//添加的是剛accept的fdaddfd( epoll_fd, conn_fd, true );} else if ( events[i].events & EPOLLIN ) { //有可讀取數據pthread_t thread;fds fds_for_new_worker;fds_for_new_worker.epoll_fd = epoll_fd;fds_for_new_worker.sock_fd = fd; //內核事件表中的fd,不要搞混//新啟動一個線程為sock_fd服務pthread_create( &thread, NULL, worker, (void *)&fds_for_new_worker );} else {printf( "something else happened\n" );}}}close(sock_fd);return 0; }總結
以上是生活随笔為你收集整理的I/O复用之 EPOLLONESHOT 事件的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: DNF中的生锈的小太刀加13的值多少钱?
- 下一篇: 二维map —— HDU1263