首先介紹阻塞與非阻塞:
阻塞是個什麼概念呢?好比某個時候你在等快遞,可是你不知道快遞何時過來,並且你沒有別的事能夠幹(或者說接下來的事要等快遞來了才能作);那麼你能夠去睡覺了,由於你知道快遞把貨送來時必定會給你打個電話(假定必定能叫醒你)。
非阻塞忙輪詢。接着上面等快遞的例子,若是用忙輪詢的方法,那麼你須要知道快遞員的手機號,而後每分鐘給他掛個電話:「你到了沒?」
while true { for i in stream[]; { if i has data read until unavailable } }
while true { select(streams[]) for i in streams[] { if i has data read until unavailable } }
while true { active_stream[] = epoll_wait(epollfd) for i in active_stream[] { read or write till unavailable } }
kqueue與epoll很是類似,最初是2000年Jonathan Lemon在FreeBSD系統上開發的一個高性能的事件通知接口。註冊一批socket描述符到 kqueue 之後,當其中的描述符狀態發生變化時,kqueue 將一次性通知應用程序哪些描述符可讀、可寫或出錯了。數組
kqueue的接口包括 kqueue()、kevent() 兩個系統調用和 struct kevent 結構:多線程
struct kevent {
uintptr_t ident; /* 事件 ID */
short filter; /* 事件過濾器 */
u_short flags; /* 行爲標識 */
u_int fflags; /* 過濾器標識值 */
intptr_t data; /* 過濾器數據 */
void *udata; /* 應用透傳數據 */
};
在一個 kqueue 中,{ident, filter} 肯定一個惟一的事件:
事件的 id,通常設置爲文件描述符。併發
能夠將 kqueue filter 看做事件。內核檢測 ident 上註冊的 filter 的狀態,狀態發生了變化,就通知應用程序。kqueue 定義了較多的 filter:socket
與socket讀寫相關的filter:ide
行爲標誌flags:性能
過濾器標識值:ui
註冊事件到 kqueuespa
bool Register(int kq, int fd)
{
struct kevent changes[1];
EV_SET(&changes[0], fd, EVFILT_READ, EV_ADD, 0, 0, NULL);
int ret = kevent(kq, changes, 1, NULL, 0, NULL);
return true;
}
Register 將 fd 註冊到 kq 中。註冊的方法是經過 kevent() 將 eventlist 和 neventlist 置成 NULL 和 0 來達到的。
人們通常將 socket IO 設置成非阻塞模式,以提升讀寫性能的同時,避免 IO 讀寫不當心被鎖定。爲了達到某種目的,有人會經過 getsocketopt 來偷看 socket 讀緩衝區的數據大小或寫緩區可用空間的大小。在 kevent 返回時,將讀寫緩衝區的可讀字節數或可寫空間大小告訴應用程序。基於這個特性,使用 kqueue 的應用通常不使用非阻塞 IO。每次讀時,根據 kevent 返回的可讀字節大小,將接收緩衝區中的數據一次性讀完;而發送數據時,也根據 kevent 返回的寫緩衝區可寫空間的大小,一次只發可寫空間大小的數據。線程
文章部分整合知乎上關於epoll和select的回答:https://www.zhihu.com/question/20122137