I/O复用
在上一章中看到TCP客户端同时处理两个输入:标准输入和TCP套字节。我们遇到的问题是客户阻塞于(标准输入上的)fgets()调用,而服务器进程退出。服务器TCP虽能正确地客户TCP发了一个FIN,但客户进程正阻塞于从标准输入读操作,它直到对套字节调用read()时才能觉察到连接关闭,这可能已经过了很长时间。我们需要这样的能力:如果一个或多个I/O条件满足(例如,输入已准备好,或者文件描述符可以承接更多的输出)时,我们会得到通知。这个能力被称为I/O复用,是由系统调用select()/poll()支持的。
I/O复用典型应用场合:
1、 当程序处理多个文件描述符和套字节时,必须使用I/O复用。
2、 如果一个TCP服务器既要处理监听套字节,又要处理已连接套字节,一般也要用到I/O复用。
3、 如果一个服务器既要处理TCP,又要处理UDP,一般也要使用I/O复用。
4、 如果一个服务器要处理多个服务或者多个协议,一般要使用I/O复用。
5、 I/O复用并非只限于网络编程,许多其他类型的应用程序也需要使用这项技术。
Linux下可用的五个I/O模型:
1、 阻塞式I/O
2、 非阻塞式I/O
3、 I/O复用
4、 信号异步
5、 异步I/O
对最大描述符加1这点很重要,其原因就在于:我们指定的是文件描述符的个数而不是其最大其最大值,而文件描述符是从0开始的。Maxfd参数之所以存在是因为它减少了内核测试和复制整个fd_set结构到内核空间给系统带来负担。
使用select()的时候,需要注意以下三个要点:
1、 当使用select()时,两个最常见的编程错误:忘了对最大文件描述符加1和忘了文件描述符集是值结果参数,select()返回时会将那些没准备好的bit置为0,所以如果要再次调用select()时,一定重新用FD_SET设置你感兴趣的文件描述符的对应bit。
2、 对于一个套字节,如果该套字节关闭或出错,select()将返回该套字节既可读又可写,所以对于select()返回可读状态不能只认为它仅仅可读,我们还要检查read()或write()的返回值判断是否该链接已经关闭。
3、 对于监听套字节(对其调用了listen的文件描述符),select将返回可读表示该套字节上有新的连接到来或者套字节出错,我们可以调用accept判断这两种情况,如果accept返回非负数表示有新的连接,并且该返回值为新的已连接套字节,如果accept返回-1表示套字节出错,如已关闭。
客户端:
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <sys/select.h>
int main()
{
int fd,ret;
fd_set set,rset;
int maxfd=0;
char buff[1024]={0};
struct sockaddr_in servaddr;
fd=socket(AF_INET,SOCK_STREAM,0);
if(fd<0)
{
perror("opening socket error");
return -1;
}
memset(&servaddr,0,sizeof(servaddr));
servaddr.sin_family=AF_INET;
if(inet_pton(AF_INET,"192.168.7.92",&servaddr.sin_addr.s_addr)<=0)
{
perror("IP error");
return -2;
}
servaddr.sin_port=9000;
ret=connect(fd,(struct sockaddr *)&servaddr,sizeof(servaddr));
if(ret < 0)
{
perror("connecting error");
goto failed;
}
FD_ZERO(&set);//set指向的文件描述符集合对应的位全部初始化为0
FD_SET(fd,&set);//把对应描述符的位置为1,表示关心该设备状态
FD_SET(0,&set);//表示关心输入设备状态
maxfd=(maxfd > fd ? maxfd : fd);
while(1)
{
printf("All ready,please enter the message....\n");
memset(buff,0,sizeof(buff));
rset = set;
ret = select(maxfd+1,&rset,NULL,NULL,NULL);//计算出所关心的最大描述符,准备数据
if(ret < 0)
{
perror("select error");
break;
}
if(FD_ISSET(0,&rset))//键盘输入设备准备好,开始从内核读入程序
{
ret=read(0,buff,1024);
if(ret <= 0)
{
FD_CLR(0,&set);
continue;
}
ret=write(fd,buff,strlen(buff));//将读入的数据写到socket套字节
if(ret < 0)
{
FD_CLR(fd,&set);
close(fd);
}
}
if(FD_ISSET(fd,&rset))//测试socket套字节是否准备好
{
ret = read(fd,buff,sizeof(buff)-1);
if(ret <= 0)
{
FD_CLR(fd,&set);//读取失败,表示不关心该套字节状态
close(fd);
break;
}
buff[ret]='\0';
printf("The message is:%s",buff);
}
}
failed:
close(fd);
return 0;
}
服务器端:
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <sys/select.h>
int main()
{
int fd,ret;
char buff[1024]={0};
struct sockaddr_in servaddr,clivaddr;
int len=sizeof(clivaddr);
fd=socket(AF_INET,SOCK_STREAM,0);
if(fd<0)
{
perror("opening socket error");
return -1;
}
memset(&servaddr,0,sizeof(servaddr));
servaddr.sin_family=AF_INET;
if(inet_pton(AF_INET,"192.168.7.92",&servaddr.sin_addr.s_addr)<=0)
{
perror("IP error");
return -2;
}
servaddr.sin_port=9000;
ret=bind(fd,(struct sockaddr *)&servaddr,sizeof(servaddr));
if(ret < 0 )
{
perror("binding error");
goto failed;
}
if((ret=listen(fd,10) != 0))
{
perror("listening error");
goto failed;
}
int nsock;
int maxfd;
fd_set set,rset;
FD_ZERO(&set);//初始化
FD_SET(fd,&set);//表示关心该端口
maxfd=fd;
int i;
while(1)
{
rset = set;
ret=select(maxfd+1,&rset,NULL,NULL,NULL);
if(ret < 0)
{
perror("select error");
break;
}
if(FD_ISSET(fd,&rset))//监听套字节状态是否准备好
{
//接受套字节
nsock=accept(fd,(struct sockaddr *)&clivaddr,&len);
if(nsock < 0)
{
perror("accept error");
break;
}
else
{
printf("socket service starting...\n");
}
FD_SET(nsock,&set);//重新设置表示关心的端口状态
maxfd=(maxfd > nsock ? maxfd : nsock);
FD_CLR(fd,&rset);//监听套字节只能accept不能read
}
for(i=0;i<=maxfd;i++)//处理其他客户端发送的套字节
{
if(i == fd)
continue;
if(!FD_ISSET(i,&rset))
{
continue;
}
ret = read(i,buff,strlen(buff));
if(ret < 0)
{
perror("read error");
}
if(ret == 0)
{
FD_CLR(i,&set);//该套字节没有准备好时,表示不关心该状态
close(i);
continue;
}
if(write(i,buff,ret) < 0)
{
FD_CLR(i,&set);//写入出错时表示不关心该状态
close(i);
}
}
}
printf("close....\n");
close(nsock);
failed:
close(fd);
return 0;
}