select函数详解:IO复用

select函数概述

select函数是一种用于实现I/O复用的方法,它可以让程序在多个文件描述符(例如套接字)之间进行选择,以便在其中任何一个或多个可用时执行I/O操作。这种机制使得程序能够更高效地处理多个I/O操作。下面将对select的原理和工作机制进行详细介绍,并分析select函数的优势和局限。

1. select的原理和工作机制

select函数的原理和工作机制可以概括为以下几个步骤:

  1. 初始化文件描述符集合:程序需要为select函数准备三个文件描述符集合,分别表示要监控的读、写和异常条件。这些集合通常由FD_SET、FD_CLR、FD_ISSET和FD_ZERO这四个宏来操作。

  2. 调用select函数:程序调用select函数,并传入监控的文件描述符集合。此外,还需要设置一个超时时间,以便在没有任何I/O事件发生时,select函数能够在超时后返回。

  3. 等待I/O事件:select函数会阻塞,直到至少有一个文件描述符准备好进行I/O操作,或者超时时间到达。

  4. 检查文件描述符状态:select函数返回后,程序需要检查文件描述符集合的状态,以确定哪些文件描述符准备好进行I/O操作。然后,程序可以根据文件描述符的状态来执行相应的读、写或异常处理操作。

  5. 重复以上过程:在执行完当前的I/O操作后,程序可以再次调用select函数,以继续监控文件描述符的状态。

2.select函数详解

为了更好地理解select函数,本节将详细介绍其函数原型、参数解析以及返回值分析。
2.1. 函数原型
在C语言中,select函数的原型如下:
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
2.2. 参数解析
接下来,我们将逐个解析select函数的参数。
2.2.1. nfds
nfds表示需要监控的文件描述符的最大值加1。这个值通常设为所有文件描述符中的最大值加1,以确保select能够正确地监控所有需要的文件描述符。
2.2.2. readfds, writefds, exceptfds
readfds,writefds和exceptfds分别表示要监控的读、写和异常条件的文件描述符集合。它们是由fd_set类型表示的位图结构。可以使用以下四个宏来操作这些集合:
• FD_SET(fd, &set): 将文件描述符fd添加到set集合中。
• FD_CLR(fd, &set): 从set集合中删除文件描述符fd。
• FD_ISSET(fd, &set): 检查fd是否在set集合中。
• FD_ZERO(&set): 清空set集合。
2.2.3. timeout
timeout参数是一个timeval结构指针,用于设置select函数的超时时间。当timeout为NULL时,select将无限期地等待,直到有文件描述符准备好。当timeout设置为0时,select将立即返回。当timeout设置为非零值时,select将等待指定的时间,直到有文件描述符准备好或超时。
timeval结构如下:
struct timeval {
    long tv_sec;   // seconds
    long tv_usec;  // microseconds
};

2.3. 返回值分析

select函数的返回值表示以下三种情况:
1. 返回值大于0:表示有准备好的文件描述符,即已经发生的I/O事件数量。
2. 返回值等于0:表示超时,即在指定的时间内没有任何I/O事件发生。
3. 返回值小于0:表示发生错误。在这种情况下,可以使用perror或strerror函数来获取错误信息。
在调用select函数后,可以通过检查readfds,writefds和exceptfds集合的状态,以确定哪些文件描述符准备好进行I/O操作。然后,程序可以根据文件描述符的状态来执行相应的读、写或异常处理操作。

3.使用IO多路复用完成TCP并发服务器

#include <myhead.h>
#define SERIP "192.168.0.135"
#define SERPORT 8888
#define BACKLOG 10
int main(int argc, const char *argv[])
{
	//1创建套接字
	int oldfd = socket(AF_INET,SOCK_STREAM,0);
	if(oldfd==-1)
	{
		perror("socket");
		return -1;
	}

	//2启动端口号快速复用
	int kkk=9;
	if(setsockopt(oldfd,SOL_SOCKET,SO_REUSEADDR,&kkk,sizeof(kkk))==-1)
	{
		perror("setsockopt");
		return -1;
	}
	printf("端口号快速复用成功\n");
	//定义要绑定的服务器Ip和端口号
	struct sockaddr_in server = {
	.sin_family = AF_INET,
	.sin_port = htons(SERPORT),
	.sin_addr.s_addr = inet_addr(SERIP)
	};
	//3绑定IP和端口号
	if(bind(oldfd,(struct sockaddr *)&server,sizeof(server))==-1)
	{
		perror("bind");
		return -1;
	}

	//4监听
	if(listen(oldfd,BACKLOG)==-1)
	{
		perror("listen");
		return -1;
	}
	printf("监听成功\n");
	struct sockaddr_in client;
	int client_len = sizeof(client);
	char buff[1024];

	fd_set readfds,temp;//1、定义容器存储描述符
	FD_ZERO(&readfds);//2、清空容器
	FD_SET(0,&readfds);//3放入输入描述符
	FD_SET(oldfd,&readfds);//4、放入台阶在描述符

	int maxfd = oldfd;//定义最文件描述符
	int newfd;

	while(1)
	{
		temp = readfds;
		int res = select(maxfd+1,&temp,NULL,NULL,NULL);//永久阻塞
		if(res==0)
		{
			printf("timeout");
			return -1;
		}
		if(res==-1)
		{
			perror("select");
			return -1;
		}
		//程序执行至此说明描述符有事件触发了select函数
		for(int i = 0;i<=maxfd;i++)//遍历所有的文件描述符
		{
			
			if(!FD_ISSET(i,&temp))//描述符不存在集合内继续下次循环
			{
				continue;
			}

			if(i==oldfd)//套接字事件解除的阻塞
			{
				//5接收客户端请求
				newfd = accept(oldfd,(struct sockaddr *)&client,&client_len);
				if(newfd==-1)
				{
					perror("accept");
					return -1;
				}
				printf("客户端%s:%d已上线\n",inet_ntoa(client.sin_addr),ntohs(client.sin_port));
				FD_SET(newfd,&readfds);//新客户端产生后将新描述符放入集合
				maxfd = newfd>maxfd?newfd:maxfd;//更新最大文件描述符
			}
			else//newfd产生的解除阻塞
			{
				bzero(buff,sizeof(buff));//清空数组
				int len = recv(newfd,buff,sizeof(buff),0);//接收消息
				if(len==0)
				{
					printf("客户端下线\n");
					FD_CLR(i,&readfds);
					maxfd = newfd>maxfd?newfd:maxfd;//再次更新最大文件描述符
				}
				printf("收到信息:%s\n",buff);
				strcat(buff,"*_*");
				send(newfd,buff,sizeof(buff),0);//回复客户端消息
				printf("回复成功\n");	
			}
		
		}
	}
	close(oldfd);
	return 0;
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值