当前位置: 首页 > news >正文

Linux多路转接

文章目录

    • IO模型
    • 多路转接
      • select 和 poll
      • epoll

IO模型

在还在学习语言的阶段,C++里使用cin,或者是C使用scanf的时候,总是要等着我们输入数据才执行,这种IO是阻塞IO。下面是比较正式的说法。
阻塞IO: 在内核将数据准备好之前,系统调用会一直等待数据的获取数据的做法,就是阻塞IO。

所以网络套接字,在你没有自行设置的情况下,用的也是阻塞方式IO。

上面说的仿佛只有读取一种情况,那么写呢?
实际上写也是有一样的问题,内核有缓冲区的空间才写,没有缓冲区空间就一样得阻塞。

非阻塞IO:顾名思义,如果需求的数据,内核还没卓备好,那么操作系统就会直接返回。

在C语言里,如果你使用C接口的非阻塞IO,如果没收到数据系统调用就返回,那么 宏变量 errno就会被设置,其值是EWOULDBLOCK 错误码。
C在C11标准之后,C++11标准之后,都是支持线程安全的。

那么问题来了,究竟什么是IO呢?
等待数据 + 拷贝数据
我们发现不论是网络的套接字,亦或者是我们常用的自己的输入输出,其实无非都在等待一些数据,把这些数据拷贝进我们的内存交由程序处理。

多路转接

理解多路转接之前,我们先思考一个问题,
IO=等待+拷贝。

那我们该什么时候区拷贝呢?
一些比较经典的操作就是,轮询,信号。
所谓轮询就是,每当我需要数据,我就问问你,数据好了没,没有我就稍等一会再来接着问,知道数据好了我取走。
所谓信号就是,当你数据好了你来通知我,让我来取走数据。

我们知道,一个主机可以和其他多个主机建立TCP链接,也就是需要使用多个套接字。

那么每当有一个新的连接来临,我们不想中断我主线程的业务,但是新连接的数据收发也要管理。该如何呢? 其中一般想到的是,开多个线程。
开多线程固然是一种解决方案,其对于一般服务器负载也没问题。

那么有成千上万的连接来临呢?要知道创建新线程也是有开销的,根据我的Linux下的POSIX线程库正常创建线程(不重新设置栈大小等),那么每一个线程约需要10MiB的空间。
算下来4GiB的内存,用户一般有3GiB,那么就是说约莫只有300个线程的情况,显然算不上高并发。

因此就有一种IO模型,其处于非阻塞IO,你的每一个文件描述符(windows下叫文件句柄),都有一个中间者来给你管理,当这些句柄有数据来临时,他来通知你,告诉你改处理这些数据了。而这就是多路转接。

下面介绍Linux多路转接常用的函数,select,poll,和epoll

select 和 poll


int select (int __nfds, fd_set *__restrict __readfds,fd_set *__restrict __writefds,fd_set *__restrict __exceptfds,struct timeval *__restrict __timeout);void FD_CLR(int fd, fd_set *set);int  FD_ISSET(int fd, fd_set *set);void FD_SET(int fd, fd_set *set);void FD_ZERO(fd_set *set);

fd_set # 文件描述符集的类型

注:select和poll在2.6版本之后用的较少,因为后来的机器内存都相对较大,同时主要因为有了epoll的出现,使之取代了select。

select维护了一个文件描述符集,FDS,其类型如上面的 fd_set,你可以用一些列函数接口来操作。当你有一个文件描述符是5号文件描述符,那么你就可以调用 FD_SET取设置入你的fd_set的数据类型里面。然后最后交给select帮你管理。

