【netty系列-06】深入理解select、poll和epoll多路复用的区别

news2025/1/11 18:45:32

Netty系列整体栏目


内容链接地址
【一】深入理解网络通信基本原理和tcp/ip协议https://zhenghuisheng.blog.csdn.net/article/details/136359640
【二】深入理解Socket本质和BIOhttps://zhenghuisheng.blog.csdn.net/article/details/136549478
【三】深入理解NIO的基本原理和底层实现https://zhenghuisheng.blog.csdn.net/article/details/138451491
【四】深入理解反应堆模式的种类和具体实现https://zhenghuisheng.blog.csdn.net/article/details/140113199
【五】深入理解直接内存与零拷贝https://zhenghuisheng.blog.csdn.net/article/details/140721001
【六】select、poll和epoll多路复用的区别https://zhenghuisheng.blog.csdn.net/article/details/140795733

深入理解直接内存与零拷贝

  • 一.深入理解select、poll和epoll多路复用的区别
    • 1,多路复用-select
    • 2,多路复用-poll
    • 3,多路复用-epoll
    • 4,select和poll慢的原因
    • 5,epoll快的原因
    • 6,总结

一.深入理解select、poll和epoll多路复用的区别

在前面几篇文章中,了解到nio网络变成中使用了反应堆模式,在反应堆中除了读写事件之外,处理一些业务事件采用的io就是多路复用io,在多路复用io中,主要有三种方式,分别是:select、poll和epool 三种模式。接下来分别讲解这三种模式的区别和优缺点。

在讲解三种模式之前,再谈一下在BIO中的读写数据的场景。在阻塞IO的bio中,并没有引入多路复用,也就是说在读取数据时,只需要执行一条read命令即可,但是在nio中,以select为例,那么就还需要执行一条额外的select命令,效率如何先不讲,相对于bio来说,其效率肯定是快于nio的,因为少执行了一条命令。但是上面的情况只适用于数据量少的场景,如果请求数打起来,那么这个bio的效率也是不行的,因此网络编程的更高的关注度还是在BIO编程上面,通过多路复用,可以通过很少的线程数量处理很多的服务和请求

因此如果是并发量较少的场景,可以优先选择使用BIO;并发量较大的场景,优先选择使用NIO。

1,多路复用-select

示例代码如下,如在Linux下面的一段c语言代码示例,fd表示文件描述符,在unix或者linux中,一切东西都可以通过文件表示,设计的初衷就是一切皆文件,每个文件有一个标识符,被称为fd

#include <stdio.h>
#include <sys/select.h>
#include <unistd.h>
#include <fcntl.h>
int main() {
    fd_set read_fds;
    int max_fd, retval;
    struct timeval timeout;
    FD_ZERO(&read_fds);
    FD_SET(STDIN_FILENO, &read_fds);
    max_fd = STDIN_FILENO;
    timeout.tv_sec = 5;
    timeout.tv_usec = 0;
   	//执行select
    retval = select(max_fd + 1, &read_fds, NULL, NULL, &timeout);
    return 0;
}

select是最早提出来并实现的一种多路复用的机制,因此这种机制比较通用,在大量的领域以及不同的操作系统中都支持这种方式,这种 支持率最广泛

但是通过上面这段代码可知,在需要执行一条select函数时,需要传递多个参数,需要传递的文件描述符有多个,但是一个进程打开这个文件描述符的个数有限,默认每个进程最大只能打开1024给文件,其次是每次找到对应的文件描述符时,需要经过一些遍历这些全部的文件,效率也比较慢。总结就是:一个进程打开文件有限,最多只能1024个;每次要遍历全部文件,查询效率慢

2,多路复用-poll

上面的pollfd使用数组来实现,每个并且设置了数组的容量大小,poll直接通过动态数组实现,这样可以保证每个进程打开的文件数是远高于固定数组的

#include <stdio.h>
#include <poll.h>
#include <unistd.h>

