事件驅動模型
在網(wǎng)絡編程里的模型里面,有一類叫事件驅動(Event-driven)模型。(在其他的資料里面也叫 : io多路復用 )。
這類io模型的好處是:在于單個process就可以同時處理多個網(wǎng)絡連接的IO。它的基本原理就是select,poll,epoll這個function會不斷的輪詢所負責的所有socket,當某個socket有數(shù)據(jù)到達了,就通知用戶進程。
當用戶進程調(diào)用了select/ poll/ epoll,那么整個進程會被block,而同時,kernel會“監(jiān)視”所有select負責的socket,當任何一個socket中的數(shù)據(jù)準備好了,select就會返回。這個時候用戶進程再調(diào)用read操作,將數(shù)據(jù)從kernel拷貝到用戶進程。
這個圖和blocking IO的并沒有太大的不同,事實上,還更差一些。因為這里需要使用兩個system call (select 和 recvfrom),而blocking IO只調(diào)用了一個system call (recvfrom)。但是,用select/ poll/ epoll的優(yōu)勢在于它可以同時處理多個connection。
這里有一個講的很好的教程,推薦 :
http://lifeofzjs.com/blog/2015/05/16/how-to-write-a-server/
https://segmentfault.com/a/1190000003063859
如果有這么一個函數(shù),在某個fd可以讀的時候告訴我,而不是反復地去調(diào)用read。(這句話說的太好了,反復多讀讀)這種方式叫做事件驅動,在linux下可以用select/poll/epoll這些I/O復用的函數(shù)來實現(xiàn),因為要不斷知道哪些fd是可讀的,所以要把這個函數(shù)放到一個loop里,這個就叫事件循環(huán)(event loop)。
示例代碼如下:

在這個while里,調(diào)用epoll_wait會將進程阻塞住,直到在epoll里的fd發(fā)生了當時注冊的事件。需要注明的是,select/poll不具備伸縮性,復雜度是O(n),而epoll的復雜度是O(1),在Linux下工業(yè)程序都是用epoll。
epoll的實現(xiàn)方式
epoll的邏輯是:
使用epoll_ctl 函數(shù),維護socket的等待隊列
a. 比如把bind之后的socket加入隊列,監(jiān)聽新加入的socket,進行accept。
b. 或者把accept的socket加入隊列,監(jiān)聽socket上面是否可讀把epoll_wait 函數(shù),負責獲得就緒列表

int wait_fds = epoll_wait(epoll_fd, evs, cur_fds, -1);
// wait_fds 返回的是這一次系統(tǒng)調(diào)用獲得的可讀的fd的數(shù)量
// evs是一個數(shù)量
// 比如wait_fds = 5, 那么表示這一次返回了5個可讀的fd,他們被存放在evs數(shù)組中的0~5的位置上面
// 遍歷這一次調(diào)用獲得的可讀的fd列表
for (i = 0; i < wait_fds; i++)
{
// 處理新接入的accept
// evs[i].data.fd 這個fd表示的真正的fd
if (evs[i].data.fd == listen_fd && cur_fds < MAXEPOLL)
{
}
else
{
// 處理可讀的fd
nread = read(evs[i].data.fd, buf, sizeof(buf));
if (nread <= 0) //!> 結束后者出錯
{
// xxxx
}
}
}
select最早于1983年出現(xiàn)在4.2BSD中,它通過一個select()系統(tǒng)調(diào)用來監(jiān)視多個文件描述符的數(shù)組,當select()返回后,該數(shù)組中就緒的文件描述符便會被內(nèi)核修改標志位,使得進程可以獲得這些文件描述符從而進行后續(xù)的讀寫操作。
select目前幾乎在所有的平臺上支持,其良好跨平臺支持也是它的一個優(yōu)點,事實上從現(xiàn)在看來,這也是它所剩不多的優(yōu)點之一。
select的一個缺點在于單個進程能夠監(jiān)視的文件描述符的數(shù)量存在最大限制,在Linux上一般為1024,不過可以通過修改宏定義甚至重新編譯內(nèi)核的方式提升這一限制。
另外,select()所維護的存儲大量文件描述符的數(shù)據(jù)結構,隨著文件描述符數(shù)量的增大,其復制的開銷也線性增長。同時,由于網(wǎng)絡響應時間的延遲使得大量TCP連接處于非活躍狀態(tài),但調(diào)用select()會對所有socket進行一次線性掃描,所以這也浪費了一定的開銷

在寫server的時候的時候,經(jīng)常要寫一個client作為調(diào)代碼的工具,后來發(fā)現(xiàn)一個好用的linux命令 ncat
#這個表明本地的8000端口建立tcp連接,然后下面就可以輸入要給server發(fā)送的消息了。
ncat 127.0.0.1 8000
代碼放在github上面了:
demo-epoll
參考:
epoll 分析
其他:
一個基于python的單線程的echo服務器
http://www.itdecent.cn/p/8f1941c4a549
基于python的一個多線程echo服務器
http://www.itdecent.cn/p/2ffde49b55c3
一個基于select模型的echo服務器
http://www.itdecent.cn/p/8a360a3f13aa
關于select,poll,epoll3個網(wǎng)絡編程異步模型的區(qū)別
http://www.itdecent.cn/p/3bf72e232fb8