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