Linux C编程 SelectIO复用

I/O复用简述

概念

解决进程或线程阻塞到某个 I/O 系统调用而出现的技术,使进程不阻塞于某个特定的 I/O 系统调用
使用一个函数去检测多个句柄的IO

使用场合

  1. 当客户端处理多个描述符(通常是交互式输入、网路套接字)时,使用IO复用
  2. tcp服务器既要处理监听套接字,又要处理已连接套接字,一般要使用I/O复用。
  3. 如果一个服务器既要处理tcp又要处理udp,一般要使用I/O复用。
  4. 如果一个服务器要处理多个服务或多个服务时,一般要使用I/O复用。

IO复用常用函数

	select 、poll
	epoll

select编程

select函数简介

  1. select 函数
	   #include <sys/time.h>
       #include <sys/types.h>
       #include <unistd.h>

       int select(int nfds, fd_set *readfds, fd_set *writefds,
                  fd_set *exceptfds, struct timeval *timeout);
    //功能:轮询扫描多个描述符中的任一描述符是否发生响应,发生相应就立即返回,
    //没发生响应,等待timeout时间,返回

  • 参数:
参数名称说明
nfds指定要检测描述符的范围所检测描述符最大值+1
readfds可读描述符集监测该集合中的任意描述符是否有数据可读
writefds可写描述符集监测该集合中的任意描述符是否有数据可写
exceptfds异常描述符集监测该集合中的任意描述符是否发生异常
timeout超过规定时间后唤醒超过规定时间后唤醒
  • 返回值
    0:超时
    -1:出错
    大于0:准备好的文件描述符数量
  • 结构体
//超时结构体:

struct timeval{
	long tv_sec;//秒
	long tv_usec;//微秒
};
 
//比如等待10.2秒
struct timeval timeout;
timeoout.tv_sec = 10;
timeoout.tv_usec = 200000;
 
//将select函数的timeout参数设置为NULL则永远等待
  1. 集合操作函数
    描述符集合的操作:
    select()函数能对多个文件描述符进行监测,如果一个参数对应一个描述符,那么select函数的4个参数最多能监测4个文件描述,那他如何实现对多个文件描述符的监测的呢?
    大家想一想文件描述符基本具有3种特性(读、写、异常),如果我们统一将监测可读的描述符放入可读集合(readset),监测可写的描述符放入可写集合(writeset),监测异常的描述符放入异常集合(exceptset)。然后将这3个集合传给select函数,是不是就可监测多个描述符呢.
	//将一个文件描述符从集合中删除
  	   void FD_CLR(int fd, fd_set *set);
  	//检测该描述符在那个集合中的事件是否发生
       int  FD_ISSET(int fd, fd_set *set);
    //将文件描述符加入一个集合
       void FD_SET(int fd, fd_set *set);
    //初始化集合
       void FD_ZERO(fd_set *set);

tips
每次selcet之后必须重新初始化以及添加文件描述符,和集合,否则会发生错误
使用示例:集合操作函数必须放在while循环里。


while(1)
{
	fd_set rset;//创建一个描述符集rset
	FD_ZERO(&rset);//对描述符集rset清零
	FD_SET(0, &rset);//将描述符0加入到描述符集rset中
	FD_SET(4, &rset);//将描述符4加入到描述符集rset中
	FD_SET(5, &rset);//将描述符5加入到描述符集rset中
	
	if(select(5+1, &rset, NULL, NULL, NULL) > 0)
	{
		if(FD_ISSET(0, &rset))
		{
			//描述符0可读及相应的处理代码
		}
		
		if(FD_ISSET(4, &rset))
		{
			//描述符4可读及相应的处理代码
		}
		if(FD_ISSET(5, &rset))
		{
			//描述符5可读及相应的处理代码
		}
	}
}

select编程示例

//tcp_select_server.c
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/time.h>
#include <sys/types.h>

#define MAXBUFF  1024
static void usaAge(char * proc)
{
	printf("Please enter: %s [local_ip] [local_port] [listnum]\n",proc);
}

