io复用函数的使用

news2024/11/27 14:29:45

目录

一、概念

 二、使用

1.select系统调用

代码实现

前言:

        一般多客户端在和服务器通信时,服务器在执行recv时会先阻塞,然后按照顺序依次处理客户端,无论客户端有无数据都会被处理,这样大大降低了执行效率。此时就引入i/o复用技术,提高网络程序效率

        io的处理方式是没有数据的客户端忽略不管,一旦接收就阻塞起来,有数据的客户端接收。

一、概念

  • I/O复用可以同时监听多个文件描述符/多个套接字,以及检测套接字描述符内有无数据
  • 通常,以下情况下需要使用i/o复用:
    • 客户端程序要同时处理多个socket,虽然多线程也可以解决该问题,但是当客户端个数逐渐增大时,在多线程之间切换的开销会大大加大。
    • 客户端程序要同时处理用户输入和网络连接
    • TCP服务器要同时处理监听socket和连接socket。这是i/o复用使用最多的场合
    • 服务器要同时处理TCP请求和UDP请求
    • 服务器要同时监听多个端口,或处理多种服务
  • Linux下实现i/o复用的系统调用主要有select、poll和epoll。
  • i/o复用操作流程:

    • 先检查哪些描述符上有数据,哪些无数据

    • 找到有数据的描述符,然后进行处理

        需要指出的是,I/O 复用虽然能同时监听多个文件描述符,但它本身是阻塞的。并且当多个文件描述符同时就绪时,如果不采取额外的措施,程序就只能按顺序依处理其中的每一个文件描述符,这使得服务器看起来好像是串行工作的。如果要提高并发处理的能力,可以配合使用多线程或多进程等编程方法。

 二、使用

1.select系统调用

  • 用途:在一段指定时间内,监听用户感兴趣的文件描述符上的可读、可写和异常等事件。
  • 函数原型:
#include<sys/select.h>

int select(int nfds,fd_set* readfds,fd_set* writefds,fd_set* exceptfds,struct timeval* timeout);
  • 参数:
    • 第一个参数nfds指定被监听的文件描述符总数。通常被设置为select监听的所有文件描述符中的最大值+1,因为文件描述符是从0开始计数的。
    • 第二、三、四个参数分别指向可读、可写和异常等事件对应的文件描述符集合。应用程序调用select函数时,通常从这三个参数中传入自己感兴趣的文件描述符。
    • 第五个参数timeout用来设置select函数的超时事件。它是timeval结构类型的指针,采用指针参数是因为内核将修改它以告诉应用程序select等待了多久。
      • struct timeval
        {
            long tv_sec;//微秒
            long tv_usec;//微秒数
        }

        select提供了一个微秒级的定时方式。如果timeout变量的tv_sec成员和tv_usec成员都传递0,则select将立即返回。如果timeout传递NULL,则select将一直阻塞,直到某个文件描述符就绪。

    • 返回值:select调用返回时,内核将修改它们来通知应用程序哪些文件描述符已经就绪。这三个参数时fd_set结构指针类型。fd_set结构体定义在下面讲。
  • 返回值:
    • 成功时返回就绪(可读、可写和异常)文件描述符的总数(不会告知是哪个文件描述符,而是总数)。       
    • 返回值为0表示超时,在超时时间内没有任何文件描述符就绪
    • 返回值为-1可能是失败,并设置srrno;如果在在select等待期间,程序收到信号,则select也会立即返回-1,并设置为EINTR。

 fd_set结构体定义:

        由以上定义可见,该结构体仅包含了一个整型数组,该数组的每个元素的每一位(bit)标记一个文件描述符。fd_set能容纳的文件描述符由FD_SETSIZE指定,可容纳文件描述符的位数是1024位,0-1023。按位偏移,0放在第一个位上。

        由于位操作的繁琐(按位与规则:1&1=1. 1&0=0. 0&1=0. 0&0=0. 只要和1相与结果不为0则为真,也就是该文件描述符被设置过。1>>3 就是第三位和1按位与操作),我们使用下面的一系列宏来访问fd_set结构体中的位:

#include<sys/select.h>
FD_ZERO(fd_set *fdset); //轻触fdset中所有位
FD_SET(int fd,fd_set *fdset);//设置fdset的位fd
FD_CLR(int fd,fd_set *fdset);//清除fdset的位fd
int FD_ISSET(int fd,fd_set *fdset);//测试fdset的位fd是否被设置

 什么叫事件就绪?
以读事件和写事件为例:

  • 读事件:当执行recv()时,把数据发送到接收缓冲区,如果接收缓冲区满,此时recv就会被阻塞,读事件就没有就绪,反之,缓冲区未满,读事件就绪
  • 写事件:当执行send()时,把数据写入到发送缓冲区,如果发送缓冲区满,此时send就会被阻塞,写事件就没有就绪,反之,缓冲区未满,写事件就绪

