在OSX/iOS中IO多路復用通常會選擇select和kqueue,最近在嘗試優化socket改進通信效率,所以總結一下兩種模型的用法。
select
select是socket編程中非常重要的一個函數,並且也是兼容性最好的一種模型,在unix、linux、windows都有對應的實現,其函數原型是:
int select( int nfds, fd_set* readfds, fd_set* writefds, fd_set* errorfds, const struct timeval* timeout );
nfds最大的文件描述符加1;
readfds:用於檢查可讀性的描述符集合,同時也是可讀描述符的結果返回;
writefds:用於檢查可寫性的描述符集合,同時也是可寫描述符的結果返回;
errorfds:用於檢查異常的描述符集合,同時也是異常描述符的結果返回;
timeout:一個指向timeval結構的指針,用於決定select等待I/O的最長時間,它可以使select處於三種狀態:
(1)第一,若將NULL以形參傳入,即不傳入時間結構,就是將select置於阻塞狀態,一定等到監視文件描述符集合中某個文件描述符發生變化為止;
(2)第二,若將時間值設為0秒0毫秒,就變成一個純粹的非阻塞函數,不管文件描述符是否有變化,都立刻返回繼續執行,文件無變化返回0,有變化返回一個正值;
(3)第三,timeout的值大於0,這就是等待的超時時間,即 select在timeout時間內阻塞,超時時間之內有事件到來就返回了,否則在超時後不管怎樣一定返回,>返回值同上述。
函數返回值:
負值:select錯誤
正值:某些文件可讀寫或出錯
零值:等待超時,沒有可讀寫或錯誤的文件
kqueue
kqueue是FreeBSD上的一種的多路復用機制,所以剛好能在OSX/iOS中使用。它是針對傳統的select處理大量的文件描述符性能較低效而開發出來的。注冊一批描述符>到kqueue以後,當其中的描述符狀態發生變化時,kqueue將一次性通知應用程序哪些描述符可讀、可寫或出錯了.
kqueue模型最主要的函數就是kevent,它與select類似,提供向內核注冊/反注冊/修改事件和返回就緒事件或錯誤事件,函數原型為:
int kevent( int kq, const struct kevent *changelist, int nchanges, struct kevent *eventlist, int nevents, const struct timespec *timeout );
kq:由kqueue()返回的一個內核事件隊列標識;
changelist:注冊/反注冊事件的列表;
nchanges:changelist的個數;
eventlist:用於返回有事件發生的列表;
nevents:傳入的eventlist的最大長度;
timeout:一個指向timeval結構的指針,指定超時時間;
函數返回值與select類似。
另外比較重要的就是struct kevent這個結構體了,它既是注冊、反注冊、修改事件的載體,也是事件返回的載體,它的原型為:
struct kevent { uintptr_t ident; short filter; u_short flags; u_int fflags; intptr_t data; void *udata; };
由於kqueue不僅僅用於socket,還可用於如文件狀態、信號、進程等用途,以下僅僅當在網絡編程時這些元素的值及含義:
ident:事件的id,實際應用中,一般設置為文件描述符;
filter:指定你希望內核用於ident成員的過濾器,我們可以指定EVFILT_READ(讀狀態)和EVFILT_READ(寫狀態);
flags:告訴內核應當對該事件在隊列做何處理,我們可以指定EV_ADD(注冊)、EV_DELETE(刪除)、EV_ENABLE(開啟,默認)、EV_DISABLE(停用),當flags用於eventlist返回事件時,可能包含EV_ERROR的掩碼表示描述符錯誤事情;
fflags:用於指定你想讓內核使用的特定於過濾器的標志;
data:用於保存任何特定於過濾器的數據,當filter指定為EVFILT_READ或EVFILT_READ時,data表示可讀或可寫的數據長度,當事件的描述符出錯時,data表示錯誤>代碼;
udata:data成員並不由kqueue使用,kqueue會把它的值不加修改地透傳,用於類似於上下文。
總結
select的缺點:
(1)每次調用select,都需要把fd集合從用戶態拷貝到內核態,這個開銷在fd很多時會很大
(2)同時每次調用select都需要在內核遍歷傳遞進來的所有fd,這個開銷在fd很多時也很大
(3)select支持的文件描述符數量太小了,默認是1024(不同平台對FD_SETSIZE設定不一樣)
(4)select接口使用並不靈活,無法分步提交集合,也無法將提交和查詢分步,必須在一次調用中完成
(4)fd_set不能逆向轉換為fd,需要再單獨維護一份描述符的列表
而kqueue剛好能彌補這些缺陷,但卻無法在其它平台使用
關於兩種模型的實現示例下載:
SocketServer_select.zip
SocketServer_kqueue.zip