日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

I/O多路复用:select、poll和epoll详解

發(fā)布時間:2024/2/28 编程问答 29 豆豆
生活随笔 收集整理的這篇文章主要介紹了 I/O多路复用:select、poll和epoll详解 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

I/O多路復(fù)用

I/O復(fù)用使得程序能同時監(jiān)聽多個文件描述符,這對提高程序的性能至關(guān)重要。通常,網(wǎng)絡(luò)程序在下列情況下需要使用I/O復(fù)用技術(shù):

服務(wù)端程序要同時處理多個 socket。比如非阻塞 connect 技術(shù)。

服務(wù)端程序要同時處理多用戶請求和網(wǎng)絡(luò)連接。

服務(wù)器要同時處理監(jiān)聽 socket 和連接 socket。這是 I/O復(fù)用使用最多的場合。

I/O復(fù)用雖然能同時監(jiān)聽多個文件描述符,但它本身是阻塞的。并且當(dāng)多個文件描述符同時就緒時,如果不采取額外的措施,程序就只能按順序一次處理其中的每一個文件描述符,這使得服務(wù)器程序看起來像是串行工作的。如果要實現(xiàn)并發(fā),只能使用多進程或多線程等編程手段。Linux 下實現(xiàn) I/O復(fù)用的系統(tǒng)調(diào)用主要有 select、poll 和 epoll。

?

select

select系統(tǒng)調(diào)用的用途是:在一段指定時間內(nèi),監(jiān)聽用戶感興趣的文件描述符上的可讀、可寫和異常事件。內(nèi)核通過對這些參數(shù)在線修改來反饋其中的就緒事件。每次調(diào)用select都要重置這3個參數(shù)。函數(shù)原型:

#include <sys/select.h> int select(nfds,?fd_set *readfds,?fd_set *writefds,?fd_set *exceptfds,?&timeout);

nfds:指定被監(jiān)聽的文件描述符的總數(shù)。

select將事件分為可讀(readfds)、可寫(writefds)和異常(exceptfds),并將相應(yīng)的事件放入對應(yīng)的文件描述符集合中。

timeout:用來設(shè)置select函數(shù)的超時時間,采用指針參數(shù)是因為內(nèi)核將修改它以告訴應(yīng)用程序select等待了多久。如果給timeout變量的成員都傳遞0,則select將立即返回。如果給timeout傳遞NULL,則select將一直阻塞,直到某個文件描述符就緒。

select成功時返回就緒文件描述符的總數(shù)。如果在超時時間內(nèi)沒有任何文件描述符就緒,select將返回0。select失敗時返回-1并設(shè)置errno。使用以下函數(shù)完成對事件的操作:

void FD_CLR(int fd, fd_set *set); ??? //將fd從set上刪除 void FD_ZERO(fd_set *set); ??????? ?//將set中所有設(shè)置位清除 void FD_SET(int fd, fd_set *set); ??? //將fd添加到相應(yīng)的集合上 int FD_ISSET(int fd, fd_set *set); ???//判斷fd是不是在set集合上

select工作原理:

select示例代碼:

FD_ZERO(&readset); //清空一個文件描述符集合 FD_SET(new_sock, &readset); //將一個文件描述符添加到一個指定的文件描述符集合 maxfd=new_sock+1;while(1) {r_readset = readset; //因為每次會修改傳入的事件,所以用變量記錄事件,每次調(diào)用select時需要傳入變量r_readset,而不是readset。timeout.tv_sec = 0;timeout.tv_usec = 500000;//io復(fù)用if((nfound = select(maxfd,&r_readset,(fd_set *)0,(fd_set *)0,&timeout))<0){perror("select");continue;}else if(nfound==0){continue;}if(FD_ISSET(new_sock,&r_readset))//檢查集合中指定的文件描述符是否可以讀{//接收消息byteread=recv(new_sock,&msgs,sizeof(msgs),0);if(byteread<0) //recv出錯{perror("recv:");break;}if(byteread==0) //連接中止,發(fā)送下線信息{break;}//完成相應(yīng)的操作}//end if }//end while

select的參數(shù)類型fd_set沒有將文件描述符和事件綁定,它只是一個文件描述符集合,所以select需要提供3種類型的參數(shù)分別傳入。由于內(nèi)核對fd_set集合是在線修改的,應(yīng)用程序下次調(diào)用select前需要重置這3個fd_set集合。工作流程:

