IO多路复用-select(附通信代码)

IO多路复用-select

1. IO多路复用概述

I/O多路复用(I/O Multiplexing)是一种通过一种机制同时监听多个文件描述符(sockets、文件、设备等)的技术。它可以使一个进程在等待多个 I/O 操作完成时不会阻塞,从而提高程序的性能和响应性

通过这种方式在单线程/进程的场景下也可以在服务器端实现并发。常见的IO多路转接方式有:select、poll、epoll

I/O多路复用的优势在于它可以有效地管理大量的连接,避免了创建大量线程或进程的开销。它适用于需要同时处理多个连接,但每个连接的数据流量相对较小的情况,如网络服务器、聊天程序等。通过选择适当的多路复用机制,可以使程序更加高效地处理并发的 I/O 操作。

IO多路复用和多线程/进程的对比:

  • 多线程/进程并发是主线程调用accept函数, 检测客户端连接请求, 有则连接, 没有则阻塞
  • 子线程调用read/write函数

IO多路复用

  • 委托内核检测服务器端所有的文件描述符(通信和监听两类)
  • 检测到已就绪的文件描述符阻塞解除,并将这些已就绪的文件描述符传出

与多进程和多线程技术相比,I/O多路复用技术的最大优势是系统开销小,系统不必创建进程/线程,也不必维护这些进程/线程,从而大大减小了系统的开销。

2. select

2.1 函数原型

这个函数是跨平台的,Linux、Mac、Windows都支持

函数的函数原型:

#include <sys/select.h>
struct timeval {time_t      tv_sec;         /* seconds */suseconds_t tv_usec;        /* microseconds */
};int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval * timeout);

函数参数:

  • nfds:委托内核检测的这三个集合中最大的文件描述符 + 1
    • 内核需要线性遍历这些集合中的文件描述符,这个值是循环结束的条件
    • 在Window中这个参数是无效的,指定为-1即可
  • readfds:文件描述符的集合, 内核只检测这个集合中文件描述符对应的读缓冲区
    • 传入传出参数,读集合一般情况下都是需要检测的,这样才知道通过哪个文件描述符接收数据
  • writefds:文件描述符的集合, 内核只检测这个集合中文件描述符对应的写缓冲区
    • 传入传出参数,如果不需要使用这个参数可以指定为NULL
  • exceptfds:文件描述符的集合, 内核检测集合中文件描述符是否有异常状态
    • 传入传出参数,如果不需要使用这个参数可以指定为NULL
  • timeout:超时时长,用来强制解除select()函数的阻塞的
    • NULL:函数检测不到就绪的文件描述符会一直阻塞。
    • 等待固定时长(秒):函数检测不到就绪的文件描述符,在指定时长之后强制解除阻塞,函数返回0
    • 不等待:函数不会阻塞,直接将该参数对应的结构体初始化为0即可。

函数返回值:

  • 大于0:成功,返回集合中已就绪的文件描述符的总个数
  • 等于-1:函数调用失败
  • 等于0:超时,没有检测到就绪的文件描述符

另外初始化fd_set类型的参数还需要使用相关的一些列操作函数,具体如下:

// 将文件描述符fd从set集合中删除 == 将fd对应的标志位设置为0        
void FD_CLR(int fd, fd_set *set);
// 判断文件描述符fd是否在set集合中 == 读一下fd对应的标志位到底是0还是1
int  FD_ISSET(int fd, fd_set *set);
// 将文件描述符fd添加到set集合中 == 将fd对应的标志位设置为1
void FD_SET(int fd, fd_set *set);
// 将set集合中, 所有文件文件描述符对应的标志位设置为0, 集合中没有添加任何文件描述符
void FD_ZERO(fd_set *set);
2.2 fd_set细节描述

这个类型的数据有128个字节,也就是1024个标志位,和内核中文件描述符表中的文件描述符个数是一样的。

下图中的fd_set中存储了要委托内核检测读缓冲区的文件描述符集合。

  • 如果集合中的标志位为0代表不检测这个文件描述符状态
  • 如果集合中的标志位为1代表检测这个文件描述符状态

在这里插入图片描述

内核在遍历这个读集合的过程中,如果被检测的文件描述符对应的读缓冲区中没有数据,内核将修改这个文件描述符在读集合fd_set中对应的标志位,改为0,如果有数据那么这个标志位的值不变,还是1。

在这里插入图片描述

当select()函数解除阻塞之后,被内核修改过的读集合通过参数传出,此时集合中只要标志位的值为1,那么它对应的文件描述符肯定是就绪的,我们就可以基于这个文件描述符和客户端建立新连接或者通信了。

3. 并发处理

