io多路復用之select,poll,epoll總結


io多路復用在網絡編程中比較常用。這個概念比較雜所以慢慢梳理,如果基礎知識不夠可以先去segmentfault這篇文章去看一下。這里面基礎知識講的比較好。對於用戶態內核態阻塞文件描述符io描述等都講解的很通俗易懂,至於后面的三個區別和我的也差不多。所以在這兒我盡量總結一下。結合了去年一英文博客,英語好的可以直接去看英文博客,英文講解的很通俗易懂。

segamentfault上的基礎介紹。Linux IO模式及 select、poll、epoll詳解

三個多路復用select poll epoll區別英文網頁select / poll / epoll: practical difference for system architects

最初是看的這個人的三篇博客,說實話個人感覺新手看着比較亂,專有名詞比較多點擊打開鏈接   點擊打開鏈接   點擊打開鏈接

還有一篇總結文章點擊打開鏈接


下面是我的筆記。可能寫的比較跳躍,有不好請指正。首先各自上示例代碼(僅僅示例,還不夠直接執行),然后寫了些總結。建議懂得多的可以直接上代碼,少的先看解釋再看代碼。

區分select ,poll,epoll,

select

簡介

select是1983年的4.2BSD提出。系統在select用32*32=1024位來進行查詢。返回的時候數組如readfds是已經處理過的了,返回時只有准備好事件的fd。所以需要輪訓(要用FD_ISSET挨個比較)和重新賦值。FD_ISSET(fd,&readfds)


原型

#include <sys/select.h>
#include <sys/time.h>
int select(int maxfdp1,fd_set *readset,fd_set *writeset,fd_set *exceptset,const struct timeval *timeout)

使用方法

總共分三步,

1.三個fd_set初始化,用FD_ZERO FD_SET
2.調用select
3.用fd遍歷每一個fd_set使用FD_ISSET。如果成功就處理。


缺點。

1.每次調用select都要把fd_set傳輸一遍,
2.第三步都要輪巡一次。
3.1024個最大限制。
4.其它線程突然要用socket,會沖突。


仍然在現實保留的原因

1.歷史遺留問題,因為select發展了很久的時間,額可以肯定大多的平台都支持他了,因為你無法保證新的平台都支持poll或者epoll。放心,我們說的不是enaic那種元祖機子,你聽說過xp嗎?你知道他在全中國全世界知道今天2016/9/10仍然占據多少比例么。oh no,它只支持iselect。
2.時間高精度,因為select可以精確到ns級別。而后二者只能精確到ms級別。當然你會說很多系統調用都沒有那么高精度的。但是對於實時操作系統,也就是類似工業控制的高精領域,或者說比如核電站,核反應堆,oh,no這兒用select不止是讓系統更安全,讓你不被老板炒魷魚,更是關系到我們大眾安全的問題,請你一定不要忘了這一點。
3,當然如果是簡單應用場景,比如低於200個socket,那么你用什么其實問題都不打,更多的問題是在與程序員的編程水平了。


代碼

<span style="font-size:18px;"><span style="font-family:Microsoft YaHei;font-size:14px;"><strong><span style="font-family:SimSun;"><span style="font-family:Microsoft YaHei;"><span style="font-family:FangSong_GB2312;">fd_set fd_in, fd_out;
struct timeval tv;

// Reset the sets
FD_ZERO( &fd_in );
FD_ZERO( &fd_out );

// Monitor sock1 for input events
FD_SET( sock1, &fd_in );

// Monitor sock2 for output events
FD_SET( sock2, &fd_out );

// Find out which socket has the largest numeric value as select requires it
int largest_sock = sock1 > sock2 ? sock1 : sock2;

// Wait up to 10 seconds
tv.tv_sec = 10;
tv.tv_usec = 0;

// Call the select
int ret = select( largest_sock + 1, &fd_in, &fd_out, NULL, &tv );

// Check if select actually succeed
if ( ret == -1 )
// report error and abort
else if ( ret == 0 )
// timeout; no event detected
else
{
if ( FD_ISSET( sock1, &fd_in ) )
// input event on sock1

if ( FD_ISSET( sock2, &fd_out ) )
// output event on sock2
}</span></span></span></strong></span></span>



poll

原型

# include <poll.h>
int poll ( struct pollfd * fds, unsigned int nfds, int timeout);

struct pollfd {
int fd;         /* 文件描述符 */
short events;         /* 等待的事件 */
short revents;       /* 實際發生了的事件 */
} ; 


簡介

poll system v release3出現。這兒不用三個set來傳遞東西了,而是使用一個fds指向一個組,這個組里每個結構體有着pollfd,包含三個元素fd,event,revent。在內核調用后revent會被標記,如果處理好了,我們直接if一個就可以檢查是否可以讀寫或者什么io操作了。select每次set都要重新生成。而這兒就不用了,因為這兒是以fd為單位的。

使用方法

也是分三步
1.pollfd初始化,綁定sock,設置事件event,revent。設置時間限制。
2.調用poll
3.遍歷看他的事件發生了么,如果發生了置0。


優勢,

1.無上限1024.這數字怎么這么別扭。。。
2.由於它不修改pollfd里的數據,所以它可以不用每次都填寫了。
3.方便的知道遠程的狀態比如宕機


缺點。

1,還要輪巡
2.不能動態修改set。
其實大多數client不用考慮這個,除非p2p應用。一些server端用不用考慮這個問題。
大多時候他都比select更好。甚至如下場景比epoll還好。


使用場景

