select poll epoll相關知識速記

緣起

面試的時候常常被問的一個很蛋疼的問題,常常被問,可是知識很零散,難記憶,看完就忘php

select

做用

能夠監視文件描述符是否能夠讀寫,要求監視的文件描述符是非阻塞的html

 

誕生背景

產生與上個世紀80年代的UNIX系統,到1993年寫入POSIX1.b規範(一個操做系統的編程接口的規範,你要是寫個操做系統想被兼容得遵照這個規範)。因爲那個年代尚未多線程(2年後線程相關的內容才寫入POSIX1.c規範),尚未什麼C10K問題,因此在設計select的時候體現了那個年代的特色。linux

 

接口

int select(int nfds, fd_set *readfds, fd_set *writefds,
                  fd_set *exceptfds, struct timeval *timeout);

nfds 是參數2,3,4中最大的文件描述符 + 1nginx

readfds 是要檢測fd的可讀事件,當fd可讀的時候select就返回面試

writefds 是要檢測fd的寫事件,當fd可寫的時候select就返回編程

excpetfds 是要檢測fd的出錯事件,當fd出錯的時候select就返回,當是NULL的時候,就是檢測readfds和writefds的出錯事件,因此通常都寫NULLwindows

timeout 是一個納秒級的超時時間api

 

想使用這個函數須要設置個fd_set結構,因此就用到void FD_SET(int fd, fd_set *set);這個宏,用來設置fd_set數組

 

使用過程

fd_set fd_in, fd_out;
struct timeval tv;
 
// 初始化fd_set
FD_ZERO( &fd_in );
FD_ZERO( &fd_out );
 
// 把網絡IO複製到fd_set
FD_SET( sock1, &fd_in );
FD_SET( sock1, &fd_out );
 
// 用戶初始化select
int largest_sock = sock1 > sock2 ? sock1 : sock2;
 
// 設置select的超時時間
tv.tv_sec = 10;
tv.tv_usec = 0;
 
// 調用select 並阻塞在這裏等待 IO事件
int ret = select( largest_sock, &fd_in, &fd_out, NULL, &tv );
 
// 檢查返回值的狀態
if ( ret == -1 )
    // 異常狀況
else if ( ret == 0 )
    // 超時或者沒有能夠監控的fd
else
{
    // 檢測每一個IO事件是否能夠讀寫
    if ( FD_ISSET( sock1, &fd_in ) )
        // IO可讀
 
    if ( FD_ISSET( sock2, &fd_out ) )
        // IO可寫
}
View Code

 

能夠看到使用select的時候,每一個fd對應一個fd_set結構,而後調用FD_SET,調用select之後進入polling,等返回之後經過FD_ISSET對每一個fd_set檢測是否可讀可寫。服務器

 

存在問題

是否是會兒尚未如今nginx幾萬併發的場景,select只能對1024個fd進行監控

select 函數會修改fd_set,因此每次調用完select之後須要從新經過FD_SET設置fd_set

select 返回之後並不知道具體哪一個fd能夠讀寫,須要使用FD_ISSET把全部的fd檢測一遍才知道具體是哪一個可讀可寫

那年代估計不像如今這麼普遍的用多線程,因此select中的fd_set在調用select的時候至關於被獨佔的

 

優勢

使用簡單,POSIX標準因此跨平臺比較好

 

POLL

功能和select相同,可是主要解決select的一些限制

 

接口

int poll(struct pollfd fds[], nfds_t nfds, int timeout);

 

fds 是一個pollfd的數組,和select的fd_set差很少,下面具體解釋

nfds 是fds數組的長度,能夠看到沒有select還須要求一個fd最大值再加1那麼麻煩

timeout 是超時的毫秒數

 

pollfd的結構

struct pollfd {
    int    fd;       /* 文件描述符 */
    short  events;   /* 須要監聽的事件 */
    short  revents;  /* 返回的事件 */
};

 

對比一下select,接口上更加優雅,首先,pollfd 經過單獨的events來區分了監控的是什麼樣的事件,而不是像select那樣經過參數來區分。

 

使用過程

// 建立pollfd
struct pollfd fds[2];
 
// 設置pollfd的fd和要監控的事件,sock1監控讀,sock2監控寫
fds[0].fd = sock1;
fds[0].events = POLLIN;
fds[1].fd = sock2;
fds[1].events = POLLOUT;
 
// 10秒超時,開始等待sock1和sock2上的事件
int ret = poll( &fds, 2, 10000 );
// 有事件返回
if ( ret == -1 )
    // 出錯了
else if ( ret == 0 )
    // 超時
else
{
    // 對每一個pollfd檢測是否有就緒的事件
    if ( pfd[0].revents & POLLIN )
        pfd[0].revents = 0;
        // 可讀

    if ( pfd[1].revents & POLLOUT )
        pfd[1].revents = 0;
        // 可寫
}
View Code

 

與select相同,都是建立結構,設置,開始polling,逐個檢測事件

 

相比於SELECT的改進

對於能夠監控fd的數量沒有限制,而不是像select那樣最大才1024個

每次poll以後不須要從新設置pollfd,而不像fd_set須要從新設置

 

兼容性

vista以前的windows上沒有poll

#if defined (WIN32)
static inline int poll( struct pollfd *pfd, int nfds, int timeout) { return WSAPoll ( pfd, nfds, timeout ); }
#endif

 

 

EPOLL

linux平臺上最新的polling技術,出現與linux2.6版本,linux2.6發佈是在2003年(竟然epoll出現已經12年了)。

 

接口

int epoll_create(int size);

 

用於建立一個size大小的epoll,返回一個epfd的描述符

 

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

 

修改某個文件描述符的狀態

epfd 是建立的epoll

