C语言,select函数的使用方法以及为何使用

本文介绍了在多进程服务器端存在的问题,即频繁创建进程带来的资源消耗。为解决这一问题,文章提出了I/O复用的概念,并详细阐述了如何使用select函数来实现。select函数允许同时监视多个文件描述符,通过设置和检查fd_set结构体,监控读、写和异常状态。在给出的示例代码中,展示了如何监听标准输入并处理输入数据。通过使用select,可以在不创建新进程的情况下提高服务器并发处理能力。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

为什么使用select函数以及如何使用select函数

为何使用select函数

多进程服务器端的缺点和解决方法

为了构建并发服务器,只要有客户端连接请求就会创建进程。这的确是实际操作中采用的一种方案,但并非十全十美,因为创建进程时需要付出极大代价,这需要大量的运算和内存空间,由于每个进程都具有独立的内存空间,所以相互间的数据交换也要求采用相对复杂的方法(IPC属于相对复杂的通信方法)。
那有何解决方案呢?能否在不创建进程的同时向多个客户端提供服务? 答案是肯定能,I/O复用就是解决方法之一(还可以通过线程池解决,之后会讲解线程池)

如何使用select函数

运用select函数是最具代表性的实现复用服务器端的方法,而且他具有很好的移植性(Linux与Windows之间有同名函数),接下来以Linux为例。

select函数具体参数以及返回值

#incldue <sys/select.h>
#include <sys/time.h>
int select (int maxfd,fd_set* readset,fd_set* writeset,fd_set*exceptset,const struct timeval* timeout);

maxfd:监视对象文件描述符数量(一般是最大的文件描述符值加1)
readset:将所有关注“是否存在待读取数据”的文件描述符注册到fd_set型变量,并传递其地址值。
wirteset:将所有关注“是否可传输无阻塞数据”的文件描述符注册到fd_set型变量,并传递其地址值。
exceptset:将所有关注“是否发生异常”的文件描述符注册到fd_set型变量,并传递其地址值。
timeout:调用select函数后,为防止无线阻塞的状态,传递超时(time-out)信息。
返回值:错误返回-1,超时返回0。因发生关注的时间返回时,返回大于0的值,该值是发生时间的文件描述符数。

设置文件表描述符

利用select函数可以同时监视多个文件描述符。当然,监视文件描述符可以视为监视套接字。此时首先需要将要坚实的文件描述符集中到一起。集中时也要按照监视项(接收、传输、异常)进行区分,即按照上述3中监视项分成3类。
对fd_set变量中注册或更改值的操作都有下列宏完成。
1、FD_ZERO(fd_set* fdset):将fd_set变量的所有位初始化位0。
2、FD_SET(int fd,fd_set* fdset):在参数fdset指向的变量中注册文件描述符fd的信息。
3、FD_CLR(int fd,fd_set* fdset):从参数fdset指向的变量中清除文件描述符fd的信息。
4、FD_ISSET(int fd,fd_set* fdset):若参数fdset指向的变量中包含文件描述符fd的信息。则返回“真”。(用于验证select函数的调用结果)

操作fd0fd1fd2
初始值010
FD_ZERO(&set)000
FD_SET(1,&set)010
FD_SET(2,&set)011
FD_CLR(2,&set)010

调用select函数后查看结构

若select函数的返回值如果是大于0的整数,说明相应数量的文件描述符发生变化。
Example:
调用select函数前

fd0fd1fd2
011

调用select函数后(fd1变化后)

fd0fd1fd2
010

由上面两张表格可知,select函数调用完成后,向其传递的fd_set变量中将发生变化。原来为1的所有位均变为0,但发生变化的文件描述符对应为除外。因此,可以认为值仍为1的位置上的文件描述符发生了变化

select函数调用示例

#include <stdio.h>
#include <unistd.h>
#include <sys/time.h>
#include <sys/select.h>
#define BUF_SIZE 30

int main(int argc,char* argv[])
{
	fd_set reads,temps;
	int result,str_len;
	char buf[BUF_SIZE];
	struct timeval timeout;
	/* 看似复杂,实则简单。首先初始化fd_set变量,然后将文件描述符0对应的位设置为1。
	换言之,需要见识标准输入的变化*/
	FD_ZERO(&reads);
	FD_SET(0,&reads);

	while(1)
	{
		/* 将准备好的fd_set变量reads的内容复制到temps变量,因为之前讲过,调用select函数后,
		出发生变化的文件描述符对应位外,剩下的所有位将初始化为0。因此,为了记住初始值,
		必须经过这种复制过程。这是使用select函数的通用方法。*/
		temps = reads;
		/* 将初始化timeval结构体的代码插入循环后,每次调用selece函数前都会初始化新值*/
		timeout.tv_sec = 5;
		timeout.tv_usec = 0;
		// 调用select函数。如果有控制台输入数据,则返回大于0的整数;如果没有输入数据而引
		发超时,则返回0。
		result = select(1,&temps,0,0,&timeout);
		if(-1 == result)
		{
			puts("select() error!");
			break;
		}
		else if(0 == result)
		{
			puts("Time-out!");
		}
		else
		{
			/* select函数返回大于0的值时运行的区域。炎症发生变化的文件描述符是否为标准输入。
			若是,则从标准输入读取数据并发控制台输出。*/
			if(FD_SET(0,&temps))
			{
				str_len = read(0,buf,BUF_SIZE);
				buf[str_len] = 0;
				printf("message from console:%s",buf);
			}
		}
	}
	return 0;
}

总结

本文旨在用通俗易懂的方式来帮助初学者了解select,如有任何疑问或是错误,欢迎评论。

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值