IO復用:
I/O復用使得程序可以同時監聽多個文件描述符,這對提高程序的性能至關重要。例如TCP服務器要同時處理監聽socket和連接socket,客戶端要同時處理用戶輸入和網絡連接.
Linux下實現I/O復用的系統調用主要有select、poll和epoll.
select函數:
#include <sys/select.h> int select(int nfds,fd_set* readfds,fd_set* writefds,fd_set* excepefds,struct timeval* timeout);
函數參數介紹:
(1)nfds:指定被監聽的文件描述符的總數.它通常被設置為select監聽的所有的文件描述中的最大值加1.因為文件描述符是從0開始的.
(2)readfds、writefd和exceptfds參數:指向可讀、可寫和異常等事件對應的文件描述符集合.select調用返回時,內核將修改它們來通知應用程序哪些文件描述符已經就緒.(fd_set結構體僅包含一個整型數組,該數組的每個元素的每一位(bit)都標記一個文件描述符.fd_set能夠容納的文件描述符數量是由FD_SETSIZE來指定,這就限制了select能同時處理的文件描述符的總量).
因為位操作比較繁瑣,所以使用下列宏來實現:
FD_ZERO(fd_set *fdset); //清除fdset的所有位
FD_SET(int fd,fd_set *fdset); //設置fdset的位
FD_CLR(int fd,fd_set *fd_set); //清除fdset的位fd
int FD_ISSET(int fd,fd_set *fd_set);//判斷fdset的位fd是否被設置
(3)timeout參數:被用來設置select函數的超時時間.使用指針參數是因為內核將修改它以告訴用戶select等了多久.
struct timeval結構體定義:
struct timeval{ long tv_sec;/*秒數*/ long tv_usec; /*微秒*/ };
返回值:>0 成功時返回就緒(可讀、可寫和異常)文件描述符的總數.
=0 在超時時間內沒有任何文件描述符就緒.
=-1 失敗時,同時並設置errno.
例子:利用select接受普通數據和帶外數據都將使select返回.但socket處於不同的就緒狀態:前者處於可讀狀態,後者處於異常狀態.
#include <sys/socket.h> #include <sys/types.h> #include <netinet/in.h> #include <arpa/inet.h> #include <assert.h> #include <stdio.h> #include <stdlib.h> #include <errno.h> #include <fcntl.h> int main(int argc,const char* argv[]){ if(argc<=2){ printf("usage:%s ip port\n",argv[0]); return -1; } const char* ip=argv[1]; int port=atoi(argv[2]); struct sockaddr_in address; address.sin_family=AF_INET; inet_pton(AF_INET,ip,&address.sin_addr); address.sin_port=htons(port); int sockfd=socket(AF_INET,SOCK_STREAM,0); assert(sockfd!=-1); int ret=bind(sockfd,(struct sockaddr*)&address,sizeof(address)); assert(ret!=-1); ret=listen(sockfd,5); assert(ret!=-1); struct sockaddr_in client_address; socklen_t len=sizeof(client_address); int connfd=accept(listenfd,(struct sockaddr*)&client_address,&len); assert(connfd>=0); fd_set readSet; fd_set exceptionSet; FD_ZERO(&readSet); FD_ZERO(&exceptionSet); char buf[1024]; while(1){ memset(buf,'\0',1024); FD_SET(connfd,&readSet); FD_SET(connfd,&exceptionSet); ret=select(connfd+1,&readSet,NULL,&exceptionSet,NULL); if(ret<0){ printf("select error\n"); break; } if(FD_ISSET(connfd,&readSet)){ ret=recv(connfd,buf,sizeof(buf),0); if(ret<=0){ break; } printf("recv data:%s and length:%d\n",buf,ret); } else if(FD_ISSET(connfd,&exceptionSet)){ ret=recv(connfd,buf,sizeof(buf),MSG_OOB); if(ret<=0){ break; } printf("recv oob data:%s and length:%d\n",buf,ret); } } close(connfd); close(sockfd); return 0; }
當你編寫的程序需要同時處理多個描數字(socket或file或device),你又不知道什麼時候應該(比方說有數據可以讀了)去操作(讀/寫)哪個描數字。這時候I/O復用就需要登場了。
I/O復用是一種讓進程預先“警告”內核能力,使得內核一旦發現進程預先告知時指定的一個或多個I/O條件(就是描述符)就緒(可以讀/寫了),內核就通知進程。linux有4個調用可實現I/O復用:select、poll繼承自Unix系統。pselect是select到Posix版。epoll是linux2.6內核特有的。
有5種模型.
常用異步IO的路過一下. SIGIO是需要用到信號量的, 資源太受限制. 而常說的這個異步IO這個是操作系統底層通過fd上可都可寫的事件來進行邊緣觸發或者電平觸發, 直接進入回調函數的高效處理方法, 比如說epoll或者kqueue, 不過這個算是相對比較新的技術, 比如說epoll是linux2.6+才有的技術, 在那之前一般用的是多路復用.