虽然答题过程如上输代码一言,但是其实际使用并不方便。原因也十分简单,select的思想处理其实是一种轮询的方式。、,这导致你每次都要设置文件描述符。
下面是一段示例代码。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/select.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>#define MAX_FD 10  // 最大文件描述符数量
#define BUFFER_SIZE 1024  // 读取缓冲区大小int main() {int fd[MAX_FD];  // 存储文件描述符的数组fd_set read_fds;  // select的可读文件描述符集合int max_fd = 0;  // 当前最大的文件描述符char buffer[BUFFER_SIZE];  // 读取数据的缓冲区int i, ret;// 初始化文件描述符,这里只是示例,实际情况可能是套接字for (i = 0; i < MAX_FD; i++) {fd[i] = -1;  // 初始化为-1,表示未使用}// 假设我们监听标准输入(文件描述符0)fd[0] = 0;max_fd = 0;  // 标准输入的文件描述符是0while (1) {// 清空fd集合FD_ZERO(&read_fds);// 将需要监听的文件描述符加入到fd集合for (i = 0; i <= max_fd; i++) {if (fd[i] != -1) {FD_SET(fd[i], &read_fds);}}// 设置超时时间,这里设置为永远等待struct timeval timeout;timeout.tv_sec = 10;  // 10秒timeout.tv_usec = 0;  // 0微秒// 调用selectret = select(max_fd + 1, &read_fds, NULL, NULL, &timeout);if (ret == -1) {perror("select error");exit(EXIT_FAILURE);} else if (ret == 0) {printf("select timeout\n");continue;}// 检查哪个文件描述符可读for (i = 0; i <= max_fd; i++) {if (fd[i] != -1 && FD_ISSET(fd[i], &read_fds)) {// 这里处理文件描述符i的数据memset(buffer, 0, BUFFER_SIZE);ssize_t count = read(fd[i], buffer, BUFFER_SIZE - 1);if (count > 0) {printf("Read from fd %d: %s\n", fd[i], buffer);} else if (count == 0) {// EOF,可能需要关闭文件描述符close(fd[i]);fd[i] = -1;} else {// 读取错误perror("read error");}}}}return 0;
}

你会发现意见很让人觉得效率低且麻烦的事情,那就是select每一次都需要遍历。如同轮询一般,因为你放进去的select文件描述符发生事件时,select并不会告诉你具体是谁发生了,只知道在 FD_MAX(目前最大的文件描述符为止),有事件发生,这就显得麻烦且效率低下。不过因为select上限文件描述符大多数都是1024,也就是 FD_SETSIZE 宏。所以select的整体效率不算高,但是其适用于一些比较没有那么支持性能的机器。
总结:
1.每次调用select都需要把fd集合从用户态往内核态拷贝一次,而每次拷贝都需要通过系统调用进入内核态,且在内核也是遍历访问这个开销在fd很多时会很大
2.select支持的文件描述符数量太小了,默认是1024
3.select返回后,需要遍历文件描述符集合,来获取已经就绪的socket
4.select不支持O_NONBLOCK
5.每次对要用第三方数组,动不动就需要遍历,十分耗时

poll类似select,解决了文件描述符上限,同时解决了输入输出每次重置的问题。(也就是select每次都要传一个表进去,同时也要传出来。)
具体用法不多叙述,可以自行百度。

epoll

epoll整体设计理念相较于select就比较人性化,我们知道每当有数据来临的时候,目前许多OS采用的都是硬件中断,让CPU临时去被数据接受之后存储起来。比如你的键盘输入就是如此。
那么为什么不把每个文件描述符有数据需要处理时,都会有信号,那么既然如此我维护这份记录就行,因此epoll就是如此做的。每当一个进程调用epoll时,会创建一个红黑树,将你关心的文件描述符添加进去,每当有事件来临,他就去红黑树里面找关心了这个事件与否,然后如果发现时关心了的,那么就通过回调(ep_poll_callback )把这个节点给放到 另外的就绪队列上去,如此你就知道这个事件需要用了。

因此总结一下:使得epoll关心文件描述符的方法
调用epoll_create创建一个epoll句柄;
调用epoll_ctl, 将要监控的文件描述符进行注册;
调用epoll_wait, 等待文件描述符就绪;