3.1 服务器端处理流程
  1. 创建监听的套接字 lfd = socket();

  2. 将监听的套接字和本地的IP和端口绑定 bind()

  3. 给监听的套接字设置监听 listen()

  4. 创建一个文件描述符集合 fd_set,用于存储需要检测读事件的所有的文件描述符

    通过 FD_ZERO() 初始化

    通过 FD_SET() 将监听的文件描述符放入检测的读集合中

  5. 循环调用select(),周期性的对所有的文件描述符进行检测

  6. select() 解除阻塞返回,得到内核传出的满足条件的就绪的文件描述符集合

    通过FD_ISSET() 判断集合中的标志位是否为 1

    如果这个文件描述符是监听的文件描述符,调用 accept() 和客户端建立连接

    将得到的新的通信的文件描述符,通过FD_SET() 放入到检测集合中

    如果这个文件描述符是通信的文件描述符,调用通信函数和客户端通信(这个一定是在第二轮开始今后才能检测到)

    ​ 如果客户端和服务器断开了连接,使用FD_CLR()将这个文件描述符从检测集合中删除

    ​ 如果没有断开连接,正常通信即可

  7. 重复第6步

在这里插入图片描述

3.2 通信代码

服务器端代码

//
// Created by 47468 on 2024/1/24.
// server.cpp
#include "arpa/inet.h"
#include <cstdio>
#include <cstring>
#include "unistd.h"
#include "iostream"
#include "string"
#include "cctype"
using namespace std;int main(){// 1. 创建监听的套接字int lfd = socket(AF_INET, SOCK_STREAM, 0);// 2. 绑定sockaddr_in saddr{};saddr.sin_family = AF_INET;saddr.sin_port = htons(9999);saddr.sin_addr.s_addr = INADDR_ANY;int res = bind(lfd, (struct sockaddr *) &saddr, sizeof(saddr));if(res == -1){perror("bind");close(lfd);return -1;}// 3.设置监听res = listen(lfd, 128);if(res == -1){perror("listen");close(lfd);return -1;}// 将监听的fd的状态检测委托给内核检测int maxfd = lfd;// 初始化检测的读集合fd_set rdset;fd_set rdtemp;// 初始化FD_ZERO(&rdset);// 将监听的lfd设置到检测的读集合中FD_SET(lfd, &rdset);// 通过select委托内核检测读集合中的文件描述符状态, 检测read缓冲区有没有数据// 如果有数据, select解除阻塞返回// 应该让内核持续检测while (true){// 默认阻塞// rdset 中是委托内核检测的所有的文件描述符rdtemp = rdset;int num = select(maxfd + 1, &rdtemp, nullptr, nullptr, nullptr);// rdset中的数据被内核改写了,// 只保留了发生变化的文件描述的标志位上的1,// 没变化的改为0// 只要rdset中的fd对应的标志位为1 -> 缓冲区有数据了// 判断// 有没有新连接if(FD_ISSET(lfd, &rdtemp)){sockaddr_in cliaddr{};int len = sizeof(cliaddr);int cfd = accept(lfd, (struct sockaddr *) &cliaddr, (socklen_t *) (&len));char ip[32];cout << "有客户端成功连接, 客户端ip: "<< inet_ntop(AF_INET, &cliaddr.sin_addr.s_addr, ip, sizeof(ip))<< ", port: "<< ntohs(cliaddr.sin_port)<< endl;// 得到了有效的文件描述符// 通信的文件描述符添加到读集合// 在下一轮select检测的时候, 就能得到缓冲区的状态FD_SET(cfd, &rdset);// 更新maxfdmaxfd = max(maxfd, cfd);}// 再检测有没有通信的文件描述符for (int i = 0; i <= maxfd; ++i) {// 判断从监听的文件描述符之后到maxfd这个范围内的文件描述符是否读缓冲区有数据if(i != lfd && FD_ISSET(i, &rdtemp)){// 接收数据char buf[10] = {0};// 一次只能接收10个字节, 客户端一次发送100个字节// 一次是接收不完的, 文件描述符对应的读缓冲区中还有数据// 下一轮select检测的时候, 内核还会标记这个文件描述符缓冲区有数据 -> 再读一次// 	循环会一直持续, 直到缓冲区数据被读完位置ssize_t len = read(i, buf, sizeof(buf));if(len == 0){cout << "客户端断开了连接..." << endl;FD_CLR(i, &rdset);close(i);}else if(len > 0){buf[len] = '\0';// 收到了数据cout << "客户端: " << buf << endl;// 数据处理for (int j = 0; j < len; ++j) {buf[j] = toupper(buf[j]);}// 发送回去write(i, buf, len);} else{// 异常perror("read");}}}}return 0;
}