文件描述符就绪条件:

哪些情况下文件描述符可以被认为是可读、可写或异常情况,对于select的使用非常的关键。

  • 在网络编程中以下情况socket可读:
    • socket内核接收缓冲区中字节数大于或等于其低水位标记SO_RECVLOWAT。此时可以无阻塞的读该socket,并且读操作返回的字节数大于0.
    • socket通信的对方关闭连接。此时对该socket的读操作返回0
    • 监听socket上有新的连接请求
    • socket上有未处理的错误。此时可以使用getsockopt来读取和清除该错误。
  • 以下socket可写:
    • socket内核发送缓冲区中的可用字节数>=其低水位标记SO_SNDLOWAT。此时我们可以无阻塞的写该socket,并且写操作返回的字节数>0
    • soket的写操作被关闭。对写操作被关闭的socket执行写操作将出发一个SIGPOPE信号。
    • socket使用非阻塞connect连接成功或失败(超时)之后
    • socket上有未处理的错误。此时我们可以使用getsockopt来读取和清除该错误
  • 网络编程中,select能处理的异常只要一种:socket上接收到带外数据

代码实现

i/o实现流程:

1.i/o函数检测描述符的数据有无情况。返回值会告知有几个

2.具体检测哪个文件描述符有数据

2.多个描述符内查找我们感兴趣的事件

下面用代码实现在键盘输入文件描述符,有数据打印出来,没数据就进行检测,最长等待5秒:

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<sys/select.h>
#include<sys/time.h>

#define STDIN 0
int main()
{
	int fd=STDIN;
	fd_set fdset;//收集描述符
//1.描述符中最大值+1
	while(1)//如果有多个描述符就要循环存放
	{
		FD_ZERO(&fdset);//清空集合
		FD_SET(fd,&fdset);//把键盘对应的描述符fdset添加到集合内
		struct timeval tv={5,0};//超时时间
		int n=select(fd+1,&fdset,NULL,NULL,&tv);//当前操作写事件
		if(n==-1)
		{
			printf("select err\n");
		}
		else if(n==0)
		{
			printf("time out\n");
		}
		else//就绪状态,有用户输入
		{
			if(FD_ISSET(fd,&fdset))//fd_isset()判断当前描述符内是否有数据,有数据则返回为真,执行下面语句
			{
				char buff[128]={0};
				read(fd,buff,127);//开始读取数据
				printf("buff=%s\n",buff);//输出数据
			}
		}
	}
}

运行结果: 

如果超过5秒未输入或者输入的时间超过5秒还未发送,都会弹出“time out”超时的提示

如果上次的还没发出去(输入的太慢超过5秒)会在缓冲区内存着跟着下次一起发送


用select实现tcp服务器代码:

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<sys/select.h>
#include<sys/time.h>
#include<netinet/in.h>
#include<arpa/inet.h>

#define MAXFD 10
//初始化描述符
void fds_init(int fds[])
{
	for(int i=0;i<MAXFD;i++)
	{
		fds[i]=-1;
	}
}
//添加描述符
void fds_add(int fd,int fds[])
{
	for(int i=0;i<MAXFD;i++)
	{
		if(fds[i]=-1)//说明未被使用
		{
			fds[i]=fd;
			break;
		}
	}
}
//移除描述符
void fds_del(int fd,int fds[])
{
	for(int i=0;i<MAXFD;i++)
	{
		if(fds[i]==fd)
		{
			fds[i]=-1;
			break;
		}
	}
}

//创建tcp监听套接字
int socket_init()
{
	int sockfd=socket(AF_INET,SOCK_STREAM,0);
	if(sockfd==-1)
		return -1;
	struct sockaddr_in saddr;
	memset(&saddr,0,sizeof(saddr));
	saddr.sin_family=AF_INET;
	saddr.sin_port=htons(6000);
	saddr.sin_addr.s_addr=inet_addr("127.0.0.1");
	int res=bind(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));
	if(res==-1)
		return -1;
	res=listen(sockfd,5);
	if(res==-1)
		return -1;
	return sockfd;

}

int accept_client(int sockfd)
{
	struct sockaddr_in caddr;
	int len=sizeof(caddr);
	int c=accept(sockfd,(struct sockaddr*)&caddr,&len);
	return c;
}

