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+才有的技術, 在那之前一般用的是多路復用.