op 是修改的操做類型,能夠是EPOLL_CTL_ADD 或者 EPOLL_CTL_DEL,表明添加和刪除

fd 是要操做的文件描述符

 

event 是文件描述符fd上掛的一個context,是一個epoll_event結構體,下面是epoll_event的結構體內容:

typedef union epoll_data {
   void        *ptr;
   int          fd;
   __uint32_t   u32;
   __uint64_t   u64;
} epoll_data_t;

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

 

其中epoll_event.events 和 pollfd中的events 差很少,不過事件更加豐富,data是對於文件描述符上能夠掛的衛星數據,也更加靈活。

 

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

 

epdf 是epoll_create時候返回的

events 是polling 返回的結果會賦值在events

maxevents 是一次通知用戶最大的events數量,通常就是events的數組長度

timeout 是超時時間

相比select/poll,epoll_wait 只返回可讀寫的事件,而不是所有返回

 

關於幾個size的理解

epoll_create 時候的size是指 epoll在覈心態監控fd的最大數量

epoll_wait 時候的events 是指一次通知的數據,這個數量認爲是一次批量,確定是小於等於epoll_create時候的大小,比這個再大也沒用了

epoll_wait 時候的maxevents 是爲了防止一次通知events溢出的一個邊界,若是設置的比events的數組長度小,那就至關於批量變小,比這個大會溢出,因此應該是相等就能夠了

 

使用過程

// 建立
int pollingfd = epoll_create( 0xCAFE ); 

// 建立一個epoll_event 用來一下子epoll_ctl的時候EPOLL_CTL_ADD用
struct epoll_event ev = { 0 };

// 假設sock1是個網絡鏈接
int sock1 = pConnection1->getSocket();

// 給這個sock1掛一點衛星數據,這裏能夠是任意的東西,咱們就放個他的connection
ev.data.ptr = pConnection1;

// 來監控sock1的可讀事件
ev.events = EPOLLIN | EPOLLONESHOT;

// 把設置好的epoll_event 添加到建立的epollfd
if ( epoll_ctl( epollfd, EPOLL_CTL_ADD, sock1, &ev ) != 0 )
    // 出錯

// 建立一些epoll_event用來在用戶態來接收
struct epoll_event pevents[ 20 ];

// 等待可讀事件
int ready = epoll_wait( pollingfd, pevents, 20, 10000 );
if ( ret == -1 )
    // 出錯
else if ( ret == 0 )
    // 超時
else
{
    // ret是返回了多少個能夠讀寫的事件
    for ( int i = 0; i < ret; i++ )
    {
        // 判斷通知到用戶這個究竟是個什麼事件
        if ( pevents[i].events & EPOLLIN )
        {
            // 取到當初咱們掛在上面的衛星數據
            Connection * c = (Connection*) pevents[i].data.ptr;
            // 對這個socket進行一些操做
            c->handleReadEvent();
         }
    }
}
View Code

 

epoll的使用過程仍是比select和poll複雜很多的,首先你得建立一個epoll,而後建立和設置epoll_event,再經過epoll_ctl添加到epoll,最後epoll_wait,遍歷通知過來的events

 

比select和poll的改進

最大的改進就是不須要在遍歷全部事件了,不須要FD_ISSET,也不須要遍歷全部pollfd.revents,取而代之的是,內核幫咱們把active的fd賦值到epoll_wait的events上

pollfd封裝了一個event,而不是像select的fd_set只有一個fd屬性,epoll_event 比pollfd又多了一個data的衛星數據,能夠聽任意的東西上去

select和poll一旦進入polling階段,就無法對fd作修改了,可是epoll_ctl能夠在任意的線程裏在任意事件動態的添加,刪除epoll_event

 

缺點

改變epoll中fd的監聽事件類型須要epoll_ctl的系統調用,而在poll中只須要在用戶態作BITMASK

只能在linux上用,雖然有libevent這種東西

epoll的api比select和poll複雜

 

該如何選擇

若是鏈接數很低小於1024,epoll對比select和poll是沒有性能提高的,選擇select仍是poll就看我的喜愛了,通常select就行,好比fpm,epoll早就出了fpm也沒改,PHP不多有人能worker開1000以上

若是鏈接是短鏈接,常常accept出一些fd添加到epoll中的系統調用開銷須要考慮,具體性能還須要再綜合考慮,好比nginx,雖然都是短鏈接,可是有高併發,幾萬併發select每次遍歷一遍全部fd更耗

若是是長鏈接,而且都是idle的,例如一些聊天的服務器,一個鏈接,半天才說一句話,都是掛機的,可是鏈接幾十萬,那有我的說句話,服務區須要讀,你遍歷幾十萬個fd就不值了

若是你的應用是多線程來處理網絡的,那麼爲了利用多線程仍是使用epoll比較好,能夠用多線程配合邊緣觸發(若是可讀只通知一次,無論讀完沒讀完,水平觸發沒讀完就一直通知,因此效率會比邊緣觸發低一些),這也是邊緣觸發推薦的使用方式。

 

爲何epoll高效

簡單來講是這樣的select和poll當檢測到fd就緒之後,就通知到用戶態了,函數也就返回了。而epoll在add的時候就開始監聽,發現他就緒之後就放到一個就緒表裏,epoll_wait只是定時查看一下這個就緒表裏的數據。

 

參考文章

https://cs.uwaterloo.ca/~brecht/papers/getpaper.php?file=ols-2004.pdf

http://www.ulduzsoft.com/2014/01/select-poll-epoll-practical-difference-for-system-architects/

http://www.unix.org/what_is_unix/history_timeline.html

http://en.wikipedia.org/wiki/Asynchronous_I/O

http://en.wikipedia.org/wiki/POSIX

http://blog.csdn.net/vividonly/article/details/7539342

相關文章
相關標籤/搜索