客户端代码

//
// Created by 47468 on 2024/1/24.
// client.cpp
#include "arpa/inet.h"
#include "cstdio"
#include <cstdlib>
#include "unistd.h"
#include "iostream"
using namespace std;
#include <cstring>int main(){int fd = socket(AF_INET, SOCK_STREAM, 0);if(fd == -1){perror("socket");exit(0);}sockaddr_in addr;addr.sin_family = AF_INET;inet_pton(AF_INET, "192.168.110.129", &addr.sin_addr.s_addr);addr.sin_port = htons(9999);int res = connect(fd, (struct sockaddr *) &addr, sizeof(addr));if(res == -1){perror("connect");close(fd);return -1;}// 通信while (true){// 键盘输入数据char readBuf[1024];cout << "请输入要发送的字符串:" << endl;cin.getline(readBuf, sizeof(readBuf));// 把数据发送到客户端write(fd, readBuf, strlen(readBuf));// 再把客户端发回来的数据读出来// 如果客户端没有发送数据, 默认阻塞read(fd, readBuf, sizeof(readBuf));cout << readBuf << endl;}close(fd);return 0;
}

测试:

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

主要实现了服务器和两个客户端通信的流程, 服务器端一次最多接受10个字节, 多于10个字节后, 会分多次接收

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

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

相关文章

luceda ipkiss教程 57:画微环调制器

案例分享&#xff1a;画微环调制器 全部代码如下&#xff1a; from si_fab import all as pdk from ipkiss3 import all as i3class DC(i3.PCell):straight_length i3.PositiveNumberProperty(default200)radius i3.PositiveNumberProperty(default50)spacing i3.Positive…

幻兽帕鲁PalWorld服务器搭建详细教程

幻兽帕鲁PalWorld是一款由Pocketpair开发的游戏&#xff0c;融合了多种玩法&#xff0c;其独特的题材和画风吸引了很多玩家。为了更好地进行游戏体验&#xff0c;很多玩家选择自行搭建服务器。本文将详细介绍如何搭建幻兽帕鲁PalWorld服务器。 第一步&#xff1a;购买服务器 根…

Unity | 渡鸦避难所-8 | URP 中利用 Shader 实现角色受击闪白动画

1. 效果预览 当角色受到攻击时&#xff0c;为了增加游戏的视觉效果和反馈&#xff0c;可以添加粒子等动画&#xff0c;也可以使用 Shader 实现受击闪白动画&#xff1a;受到攻击时变为白色&#xff0c;逐渐恢复为正常颜色 本游戏中设定英雄受击时播放粒子效果&#xff0c;怪物…

小程序直播项目搭建

项目功能&#xff1a; 登录实时聊天点赞功能刷礼物取消关注用户卡片直播带货优惠券直播功能 项目启动&#xff1a; 1 小程序项目创建与配置&#xff1a; 第一步 需要登录小程序公众平台的设置页面进行配置&#xff1a; 首先需要是企业注册的才可以个人不能开通直播功能。服务类…

Java-Objec

Objec Class Object是类Object结构的根。 每个班都有Object作为超类。 所有对象&#xff08;包括数组&#xff09;都实现了这个类的方法。 一.构造 二.常用方法 Modifier and TypeMethod and Descriptionprotected Objectclone() 创建并返回此对象的副本。 booleanequals(Obj…

应用实践|基于Python手把手教你实现雪花算法

&#x1f4eb; 作者简介&#xff1a;「六月暴雪飞梨花」&#xff0c;专注于研究Java&#xff0c;就职于科技型公司后端工程师 &#x1f3c6; 近期荣誉&#xff1a;华为云云享专家、阿里云专家博主、 &#x1f525; 三连支持&#xff1a;欢迎 ❤️关注、&#x1f44d;点赞、&…

【机器学习300问】17、什么是欠拟合和过拟合?怎么解决欠拟合与过拟合?

一个问题出现了&#xff0c;我们首先要描述这个问题&#xff0c;然后分析问题出现的原因&#xff0c;找到原因后提出解决方案。废话不多说&#xff0c;直接上定义&#xff0c;然后通过回归和分类任务的例子来做解释。 一、什么是欠拟合和过拟合&#xff1f; &#xff08;1&am…

文件上传技术总结

语言可解析的后缀 &#xff08;前提&#xff1a;在Apache httpd.conf 配置文件中有特殊语言的配置 AddHandler application/x-httpd-php .php 搭配大小写、双重、空格来进行 其中&#xff1a; phtml、pht、php3、php4和php5都是Apache和php认可的php程序的文件后缀 常见的…

解决vld内存泄露检测工具只支持到vs2015的问题,visual studio2015以上版本安装vld内存泄漏检测工具[实测vs2022生效]