int main() {
    struct pollfd fds[1];
    int retval;
    fds[0].fd = STDIN_FILENO;
    fds[0].events = POLLIN;
    retval = poll(fds, 1, 5000);
}

但是除了上面的数组优化之外,其其他的工作方式还是和原来的poll的方式一样,并且需要去轮训一个更大的数组,这也在找对应的注册事件需要消耗的时间也更多,因此也被淘汰

3,多路复用-epoll

经过长时间的发展,最终引入了epoll模型,epoll模型是poll模型的升级版本,将pool一个函数要做的事情拆分成了三件,分别是:创建、注册、等待 ,让每一件事情都更加细致,灵活度更高。除此之外,上面两个在数据拷贝时,需要从用户态切换到内核态,但是在epoll中通过共享区间来实现,从而加快整体效率

#include <stdio.h>
#include <stdlib.h>
#include <sys/epoll.h>
#include <unistd.h>
#define MAX_EVENTS 10

int main() {
    //参数定义
    int epoll_fd, nfds, i;
    struct epoll_event event, events[MAX_EVENTS];
    //创建
    epoll_fd = epoll_create1(0);
    event.data.fd = STDIN_FILENO;
    event.events = EPOLLIN;
    //注册
    if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, STDIN_FILENO, &event) == -1) {
        perror("epoll_ctl()");
        exit(EXIT_FAILURE);
    }
    //等待
    nfds = epoll_wait(epoll_fd, events, MAX_EVENTS, 5000);
    close(epoll_fd);
    return 0;
}

4,select和poll慢的原因

在研究为什么epoll快之前,先研究一下其他两个为什么慢。如下图所示,在客户端向服务端发起请求之后,首先会先通过三次握手建立连接,三次握手成功之后,如服务端首先会在工作队列中创建一个工作线程,并且会创建一个ServerSocket用于接收客户端的数据,socket中除了读写Buffer之外,还会有一个线程等待列表,用于标识该socket于哪个线程对应,由于此时线程A创建的socket未监听到外部的连接,因此此时线程等待列表为空

如服务端的一段伪代码,首先创建一个客户端的socket连接,然后监听是否有客户端连接这个socket,如果有的话则读取数据

ServerSocket socket = new ServerSocket("80");	//创建一个socket连接
socket.accept();	//监听连接
socket.read();		//读取数据

首先第一行代码服务端创建一个socket,此时服务端创建一个线程A,并且在对应的进程中创建一个Socket对象,并且在内部会有一个线程的等待列表,由于此时并未有客户端来连接,因此该列表为空

请添加图片描述

在前面bio文章说过,服务端在没有被客户端请求时,则处于阻塞状态。当执行到第二句监听时,那么线程A就会挂起处于阻塞状态,并且从工作队列中加入到阻塞队列,线程A也不会消耗时间片。此时没有外部的客户端来连接,因此在线程等待列表此时也为空

请添加图片描述

当外部有客户端来连接时,此时服务端这边监听到了数据,那么会执行第三句代码,读取客户端传来的数据。与此同时,线程A又会从阻塞状态变为就绪状态并加入到工作队列里面。并且在服务端对应的的socket中,线程等待列表中会将客户端的目标ip和目标端口号记录,这样就可以知道线程A对应的socket是哪一个

请添加图片描述

此时读数据的过程就来了,线程A需要定位到是哪一个Socket,那么就需要根据socket里面的等待列表中的ip和端口号进行匹配,假设此时就是已经连接了1024个socket,如下面这段伪代码,就是需要任意一个请求都是要遍历全部的socket,因此效率是比较慢的

//1024个socket数组
int fds[] = {socket,...}
struct thread;
for(int i = 0; i< fds.size ; i++){
    //匹配逻辑,找到线程A对应的socket
    if(fds[i].host == thread.host && fds[i].port == thread.port){
        //删除socket中的等待列表
        fds[i].list.remove;
        //read读取数据
    }
}

因此select慢是因为数组固定+循环找socket是比较慢的;而poll对人支持的是动态数组,但是也需要遍历全部socket,然后通过host主机+端口号找到对应的socket,并且随着动态数组的容量越大,寻找的时间也会相对较长,因此select和poll相对来说都是比较慢的