int main()
{
	int fds[MAXFD];//用于收集描述符
	fds_init(fds);//初始化描述符
	int sockfd=socket_init();//初始化套接字
	if(sockfd==-1)
		exit(0);
	fds_add(sockfd,fds);//将套接字添加到描述符
	fd_set fdset;//集合收集描述法
	//找到描述符最大值,并将描述法都添加到集合内
	while(1)
	{
		FD_ZERO(&fdset);
		int maxfd=-1;
		for(int i=0;i<MAXFD;i++)
		{
			if(fds[i]==-1)
				continue;
			FD_SET(fds[i],&fdset);//将数组中有效(>=0)的描述符添加到集合
			if(fds[i]>maxfd)
			{
				maxfd=fds[i];//找到最大的描述符
			}
		}
		struct timeval tv={5,0};
		int n=select(maxfd+1,&fdset,NULL,NULL,&tv);//读
		if(n==-1)
		{
			printf("select err\n");
		}
		else if(n==0)
		{
			printf("time out\n");
		}
		else
		{
			for(int i=0;i<MAXFD;i++)
			{
				if(fds[i]==-1)
				{
					continue;
				}
				if(FD_ISSET(fds[i],&fdset))//测试该监听套接字是否有数据
				{
					if(fds[i]==sockfd)//监听套接字,accept
					{
						int c=accept_client(fds[i]);
						if(c!=-1)
						fds_add(c,fds);//添加新接收的连接
					}
				
					else//连接套接字,recv处理
					{
						char buff[128]={0};
						int num=recv(fds[i],buff,127,0);
						if(num<=0)
						{
							close(fds[i]);
							fds_del(fds[i],fds);
						}
						else
						{
							printf("recv:%s\n",buff);
							send(fds[i],"ok",2,0);
						}
					}
				}
			}
		}
	}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/85825.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

代理 模式

代理模式 Proxy Pattern 为其他对象提供一个代理以控制对这个对象的访问 可以详细控制访问某个&#xff08;某类&#xff09;对象的方法&#xff0c;在调用这个方法前做前置处理&#xff0c;调用这个方法后做后置处理。 静态代理 直接写死的代码的代理逻辑 动态代理 动态…

12.2、后渗透测试--令牌窃取

攻击机kali&#xff1a;192.168.11.106靶机windows server 2008 R2&#xff1a;192.168.11.134&#xff08;包含ms17_010漏洞&#xff09;一、令牌简介与原理 令牌(Token) 就是系统的临时密钥&#xff0c;相当于账户名和密码&#xff0c;用来决定是否允许这次请求和判断这次请求…

二进制搭建k8s——部署node节点

上篇&#xff1a;二进制搭建k8s——部署etcd集群和单master 二进制搭建k8s——部署node节点二进制搭建k8s——部署node节点环境部署node节点部署网络组件方法一&#xff1a;部署Flannel方法二&#xff1a;部署 CalicoCNI网络插件介绍Kubernetes的三种网络K8S 中 Pod 网络通信&a…

浅浅讲解下Linux内存管理之CMA

说明&#xff1a; Kernel版本&#xff1a;4.14ARM64处理器&#xff0c;Contex-A53&#xff0c;双核使用工具&#xff1a;Source Insight 3.5&#xff0c; Visio 1. 概述 Contiguous Memory Allocator, CMA&#xff0c;连续内存分配器&#xff0c;用于分配连续的大块内存。CMA…

c语言内存和文件处理有关知识

内存 分配内存的函数calloc&#xff0c;malloc 定义于头文件 <stdlib.h>功能malloc分配内存(函数)calloc分配并清零内存(函数)realloc扩充之前分配的内存块(函数)free归还还之前分配的内存(函数)aligned_alloc(C11)分配对齐的内存(函数) 函数原型 void *malloc(unsigne…

Java基础之Collection的ArrayList

Java基础之Collection的ArrayList一、add()与addAll()二、remove()三、trimToSize()1、案例一、add()与addAll() 跟C 的vector不同&#xff0c;ArrayList没有push_back()方法&#xff0c;对应的方法是add(E e)&#xff0c;ArrayList也没有insert()方法&#xff0c;对应的方法是…

Oracle---初学篇

Oracle初学篇 Oracle的启动&#xff0c;监听&#xff0c;用户 文章目录Oracle初学篇Oracle的启动Oracle的监听监听服务的主要文件1.listener.ora2.tnsnames.ora3.sqlnet.oraOracle用户Oracle安装成功后默认的三个用户创建用户Oracle的启动 之前写了关于如何在CentOS7上搭建Ora…

2021年全国研究生数学建模竞赛华为杯D题抗乳腺癌候选药物的优化建模求解全过程文档及程序

2021年全国研究生数学建模竞赛华为杯 D题 抗乳腺癌候选药物的优化建模 原题再现&#xff1a; 一、背景介绍   乳腺癌是目前世界上最常见&#xff0c;致死率较高的癌症之一。乳腺癌的发展与雌激素受体密切相关&#xff0c;有研究发现&#xff0c;雌激素受体α亚型&#xff0…

LeetCode 0547. 省份数量:图的连通分量

【LetMeFly】547.省份数量 力扣题目链接&#xff1a;https://leetcode.cn/problems/number-of-provinces/ 有 n 个城市&#xff0c;其中一些彼此相连&#xff0c;另一些没有相连。如果城市 a 与城市 b 直接相连&#xff0c;且城市 b 与城市 c 直接相连&#xff0c;那么城市 a …

Windows文件夹开启大小写敏感

Windows 的文件系统的文件名&#xff0c;是大小写不敏感的&#xff0c;也就是你的文件名是 a.txt 或者 A.txt&#xff0c;在 Windows 中都是一视同仁&#xff0c;认为是同一个文件。 自从 Windows 10 引入 Linux 子系统&#xff08;WSL&#xff09;后&#xff0c;有越来越多开…

JAVA毕业设计——基于ssm的汽车租赁管理系统 (源代码+数据库)

代码地址 https://github.com/ynwynw/carRental-public 毕业设计所有选题地址 https://github.com/ynwynw/allProject 基于Springboot的汽车租赁管理系统 (源代码数据库)601 一、系统介绍 汽车租赁系统总共分为两个大的模块&#xff0c;分别是系统模块和业务模块。其中系统模…

基于混沌原子搜索优化的电力系统(HPS)负载频率自动控制(ALFC)(Matlab代码实现)

&#x1f468;‍&#x1f393;个人主页&#xff1a;研学社的博客 &#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜…

Spring Security 中的四种权限控制方式

Spring Security 中对于权限控制默认已经提供了很多了&#xff0c;但是&#xff0c;一个优秀的框架必须具备良好的扩展性&#xff0c;恰好&#xff0c;Spring Security 的扩展性就非常棒&#xff0c;我们既可以使用 Spring Security 提供的方式做授权&#xff0c;也可以自定义授…

如何实现外网访问API接口

Application Programming Interface 缩写为API&#xff0c;中文翻译为“应用程序接口”&#xff0c;是一些预先定义的函数&#xff0c;或指软件系统不同组成部分衔接的约定。目的是提供应用程序与开发人员基于某软件或硬件得以访问一组例程的能力&#xff0c;而又无需访问源码&…

TVS管开关电源防护应用及电源防护元件的品类

瞬态抑制二极管简称TVS管。其作用原理是能够在极短的时间内承受反向电压的冲击&#xff0c;使得两极之间的电压钳位在特定电压水平上&#xff0c;有效避免了对后面电路的冲击&#xff0c;从而保护了被保护电子线路中的精密元件不受其损害。 瞬态抑制二极管TVS的钳位响应速度为为…

pytest + yaml 框架 -7.用例分层机制

前言 当我们测试流程类的接口&#xff0c;需反复去调用同一个接口&#xff0c;就会想到复用API&#xff0c;在代码里面可以写成函数去调用。 那么在yaml 文件中&#xff0c;我们可以把单个API写到一个yaml 文件&#xff0c;测试用例去调用导入API。 pip 安装插件 pip instal…

电子产品设计的流程有哪些

电子产品设计过程是指导工业外观设计的具体环节&#xff0c;主要包括产品市场需求分析、产品设计、产品原型设计、生产测试设计、大规模生产等方法和步骤。 一、电子产品设计流程是什么? 1.产品市场需求分析是电子产品设计成功的第一步&#xff0c;也是非常重要的一步。开发者…

JavaSE(数组)

1. 数组 数组创建及初始化 总结&#xff1a; 三种写法包括了动态初始化和静态初始化&#xff0c;其中省略格式不能再省略&#xff08;拆分&#xff09;&#xff1b;没有初始化时&#xff08;默认值为基类类型对应的默认值&#xff09;其中引用类型的默认值为null 三种写法 1…

RK3288-8.1-添加一个人体感应的开关-pir_pin_status

收到一个人体感应的需求,在设置添加一个开关 第一步,确定人体感应接口的GPIO 可知GPIO7_B5为对应GPIO 第二步,注册对应DTS 然后去驱动内注册对应的节点 很基础的东西,就不多说了,省略一万字......................(得到节点) cat sys/devices/platform/attr/pir_pin_status …

CAD尺寸标注不显示数值

在创建一个新的CAD图纸后&#xff0c;使用CAD尺寸标注会看见只有一条直线而没有数字显示&#xff0c;这是为什么呢&#xff1f;实际上是存在数值的&#xff0c;只是默认的数值很小&#xff0c;如果图形尺寸过大就看不清楚需要放大才能看见&#xff0c;就需要调整CAD尺寸标注的格…