多路 IO 转接 :select 函数
(1)頭文件:
#include <sys/select.h>(2)函數原型:
int select( int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds,struct timeval *timeout );(3)參數說明
1)參數1:監聽的所有文件描述符中,最大文件描述符的編號 + 1(即監聽 fd 的總個數)。
2)參數2、3、4:監聽的文件描述符 “是否可讀”、“是否可寫”、“是否異常” 的事件;fd_set代表文件描述符集合,本質是位圖。
3)參數5:設定超時返回結果,若填 NULL 代表永久阻塞,直至有事件發生, 若值為0代表立即返回,非阻塞效果。
這里有兩點要注意的地方:
1)是最常監聽 readfds;writefds 在 epoll 反應堆會使用;exceptfds 一般是內核給系統函數內部使用。
2)以參 2 readfds 為例,并不是我監聽它可讀,它就一定可讀,因此在設計上,都是傳入傳出參數。傳入體現在告訴內核,我想監聽誰是否可讀。傳出體現在通過 select 函數的監聽,內核告訴我具體結果,誰是可讀的。如下圖
(4)返回值:
成功返回所監聽的滿足條件的文件描述符總數量。
例如:
監聽 r:fd1 fd2 fd3
w:fd1 fd3
err:fd1 fd2
滿足條件 r:fd1 fd3
w:fd1 ; err:fd2
則返回值為4 (不要在意 fd1 是否有重復,因為他們屬于不同事件)。
失敗:返回 -1,設置 errno (如果 timeout =0, errno = EAGAIN,代表正常)。
(5)添加監聽的文件描述符函數:
(6)在返回值中確定哪個文件描述符可讀,哪個可寫
fd_set readfds; // 定義監聽讀事件的文件描述符集合 FD_ZERO(&readfds); // 清空 FD_SET(fd1, &readfds); FD_SET(fd2, &readfds); FD_SET(fd3, &readfds); // 監聽 fd1、fd2、fd3 是否可讀 select(); //以上面結果為例(不考慮 writefds 和 exceptfds)返回值 = 1,但我不知道是誰 //判斷是否在相應集合中 int ret = FD_ISSET(fd1, & readfds); // 發現 ret = 1 說明可讀,意味著客戶端向服務器發送數據,可以通過 fd1 讀取數據 int ret = FD_ISSET(fd2 & readfds); // 顯然 結果 = 0 int ret = FD_ISSET(fd3, & readfds); // 顯然 結果 = 0(7)select 函數優缺點
優點:一個線程就可以支持多個客戶端(多路 IO 都有這個優點),可以跨平臺(select 獨有)。
缺點:
1)同時監聽文件描述符的上限是 1024 個,注意不是因為打開文件的上限是 1024(上限數可以修改),而是因為在 select 底層實現時候,fd_set使用了宏 FD_SETSIZE = 1024。
2)返回值是一個數量,需要循環遍歷判斷到底誰符合條件,因此高并發少訪問的時候,效率低。
3)監聽集合和滿足監聽條件的集合是同一個集合,select 后會改變原監聽集合,使其無法再次使用,因此,select 前需要將原監聽集合保存。
(8)監聽事件分類:
需要監聽事件分為 2 類,首先一類是建立連接請求(其實是監聽讀事件即服務器寫給我請求,我來讀),另一類是讀寫事件請求 (這些都是建立連接之后的事)。就是說,最開始需要監聽一個讀描述符,有結果,意味著有客戶端請求連接,此時服務器調用 accept 函數便會立即建立連接(雖然 accept 是阻塞函數,但此時不會有阻塞效果),然后再監聽讀寫。
總結
以上是生活随笔為你收集整理的多路 IO 转接 :select 函数的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 链路层寻址和ARP
- 下一篇: select 版 高并发服务器