5,epoll快的原因

快慢是相对的,在得知select和poll慢的原因之后,那么如何优化这种效率慢的问题,epoll的出现成为关键。在很多架构中,当存在解决不了的问题时,那么就会考虑在中间加一层,如熟悉的缓存等。

根据poll对select的优化之后可知,此时存在的瓶颈是遍历时需要找数据比较就,那么能不能通过加一层中间件,提前将外部要连接的socket加入到一个缓存队列里面,这样每次就不需要去全部的socket列表中找对应的socket,直接从缓存队列那数据岂不是更快,当然,epoll也是这样设计的。

epoll的设计如下,在原有工作队列和Socket列表的基础上,增加了一个中间层eventPool列表。首先创建socket和监听数据是和select的流程一样的,主要是第二和第三步,当有数据来之前和之后,底层是如何实现的,其原理如下:在监听的时候,线程A会阻塞,并且此时线程A加入到eventPoll列表中

请添加图片描述

当客户端socket往服务端发送数据时,服务端会通过 中断机制 去给对应的进程去接收数据,线程A由阻塞状态变为就绪状态加入到工作队列中,此时会触发Socket列表中的对应socket的readBuffer接收数据,并且此时会将刚刚的socket加入到eventPoll的socket列表中

请添加图片描述

在线程A被唤醒之后,需要从全部的socket中找到对应的线程的socket,用于处理数据,发送数据等。用上面的select方式需要遍历全部的socket,但是通过epoll方式只需要遍历eventPoll中的socket即可,这样就大大的节省了找到socket的时间。select需要遍历1024个socket,而socket只需要在socket列表中查找,可能就只有1,2个socket,因此epoll的的效率远高于select。因此所谓epoll能支持百万连接时完全有可能的,因为那一刻发送数据的连接并不太多。

eventPoll中的Socket采用双向联表的方式实现,在该列表中,不会存真正的进程中的socket,而是会存一个引用地址,可以通过该地址快速的找到该线程对应的Socket。如线程A通过eventPoll中的socket列表找到socket1,此时socket1中,只存了一个真正的 serverSocket 的一个地址,通过这个地址快速定位到具体的serverSocket,而不需要像select模式一样,需要将socket列表中的全部socket列表遍历一遍

eventpoll类似于一块共享内存,用于多个进程间的通讯。

6,总结

  • select模式是最早实现的多路复用的模式,因此支持的应用程序最多,应用也是最广泛的。但是效率也相对地下,其一是该文件描述符是有限的,默认最大为1024,内部使用固定数组控制,其二是在线程被唤醒之后定位socket耗时长,需要通过线程中的四元组定位,并且需要遍历全部的文件描述符。四元组指的是: 源ip、源端口号、目标ip、目标端口号
  • poll模式在select的模式进行了优化,内部将固定数组改成了动态数组,可以支持更多的文件描述符,但也还存在线程唤醒定位socket的问题,需要遍历全部的socket,并且随着socket支持的数量大于1024,因此提供四元组在遍历数据时也会很慢,影响整体性能
  • epoll模式为了解决前二者问题,内部也使用动态数组解决文件描述符不够的问题,同时在socket和线程之间加了一层,让被唤醒的socket在定位线程时,不需要去遍历全部的socket,而是只需要遍历中间层中的socket即可,这样大大的减少比遍历全部socket的时间消耗。通过高效的io事件通知机制,来处理大量文件描述符的场景

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

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

相关文章

分布式架构网络通信(RPC,RMI) 03

文章目录 1. 基本原理2. RPC 远程过程调用3. RMI 远程方法调用4. RMI代码实现4. BIO、NIO、AIO4.1 同步和异步4.2 阻塞和非阻塞4.3 BIO4.4 NIO4.5 AIO 1. 基本原理 要实现网络机器间的通讯&#xff0c;首先得来看看计算机系统网络通信的基本原理&#xff0c;在底层层面去看&am…