目录 一.vld工具下载二.vld应用安装三.visual studio2022环境配置四.visual studio2022 vld内存检测测试 一.vld工具下载 Visual Leak Detector github链接: https://kinddragon.github.io/vld/ 下载直达链接: https://github.com/KindDragon/vld/releases/tag/v2.5.1 下拉至…

Spring Boot 学习之——@SpringBootApplication注解(自动注解原理)

SpringBootApplication注解 springboot是基于spring的新型的轻量级框架&#xff0c;最厉害的地方当属**自动配置。**那我们就可以根据启动流程和相关原理来看看&#xff0c;如何实现传奇的自动配置 SpringBootApplication//标注在某个类上&#xff0c;表示这个类是SpringBoot…

初识汇编指令

1. ARM汇编指令 目的 认识汇编, 从而更好的进行C语言编程 RAM指令格式: 了解 4字节宽度 地址4字节对齐 方便寻址 1.1 指令码组成部分 : condition: 高4bit[31:28] 条件码 0-15 &#xff08;16个值 &#xff09; 条件码: 用于指令的 条件执行 , ARM指定绝大部分 都可…

MySql索引事务讲解和(经典面试题)

&#x1f3a5; 个人主页&#xff1a;Dikz12&#x1f525;个人专栏&#xff1a;MySql&#x1f4d5;格言&#xff1a;那些在暗处执拗生长的花&#xff0c;终有一日会馥郁传香欢迎大家&#x1f44d;点赞✍评论⭐收藏 目录 索引 概念 索引的相关操作 索引内部数据结构 事务 为…

Linux启动级别和密码问题文件

1、linux启动级别 如果安装的linux默认带的图形化界面&#xff0c;默认的运行级别为5 graphical.target 因为图形化太耗费资源了&#xff0c;想每次启动的时候&#xff0c;更改它的默认允许级别为命令行&#xff08;文本&#xff09; cat /etc/inittab 修改为命令行 多用户…

洛谷刷题-【入门2】分支结构

目录 1.苹果和虫子 题目描述 输入格式 输出格式 输入输出样例 2.数的性质 题目描述 输入格式 输出格式 输入输出样例 3.闰年判断 题目描述 输入格式 输出格式 输入输出样例 4.apples 题目描述 输入格式 输出格式 输入输出样例 5.洛谷团队系统 题目描述 …

大数据开发之SparkSQL

第 1 章&#xff1a;spark sql概述 1.1 什么是spark sql 1、spark sql是spark用于结构化数据处理的spark模块 1&#xff09;半结构化数据&#xff08;日志数据&#xff09; 2&#xff09;结构化数据&#xff08;数据库数据&#xff09; 1.2 为什么要有sparksql hive on s…

【教学类-综合练习-08】20240105 大3班 综合材料(美术类:骰子、面具、AB手环)

背景需求 年终了&#xff0c;清理库存&#xff0c;各种打印的题型纸都拿出来&#xff0c;当个别化学习材料 教学过程&#xff1a; 时间&#xff1a;2024年1月2日上午 班级&#xff1a;大3班&#xff08;2周才去一次&#xff09; 人数&#xff1a;17人

后端开发_单元测试

后端开发_单元测试 1. 简介2. JUnit 4使用方法2.1 jar包引入2.2 测试用例1. 简介 2. JUnit 4使用方法 2.1 jar包引入 1. 本地依赖引入方式 Junit4.jar包 2. maven方式引入jar <dep

SpringSecurity认证登录成功后获取角色菜单

目录 前言 一、RBAC模型 二、实战应用 1. 建立用户、角色、资源实体类 2. 数据层查询角色资源 3. 业务层实现&#xff0c;调用数据层查询接口 4. SystemController控制器菜单获取方法 5. menu.jsp菜单页面实现 前言 本篇文章接SSM项目集成Spring Security 4.X版本&…

【Java程序员面试专栏 专业技能篇】计算机网络核心面试指引

关于计算机网络部分的核心知识进行一网打尽,包括计算机的网络模型,各个层的一些重点概念,通过一篇文章串联面试重点,并且帮助加强日常基础知识的理解,全局思维导图如下所示 分层基本概念 计算机网络模型的分层及具体作用 计算机网络有哪些分层模型 可以按照应用层到物…

2023春秋杯冬季赛 --- Crypto wp

文章目录 前言Cryptonot_wiener 前言 比赛没打&#xff0c;赛后随便做一下题目 Crypto not_wiener task.py: from Crypto.Util.number import * from gmpy2 import * import random, os from hashlib import sha1 from random import randrange flagb x bytes_to_long(f…