1.你要跨平台,因為epoll只支持linux。
2.socket數目少於1000個。
3.大於1000但是是socket壽命比較短。
4.沒有其他線程干擾的時候。


代碼

<span style="font-size:18px;"><span style="font-family:Microsoft YaHei;font-size:14px;"><strong><span style="font-family:SimSun;"><span style="font-family:Microsoft YaHei;"><span style="font-family:FangSong_GB2312;">// The structure for two events
struct pollfd fds[2];

// Monitor sock1 for input
fds[0].fd = sock1;
fds[0].events = POLLIN;

// Monitor sock2 for output
fds[1].fd = sock2;
fds[1].events = POLLOUT;

// Wait 10 seconds
int ret = poll( &fds, 2, 10000 );
// Check if poll actually succeed
if ( ret == -1 )
// report error and abort
else if ( ret == 0 )
// timeout; no event detected
else
{
// If we detect the event, zero it out so we can reuse the structure
if ( pfd[0].revents & POLLIN )
pfd[0].revents = 0;
// input event on sock1

if ( pfd[1].revents & POLLOUT )
pfd[1].revents = 0;
// output event on sock2
}</span></span></span></strong></span></span>

epoll

原型

#include <sys/epoll.h>
int epoll_create(int size);
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);


簡介

epoll是2.6內核才加進去的東西。所以很多舊的地方見不到他,而且只在linux下支持。epoll是直接在內核里的,用戶調用系統調用去注冊,因此省去了每次的復制和輪詢的消耗。這兒用了三個系統調用,epollcreate只要每次調用開始調用一次創造一個epoll就可以了。然后用epoll_ctl來進行添加事件,其實就是注冊到內核管理的epoll里。然后直接epoll_wait就可以了。系統會返回系統調用的。


使用方法

epoll稍微復雜些了。

1.准備工作多了,很復雜,這個記錄數據在內核里。
1)構建epoll描述符,通過調用epoll_create
2)用需要的時間和上下文數據指針初始化。
3)調用epoll_ctl 添加文件描述符。
4)調用epoll_wait每次處理20個事件。這兒是接收一個空數組,然后填上東西。也就是有200個東西過來,我可能只填了一個。當然如果50個完成了也是回復20.剩下的不會被漏掉,下次再來處理。
5)遍歷返回的數據。注意這兒返回的都是有用的東西。


優點

1.只返回觸發的事件。少了拷貝消耗,迭代輪訓消耗。
2.可以綁定更多上下文,不僅僅是socket。
3.任何時間處理socket。這些問題都是有內核來處理。了。這個還需要繼續學習啊。
4.可以邊緣觸發。
5.多線程可以在同一個epoll wait里等待。



缺點

1.讀寫狀態變更之類的就要麻煩些,在poll里只要改一個bit就可以了。在這里面則需要改更多的位數。並且都是system call。
2.創建socket也需要兩次系統調用,麻煩。
3.只有linux下可以使用
4.復雜難調試



適合場景

1.多線程,多連接。在單線程還不如poll
2.大量線程監控1000上,
3.相對長壽命的連接。系統調用會很耗時。
4.linux依賴的事情。


代碼

<span style="font-size:18px;"><span style="font-family:Microsoft YaHei;font-size:14px;"><strong><span style="font-family:SimSun;"><span style="font-family:Microsoft YaHei;"><span style="font-family:FangSong_GB2312;">// Create the epoll descriptor. Only one is needed per app, and is used to monitor all sockets.
// The function argument is ignored (it was not before, but now it is), so put your favorite number here
int pollingfd = epoll_create( 0xCAFE );

if ( pollingfd < 0 )
// report error

// Initialize the epoll structure in case more members are added in future
struct epoll_event ev = { 0 };

// Associate the connection class instance with the event. You can associate anything
// you want, epoll does not use this information. We store a connection class pointer, pConnection1
ev.data.ptr = pConnection1;

// Monitor for input, and do not automatically rearm the descriptor after the event
ev.events = EPOLLIN | EPOLLONESHOT;
// Add the descriptor into the monitoring list. We can do it even if another thread is
// waiting in epoll_wait - the descriptor will be properly added
if ( epoll_ctl( epollfd, EPOLL_CTL_ADD, pConnection1->getSocket(), &ev ) != 0 )
// report error

// Wait for up to 20 events (assuming we have added maybe 200 sockets before that it may happen)
struct epoll_event pevents[ 20 ];

// Wait for 10 seconds
int ready = epoll_wait( pollingfd, pevents, 20, 10000 );
// Check if epoll actually succeed
if ( ret == -1 )
// report error and abort
else if ( ret == 0 )
// timeout; no event detected
else
{
// Check if any events detected
for ( int i = 0; i < ret; i++ )
{
if ( pevents[i].events & EPOLLIN )
{
// Get back our connection pointer
Connection * c = (Connection*) pevents[i].data.ptr;
c->handleReadEvent();
}
}
}</span></span></span></strong></span></span>



libevent

是一種對select,poll epoll封裝的方法,實際底層還是這三個東西,優缺點都是存在的。只是需要你去適應多復雜的環境。因此三者自動調用,但是還是用起來麻煩。比寫三個好一些。








select傳輸的是rdfd,wfd,每次改動后都需要重新傳遞。poll穿的是以fd為單位的event,每次event改了可以不用重新傳輸。
epoll是用一個描述子代替了前面的所有描述。而且是在內核空間里的。


注意!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系我们删除。



 
  © 2014-2022 ITdaan.com 联系我们: