使用套接字實現通訊的例項中,伺服器端在呼叫recv函式或recvfrom函式接收客戶端傳送來的訊息或在呼叫accept函式時,都將處於阻塞狀態。當程序處於阻塞狀態時,程式將停止執行,這將限制程式的處理能力和功能。
linux系統提供了fcntl函式來實現將套接字埠設定為非阻塞狀態的功能,使用該函式設定套接字為非阻塞的**如下: ……
sock = sock(pf_inet, sock_stream, 0);
fcntl(sock, f_setfl, o_nonblock); ……
這樣程式就不會停止在accept函式或recv函式了。但是,卻帶來了乙個新的問題,如何保證能夠收到到來的資訊?在這一情況下,只能不斷地查詢套接字是否收到了資訊。而程式無法在監聽同時,處理其他客戶機來進行連線。其**如下: ……
sock = sock(pf_inet, sock_stream, 0);
fcntl(sock, f_setfl, o_nonblock); ……
while ( 1 )
…… 這種查詢會極大地占用cpu的時間,更好的辦法是使用select函式。select函式提供了實現多路復用的功能。例如,使用select函式可以在監聽其他客戶機連線請求的同時,儲存與已經建立連線的客戶機之間的通訊。select函式的具體資訊如下表所示:
標頭檔案
#include
函式原型
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds struct timeval *timeout)
返回值 成功
失敗是否設定errno
存在3種情況,具體參考該函式的說明資訊 -1
是說明:select函式充許程式同時對多個檔案描述符進行監視。引數nfds為要監視的3個檔案描述符最大的檔案描述符的數值加1。引數readfds、writefds和exceptfds為要監視的讀、寫和異常條件的檔案描述符集合。可以使用fd_set函式將某個檔案描述符加入該集合,fd_set函式的具體資訊如下:
voidfd_set(int fd, fd_set *set);
當select函式發現有檔案描述符準備好之後,將返回準備好的檔案描述符的數量。這時候並不知道哪個檔案描述符集合中哪個檔案描述符已經準備好了,fd_isset將獲得準備好的檔案描述符資訊。該函式的具體資訊如下:
int fd_isset(int fd, fd_set *set);
除了fd_set函式和fd_isset函式,還有fd_zero函式和fd_clr函式。fd_zero函式用於清除檔案描述符集,而fd_clr函式用於將fd檔案描述符從檔案描述符集中移除。這兩個函式的定義如下:
void fd_zero(fd_set *set);
void fd_clr(int fd, fd_set *set);
引數timeout為select函式等待時間。timeout為指向timeval結構體的指標。timeval結構體定義如下:
struct timeval ;
當timeout為null時,表示select函式將無限期等待,除非捕獲資訊中斷這一等待過程。select函式的返回值有3中情況,如下表:
返回值 說明
>0
返回準備好的檔案描述符 0
到達timeout中的等待時間,沒有檔案描述符準備好 -1
select函式被訊號等特殊情況中斷
錯誤資訊:
ebadf:檔案描述符集中存在非法的檔案描述符。
eintr:捕獲到訊號。
einval:nfds引數為負值或timeout引數非法。
enomem:記憶體不足。
乙個簡單的例子:
#include #include #include #include int main(void)
else if (retval)
else
return (0);
}執行結果:
[root@localhost test]# ./select
no data within five seconds.
[root@localhost test]#
網路的多路i/o復用實現例項:
通過使用select函式,避免了程式在接收客戶機傳送來的訊息時陷入阻塞狀態。如果存在多個套接字,可以將其加入檔案描述符集中,這樣就可以同時對多個套接字通訊進行監視。
這裡只是給出使用select監視套接字集的乙個簡單的框架,因此只是對乙個套接字進行了監視。具體**如下:
#include #include #include #include #include #include #include int main(int argc, char *argv)
; if (argc != 2)
port = atoi(argv[1]);
srv_sock = socket(pf_inet, sock_dgram, 0);
if (srv_sock < 0)
memset(&srv_addr, 0, sizeof(srv_addr));
srv_addr.sin_family = af_inet;
srv_addr.sin_addr.s_addr = htonl(inaddr_any);
srv_addr.sin_port = htons(port);
ret = bind(srv_sock, (struct sockaddr*)&srv_addr, sizeof(srv_addr));
if (ret < 0)
//在while迴圈中呼叫select函式,每次等待3秒,如果在這段時間內
//沒有資料到達伺服器的套接字埠,將繼續處於迴圈中
//當資料到達時,將輸出收到的內容
while ( 1 )
if (fd_isset(srv_sock, &read_fds))
printf("server receive : %s\n", recv_buf);
memset(recv_buf, 0, sizeof(recv_buf));
} printf("waiting...\n");
} return (0);
}
I O 多路轉接 select 的使用
select使用的一般步驟 1 清空fd set 2 把描述符加入fd set 3 呼叫select,更新所有描述符的狀態 4 依次檢測每個描述符,若可讀或者可寫,則從描述符中讀 寫資料 5 每次select之前都要重新對fd set清零,並把關心的描述符重新加進去 錯誤的做法 fd set set...
多路復用之select網路程式設計
用基本的linux unix基本函式加上select呼叫編寫乙個完整的伺服器和客戶端例子,可在linux ubuntu 和unix freebsd 上執行 客戶端從標準輸入讀入一行,傳送到服務端 服務端從網路讀取一行,然後輸出到客戶端 客戶端收到服務端的響應,輸出這一行到標準輸出 include i...
select用於實現I O多路復用
阻塞和非阻塞 阻塞函式在完成其指定的任務以前不允許程式呼叫另乙個函式。例如,程式執行乙個讀資料的函式呼叫時,在此函式完成讀 操作以前將不會執行下一程式語句。當伺服器執行到accept語句時,而沒有客戶連線服務請求到來,伺服器就會停止在accept語句上等待連線服務請求 的到來。這種情況稱為阻塞 bl...