int main(int argc,char *argv[])
{
	int socked,new_fd;
	int listnum;
	socklen_t sender_len;
	int sendBuf_len=0;
	int recvBUf_len=0;
	struct sockaddr_in myaddr,sender;
	fd_set rfds;
	struct timeval tv;
	int retval,maxfd=1;
	char sendBuf[MAXBUFF+1];
	sender_len=sizeof(sender);
	if(argc==1)
	{
		usaAge(argv[0]);
	}
	bzero(&myaddr,sizeof(myaddr));
	myaddr.sin_family=AF_INET;
	if(argv[1])
	{
		myaddr.sin_addr.s_addr=inet_addr(argv[1]);
	}
	else
	{
		myaddr.sin_addr.s_addr=htonl(INADDR_ANY);
	}
	
	if(argv[2])
	{
		myaddr.sin_port=htons(atoi(argv[2]));
	}
	else
	{
		myaddr.sin_port=htons(8888);
	}
	
	if(argv[3])
	{
		listnum=atoi(argv[3]);
	}
	else
	{
		listnum=5;
	}
	socked=socket(AF_INET,SOCK_STREAM,0);
	if(socked==-1)
	{
		perror("socket failed");
	}
	
	if(bind(socked,(struct sockaddr*)&myaddr,sizeof(myaddr))<0)
	{
		perror("bind error");
	}
	
	if (listen(socked, listnum) == -1) 
	{
         perror("listen error");
    }
	else
	{
		printf("listen ok\n");
	}
	
	while(1)
	{
		new_fd=accept(socked,(struct sockaddr*)&sender,&sender_len);
		if(new_fd<0)
		{
			perror("accept error");
		}
		else
		{
			printf("Get connectls,ip is :%s port is %d\n",inet_ntoa(sender.sin_addr),ntohs(sender.sin_port));
		}
		while(1)
		{
			FD_ZERO(&rfds);
			FD_SET(0,&rfds);
			FD_SET(new_fd,&rfds);
			
			maxfd=new_fd;
			tv.tv_sec=1;
			tv.tv_usec=0;
			retval=select(maxfd+1,&rfds,NULL,NULL,&tv);
			if(retval==-1)
			{
				perror("select failed");
			    exit(EXIT_FAILURE);
			}
			else if(retval==0)
		    {
				printf("Timeout\n");
			}
			else
			{
				if(FD_ISSET(0,&rfds))
				{
					printf("You can sendMessage\n");
					memset(sendBuf,0,MAXBUFF+1);
					fgets(sendBuf,MAXBUFF,stdin);
					if(!strncasecmp(sendBuf,"quit",4))
					{
						printf("I will quit\n");
						break;
					}
					sendBuf_len=send(new_fd,sendBuf,strlen(sendBuf)-1,0);
					if (sendBuf_len > 0)
                       	printf ("send successful,%d byte send!\n",sendBuf_len);
                     else {
							printf("send failure!");
							break;
						}
				}
				if(FD_ISSET(new_fd,&rfds))
				{
					printf("Receive Message\n");
					bzero(sendBuf, MAXBUFF + 1);
					recvBUf_len=recv(new_fd,sendBuf,MAXBUFF,0);
					if (recvBUf_len > 0)
                         printf ("recv success :'%s',%dbyte recv\n", sendBuf, recvBUf_len);
                    else 
					{
						printf("receive failure!");
						break;
                    }
					
				}
			}
		}
		close(new_fd);
		printf("need other connect\n");
		fflush(stdout);
		bzero(sendBuf,MAXBUFF+1);
		fgets(sendBuf, MAXBUFF, stdin);
		if (!strncasecmp(sendBuf, "no", 2)) 
		{
			printf("quit!\n");
			break;
		}
	}
	close(socked);
	return 0;
}
//tcp_select_cilent.c
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <sys/socket.h>
#include <resolv.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <sys/time.h>
#include <sys/types.h>

#define MAXBUFF 1024
static void usaAge(char * proc)
{
	printf("Please enter: %s [send_ip] [send_port\n",proc);
}

int main(int argc,char *argv[])
{
	int socked,new_fd;
	socklen_t sender_len;
	int sendBuf_len=0;
	int recvBUf_len=0;
	struct sockaddr_in sender;
	fd_set rfds;
	struct timeval tv;
	int retval,maxfd=1;
	char sendBuf[MAXBUFF+1];
	sender_len=sizeof(sender);
	
	if(argc==1)
	{
		usaAge(argv[0]);
	}
	bzero(&sender,sizeof(sender));
	sender.sin_family=AF_INET;
	if(argv[1])
	{
		sender.sin_addr.s_addr=inet_addr(argv[1]);
	}
	else
	{
		sender.sin_addr.s_addr=htonl(INADDR_ANY);
	}
	
	if(argv[2])
	{
		sender.sin_port=htons(atoi(argv[2]));
	}
	else
	{
		sender.sin_port=htons(8888);
	}
	socked=socket(AF_INET,SOCK_STREAM, 0);
	if(socked<0)
	{
		perror("socket failed");
	}
	 if (connect(socked, (struct sockaddr *) &sender, sizeof(sender)) != 0) 
    {
        perror("Connect ");
        exit(EXIT_FAILURE);
    }
	else
	{
		printf("Get connect,ip is :%s port is %d\n",inet_ntoa(sender.sin_addr),ntohs(sender.sin_port));
	}
	printf("\nget ready pls chat\n");
	while(1)
	{
		FD_ZERO(&rfds);
        FD_SET(0, &rfds);
        FD_SET(socked, &rfds);
        maxfd = socked;
        tv.tv_sec = 1;
        tv.tv_usec = 0;
        retval = select(maxfd + 1, &rfds, NULL, NULL, &tv);
		if(retval==-1)
		{
			perror("select failed");
			 exit(EXIT_FAILURE);
		}
		else if(retval==0)
		{
			printf("Timeout\n");
		}
		else
		{
			if(FD_ISSET(0,&rfds))
				{
					printf("You can sendMessage\n");
					memset(sendBuf,0,MAXBUFF+1);
					fgets(sendBuf,MAXBUFF,stdin);
					if(!strncasecmp(sendBuf,"quit",4))
					{
						printf("I will quit\n");
						break;
					}
					sendBuf_len=send(socked,sendBuf,strlen(sendBuf)-1,0);
					if (sendBuf_len > 0)
					printf ("send successful,%d byte send!\n",sendBuf_len);
					else 
					{
						printf("send failure!");
						break;
					}
				}
			 if(FD_ISSET(socked,&rfds))
			 {
				printf("Receive Message\n");
				bzero(sendBuf, MAXBUFF + 1);
				recvBUf_len=recv(socked,sendBuf,MAXBUFF,0);
				if (recvBUf_len > 0)
					 printf ("recv success :'%s',%dbyte recv\n", sendBuf, recvBUf_len);
				else 
				{
					printf("receive failure!");
					break;
				}			 
			 }
		}
	}
	close(socked);
	return 0;
} 

tips
程序调试的时候我发现了bug,就是你即使开始不传入argv的值,除了argv[1]=0x00,以后agrv[2]以后还是会有值。
在这里插入图片描述
在这里插入图片描述
导致在使用默认参数传参时,开启的端口不是8888,bug自行修改,或者直接传入参数运行
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值