前面提到過,使用多程序和多執行緒來提高系統執行的效率,多執行緒的併發相對多程序來說占用的資源更少,效率相比多程序更快, select()函式允許程序指示核心等待多個事件(檔案描述符)中的任何乙個發生,並只在有乙個或多個事件發生或經歷一段指定時 間後才喚醒它,然後接下來判斷究竟是哪個檔案描述符發生了事件並進行相應的處理。
#include
#include
struct timeval
;fd_zero
(fd_set* fds)
//清空集合
fd_set
(int fd, fd_set* fds)
//將給定的描述符加入集合
fd_isset
(int fd, fd_set* fds)
//判斷指定描述符是否在集合中
fd_clr
(int fd, fd_set* fds)
//將給定的描述符從檔案中刪除
intselect
(int max_fd, fd_set *readset, fd_set *writeset, fd_set *exceptset,
struct timeval
*timeout)
;說明: select監視並等待多個檔案描述符的屬性發生變化,它監視的屬性分3類,分別是readfds
(檔案描述符有資料
到來可讀)、 writefds
(檔案描述符可寫)、和exceptfds
(檔案描述符異常)。呼叫後select函式會阻塞,直到有描述
符就緒(有資料可讀、可寫、 或者有錯誤異常),或者超時( timeout 指定等待時間)發生函式才返回。當
select
()函式返回後,可以通過遍歷 fdset,來找到 究竟是哪些檔案描述符就緒。
1. select函式的返回值是就緒描述符的數目,超時時返回0,出錯返回-
1; 2. 第乙個引數max_fd指待測試的fd的總個數,它的值是待測試的最大檔案描述符加1。linux核心從0開始到max_fd-
1掃瞄檔案描述 符,如果有資料出現事件(讀、寫、異常)將會返回;假設需要監測的檔案描述符是8,9
,10,那麼
linux核心實際也要監測0
~7,此時真 正帶測試的檔案描述符是0
~10總共11個,即max(8
,9,10
)+1,所以第乙個
引數是所有要監聽的檔案描述符中最大的+
1。 3. 中間三個引數readset、writeset和exceptset指定要讓核心測試讀、寫和異常條件的fd集合,如果不需要測試的
可以設定為 null;
5. 最後乙個引數是設定select的超時時間,如果設定為null則永不超時;
需要注意的是待測試的描述集總是從0, 1, 2, .
..開始的。 所以, 假如你要檢測的描述符為8, 9, 10,
那麼系統實際也要 監測0, 1, 2, 3, 4, 5, 6
,7, 此時真正待測試的描述符的個數為11個, 也就是
max(8, 9, 10) +
1
在linux核心有個引數__fd_setsize定義了每個fd_set的控制代碼個數中,這也意味著select所用到的fd_set是有
限的,也正是這個 原因select
()預設只能同時處理1024個客戶端的連線請求:
/linux/posix_types.h:
#define __fd_setsize 1024
基於select的i/o復用模型的是單程序執行可以為多個客戶端服務,這樣可以減少建立執行緒或程序所需要的cpu時間片或記憶體 資源的開銷;此外幾乎所有的平台上都支援select(),其良好跨平台支援是它的另乙個優點。當然它也有兩個主要的缺點:下面給出select多路復用提公升socket伺服器端效率的偽**:1. 每次呼叫 select()都需要把fd集合從使用者態拷貝到核心態,之後核心需要遍歷所有傳遞進來的fd,這時如果客戶端fd很多 時會導致系統開銷很大;
2. 單個程序能夠監視的檔案描述符的數量存在最大限制,在linux上一般為1024,可以通過setrlimit()、修改巨集定義甚至重 新編譯核心等方式來提公升這一限制,但是這樣也會造成效率的降低;
#include
<>
intmain
(int argc,
char
*ar**)
array[0]
=listen_fd;
//加入listen_fd,
while(1
)//強調一下這個for迴圈,函式select每次返回都會清空rd_set集合,所以每一次迴圈都要重新加入所有檔案描述符,引數max_fd的值就是集合中最大的檔案描述符的值。
rv=select
(max_fd+1,
&rd_set,
null
,null
,null);
//最後乙個引數設為null,代表select永不超時,如果沒有檔案描述符響應,會永遠阻塞。rv<0,函式select的呼叫出錯,rv==0,代表select函式超時,這裡不會發生。至此函式select阻塞,第一次能繼續執行下去會是listen_fd發生響應。if(
fd_isset
(listen_fd/*array[0]*/
,&fd_set)
)//用巨集fd_isset測試fd_set中相應位是否為1,如果是,進入條件語句。}if
(!judge)
//fd_set滿執行該條件
continue;}
//如果select函式返回,而且響應並不是listen_fd,則響應來自客戶端
for(
int i=
1;i<__fd_setsize;i++) }
}close
(listen_fd)
;return0;
}
select系列函式,巨集的工作原理:
先來看看fd_set的大小
#include
#include
intmain()
compile and run:
iot@public_rpi:~/zhanghang $ gcc test.c
iot@public_rpi:~/zhanghang $ ./a.out
128
執行結果顯示fd_set的大小為128個位元組,也就是128byte*
8bit=
1024bit
fd_set的工作原理就是將加入集合的檔案描述符的值對應到fd_set的1024個位的相應位上。
巨集fd_zero
(&rd_set)會將fd_set的所有位全部置為0。
假設兩個檔案描述符的值分別為7和10,則執行fd_set(8
,&rd_set)
,fd_set(10
,&rd_set)後,fd_set的第8和10位
置為1(從右往左)。在select函式返回後,假設8號位響應使select返回,而10號位並沒有響應,此時select返回
後,只有8號位為1,10號位已經被置為0。因此,每一次while(1
)迴圈,都要將原來監聽的檔案描述符重新加入到
fd_set中,所以使用乙個陣列儲存原來的檔案描述符會使加入更方便。
另外,fd_isset
(a,&rd_set)用於判斷rd_set的a號位是否為1。
fd_clr
(a,&rd_set)表示清除rd_set中的a號位。
最後分析一下select多路復用的缺點:
select缺點:
(1)每次呼叫select,都需要把fd集合從使用者態拷貝到核心態,這個開銷在fd很多時會很⼤
(2)同時每次呼叫select都需要在核心遍歷傳遞進來的所有fd,這個開銷在fd很多時也很⼤
(3)select支援的檔案描述符數量太小了,預設是1024
多路復用 select
在之前寫過一篇五種i o模型,感興趣的可以去看一下,今天主要講其中的一種,那就是i o多路復用。因為i o多路復用可以使乙個程序同時處理多個連線。這對提高程式的效能至關重要。對於io復用的概念與理解在上文說的挺清楚了。本文主要說實現io復用的系統呼叫。在linux下,實現io復用的系統呼叫主要有三個...
select多路復用
在某些場景下我們需要同時從多個通道接收資料。通道在接收資料時,如果沒有資料可以接收發生阻塞。你也許會寫出 使用遍歷的方式來實現 for 這種方式雖然可以實現從多個channel接收值的需求,但是執行效能會差很多。為了應對這種場景,go內建了select關鍵字,可以同時響應多個通道的操作。select...
select 函式 多路復用
select 的機制中提供一fd set的資料結構,實際上是一long型別的陣列,每乙個陣列元素都能與一開啟的檔案控制代碼 不管是socket控制代碼,還是其他 檔案或命名管道或裝置控制代碼 建立聯絡,建立聯絡的工作由程式設計師完成,當呼叫select 時,由核心根據io狀態修改fd set的內容,...