深入理解select網路模型(linux/windows)
IO模型主要分為以下幾種
(1)阻塞I/O模型
(2)非阻塞IO模型
(3)IO複用模型(select 、poll)
(4)訊號驅動式IO模型
(5)非同步IO模型
select模型屬於IO複用模型,所謂的IO複用就是核心一旦發現程序指定的一個或多個IO就緒,它就通知程序,讓程序去完成IO操作。
在select模型中,我們會阻塞於select呼叫,直到呼叫超時或者套接字變為可讀它才返回。
由於select對比於普通IO模型,需要兩個系統呼叫,在效率上還略有劣勢。但是他真正的好處在於,可以等待多個描述符就緒。
與IO複用密切相關的另一種模型就是在多執行緒中去使用阻塞式IO,這種模型和IO複用很類似。
select函式的原型:int select(int maxfdpl,fd_set* readset,fd_set* writeset,fd_set* exceptset,const struct timeval* timeout);
我們將通過對函式傳參,告訴核心對那些描述符感興趣,在linux中,不僅僅侷限於套接字,任何描述符都可以。
關於timeval這個結構體,它是用來設定阻塞時間的,經過測試,這個阻塞的時間是整體的阻塞時間,而不是集合內的單個套接字的阻塞時間。
比如,我設定阻塞時間為10s,那麼連線了一個客戶端也是阻塞10s,連線5個客戶端,也還是阻塞10s。
這個時間引數,存在三種可能
(1)傳遞一個空指標,讓它一直阻塞。
(2)傳遞普通指定的時間。
(3)傳遞時間引數被設定為0,即不等待。但是,經過我的測試·,雖然說時間設定為0,但是當有套接字變為可讀時它還是可以正常檢測出來。
本人推測,當為0時,系統只會輪詢一遍,當有套接字可讀寫時,立即返回。如果,設定了指定時間,它就會在這個時間段裡,一直反覆的
輪詢,直到輪詢到第一個可讀寫的套接字,然後立即返回。注意,是輪詢到第一個就返回,不是說一直輪詢到指定時間結束,然後把所有可
讀寫的套接字一起在集合內返回。
所有感興趣的套接字都要先新增到fd_set這個集合中,關於這個集合的是怎麼回事,普遍推論是它是一個整數陣列,每個整數都是32位整數。
那麼,每個描述符都可以在數組裡找到指定位。什麼意思呢?就是陣列中第一個元素是32為的,那它就可以表示0~31這個區間內的描述符,
然後第二個整數元素,可以表示32~63,以此類推。
當有套接字進入可讀寫狀態時,那麼它所在的位,就會被置位,然後我們通過FD_ISSET巨集,就可以檢測出哪一位被置位了,就可以得到對應
的套接字。
對於maxfdpl這個引數,它是待測試的描述符個數,在數值上等於被等待的最大的描述符的值加1.為什麼要指定它呢?前面提出過了,fd_set
是一個數組,每一位對應這描述符的值,假設最大的描述符是5,那麼我們只需要讓系統輪詢前6位就好了,後面的位就可以不要浪費時間去
輪詢了,因為根本就沒有用到,所以我們把5加1作為引數傳遞給select函式。當然,這個引數只有伯克利套接字對它感興趣,在windows中
·MSDN指出它是可以被忽略的。以上的推測,也僅限於linux的實現,對於windows並不適用。
對於windows
typedef struct fd_set {
u_int fd_count; /* how many are SET? */
SOCKET fd_array[FD_SETSIZE]; /* an array of SOCKETs */
} fd_set;
這是他的fd_set的實現,它是維護了一個socket的陣列,將socket新增進數組裡,我想這也是為什麼他保留了第一個引數,但是並不對它感興趣的原因。
select等待的最大描述符數是有限制的,在windows中是64.而linux是1024.
對於linux,當描述符數超過最大時,我在網上找到了一個例項,即便每個select檢查的描述符數,小於1024,但是整個程序檢查的描述符數
超過了1024也不行。select 的正確語義應該時,整個程序中,select 最多可以檢查1024個描述符,而不管用了多少 select 函式。
對於大於1024的描述符,FD_SET的處理方式是拿描述符對1024取模。假設描述符為1029,FD_SET(1029,&set)相當於FD_SET(5,&set),
而 5 可能不是一個有效的描述符,當 select 去檢查描述符 5 時,因為 5 不是有效的描述符,所以出 Invalid argument 或 Bad file descriptor 就比較合理了。
那麼如何超出這個最大的限制呢,僅僅通過修改FD_SETSIZE巨集的大小,對於linux通常是行不通的。對於windows。。。。。我也不瞭解。