I/O复用(一)——Select

本文深入探讨了I/O复用的概念及其在提高资源利用效率和减轻CPU负担方面的作用,详细解析了Linux下Select系统调用的原理及应用,通过实例展示了如何使用Select实现I/O复用。

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

为什么要有I/O复用

         从多进程多线程到进程池线程池,尽管我们处理事件的效率越来越高,但是却有一个问题,一直都没有解决,那就是,当服务器分配了一个线程或进程为某一个客户端服务时,该进程/线程就相当于与这个客户端绑定了,除非客户端断开连接,否则不管有没有请求事件,这个线程/进程都只能为这个客户端处理事件,我们知道,系统允许我们创建的线程/进程是有限的,这样其实会造成资源的浪费,所以,我们需要一套机制,在客户端没有请求事件的时候,该线程/进程可以去处理其他客户端的事件,也就是我们需要将多个文件描述符同时监听,当由请求时,去处理发出请求的客户端的事件。所以就有了我们的I/O复用。

什么是I/O复用

         I/O复用的本质就是一个线程监听多个文件描述符,我们可以这样理解,多线程多进程编程,就是老师对学生一对一辅导,但是不是所有的是时候学生都有问题,老师就闲了,会浪费很大的师资力量,而我们的I/O复用,就是一个老师辅导多个学生,学生有问题提出问题(发出请求事件),老师处理完就可以处理别的学生的问题了。然后就有人说了,I/O理解起来太麻烦,我用循环,轮询一遍就能找到该处理哪个事件了,但是你见过有老师一个一个问学生你有问题吗,那老师知道有人问问题就挨个问一遍,他还有力气处理问题么,同样的道理,我们的CPU那么可爱又忙碌,它时来处理事件的,不是挨个打招呼的,所以我们就需要直接告知CPU处理那个问题。

        通过上面这个简单的小故事,我们可以理解,I/O复用的两个好处

       一  它减少了进程/线程的创建数量,节省了我们的内存资源,使得资源更加高效的被利用。

      二  它减少了CPU的工作压力,CPU只需要做自己最擅长的时就好。

I/O实现

      其实在linux下I/O复用的实现有三个系统调用:poll,epoll,select,其中epoll为linux特有,今天我们就先来说说select。

      select系统调用的用途是:在一段指定时间内,监听用户感兴趣的文件描述符上的可读、可写和异常等事件。

      我们先来看一下select系统调用的原型:

            

(1)nfds 参数指定被监听的文件描述符的总数。它通常被设置成监听的所有文件描述符中的最大值加一,因为文件描述符的计数从0开始。

(2)readfds,writefds,exceptfds分别表示可读,可写,和异常等事件对应的文件描述符集合。

(3)timeout用来设置select函数的超时时间。

select成功时返回就绪文件描述符的总数,如果在超时时间内没有任何文件描述符就绪,select返回0,select失败返回-1并且设置errno。

这里面值得我们关注的有一个很重要的结构体fd_set,该结构体定义如下:

        我们可以看到,其实这个结构体仅仅包含了一个类型为long int的数组,数组长度为32;这个数组的每个元素的每一位标记一个文件描述符,也就是说做多监听1024个文件描述符,文件描述符最大是1023。

        我们都知道,这样的机制下,位操作相对来说是比较麻烦的,所以系统就很善良的为我们提供了一系列宏来访问fd_set中的位:

      另外一个值得关注的结构体就是我们的timeval,定义如下:

timeout有三种取值:

  1.  NULL               select一直阻塞,直到readfds、writefds、exceptfds集合中至少一个文件描述符就绪select才返回。
  2. 0                        select不阻塞。
  3. timeout_value  select在timeout_value这个时间段内阻塞,有事件就绪就返回或者超时返回。
     

select的应用——实现I/O复用

在实现之前我们需要注意几点问题:

(1) select返回是全部的文件描述符,我们需要轮询确定是哪个文件描述符上的事件发生

(2) select返回时会将fd_set结构的变量更改,所以我们每次select之前需要重置fd_set的值

好了,看代码喽

#include<stdlib.h>
#include<stdio.h>
#include<string.h>
#include<assert.h>
#include<arpa/inet.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<sys/select.h>
void INIT_fds(int *fds,int length)
{
	int i=0;
	for(;i<length;i++)
	{
		fds[1]=-1;
	}
}
void Insert_fd(int *fds,int fd,int length)