(1)創(chuàng)建TCP連接,并將 fd 添加到 fd_set 集合中;

(2)將 fd_set 集合從用戶態(tài)拷貝到內(nèi)核態(tài);

(3)等待相應(yīng)事件發(fā)生,內(nèi)核會修改事件集合;

(4)將內(nèi)核態(tài)的 fd_set 集合拷貝到用戶態(tài);

(5)應(yīng)用程序根據(jù)發(fā)生的事件完成相應(yīng)的操作。

?

select的缺點:

(1)進程能夠監(jiān)視的文件描述符的數(shù)量存在最大限制,一般是1024,由于select采用輪詢的方式掃描文件描述符,所以文件描述符數(shù)量越多,性能越差;

(2)內(nèi)核 / 用戶空間內(nèi)存拷貝問題,select需要復(fù)制大量的句柄數(shù)據(jù)結(jié)構(gòu),產(chǎn)生巨大的開銷;

(3)select返回的是含有整個句柄的數(shù)組,應(yīng)用程序需要遍歷整個數(shù)組才能發(fā)現(xiàn)哪些句柄發(fā)生了事件。

?

poll

poll系統(tǒng)調(diào)用和select類似,也是在一定時間內(nèi)輪詢一定數(shù)量的文件描述符,以測試其中是否有就緒事件。poll的原型如下:

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

fds:是一個struct pollfd結(jié)構(gòu)類型的數(shù)組,用于存放需要檢測其狀態(tài)的socket文件描述符;每當(dāng)調(diào)用這個函數(shù)之后,系統(tǒng)不會清空這個數(shù)組,操作起來比較方便;特別是對于socket連接比較多的情況下,在一定程度上可以提高處理的效率;poll()函數(shù)適合于大量socket描述符的情況;從下面的結(jié)構(gòu)體可以看出,結(jié)構(gòu)體pollfd將文件描述符和事件進行了綁定。