WSL桥接网络配置

仅做记录与分享&#xff0c;平台版本等不同无法指导更多。 一、需求 ubuntu虚拟机&#xff08;WSL&#xff09;桥接win11并且能联通外网&#xff08;百度之类&#xff09; 二、环境&#xff1a; 版本 Windows 11 专业版 版本号 23H2 安装日期 ‎2024/‎6/‎20 操作系统版本…

Mysql 集群搭建 05

文章目录 1. Mysql主从复制集群搭建1.1 主库配置1.2 从库配置 2. 分库分表2.1 拆分策略2.2 实现技术2.2.1 MyCat概述2.2.2 MyCat入门2.2.3 配置 schema.xml 3. 双主双从4. 双主双从读写分离 1. Mysql主从复制集群搭建 主从复制是指将主数据库的 DDL 和 DML 操作通过二进制日志…

有什么开放式耳机比较好用?耳机选购指南附赠五款开放式耳机推荐!

现在的耳机市场真的越来越多元了&#xff0c;最近的开放式耳机也是越来越火了&#xff0c;很多小伙伴都在后台开始问我&#xff0c;到底要怎么样才能选到一款比较合适自己的开放式耳机呢&#xff1f;开放式耳机现在这么多品牌&#xff0c;这么多的型号&#xff0c;真的很难选择…

如何评估自动化测试的效益

目录 自动化测试实施成本 自动化前期开发成本包括&#xff1a; 后期维护成本包括&#xff1a; 自动化测试执行次数 自动化测试实施成本比 其中“自动化测试收益”可能包括&#xff1a; “自动化测试成本”包括但不限于&#xff1a; 测试稳定性 可扩展性和可维护性 自动…

java拼接字符串的四种方法StringBuilder、StringBuffer、StringJoiner、String.join(x,x )

