Socket编程之I/O复用

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;
}


  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值