具体调用可查询手册,下面是例子

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/epoll.h>#define MAX_EVENTS 10
#define PORT 8080int main() {int listen_sock, conn_sock, epfd;struct sockaddr_in serv_addr;struct epoll_event event;struct epoll_event events[MAX_EVENTS];int num_fds;// 创建监听socketlisten_sock = socket(AF_INET, SOCK_STREAM, 0);if (listen_sock == -1) {perror("socket");exit(EXIT_FAILURE);}memset(&serv_addr, 0, sizeof(serv_addr));serv_addr.sin_family = AF_INET;serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);serv_addr.sin_port = htons(PORT);// 绑定socketif (bind(listen_sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) == -1) {perror("bind");exit(EXIT_FAILURE);}// 监听socketif (listen(listen_sock, 5) == -1) {perror("listen");exit(EXIT_FAILURE);}// 创建epoll实例epfd = epoll_create1(0);if (epfd == -1) {perror("epoll_create");exit(EXIT_FAILURE);}// 添加监听socket到epoll实例event.data.fd = listen_sock;event.events = EPOLLIN;if (epoll_ctl(epfd, EPOLL_CTL_ADD, listen_sock, &event) == -1) {perror("epoll_ctl: listen_sock");exit(EXIT_FAILURE);}// 事件循环while (1) {num_fds = epoll_wait(epfd, events, MAX_EVENTS, -1);if (num_fds == -1) {perror("epoll_wait");exit(EXIT_FAILURE);}for (int i = 0; i < num_fds; i++) {if (events[i].data.fd == listen_sock) {// 处理新的连接conn_sock = accept(listen_sock, NULL, NULL);if (conn_sock == -1) {perror("accept");exit(EXIT_FAILURE);}printf("Accepted connection on fd %d\n", conn_sock);// 将新的连接添加到epoll实例event.data.fd = conn_sock;event.events = EPOLLIN | EPOLLET; // 边缘触发模式if (epoll_ctl(epfd, EPOLL_CTL_ADD, conn_sock, &event) == -1) {perror("epoll_ctl: conn_sock");exit(EXIT_FAILURE);}} else {// 处理已连接socket的数据if (events[i].events & EPOLLIN) {char buffer[1024];ssize_t count;count = read(events[i].data.fd, buffer, sizeof(buffer));if (count == -1) {perror("read");close(events[i].data.fd);} else if (count == 0) {// 连接关闭printf("Closed connection on fd %d\n", events[i].data.fd);close(events[i].data.fd);} else {// 处理读取到的数据printf("Read %zd bytes from fd %d\n", count, events[i].data.fd);// 这里可以将数据发送回去或者进行其他处理}}}}}close(listen_sock);return 0;
}

相关文章:

Linux多路转接

文章目录 IO模型多路转接select 和 pollepoll IO模型 在还在学习语言的阶段&#xff0c;C里使用cin&#xff0c;或者是C使用scanf的时候&#xff0c;总是要等着我们输入数据才执行&#xff0c;这种IO是阻塞IO。下面是比较正式的说法。 阻塞IO: 在内核将数据准备好之前&#xf…...

IDEA导入Maven项目的流程配置以常见问题解决

1. 前言 本文主要围绕着在IDEA中导入新Maven项目后的配置及常见问题解决来展开说说。相关的部分软件如下&#xff1a; IntelliJ IDEA 2021.1JDK 1.8Window 2. 导入Maven项目及配置 2.1 导入Maven项目 下面介绍了直接打开本地项目和导入git上的项目两种导入Maven方式。 1…...

【数据分析---- Pandas进阶指南:核心计算方法、缺失值处理及数据类型管理】

前言&#xff1a; &#x1f49e;&#x1f49e;大家好&#xff0c;我是书生♡&#xff0c;本阶段和大家一起分享和探索数据分析&#xff0c;本篇文章主要讲述了&#xff1a;Pandas进阶指南&#xff1a;核心计算方法、缺失值处理及数据类型管理等等。欢迎大家一起探索讨论&#x…...

2024世界机器人大会将于8月21日至25日在京举行

2024年的世界机器人大会预定于8月21日至25日&#xff0c;在北京经济技术开发区的北人亦创国际会展中心隆重举办。 本届大会以“共育新质生产力 共享智能新未来”为核心主题&#xff0c;将汇聚来自全球超过300位的机器人行业专家、国际组织代表、杰出科学家以及企业家&#xff0…...

【Linux】lvm被删除或者lvm丢失了怎么办

模拟案例 接下来模拟lvm误删除如何恢复的案例&#xff1a; 模拟删除&#xff1a; 查看vg名&#xff1a; vgdisplayvgcfgrestore --list uniontechos #查看之前的操作 例如我删除的&#xff0c;现场没有删除就用最近的操作文件&#xff1a; 还原&#xff1a; vgcfgrestore…...

疫情防控管理系统

摘 要 由于当前疫情防控形势复杂&#xff0c;为做好学校疫情防控管理措施&#xff0c;根据上级防疫部门要求&#xff0c;为了学生的生命安全&#xff0c;要求学校加强疫情防控的管理。为了迎合时代需求&#xff0c;优化管理效率&#xff0c;各种各样的管理系统应运而生&#x…...

永久删除的Android 文件去哪了?在Android上恢复误删除的消息和照片方法?

丢失重要消息和照片可能是一种令人沮丧的经历&#xff0c;尤其是在您没有备份的情况下。但别担心&#xff0c;在本教程中&#xff0c;我们将指导您完成在Android设备上恢复已删除消息和照片的步骤。无论您是不小心删除了它们还是由于软件问题而消失了&#xff0c;这些步骤都可以…...

宠物服务小程序多生态转化

