對select函數的理解
對select函數的理解
1. 處理多個socket鏈接的方法
阻塞模式下服務端要解決多個客戶鏈接的問題的3個思路:
- 每個客戶端的socket對應一個內核執行緒,在這個執行緒內部進行阻塞的read
- 單執行緒,自己記錄一個socket列表,循環去內核中查詢socket是否ready
- 單執行緒,系統提供一個ready狀態的socket列表,主執行緒從這個列表中處理socket
思路1,如果鏈接很多(C10k)執行緒就會很多,消耗系統資源,並增加調度成本(Java BIO)。
思路2,每次都要遍歷一邊所有socket,鏈接很多時效率低,可能大部分鏈接都沒數據(select)。
思路3,比較理想(epoll)。
2. select函數
2.1. 使用方法
函數原型:
select(int nfds, fd_set *restrict readfds, fd_set *restrict writefds, fd_set *restrict errorfds, struct timeval *restrict timeout);
select() 檢查 readfds
, writefds
中的 io描符是否可讀、可寫了,如果有ready狀態的,函數就返回。
nfds
是總共fd的個數,而不是最大的fd。
使用select函數步驟:
- 初始化 fd_set,把要監控的fd仍進去
- 調用select,阻塞
- 阻塞結束後,遍歷查看fd_set中的ready的socket
fd_set 是什麼?
一個結構體:
typedef struct fd_set {
__int32_t fds_bits[__DARWIN_howmany(__DARWIN_FD_SETSIZE, __DARWIN_NFDBITS)];
} fd_set;
結構體中有一個數組,默認是是1024,這就是linux中select的函數限制最大鏈接數的原因。
重新編譯內核才能提高這個數字。
FD_ISSET
就是取對應位置狀態值(0,1),並且在用戶空間,
用戶需要遍歷編一遍這個數組來檢查是哪個socket有數據。
select的內部實現:
- readfds 從用戶空間傳遞到內核空間
- 將當前進程加入到 readfds 中的每個socket的等待隊列
- 當socket來數據了就把 執行緒喚醒(移出等待隊列)
- 把有數據的fds 從內核空間搞到用戶空間
- 用戶空間看一遍fds,知道哪個socket有數據了,然後read、accept
select的問題:
- 每次調用select就要把readfds 傳到內核,wake的時候再拿回來需要傳遞1024 * 4 bytes
- 每次需要把當前執行緒加入到所有socket的等待隊列
- 每次需要遍歷一遍readfds來查看那個socket有數據
每次調用select都會有以上兩次傳遞和兩次遍歷,當鏈接個數多時,性能下降比較快:
select可能的改進:
- readfds一直都在內核中維護,不要每次都送進來
- 可以動態單個變更內核中的readfds
- 就緒列表,傳給內核一個指針,內核把這個指針指向就緒的sockets (避免來回傳遞所有的socket)