我们看到tcp客户同时处理两个输入:标准输入和tcp套接字。此时程序有了两个焦点,当客户阻塞于标准输入上是,服务器进程被杀死,服务器tcp虽然正确给客户tcp发送了一个fin,但是既然客户进程正阻塞于标准输入读入的过程,它将看不到这个eof,直到从套接字读时为止(可能已经过了很长时间),这样的进程需要一种预先告知内核的能力,使得内核一旦发现进程指定的一个i/o或者多个i/o条件就绪时它就通知进程,这个能力称为i/o复用。
I/O复用使用在下列网络场合:
1:当客户处理多个描述符(通常是交互式输入和网络套接字)时,必须使用i/o复用
2:如果一个tcp服务器既要处理监听套接字又要处理已连接套接字,一般就要使用i/o复用。
我们先来看看unix下的5种i/o模型的基本区别:
阻塞式,非阻塞式,i/o复用,信号驱动式,异步i/o
一个输入操作通常包括两个不同的阶段:
1)等待数据准备好
2)从内核向进程复制数据
阻塞式i/o模型:
系统调用 内核 等待数据 将数据从内核复制到用户空间
recvfrom调用------------>无数据报准备好 ------------>数据报准备好复制数据报--------------------------->复制完成---->返回
非阻塞i/o模型
进程把一个套接字设置成非阻塞是在通知内核:当所请求的i/o操作非得把本进程投入睡眠才能完成时,不要把本进程投入睡眠,而是
返回一个错误。(recvfrom无数据时返回EWOULDBLOCK)
i/o复用模型:
有了i/o复用,我们就可以使用select或poll,阻塞在这两个系统调用中的某一个之上,而不是阻塞再真正的i/o系统调用上,
这种模型也可以使用多线程,每个线程阻塞于一个recvfrom之类的阻塞式i/o系统调用。
信号驱动式i/o模型
异步i/o模型
ok,下面给一个select使用模型。
int main(int argc,char* argv[]){
int i,maxi,maxfd,listenfd,connfd,sockfd;
int nready,client[FD_SETSIZE];
sszie_t n;
fd_set rset,allset.
char buf[MAXLINE];
socklen_t clilen;
struct sockaddr_in cliaddr,servaddr;
listenfd = socket(AF_INET,SOCK_STREAM,0);
bzero(&servaddr,sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(SERV_PORT);
bind(listenfd,(SA*)&servaddr,sizeof(servaddr));
listen(listenfd,LISTENQ);
maxfd = listenfd;
maxi = -1;
for(int i = 0;i < FD_SETSIZE;i++){
client[i] = -1;
}
FD_ZERO(&allset);
FD_SET(listenfd,&allset);
for(;;) {
rset = allset;
nready = select(maxfd+1,&rset,NULL,NULL,NULL);
if(FD_ISSET(listenfd,&rset)){
clilen = sizeof(cliaddr);
connfd = accept(listenfd,(SA*)&cliaddr,&clilen);
for(i =0;i < FD_SETSIZE;++i){
if(client[i] < 0){
client[i]=connfd;
break;
}
}
if(i == FD_SETSIZE)
exit(-1);//too many clients;
FD_SET(connfd,&allset);
if(connfd > maxfd)
maxfd = connfd;
if(i > maxi)
maxi = i;
if(--nready <= 0)
continue;
}
}
for(i = 0;i <= maxi;i++){
if((sockfd = client[i]) < 0)
continue;
if(FD_ISSET(sockfd,&allset)){
if((n = read(sockfd,buf,MAXLINE)) == 0){
close(sockfd);
FD_CLR(sockfd,&allset);
client[i] = -1;
}else{
write(sockfd,buf,n);
}
if(--nready <= 0)
break;
}
}
}