宠物服务如美容造型、医疗、看护寄养等有着不少需求&#xff0c;尤其是年轻人从宠物生活到饮食起居等面面俱到&#xff0c;往往不惜金钱给到较好的环境&#xff0c;如定时除虫、优质食物、玩具、检查身体、服饰&#xff1b; 近些年宠物服务店新开数量也较多&#xff0c;同行竞…...

今天细说一下工业制造行业MES系统

文章目录 前言什么是MES&#xff1f; 前言 最近几个月在做制造行业的MES系统开发&#xff0c;这类面向制造业的系统是今年做的第三个系统了&#xff0c;也算是了解较深的了&#xff0c;和一个之前转行做这一块的朋友聊了聊&#xff0c;他们集团要扩大规模&#xff0c;准备招ME…...

C++ 知识点(长期更新)

C++ 知识点 C/C++1. `cin`, `cin.get()`, `getchar()`, `getline()`, 和 `cin.getline()`的区别。2. 有关 cin >>3. 定义和声明的区别4. `union`、`struct`和`class`的区别5. 深拷贝 vs 浅拷贝6. new 和 malloc 的区别7. 被free回收的内存是立即返还给操作系统吗?为什么…...

Spring AI + 通义千问 入门学习

Spring AI 通义千问 入门学习 文章目录 Spring AI 通义千问 入门学习一&#xff0c;开发环境配置二&#xff0c;项目搭建2.1 pom文件2.2 配置文件 三&#xff0c;AI使用3.1 对话问答3.1.1 普通方式3.1.2 流方式 3.2 文字生成图片 最近AI很火&#xff0c;而Spring也出了Spring…...

38.【C语言】指针(重难点)(C)

目录: 8.const 修饰指针 *修饰普通变量 *修饰指针变量 9.指针运算 *指针或-整数 *指针-指针 *指针关系运算 往期推荐 承接上篇37.【C语言】指针&#xff08;重难点&#xff09;&#xff08;B&#xff09; 8.const 修饰指针 const 全称 constant adj.不变的 *修饰普通变量 #…...

Vue-05.指令-v-for

v-for 列表渲染&#xff0c;遍历容器的元素或者对象的属性 v-for“列表元素名 in 列表名” <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-wi…...

自动驾驶的一些大白话讲解

无人驾驶牛逼吗&#xff1f;我来帮你祛魅【原理缺陷解析】_哔哩哔哩_bilibili 5分钟讲明白自动驾驶端到端&#xff0c;解释为什么华为智驾并不是遥遥领先 到底什么是端到端自动驾驶系统&#xff1f;为何我会说这是智能驾驶的弯道超车机会&#xff1f;我希望今天用5分钟的时间&…...

Python学习笔记--参数

目录 实参与形参 不定长参数 拆分参数列表 返回值 实参与形参 1. 定义函数时&#xff0c;带默认值的形参必须放在不带默认值的形参后面 下面程序的输出结果是&#xff08; &#xff09;。 def StudentInfo(country中国,name): print(%s&#xff0c;%s%(name,country)…...

刷题——大数加法

