在前面的文章中介紹了五種 i/o 模型《i/o 模型》,這裡介紹 i/o 模型中 i/o 多路復用在 tcp 套接字程式設計中的使用。在 i/o 多路復用中主要是 select 和 poll 函式的使用。
該函式允許程序指示核心等待多個事件中的任何乙個發生,並只在乙個或多個事件發生或超過指定時間後才被喚醒。程序呼叫 select 函式是告知核心,程序對哪些描述符(讀、寫或異常)感興趣以及等待的時間。
/* io多路復用 */
/* * 函式功能:
* 返回值:準備就緒的描述符數,若超時則返回0,出錯則返回-1;
* 函式原型:
*/#include int select(int maxfdpl, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, const struct timeval *tvptr);
/* * 說明:
* 引數maxfdpl是「最大描述符加1」,即指定待測試的描述符個數;
* 引數readfds、writefds、exceptfds是指向描述符集的指標,即讓核心測試讀、寫或異常條件的描述符;
* 時間引數有三種取值:
* tvptr == null;
* 永遠等待;若捕獲到訊號則中斷此無限期等待;當所指定的描述符中的乙個已準備好或捕獲到訊號則返回;
* 若捕獲到訊號,則select返回-1,errno設定為eintr;
* * tvptr->tv_sec == 0 && tvptr->tv_usec == 0;
* 完全不等待;測試所有描述符並立即返回,這是得到多個描述符的狀態而不阻塞select函式的輪迴方法;
* * tvptr->sec != 0 || tvptr->usec != 0;
* 等待指定的秒數和微妙數;當指定的描述符已準備好,或超過指定的時間立即返回;
* 若超過指定的時間還沒有描述符準備好,則返回0;
* * tvptr的結構如下:
*/struct timeval
;
我們可以通過以下函式對 fd_set 資料結構進行處理。宣告了乙個描述符集後,必須使用 fd_zero 清空其所有位達到初始化,然後才可以設定各個位;從 select 返回時,使用 fd_isset 測試該集中的乙個給定位是否仍舊設定;
#include int fd_isset(int fd, fd_set *fdset); //測試描述符fd是否在描述符集中設定;若fd在描述符集中則返回非0值,否則返回0
void fd_clr(int fd, fd_set *fdset); //清除在fdset中指定的位fd;
void fd_set(int fd, fd_set *fdset); //設定fd在fdset中指定的位;
void fd_zero(fd_set *fdset); //清除整個fdset;即所有描述符位都為0;
select 函式有三個可能的返回值:
返回值 -1 表示出錯。這種情況下,將不修改其中任何描述符集。
返回值 0 表示沒有描述符準備就緒。若指定的描述符都沒有準備就緒,而且指定的時間已經超過,則發生這種情況。此時描述符集都被清 0。
正返回值表示已經準備就緒的描述符數,該值是三個描述符集中已準備好的描述符之和。三個描述符集中仍舊開啟的位對應與已準備就緒的描述符。
套接字描述符準備好讀,必須滿足以下條件之一:
該套接字接收緩衝區中的資料位元組數不小於套接字接收緩衝區低水位標記的當前大小。對這樣的套接字執行讀操作不會阻塞並將返回乙個大於 0 的值。可以通過 so_rcvlowat 套接字選項設定該套接字的低水位標記;
該連線的讀半部分關閉(即一端已接收到 fin 的 tcp 連線)。對這樣的套接字的讀操作將不阻塞並返回 0(即 eof 字元);
該套接字是乙個監聽套接字且已完成的連線數不為 0。對這樣的套接字的 accept 通常不會阻塞;
其上有乙個套接字錯誤待處理。對這樣的套接字的讀操作將不阻塞並返回 -1(即返回乙個錯誤),並把 errno 設定為確切的錯誤條件;
套接字描述符準備好寫,必須滿足以下條件之一:
該套接字傳送緩衝區中的可用空間位元組數不小於套接字傳送緩衝區低水位標記的當前大小,並且或該套接字已連線,或該套接字不需要連線(如 udp 套接字)。這意味著若把這樣的套接字設定為非阻塞,寫操作將不阻塞並返回乙個正值;
該鏈結的寫半部關閉。對這樣的套接字的寫操作將產生 sigpipe 訊號;
使用非阻塞式 connect 的套接字已建立連線,或connect 已經連線失敗;
其上有乙個套接字錯誤待處理。對這樣的套接字的寫操作將不阻塞並返回 -1,同時把 errno 設定成確切的錯誤條件;
若乙個套接字存在帶外資料 或仍處於 帶外標記,那麼它有異常條件待處理。
注:當某個套接字發生錯誤時,它將由 select 標記為既可讀又可寫。
在前面《
基於 tcp 套接字程式設計的分析》我們知道,當殺死伺服器子程序時,同時客戶端阻塞於 fgets 呼叫。客戶端 tcp 已經接收到來自伺服器子程序的 fin 報文段,當伺服器 tcp 接收到來自客戶端的資料時,因為先前開啟的套接字的程序已經通過 kill 函式終止,於是響應乙個 rst 。然而客戶端阻塞於從標準輸入讀入過程,並沒有收到 rst,因此 readline 返回 0(表示 eof ),則客戶端此時並未預期收到 eof ,則以出錯資訊」server terminated prematurely「退出。若要避免這種情況,可以使用 i/o 多路復用解決。則使用 select 函式修改前面文章的客戶端處理函式 str_cli 函式。這樣伺服器程序一終止,客戶端立即得到通知。
客戶端處理函式修改後具有以下的功能:
若對端 tcp 傳送資料,那麼套接字變為可讀,並且 read 返回乙個大於 0 的值;
若對端 tcp 傳送乙個 fin ,那麼套接字變為可讀,並且 read 返回 0(即 eof 字元);
若對端 tcp 傳送乙個 rst,那麼套接字變為可讀,並且 read 返回 -1,而 errno 中含有確切的錯誤**;
#include "unp.h"
void
str_cli(file *fp, int sockfd)
if (fd_isset(fileno(fp), &rset))
}}
終止網路連線通常使用 close 函式,但是 close 有自己的缺陷,我們可以使用 shutdown 函式來避免這些缺陷:
close 把描述符的引用計數減 1,僅在該計數變為 0 時才關閉套接字。然而使用 shutdown 函式可以不管引用計數就激發 tcp 的正常連線終止序列;
close 終止讀和寫兩個方向的資料傳送,而 shutdown 函式可以只關閉讀或寫的一端,或兩端都關閉;
/*
* 函式功能:關閉套接字上的輸入或輸出;
* 返回值:若成功則返回0,若出錯返回-1;
* 函式原型:
*/#include int shutdown(int sockfd, int how);
/* * 說明:
* sockfd表示待操作的套接字描述符;
* how表示具體操作,取值如下:
* (1)shut_rd 關閉讀端,即不能接收資料
* (2)shut_wr 關閉寫端,即不能傳送資料
* (3)shut_rdwr 關閉讀、寫端,即不能傳送和接收資料
* */
這是最終的版本:
#include "unp.h"
void
str_cli(file *fp, int sockfd)
write(fileno(stdout), buf, n);
}/* 當在標準輸入遇到 eof,則關閉寫端,即客戶端不能向伺服器傳送資料 */
if (fd_isset(fileno(fp), &rset))
writen(sockfd, buf, n);
} }}
該函式與 select 函式類似,只是程式設計師介面不同。該函式不是為每個狀態構造描述符集,而是構造乙個 pollfd 結構陣列,每個陣列元素指定乙個描述符編號以及對其所關心的狀態。
/*
* 函式功能:和select函式類似;
* 函式原型:
*/#include int poll(struct pollfd fdarray, nfds_t nfds, int timeout);
/* * 說明:
* timeout == -1; 永遠等待。
* timeout == 0; 不等待,測試所有的描述符並立即返回。
* timeout > 0; 等待timeout毫秒,當指定的描述符之一已經準備好,或指定的時間值已經超過時立即返回。
*/
struct pollfd;
UNIX網路程式設計 I O多路復用
目錄 unix下可用的5種i o模型 阻塞式i o模型 非阻塞式i o模型 i o復用模型 訊號驅動式i o模型 非同步i o模型 各種i o模型的比較 參考 阻塞式i o 非阻塞式i o i o復用 訊號驅動式i o sigio 非同步i o poxis的aio 系列函式 比如乙個輸入操作通常包含...
併發程式設計 網路IO模型 IO多路復用
網路io模型 一 網路io 輸入 recv recvfrom accept 阻塞io 輸出 send sendto sendall connect 會等待一段時間,但是卻是非阻塞io,因為是乙個主動的過程 二 網路io模型 跟socket有關 blocking io 阻塞io 平時用的 tcp ud...
I O多路復用
一 五種i o模型 1 阻塞i o模型 最流行的i o模型是阻塞i o模型,預設情形下,所有套介面都是阻塞的。我們以資料報套介面為例來講解此模型 我們使用udp而不是tcp作為例子的原因在於就udp而言,資料準備好讀取的概念比較簡單 要麼整個資料報已經收到,要麼還沒有。然而對於tcp來說,諸如套介面...