一、五種I/O模型
1、阻塞I/O模型
最流行的I/O模型是阻塞I/O模型,缺省情形下,所有套接口都是阻塞的。我們以數(shù)據(jù)報套接口為例來講解此模型(我們使用UDP而不是TCP作為例子的原因在于就UDP而言,數(shù)據(jù)準備好讀取的概念比較簡單:要么整個數(shù)據(jù)報已經(jīng)收到,要么還沒有。然而對于TCP來說,諸如套接口低潮標記等額外變量開始活動,導致這個概念變得復雜)。
進程調(diào)用recvfrom,其系統(tǒng)調(diào)用直到數(shù)據(jù)報到達且被拷貝到應用進程的緩沖區(qū)中或者發(fā)生錯誤才返回,期間一直在等待。我們就說進程在從調(diào)用recvfrom開始到它返回的整段時間內(nèi)是被阻塞的。
2、非阻塞I/O模型
進程把一個套接口設置成非阻塞是在通知內(nèi)核:當所請求的I/O操作非得把本進程投入睡眠才能完成時,不要把本進程投入睡眠,而是返回一個錯誤。也就是說當數(shù)據(jù)沒有到達時并不等待,而是以一個錯誤返回。
3、I/O復用模型
調(diào)用select或poll,在這兩個系統(tǒng)調(diào)用中的某一個上阻塞,而不是阻塞于真正I/O系統(tǒng)調(diào)用。 阻塞于select調(diào)用,等待數(shù)據(jù)報套接口可讀。當select返回套接口可讀條件時,調(diào)用recevfrom將數(shù)據(jù)報拷貝到應用緩沖區(qū)中。
4、信號驅(qū)動I/O模型
首先開啟套接口信號驅(qū)動I/O功能, 并通過系統(tǒng)調(diào)用sigaction安裝一個信號處理函數(shù)(此系統(tǒng)調(diào)用立即返回,進程繼續(xù)工作,它是非阻塞的)。當數(shù)據(jù)報準備好被讀時,就為該進程生成一個SIGIO信號。隨即可以在信號處理程序中調(diào)用recvfrom來讀數(shù)據(jù)報,井通知主循環(huán)數(shù)據(jù)已準備好被處理中。也可以通知主循環(huán),讓它來讀數(shù)據(jù)報。
5、異步I/O模型
告知內(nèi)核啟動某個操作,并讓內(nèi)核在整個操作完成后(包括將數(shù)據(jù)從內(nèi)核拷貝到用戶自己的緩沖區(qū))通知我們。這種模型與信號驅(qū)動模型的主要區(qū)別是: 信號驅(qū)動I/O:由內(nèi)核通知我們何時可以啟動一個I/O操作, 異步I/O模型:由內(nèi)核通知我們I/O操作何時完成。
二、I/O復用的典型應用場合:
1、當客戶處理多個描述字(通常是交互式輸入和網(wǎng)絡套接口)時,必須使用I/O復用。
2、如果一個服務器要處理多個服務或者多個協(xié)議(例如既要處理TCP,又要處理UDP),一般就要使用I/O復用。
三、支持I/O復用的系統(tǒng)調(diào)用
目前支持I/O復用的系統(tǒng)調(diào)用有select、pselect、poll、epoll:
1、select函數(shù)
該函數(shù)允許進程指示內(nèi)核等待多個事件中的任何一個發(fā)生,并僅在有一個或多個事件發(fā)生或經(jīng)歷一段指定的時間后才喚醒它。
格式為:
#include <sys/select.h> #include <sys/time.h>
int select(int maxfdp1, fd_set *readset, fd_set *writeset, fd_set *exceptset, const struct timeval *timeout); 返回:就緒描述字的正數(shù)目,0-超時,-1-出錯
| 我們從該函數(shù)的最后一個參數(shù)開始介紹,它告知內(nèi)核等待所指定描述字中的任何一個就緒可花多少時間。其timeval結(jié)構(gòu)用于指定這段時間的秒數(shù)和微秒數(shù)。
struct timeval{
long tv_sec; //seconds
long tv_usec; //microseconds
};
這個參數(shù)有三種可能:
(1)永遠等待下去:僅在有一個描述字準備好I/O時才返回。為此,我們把該參數(shù)設置為空指針。
(2)等待一段固定時間:在有一個描述字準備好I/O時返回,但是不超過由該參數(shù)所指向的timeval結(jié)構(gòu)中指定的秒數(shù)和微秒數(shù)。
(3)根本不等待:檢查描述字后立即返回,這稱為輪詢。為此,該參數(shù)必須指向一個timeval結(jié)構(gòu),而且其中的定時器值必須為0。
中間的三個參數(shù)readset、writeset和exceptset指定我們要讓內(nèi)核測試讀、寫和異常條件的描述字。如果我們對某一個的條件不感興趣,就可以把它設為空指針。struct fd_set可以理解為一個集合,這個集合中存放的是文件描述符,可通過以下四個宏進行設置:
void FD_ZERO(fd_set *fdset); //清空集合
void FD_SET(int fd, fd_set *fdset); //將一個給定的文件描述符加入集合之中
void FD_CLR(int fd, fd_set *fdset); //將一個給定的文件描述符從集合中刪除
int FD_ISSET(int fd, fd_set *fdset); // 檢查集合中指定的文件描述符是否可以讀寫 ?
目前支持的異常條件只有兩個:
(1)某個套接口的帶外數(shù)據(jù)的到達。
(2)某個已置為分組方式的偽終端存在可從其主端讀取的控制狀態(tài)信息。
第一個參數(shù)maxfdp1指定待測試的描述字個數(shù),它的值是待測試的最大描述字加1(因此我們把該參數(shù)命名為maxfdp1),描述字0、1、2...maxfdp1-1均將被測試。
一個應用select的例子:
/** *TCP回射服務器客戶端程序 */ #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/socket.h> #include <sys/types.h> #include <netinet/in.h> #include <netdb.h> #include <string.h> #include <math.h> #include <sys/select.h> #include <sys/time.h>
#define SERVER_PORT 3333 //服務器端口號
void str_cli(FILE *fp, int sockfd) { int maxfdp1, stdineof; fd_set rset; char buf[BUFSIZ]; int n;
stdineof = 0; FD_ZERO(&rset);
while(1) { if( stdineof == 0 ) FD_SET(fileno(fp),&rset); FD_SET(sockfd, &rset);
maxfdp1 = ((fileno(fp) > sockfd) ? fileno(fp) : sockfd) + 1;
select(maxfdp1, &rset, NULL, NULL, NULL);
if( FD_ISSET(sockfd, &rset) ) { if( (n = read(sockfd, buf, BUFSIZ)) == 0 ) if( stdineof == 1 ) return; else perror("server terminated prematurely"); write(fileno(stdout), buf, n); }
if( FD_ISSET(fileno(fp), &rset)) { if( (n = read(fileno(fp), buf, BUFSIZ)) == 0 ) { stdineof = 1; shutdown(sockfd, SHUT_WR); FD_CLR(fileno(fp), &rset); continue; } write(sockfd, buf, n); } } }
int main(int argc, char *argv[]) { int sockfd[5]; struct sockaddr_in servaddr; struct hostent *hp; char buf[BUFSIZ];
if( argc != 2 ) { printf("Please input %s <hostname>\n", argv[0]); exit(1); } int i; for(i = 0; i < 5; ++i) {
//創(chuàng)建socket
if( (sockfd[i] = socket(AF_INET, SOCK_STREAM,0)) < 0 ) { printf("Create socket error!\n"); exit(1); }
//設置服務器地址結(jié)構(gòu)
bzero(&servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET; if( (hp = gethostbyname(argv[1])) != NULL ) { bcopy(hp->h_addr, (struct sockaddr*)&servaddr.sin_addr, hp->h_length); } else if(inet_aton(argv[1], &servaddr.sin_addr) < 0 ) { printf("Input Server IP error!\n"); exit(1); } servaddr.sin_port = htons(SERVER_PORT);
//連接服務器
if( connect(sockfd[i],(struct sockaddr*)&servaddr, sizeof(servaddr)) < 0 ) { printf("Connect server failure!\n"); exit(1); } } str_cli(stdin, sockfd[0]);
exit(0); }
|
|