struct pollfd {int fd; ??????????//文件描述符short events; ????//注冊的事件short revents; ???//實際發(fā)生的事件,由內(nèi)核填充 }poll事件類型: POLLIN 有數(shù)據(jù)可讀 POLLRDNORM 有普通數(shù)據(jù)可讀 POLLRDBAND 有優(yōu)先數(shù)據(jù)可讀 POLLPRI 有緊急數(shù)據(jù)可讀 POLLOUT 數(shù)據(jù)可寫 POLLWRNORM 普通數(shù)據(jù)可寫 POLLWRBAND 優(yōu)先數(shù)據(jù)可寫 POLLMSGSIGPOLL 消息可用?

nfds:nfds_t類型的參數(shù),用于標記數(shù)組fds中的結(jié)構(gòu)體元素的總數(shù)量;

timeout:是poll函數(shù)調(diào)用阻塞的時間,單位是毫秒;

返回值大于0:fds中準備好讀、寫或異常事件的socket文件描述符的總數(shù)量;

返回值等于0:fds中沒有任何socket文件描述符準備好讀、寫,或異常事件;此時poll超時,超時時間是timeout毫秒;換句話說,如果所檢測的socket文件描述符上沒有任何事件發(fā)生的話,那么poll()函數(shù)會阻塞timeout所指定的毫秒時間長度之后返回,如果timeout==0,那么poll() 函數(shù)立即返回而不阻塞,如果timeout == INFTIM,那么poll() 函數(shù)會一直阻塞下去,直到所檢測的socket文件描述符上的感興趣的事件發(fā)生是才返回,如果感興趣的事件永遠不發(fā)生,那么poll()就會永遠阻塞下去。

poll統(tǒng)一處理所有事件類型,因此只需一個事件集參數(shù)。用戶通過pollfd.events傳入感興趣的事件,它是一系列事件的按位或,內(nèi)核通過修改pollfd.revents反饋其中就緒的事件。可以使用pollfd.revents與事件類型按位與進行判斷是否發(fā)生相應(yīng)的事件。poll同樣存在的問題:(1)內(nèi)核/用戶空間內(nèi)存拷貝問題;(2)應(yīng)用程序需要采用的輪詢方式來檢測就緒事件,算法時間復(fù)雜度是O(n)。相應(yīng)代碼會放到文章最后。

?

epoll

epoll是Linux特有的I/O復(fù)用函數(shù)。它在實現(xiàn)和使用上與select、poll有很大差異。首先,epoll使用一組函數(shù)來完成任務(wù),而不是單個函數(shù)。其次,epoll把用戶關(guān)心的文件描述符上的事件放在內(nèi)核里的一個事件表中,從而無需像select和poll那樣每次調(diào)用都要重復(fù)傳入文件描述符集合或事件集合。但epoll需要使用一個額外的文件描述符,來唯一標識內(nèi)核中的這個事件表。這個文件描述符使用epoll_create函數(shù)來創(chuàng)建。

#include <sys/epoll.h> int epoll_create(int size);

size:現(xiàn)在并不起作用,只是給內(nèi)核一個提示,告訴它事件表需要多大。該函數(shù)返回的文件描述符將用作其他所有epoll系統(tǒng)調(diào)用的第一個參數(shù),以指定要訪問的內(nèi)核事件表。

當(dāng)進程調(diào)用epoll_create方法時,Linux內(nèi)核會創(chuàng)建一個eventpoll結(jié)構(gòu)體,這個結(jié)構(gòu)體中有兩個成員與epoll的使用方式密切相關(guān)。eventpoll結(jié)構(gòu)體如下所示:

struct eventpoll{....//紅黑樹的根節(jié)點,這顆樹中存儲著所有添加到epoll中的需要監(jiān)控的事件struct rb_root ?rbr;//雙鏈表中則存放著將要通過epoll_wait返回給用戶的滿足條件的事件struct list_head rdlist;.... };

?

epoll_ctl( )

int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event); //用于操作epoll的內(nèi)核事件表

fd參數(shù)是要操作的文件描述符,op參數(shù)則指定操作類型。操作類型有以下3種:

EPOLL_CTL_ADD,往事件表中注冊fd上的事件。

EPOLL_CTL_MOD,修改fd上的注冊事件。

EPOLL_CTL_DEL,刪除fd上的注冊事件。

event參數(shù)指定事件,它是epoll_event結(jié)構(gòu)指針類型。epoll_event的定義如下:

struct epoll_event {__uint32_t events; ? //epoll事件epoll_data_t data; ??//用戶數(shù)據(jù) }

其中events成員描述事件類型。epoll支持的事件類型和poll基本相同。表示epoll事件類型的宏是在poll對應(yīng)的宏前加上E,比如epoll的數(shù)據(jù)可讀事件是EPOLLIN。但epoll有兩個額外的事件類型——EPOLLET和EPOLLONESHOT。它們對于epoll的高效運作非常關(guān)鍵。data成員用于存儲用戶數(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是一個聯(lián)合體,其中使用最多的成員是fd,它指定事件所從屬的目標文件描述符。epoll_ctl成功時返回0,失敗時返回-1并設(shè)置errno。

每一個epoll對象都有一個獨立的eventpoll結(jié)構(gòu)體,用于存放通過epoll_ctl方法向epoll對象中添加進來的事件。這些事件都會掛在紅黑樹上,如此,重復(fù)添加的事件就可以通過紅黑樹而高效的識別出來(紅黑樹的插入時間效率是O(lgn),其中n為元素個數(shù))。

而所有添加到epoll中的事件都會與設(shè)備(網(wǎng)卡)驅(qū)動程序建立回調(diào)關(guān)系,也就是說,當(dāng)相應(yīng)的事件發(fā)生時會調(diào)用這個回調(diào)方法。這個回調(diào)方法在內(nèi)核中叫ep_poll_callback,它會將發(fā)生的事件添加到eventpoll的rdlist雙鏈表中。

在epoll中,對于每一個事件,都會建立一個epitem結(jié)構(gòu)體,如下所示:

struct epitem{struct rb_node ?rbn; ???????? //紅黑樹節(jié)點struct list_head ???rdllink; ??//雙向鏈表節(jié)點struct epoll_filefd ?ffd; ?????//事件句柄信息struct eventpoll *ep; ???????? //指向其所屬的eventpoll對象struct epoll_event event; ??? //期待發(fā)生的事件類型 }

?

epoll_wait()

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

該函數(shù)成功時返回就緒的文件描述符的個數(shù),失敗時返回-1并設(shè)置errno。epoll_wait函數(shù)如果檢測到事件,就將所有就緒的事件從內(nèi)核事件表中復(fù)制到它的第二個參數(shù)events指向的數(shù)組中。這個數(shù)組只用于輸出epoll_wait檢測到的就緒事件,而不像select和poll的數(shù)組參數(shù)那樣及用于傳入用戶注冊的事件,又用于輸出內(nèi)核檢測到的就緒事件。這就極大地提高了應(yīng)用程序索引就緒文件描述符的效率。

當(dāng)調(diào)用epoll_wait檢查是否有事件發(fā)生時,只需要檢查eventpoll對象中的rdlist雙鏈表中是否有epitem元素即可。如果rdlist不為空,則把發(fā)生的事件復(fù)制到用戶態(tài),同時將事件數(shù)量返回給用戶。

簡單的歸納epoll的用法了:

(1)調(diào)用epoll_create(),返回一個句柄來唯一標識內(nèi)核中的事件表。

(2)使用epoll_ctl()系統(tǒng)調(diào)用,向epoll對象中添加、刪除、修改感興趣的事件,返回0表示成功,返回-1表示失敗。

(3)通過epoll_wait()系統(tǒng)調(diào)用獲取就緒事件。

?

epoll對文件描述符的操作有兩種模式:LT(Level Trigger,電平觸發(fā))模式和ET(Edge Trigger,邊沿觸發(fā))模式。LT模式是默認的工作模式,這種模式下epoll相當(dāng)于一個效率較高的poll。當(dāng)往epoll內(nèi)核事件表中注冊一個文件描述符上的EPOLLET事件時,epoll將以ET模式來操作該文件描述符。ET模式是epoll的高效工作模式。

對于采用LT工作模式的文件描述符,當(dāng)epoll_wait檢測到其上有事件發(fā)生并將此事件通知應(yīng)用程序后,應(yīng)用程序可以不立即處理該事件。這樣,當(dāng)應(yīng)用程序下一次調(diào)用epoll_wait時,epoll_wait還會再次向應(yīng)用程序通告此事件,直到該事件被處理。而對于采用ET工作模式的文件描述符,當(dāng)epoll_wait檢測到其上有事件發(fā)生并將此事件通知應(yīng)用程序后,應(yīng)用程序必須立即處理該事件,因為后續(xù)的epoll_wait調(diào)用將不再向應(yīng)用程序通知這一事件。可見,ET模式在很大程度上降低了同一個epoll事件被重復(fù)觸發(fā)的次數(shù),因此效率比LT高。

?

select、poll和epoll對比

這三組系統(tǒng)調(diào)用都能同時監(jiān)聽多個文件描述符。它們將等待由timeout參數(shù)指定的超時時間,直到一個或多個文件描述符上有事件發(fā)生時返回,返回值是就緒文件描述符的數(shù)量。返回0表示沒有事件發(fā)生。

select的參數(shù)類型fd_set沒有將文件描述符和事件綁定,它只是一個文件描述符集合,所以select需要提供3種類型的參數(shù)分別傳入。由于內(nèi)核對fd_set集合是在線修改的,應(yīng)用程序下次調(diào)用select前需要重置這3個fd_set集合。

poll的參數(shù)類型pollfd將文件描述符和事件都定義在其中,任何事件都被統(tǒng)一處理,從而使得編程接口簡潔得多。并且內(nèi)核每次修改的是revents成員,而events成員保持不變,因此下次調(diào)用poll時不需要重置pollfd的事件集合參數(shù)。由于每次select和poll調(diào)用都返回整個用戶注冊的事件集合,所以應(yīng)用程序找到就緒文件描述符的時間復(fù)雜度為O(n)。

epoll采用與select和poll完全不同的方式管理用戶注冊的事件。它在內(nèi)核中維護一個事件表,并提供了一個獨立的系統(tǒng)調(diào)用epoll_ctl來控制往其中添加、刪除、修改事件。每次epoll_wait調(diào)用都直接從該內(nèi)核事件表中取得用戶注冊的事件,而不用反復(fù)從用戶空間讀入這些事件。epoll_wait系統(tǒng)調(diào)用的events參數(shù)僅用來返回就緒的事件,這使得應(yīng)用程序索引就緒文件描述符的事件復(fù)雜度為O(1)。

?

代碼實現(xiàn)

以下代碼使用poll實現(xiàn)了一個簡單聊天室的功能,該聊天室程序能讓所有用戶同時在線群聊,它分為客戶端和服務(wù)器兩個部分。其中客戶端程序有兩個功能:一是從標準輸入讀入用戶數(shù)據(jù),并將數(shù)據(jù)發(fā)送至服務(wù)器;二是往標準輸出終端打印服務(wù)器發(fā)送給它的數(shù)據(jù)。服務(wù)器的功能是接收客戶數(shù)據(jù),并把客戶數(shù)據(jù)發(fā)送給每一個登錄到該服務(wù)器上的客戶端。下面依次給出服務(wù)器端和客戶端的代碼。

#define _GNU_SOURCE 1 //server.c #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <assert.h> #include <stdio.h> #include <unistd.h> #include <errno.h> #include <string.h> #include <fcntl.h> #include <stdlib.h> #include <poll.h>#define USER_LIMIT 5 #define BUFFER_SIZE 64 #define FD_LIMIT 65535struct client_data {sockaddr_in address;char* write_buf;char buf[ BUFFER_SIZE ]; };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; }int main( int argc, char* argv[] ) {if( argc <= 2 ){printf( "usage: %s ip_address port_number\n", basename( argv[0] ) );return 1;}const char* ip = argv[1];int port = atoi( argv[2] );int ret = 0;struct sockaddr_in address;bzero( &address, sizeof( address ) );address.sin_family = AF_INET;inet_pton( AF_INET, ip, &address.sin_addr );address.sin_port = htons( port );int listenfd = socket( PF_INET, SOCK_STREAM, 0 ); //1.創(chuàng)建socketprintf("Listenfd:[%d]\n", listenfd); //每次都是3assert( listenfd >= 0 );//socket返回值是一個文件描述符,socket類型本身也是定義為int的,既然是文件描述符,那么在//系統(tǒng)中都當(dāng)作是文件來對待。0,1,2分別表示標準輸入、標準輸出、標準錯誤。所以其他打開文件描述符都會大于2。ret = bind( listenfd, ( struct sockaddr* )&address, sizeof( address ) ); //2.bindprintf("Bind ret:[%d]\n", ret);assert( ret != -1 );ret = listen( listenfd, 5 ); //3.監(jiān)聽printf("Listen ret:[%d]\n", ret);assert( ret != -1 );client_data* users = new client_data[FD_LIMIT];pollfd fds[USER_LIMIT+1]; //文件描述符上可讀、可寫和異常事件int user_counter = 0;for( int i = 1; i <= USER_LIMIT; ++i ){fds[i].fd = -1;fds[i].events = 0;}fds[0].fd = listenfd; //指定文件描述符fds[0].events = POLLIN | POLLERR; //events告訴poll監(jiān)聽fd上的什么事件,這個例子就是監(jiān)聽可讀和錯誤事件fds[0].revents = 0; //由內(nèi)核填寫,以通知fd上實際發(fā)生的事件while( 1 ){ret = poll( fds, user_counter+1, -1 );if ( ret < 0 ){printf( "poll failure\n" );break; //跳出循環(huán)}for( int i = 0; i < user_counter+1; ++i ){if( ( fds[i].fd == listenfd ) && ( fds[i].revents & POLLIN ) ){ //監(jiān)聽的文件描述符是server創(chuàng)建的socket且有可讀事件發(fā)生,即有客戶端的連接。接下來完成accept相關(guān)工作。struct sockaddr_in client_address;socklen_t client_addrlength = sizeof( client_address );int connfd = accept( listenfd, ( struct sockaddr* )&client_address, &client_addrlength ); //acceptprintf("Connfd:[%d]\n", connfd);if ( connfd < 0 ){printf( "errno is: %d\n", errno );continue;}if( user_counter >= USER_LIMIT ){const char* info = "too many users\n";printf( "%s", info );send( connfd, info, strlen( info ), 0 );close( connfd );continue; //continue語句的作用是跳過本次循環(huán)體中余下未執(zhí)行的語句,立即進入下一次循環(huán)條件判定。}user_counter++;users[connfd].address = client_address;setnonblocking( connfd );fds[user_counter].fd = connfd;fds[user_counter].events = POLLIN | POLLRDHUP | POLLERR;fds[user_counter].revents = 0;printf( "comes a new user, now have %d users\n", user_counter );} //if( ( fds[i].fd == listenfd ) && ( fds[i].revents & POLLIN ) ) endelse if( fds[i].revents & POLLERR ){printf( "get an error from %d\n", fds[i].fd );char errors[ 100 ];memset( errors, '\0', 100 );socklen_t length = sizeof( errors );if( getsockopt( fds[i].fd, SOL_SOCKET, SO_ERROR, &errors, &length ) < 0 ){printf( "get socket option failed\n" );}continue;}//else if( fds[i].revents & POLLERR ) endelse if( fds[i].revents & POLLRDHUP ){//客戶端退出是觸發(fā),POLLRDHUP:TCP連接被對方關(guān)閉,或者對方關(guān)閉了寫操作users[fds[i].fd] = users[fds[user_counter].fd];close( fds[i].fd );fds[i] = fds[user_counter];i--;user_counter--;printf( "a client left\n" );}//else if( fds[i].revents & POLLRDHUP ) endelse if( fds[i].revents & POLLIN ){int connfd = fds[i].fd;memset( users[connfd].buf, '\0', BUFFER_SIZE );ret = recv( connfd, users[connfd].buf, BUFFER_SIZE-1, 0 );printf( "get %d bytes of client data %s from %d\n", ret, users[connfd].buf, connfd );if( ret < 0 ){if( errno != EAGAIN ){close( connfd );users[fds[i].fd] = users[fds[user_counter].fd];fds[i] = fds[user_counter];i--;user_counter--;}}else if( ret == 0 ){printf( "code should not come to here\n" );}else{for( int j = 1; j <= user_counter; ++j ){if( fds[j].fd == connfd ){continue;}fds[j].events |= ~POLLIN;fds[j].events |= POLLOUT;users[fds[j].fd].write_buf = users[connfd].buf;}}}//else if( fds[i].revents & POLLIN ) endelse if( fds[i].revents & POLLOUT ){int connfd = fds[i].fd;if( ! users[connfd].write_buf ){continue;}ret = send( connfd, users[connfd].write_buf, strlen( users[connfd].write_buf ), 0 );users[connfd].write_buf = NULL;fds[i].events |= ~POLLOUT;fds[i].events |= POLLIN;}}//for end}//while enddelete [] users;close( listenfd );return 0; } #define _GNU_SOURCE 1 //client.c #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <assert.h> #include <stdio.h> #include <unistd.h> #include <string.h> #include <stdlib.h> #include <poll.h> #include <fcntl.h>#define BUFFER_SIZE 64int main( int argc, char* argv[] ) {if( argc <= 2 ){printf( "usage: %s ip_address port_number\n", basename( argv[0] ) );return 1;}const char* ip = argv[1];int port = atoi( argv[2] );struct sockaddr_in server_address;bzero( &server_address, sizeof( server_address ) );server_address.sin_family = AF_INET;inet_pton( AF_INET, ip, &server_address.sin_addr );server_address.sin_port = htons( port );int sockfd = socket( PF_INET, SOCK_STREAM, 0 );printf("Sockfd:[%d]\n", sockfd);assert( sockfd >= 0 );if ( connect( sockfd, ( struct sockaddr* )&server_address, sizeof( server_address ) ) < 0 ){printf( "connection failed\n" );close( sockfd );return 1;}pollfd fds[2];fds[0].fd = 0; //標準輸入fds[0].events = POLLIN;fds[0].revents = 0;fds[1].fd = sockfd;fds[1].events = POLLIN | POLLRDHUP;fds[1].revents = 0;char read_buf[BUFFER_SIZE];int pipefd[2];int ret = pipe( pipefd );assert( ret != -1 );while( 1 ){ret = poll( fds, 2, -1 );if( ret < 0 ){printf( "poll failure\n" );break;}if( fds[1].revents & POLLRDHUP ){printf( "server close the connection\n" );break;}else if( fds[1].revents & POLLIN ){memset( read_buf, '\0', BUFFER_SIZE );recv( fds[1].fd, read_buf, BUFFER_SIZE-1, 0 );printf( "%s\n", read_buf );}if( fds[0].revents & POLLIN ){ret = splice( 0, NULL, pipefd[1], NULL, 32768, SPLICE_F_MORE | SPLICE_F_MOVE );ret = splice( pipefd[0], NULL, sockfd, NULL, 32768, SPLICE_F_MORE | SPLICE_F_MOVE );}}close( sockfd );return 0; }

運行過程:

(1)運行服務(wù)器端代碼

(2)運行客戶端代碼并發(fā)送消息

(3)服務(wù)器端轉(zhuǎn)發(fā)消息的情況如下:

?

參考:https://blog.csdn.net/davidsguo008/article/details/73556811

總結(jié)

以上是生活随笔為你收集整理的I/O多路复用:select、poll和epoll详解的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。