{
	int i=0;
	for(;i<length;i++)
	{
		if(fds[i]==-1)
		{
			fds[i]=fd;
			break;
		}
	}
}
void Delete(int *fds,int fd,int len)
{
	int i=0;
	for(;i<len;i++)
	{
		if(fds[i]==fd)
		{
			fds[i]=-1;
			break;
		}
	}

}
int main()
{
	int sockfd=socket(AF_INET,SOCK_STREAM,0);
	assert(sockfd!=-1);
	struct sockaddr_in ser,cli;
	memset(&ser,0,sizeof(ser));
		ser.sin_family=AF_INET;
		ser.sin_port=htons(6000);
		ser.sin_addr.s_addr=inet_addr("127.0.0.1");
		int res=bind(sockfd,(struct sockaddr*)&ser,sizeof(ser));
		assert(res !=-1);
		listen(sockfd,5);
		fd_set readfds;
		int fds[100];
		INIT_fds(fds,100);
		Insert_fd(fds,sockfd,100);
	while(1)
		{
			int max=-1;
			FD_ZERO(&readfds);
			int i=0;
			for(;i<100;i++)
			{
				if(fds[i]!=-1)
				{
					if(fds[i]>max)
					
					{
						max=fds[i];
					}
					FD_SET(fds[i],&readfds);
				}
			}
			int n=select(max+1,&readfds,NULL,NULL,NULL);
			if(n<0)
			{
				printf("select fail\n");
				continue;
			}
			int c;
			for(i=0;i<100;i++)
			{
				if(fds[i]!=-1&&FD_ISSET(fds[i],&readfds))

				{
					if(fds[i]==sockfd)
					{
						int len =sizeof(cli);
						 c=accept(sockfd,(struct sockaddr*)&cli,&len);
					if(c<0)
					{
						printf("one link error\n");
						continue;
					}
					Insert_fd(fds,c,100);
				}
				
				else
				{
					int fd=fds[i];
					char recvbuff[128]={0};
					int n = recv(c,recvbuff,127,0);
					if(n<=0)
					{
						close(fd);
						Delete(fds,fd,100);
						continue;
					}
					printf("%d: %s\n",c,recvbuff);
					send(c,"ok",2,0);
				}
			}
		}
	}
}

 

资源下载链接为: https://pan.quark.cn/s/1bfadf00ae14 “STC单片机电压测量”是个以STC系列单片机为基础的电压检测应用案例,它涵盖了硬件电路设计、软件编程以及数据处理等核心知识点。STC单片机凭借其低功耗、高性价比和丰富的I/O接口,在电子工程领域得到了广泛应用。 STC是Specialized Technology Corporation的缩写,该公司的单片机基于8051内核,具备内部振荡器、高速运算能力、ISP(在系统编程)和IAP(在应用编程)功能,非常适合用于各种嵌入式控制系统。 在源代码方面,“浅雪”风格的代码通常简洁易懂,非常适合初学者学习。其中,“main.c”文件是程序的入口,包含了电压测量的核心逻辑;“STARTUP.A51”是启动代码,负责初始化单片机的硬件环境;“电压测量_uvopt.bak”和“电压测量_uvproj.bak”可能是Keil编译器的配置文件备份,用于设置编译选项和项目配置。 对于3S锂电池电压测量,3S锂电池由三节锂离子电池串联而成,标称电压为11.1V。测量时需要考虑电池的串联特性,通过分压电路将高电压转换为单片机可接受的范围,并实时监控,防止过充或过放,以确保电池的安全和寿命。 在电压测量电路设计中,“电压测量.lnp”文件可能包含电路布局信息,而“.hex”文件是编译后的机器码,用于烧录到单片机中。电路中通常会使用ADC(模拟数字转换器)将模拟电压信号转换为数字信号供单片机处理。 在软件编程方面,“StringData.h”文件可能包含程序中使用的字符串常量和数据结构定义。处理电压数据时,可能涉及浮点数运算,需要了解STC单片机对浮点数的支持情况,以及如何高效地存储和显示电压值。 用户界面方面,“电压测量.uvgui.kidd”可能是用户界面的配置文件,用于显示测量结果。在嵌入式系统中,用
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值