網路程式設計中Select的學習記錄

2021-10-08 19:47:42 字數 4096 閱讀 9627

主要記錄個人學習過程中,對select函式的理解和使用

通過socket(),bind(),listen(),accept()就可以建立乙個簡單伺服器時,但這種伺服器在同一時間,只能有乙個客戶端與之連線。

為了能夠使得乙個伺服器能夠同時和多個伺服器連線,可以通過多程序或多執行緒的方式。多程序的方式,會頻繁的發生程序的建立和銷毀,增加cpu和記憶體的的開銷,執行緒與之類似。

因此可以使用i/o復用技術來解決這個問題,簡單來說,i/o復用就是可以使用乙個程序來處理多個i/o操作,具體解釋,如下大佬說明:

io復用、多程序和多執行緒三種併發程式設計模型

這裡只是簡述,很多大佬已經解釋的很詳細了

int ret = select(int nfds,

fd_set* readfds,

fd_set* writefds,

fd_set* exceptfds,

const struct timeval* timeout)

nfds:一般傳入監聽的檔案描述符中,最大的檔案描述符+1

readfds:讀 檔案描述符監聽集合 傳入傳出引數

writefds:寫 檔案描述符監聽集合 傳入傳出引數

exceptfds:異常 檔案描述符監聽集合 傳入傳出引數

timeout: >0: 設定監聽時常。注意:這裡傳入的不是乙個整型變數,而是乙個結構體,因此如果想設定監聽時常,需定義乙個結構體變數

null: 阻塞監聽

0: 非阻塞監聽,輪詢

fd_set rset;							//定義乙個監聽集合

void fd_zero(fd_set *set); //清空乙個檔案描述符集合

fd_zero(&rset);

void fd_set(int fd, fd_set *set); //將待監聽得檔案描述符,新增到監聽集合中

fd_set(3, &rset);

void fd_clr(int fd, fd_set *set); //將乙個檔案描述符從監聽集合中移除

fd_clr(3, &rset);

int fd_isset(int fd, fd_set *set); //判斷乙個檔案描述符是否在監聽集合中

fd_isset(3, &rset);

select重點在設定的監聽集合。

fd_set rset;	//通過該設定乙個監聽集合
在使用select函式前,和普通伺服器的建立方法一樣,通過socket,bind,listen建立出乙個監聽的檔案描述符lfd。

int lfd;

struct sockaddr_in ser_addr,cli_addr;

socklen_t cli_addr_len;

memset

(&ser_addr,0,

sizeof

(ser_addr));

//結構體初始化

ser_addr.sin_family=af_inet;

ser_addr.sin_port=

htons

(serv_port)

; ser_addr.sin_addr.s_addr=

htonl

(inaddr_any);

lfd =

socket

(af_inet,sock_stream,0)

;if(lfd <0)

sys_err

("socket error");

int opt =1;

setsockopt

(lfd,sol_socket,so_reuseaddr,

&opt,

sizeof

(opt));

//埠復用if(

bind

(lfd,

(struct sockaddr *

)&ser_addr,

sizeof

(ser_addr)))

sys_err

("bind error");

if(listen

(lfd,

128)

)sys_err

("listen error"

);

由於select的形參 readfds 是乙個傳入傳出引數,因此在這個伺服器中,設定了兩個集合,乙個為傳入集合allset,乙個為傳出集合,rset。

在lfd建立之後,建立出了兩個集合。

解釋:為什麼乙個叫傳入集合,乙個叫傳出集合。

傳入集合,allset是:傳給select函式的集合,表示有多少檔案描述符被監聽。

傳出集合,rset是:select返回的集合,表示被被監聽的檔案描述符中,哪幾個檔案描述符有事件發生。這裡集合的本質是點陣圖。

傳入集合初始化,並將lfd新增到傳入集合中,從此以後,lfd不會從傳入集合中移除。