大数加法_牛客题霸_牛客网 string solve(string s, string t) {if(s.size() < t.size()) return solve(t, s);reverse(s.begin(), s.end());reverse(t.begin(), t.end());string ans;int d 0;//进位制for(int i0; i < s.size(); i){d s[i] - 0;//取得数字值if(i < …...

Pytorch人体姿态骨架生成图像

ControlNet是一个稳定扩散模型&#xff0c;可以复制构图和人体姿势。ControlNet解决了生成想要的确切姿势困难的问题。 Human Pose使用OpenPose检测关键点&#xff0c;如头部、肩膀、手的位置等。它适用于复制人类姿势&#xff0c;但不适用于其他细节&#xff0c;如服装、发型和…...

前端面试常考的HTML标签知识!!!

语义标签 标签名语义描述header网页头部网页的主要头部区域nav网页导航网页的导航链接区域footer网页底部网页的底部区域aside网页侧边栏网页的侧边栏区域section网页区块网页的独立区块 | article | 网页文章 | 网页的独立文章区域 | 字符实体 作用&#xff1a;在网页中显…...

Oracle触发器

Oracle触发器就是特定事件发生时自动执行的存储过程。 触发器基本使用 基本语法&#xff1a; create [or replace] trigger 触发器名称 alter | before | instead of [insert] [[or] update [of 列1,列2,...]] [[or] delete] on 表名 [referencing {OLD [as] old / NEW [as]…...

GPT-5:未来已来,我们如何共舞于智能新纪元?

GPT-5&#xff1a;未来已来&#xff0c;我们如何共舞于智能新纪元&#xff1f; 在科技日新月异的今天&#xff0c;人工智能&#xff08;AI&#xff09;的每一次飞跃都深刻地改变着人类社会的面貌。从AlphaGo击败围棋世界冠军&#xff0c;到GPT系列模型引领自然语言处理&#x…...

19c补丁后oracle属主变化,导致不能识别磁盘组

补丁后服务器重启&#xff0c;数据库再次无法启动 ORA01017: invalid username/password; logon denied Oracle 19c 在打上 19.23 或以上补丁版本后&#xff0c;存在与用户组权限相关的问题。具体表现为&#xff0c;Oracle 实例的运行用户&#xff08;oracle&#xff09;和集…...

将对透视变换后的图像使用Otsu进行阈值化,来分离黑色和白色像素。这句话中的Otsu是什么意思?

Otsu 是一种自动阈值化方法&#xff0c;用于将图像分割为前景和背景。它通过最小化图像的类内方差或等价地最大化类间方差来选择最佳阈值。这种方法特别适用于图像的二值化处理&#xff0c;能够自动确定一个阈值&#xff0c;将图像中的像素分为黑色和白色两类。 Otsu 方法的原…...

Java入门学习详细版(一)

大家好&#xff0c;Java 学习是一个系统学习的过程&#xff0c;核心原则就是“理论 实践 坚持”&#xff0c;并且需循序渐进&#xff0c;不可过于着急&#xff0c;本篇文章推出的这份详细入门学习资料将带大家从零基础开始&#xff0c;逐步掌握 Java 的核心概念和编程技能。 …...

力扣-35.搜索插入位置

题目描述 给定一个排序数组和一个目标值&#xff0c;在数组中找到目标值&#xff0c;并返回其索引。如果目标值不存在于数组中&#xff0c;返回它将会被按顺序插入的位置。 请必须使用时间复杂度为 O(log n) 的算法。 class Solution {public int searchInsert(int[] nums, …...

三分算法与DeepSeek辅助证明是单峰函数

前置 单峰函数有唯一的最大值&#xff0c;最大值左侧的数值严格单调递增&#xff0c;最大值右侧的数值严格单调递减。 单谷函数有唯一的最小值&#xff0c;最小值左侧的数值严格单调递减&#xff0c;最小值右侧的数值严格单调递增。 三分的本质 三分和二分一样都是通过不断缩…...

解析奥地利 XARION激光超声检测系统:无膜光学麦克风 + 无耦合剂的技术协同优势及多元应用

在工业制造领域&#xff0c;无损检测&#xff08;NDT)的精度与效率直接影响产品质量与生产安全。奥地利 XARION开发的激光超声精密检测系统&#xff0c;以非接触式光学麦克风技术为核心&#xff0c;打破传统检测瓶颈&#xff0c;为半导体、航空航天、汽车制造等行业提供了高灵敏…...

抽象类和接口(全)

一、抽象类 1.概念&#xff1a;如果⼀个类中没有包含⾜够的信息来描绘⼀个具体的对象&#xff0c;这样的类就是抽象类。 像是没有实际⼯作的⽅法,我们可以把它设计成⼀个抽象⽅法&#xff0c;包含抽象⽅法的类我们称为抽象类。 2.语法 在Java中&#xff0c;⼀个类如果被 abs…...

【SpringBoot自动化部署】

SpringBoot自动化部署方法 使用Jenkins进行持续集成与部署 Jenkins是最常用的自动化部署工具之一&#xff0c;能够实现代码拉取、构建、测试和部署的全流程自动化。 配置Jenkins任务时&#xff0c;需要添加Git仓库地址和凭证&#xff0c;设置构建触发器&#xff08;如GitHub…...

Spring Security 认证流程——补充

一、认证流程概述 Spring Security 的认证流程基于 过滤器链&#xff08;Filter Chain&#xff09;&#xff0c;核心组件包括 UsernamePasswordAuthenticationFilter、AuthenticationManager、UserDetailsService 等。整个流程可分为以下步骤&#xff1a; 用户提交登录请求拦…...

Sklearn 机器学习 缺失值处理 获取填充失值的统计值

💖亲爱的技术爱好者们,热烈欢迎来到 Kant2048 的博客!我是 Thomas Kant,很开心能在CSDN上与你们相遇~💖 本博客的精华专栏: 【自动化测试】 【测试经验】 【人工智能】 【Python】 使用 Scikit-learn 处理缺失值并提取填充统计信息的完整指南 在机器学习项目中,数据清…...