1.直接复制以下代码运行查看运行结果 import java.util.ArrayList; import java.util.List; import java.util.StringJoiner;public class Test {public static void main(String[] args) throws Exception {List<String> strs new ArrayList<>();strs.add("…

“等保测评:如何进行有效的安全漏洞管理与网络安全法规遵从“

随着网络环境的复杂性增加&#xff0c;安全漏洞管理成为企业信息安全管理体系中的关键环节。等保测评要求企业具备发现、评估、修复和监控安全漏洞的能力&#xff0c;以保障信息系统的安全稳定运行。本文将围绕“等保测评&#xff1a;如何进行有效的安全漏洞管理”这一主题&…

qrcode生成二维码并下载【带logo图标】【带文字描述】

qrcode官网地址&#xff1a;http://jeromeetienne.github.io/jquery-qrcode/ 结果图&#xff1a; 不带文字 带文字 遇到问题&#xff1a; 1、中文乱码&#xff1a;需要先将中文字体转码。 2、qrcode.js生成的二维码是没有白边的&#xff0c;需要重新绘制边框logo文字 3、将生成…

IoTDB 入门教程 实战篇④——C#示例(开源)

文章目录 一、前文二、新建C#项目三、NuGet安装四、示例源码五、查询数据六、参考 一、前文 IoTDB入门教程——导读 本文详细阐述了如何通过一个C#项目成功连接到IoTDB时序数据库&#xff0c;进而展示了如何向该数据库高效地写入数据以及执行精确的数据查询操作。 此示例旨在为…

ImportError: /lib64/libstdc++.so.6: version `GLIBCXX_3.4.20‘ 报错解决办法

1.查找 libstdc.so.6* find / -name libstdc.so.6*2.copy一个libstdc.so.6.0.19到/usr/lib64/下 cp /usr/lib64/libstdc.so.6 /usr/lib64/3.创建软连接 ln -sf /usr/lib64/libstdc.so.6.0.31 /usr/lib64/libstdc.so.6完毕&#xff01;

RISC-V (四)内存管理

本章目的&#xff1a; 对内存进一步的管理&#xff0c;实现动态的分配和释放。 实现page级别的内存分配和释放。 内存管理分类 -自动管理内存-栈&#xff08;stack&#xff09; -静态内存-全局变量/静态变量。放在数据段里面。 -动态管理内存-堆&#xff08;heap&#xff09;…

【Docker】LXC 容器操作实战

一、实战目的 通过 lxc 来完成容器的创建&#xff0c;体会容器并了解 docker 并不是容器的唯一实现。 自 docker 0.9 版本起&#xff0c;docker 除了继续支持 LXC 外&#xff0c;还开始引入自家的 libcontainer&#xff0c;试图打造更通用的底层容器虚拟化库。如今的 docker…

【EI稳定检索】第二届能源与化学工程国际会议(EACE 2024)

第二届能源与化学工程国际会议 2024 International Conference on Energy and Chemical Engineering 【1】会议简介 第二届能源与化学工程国际会议是一个旨在促进能源科学与化学工程领域学术交流与合作的重要平台。会议汇集了全球范围内的专家学者、研究人员及行业代表&#xf…

77.SAP ME - 数据库架构

目录 1.SAP ME的数据库 2.SAPMEINT的数据库 3.SAPMII的数据库 4.基于MSSQLSERVER或ORACLE的架构 5.基于HANA的架构 - 无ODS 1.SAP ME的数据库 WIP&#xff1a;在实时事务期间&#xff0c;SAP ME 在“在制品” (WIP) 数据库表中存储数据。ODS&#xff1a;可操作数据存储 (…

树莓派笔记22_小车:小车电机开环运动与opencv摄像头巡线

今日继续学习树莓派4B 4G&#xff1a;&#xff08;Raspberry Pi&#xff0c;简称RPi或RasPi&#xff09; 本人所用树莓派4B 装载的系统与版本如下: 版本可用命令 (lsb_release -a) 查询: ​ Opencv 版本是4.5.1&#xff1a; ​ Python 版本3.7.3&#xff1a; 今日尝试搭建一台小…

车车科技合纵连横:股价今年以来跌超八成,公司看好未来市场份额

《港湾商业观察》黄懿 6月27日&#xff0c;Cheche Group Inc. (NASDAQ: CCG&#xff0c;下称“车车科技”)宣布&#xff0c;公司已与北京安鹏保险经纪有限公司&#xff08;“北京安鹏”&#xff09;建立战略合作伙伴关系。其中&#xff0c;北京安鹏是北京汽车集团有限公司&…

vllm部署的一些思考

vllm号称利用ray支持多机多卡的方式,链接如下 Distributed Inference and Serving — vLLMhttps://docs.vllm.ai/en/stable/serving/distributed_serving.html但是这种方式只是把非常大的模型如lamma 3.1 ,这个模型有405b,需要用多机多卡的方式进行分布式。 事实上,生产中…

C语言:扫雷游戏实现

一、扫雷游戏的分析和设计 扫雷游戏想必大家都玩过吧&#xff0c;初级的玩法是在一个9*9的棋盘上找到没有雷的格子&#xff0c;而今天我们就要做的就是9*9扫雷游戏的实现。 1、游戏功能和规则 使用控制台实现经典的扫雷游戏游戏可以通过菜单实现继续玩或者退出游戏扫雷的棋盘…

Git 安装教程

1、登录git 官方网站&#xff1a;https://git-scm.com/ 点击左边的 Downloads 或者 右边标识的下载标志&#xff0c;它根据电脑操作系统自动匹配版本 Downloads for Windows 2、以 windows 为例下载对应版本 网络有时可能不大好&#xff0c;阿里镜像下载超快。 下载好以后&a…

传统物流机械锁控的痛点与难点深度剖析

在当今全球化和高度竞争的商业环境中&#xff0c;物流行业作为经济发展的重要支撑&#xff0c;其高效、安全的运营至关重要。而物流锁控&#xff0c;作为保障货物在运输和存储过程中安全的关键环节&#xff0c;传统机械物流锁控方式却面临着诸多严峻的挑战&#xff0c;这些问题…