然後傳出集合初始化(此時的傳出集合剛被傳入集合初始化,所以作為引數傳給select,當select返回後,傳出集合就變成了真正的傳出集合),並傳給select函式,來記錄被監控的檔案描述符中哪些有事件發生。

lfd的作用是,當有新的客戶端連線到服務端,lfd會被加入到傳出集合中,然後通過遍歷傳出集合,操作accept函式,建立出新的檔案描述符cfd,用於客戶端與服務端的資料傳輸。

fd_set rset,allset;

//allset為傳入集合 ,表示有多少檔案描述符需要監聽,rset為傳出集合,表示監聽到了有事件發生的檔案描述符。

fd_zero

(&allset)

;//清空監聽集合

fd_set

(lfd,

&allset)

;//將待監聽的fd新增到監聽集合中

rset = allset;

//傳出集合通過傳入集合初始化,然後傳入到select函式中,根據事件發生改變

ret =

select

(maxfd+1,

&rset,null,null,null)

;//使用select監聽,返回值ret表示監聽的集合中有幾個事件發生,

//例如,被監聽的檔案描述符有10個,而真正有事件發生的檔案描述符只有5個,ret=5

if

(fd_isset

(lfd,

&rset)

)//有客戶端請求鏈結,rset集合中有lfd

int main(int argc,char *ar**)

/***注意:此時的rset為傳出集合,如果lfd在傳出集合rset中,表示有連線事件發生,如果lfd不在傳出集合中,表示沒有連線事件發生。

allset為傳入集合,傳入集合儲存的是被監控的檔案描述符,當某個檔案描述符對應的事件發生了,則傳出集合rset中會有該檔案描述符,

如果某個檔案描述符對應的事件沒有發生,則傳出集合中就沒有這個檔案描述符,我們只需要處理傳出集合rset中的事件即可。

例如lfd,在傳入集合allset中,一定會有lfd,表示lfd一定會被監聽,至於監聽了有沒有連線事件發生,就不一定了。

如果有新的客戶端連線進來,則傳出集合rset中會有lfd,然後通過accept函式,產生新的檔案描述符cfd,然後將cfd加入到allset中(之所以

加入allset中而不是rset中,是因為此時表示某個客戶端連線進來,但是並沒有發生資料的讀寫,加入到allset中表示這個客戶端會受到監控)

在本次迴圈中,會處理rset中的cfd,表示在這次迴圈中,這些cfd有資料的讀寫。**

*/if(fd_isset(lfd,&rset)) //有客戶端請求鏈結,rset集合中有lfd

for(i=lfd+1;i在**中,maxfd的作用是為了select第乙個引數的傳遞,同時減少在rset中的遍歷次數,算是一種優化,如果不設定,就得從0迴圈到1023來查詢哪些檔案描述符有事件發生。

上述**中,在判斷select得返回值,ret的大小時,我的理解是,如果ret == 1,也有可能表示沒有連線事件發生,只是有乙個讀寫事件發生,因此我把源**中的這個語句注釋掉了,如果我理解有誤,望大佬們不吝賜教。

以上為我對select函式的個人理解,如有錯誤,希望大佬們幫忙指正,提前感謝!!!!

Linux 下網路程式設計中的select

include include int select int maxfdp1,fd set readset,fd set writeset,fd set exceptset,const struct timeval timeout return 0 就緒描述字的正數目 1 出錯 0 超時 struc...

Linux 下網路程式設計中的select

include include int select int maxfdp1,fd set readset,fd set writeset,fd set exceptset,const struct timeval timeout return 0 就緒描述字的正數目 1 出錯 0 超時 struc...

Linux 網路程式設計中的 select 函式

我們這裡簡單的說下 select 的作用,並給出 select 的客戶端例項。我們知道 select 是io 多路復用的乙個最簡單支援,poll 和 epoll 是 select 的公升級版。我們通常會遇到這樣乙個問題 當客戶端阻塞在 fgets 等待客戶輸入的時候,伺服器端斷開連線。而客戶端卻不能...