网络io、io多路复用select/poll/epoll、基于事件驱动的reactor

news2024/11/28 20:56:04

一、网络IO请求

网络I/O请求是指在计算机网络中,向其他主机或服务器发送请求或接收响应的操作。这些请求可以包括获取网页、下载文件、发送电子邮件等。网络I/O请求需要使用合适的协议和通信方式来进行数据传输,例如HTTP、FTP、SMTP等。

要完成一个完整的 TCP/IP 网络通信过程,需要使用一系列函数来实现。这些函数包括 bind、listen、accept 和 recv/send 等。下面是它们的配合流程:

  1. 创建套接字(socket):使用 socket 函数创建一个套接字,指定协议族和套接字类型。
  2. 绑定地址(bind):将本地地址绑定到套接字上,使得客户端可以通过该地址访问服务器。
  3. 监听连接请求(listen):将套接字设置为监听状态,并指定最大等待连接数(backlog)。
  4. 接受连接请求(accept):当有客户端发起连接请求时,使用 accept 函数创建新的套接字用于与客户端进行通信。
  5. 读写数据(recv/send):使用新创建的套接字进行数据传输,包括从客户端读取数据和向客户端发送数据。
  6. 关闭连接(close):在通信结束后,需要使用 close 函数关闭套接字以释放资源。

对于第4步的请求,如果向下面方式处理,则只能接受一个客户端的请求。注意,如果把accept放在while循环里,也不能解决多客户端请求,反而会发生阻塞。

	int clientfd=accept(sockfd,(struct sockaddr*)&clientaddr,&len); //调用 accept() 函数后,它会一直阻塞等待直到有新的客户端连接请求到达为止。

	printf("accept\n");

	while (1){
		char buffer[BUFFER_LEN]={0};
		int ret=recv(clientfd,buffer,BUFFER_LEN,0);
		printf("ret: %d,buffer:%s\n", ret,buffer);
		send(clientfd,buffer,ret,0);
	}

因此,若要处理多客户端的情况,可以采用以下方法

  • 一请求一线程
  • select
  • poll
  • epoll

二、一请求一线程

如下图所示,一请求一线程的方式,确实可以解决多客户端连接和收发信息的情况。但是,实际业务中,面对数以万计的客户端,如果每个开辟一个线程,将会带来很大的消耗。好比你开一家餐厅,如果来一个顾客就要安排一个服务员,那如果你客流量上千,那不得雇佣一千个服务员!!因此,解决思路就是如果让一个服务员服务多个顾客。
在这里插入图片描述
在这里插入图片描述

三、IO多路复用——select的通俗理解

I/O多路复用是指一种机制,它允许单个进程可以监视多个文件描述符(通常是套接字),并在这些文件描述符中的任何一个变为可读或可写时立即进行相应的处理。这样就可以避免使用多线程/多进程方式来实现高并发。

1、select函数

select函数是一个I/O多路复用函数,用于同时监听多个文件描述符上的可读、可写、异常等事件。它可以让程序在单线程下同时处理多个I/O操作,提高程序的并发性能。
select函数的原型为:
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
参数说明:

  • nfds:需要监听的最大文件描述符值加1。
  • readfds:读事件集合。
  • writefds:写事件集合。
  • exceptfds:异常事件集合。
  • timeout:超时时间,当所有文件描述符都没有事件时,select函数会阻塞等待事件到来,如果超过了超时时间还没有事件到来,则返回0。

select函数返回值:

  • 大于0表示有文件描述符就绪;
  • 等于0表示超时;
  • 小于0表示出错。

2、accpet函数

accept()函数用于接受一个已经建立的连接,并返回一个新的套接字描述符,以便与该连接进行通信。该函数的原型如下:
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
参数解释如下:

  • sockfd:需要等待连接的套接字文件描述符。
  • addr:指向存放远程主机地址信息的缓冲区。
  • addrlen:远程主机地址信息长度。

返回值是新的套接字文件描述符,如果失败则返回-1。
该函数会一直阻塞,直到有客户端请求连接。一旦有新的连接请求,它将创建一个新的套接字,并使用该套接字来与客户端进行通信,而原始套接字则继续监听其他连接请求。

3、recv函数

recv()函数用于从已连接的套接字中接收数据。该函数的原型如下:
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
参数解释如下:

  • sockfd:需要接收数据的套接字文件描述符。
  • buf:用于存储接收到数据的缓冲区地址。
  • len:缓冲区长度。
  • flags:调用方式标志,通常设置为0。

返回值是实际读取到的字节数,如果返回0表示连接被关闭,如果返回-1表示发生错误。
该函数通过网络读取指定长度(len)的数据,并将其存储在指定地址(buf)所指向的缓冲区中。它会一直阻塞等待直到有足够的数据可供读取或者出错。
在这里插入图片描述

四、IO多路复用——poll

需要包含头文件#include <poll.h>
poll函数是一个系统调用,用于等待多个文件描述符上的事件。它与select函数类似,但提供了更好的性能和可扩展性。
在使用poll函数时,需要创建一个pollfd结构体数组来指定要监视的文件描述符及其感兴趣的事件类型。每个结构体包含以下字段:

  • fd:表示要监视的文件描述符
  • events:表示所关注的事件类型(如POLLIN表示可读事件)
  • revents:返回时表示发生了哪些事件

poll函数的参数包括一个指向pollfd结构体数组的指针、数组中元素的数量以及超时时间。具体来说,它的定义如下:
int poll(struct pollfd *fds, nfds_t nfds, int timeout);

  • fds:指向pollfd结构体数组的指针,用于描述需要监视哪些文件描述符和对应的事件类型。
  • nfds:表示fds数组中元素的数量。
  • timeout:表示超时时间,单位为毫秒。如果timeout值为负数,则表示永远等待;如果timeout值为0,则表示立即返回。

五、IO多路复用——epoll

在这里插入图片描述

需要包含头文件#include <sys/epoll.h>

1、epoll_create

epoll_create函数是用于创建一个新的 epoll 实例,它的原型如下:
int epoll_create(int size);
其中,size 参数指定了 epoll 实例中允许监听的最大文件描述符数量。该函数返回一个非负整数作为 epoll 句柄,如果出现错误则返回 -1。
注意:在 Linux 2.6.8 以前版本中,epoll_create 函数只接受一个参数,即 epoll 实例大小将被忽略。而在新版本中,则必须传递一个大于0的值作为实例大小参数。

2、epoll_ctl

epoll_ctl()函数是Linux内核提供的用于控制epoll实例的系统调用函数之一,它可以用来添加、修改或删除需要监听的文件描述符以及相应事件。其原型如下:
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
参数说明:

  • epfd:表示要操作的epoll实例标识符。
  • op:表示要进行的操作类型,有以下三种取值:
    EPOLL_CTL_ADD:将指定文件描述符加入到监听队列中;
    EPOLL_CTL_MOD:修改已经加入监听队列中的文件描述符对应事件信息;
    EPOLL_CTL_DEL:将已经加入监听队列中的文件描述符移除。
  • fd:表示需要被操作的文件描述符。
  • event:指向一个结构体变量,用来设置需要监听的事件类型以及相关属性。

在使用epoll_ctl()函数时,需要先创建一个epoll实例,并使用EPOLL_CTL_ADD操作将待监听的文件描述符添加到该实例中。如果后续需要修改所监听事件类型或者属性,则可以使用EPOLL_CTL_MOD操作。当不再需要继续监听某个文件描述符时,则可以使用EPOLL_CTL_DEL操作将其从监控列表中删除。

3、epoll_wait

epoll_wait函数是Linux内核提供的用于异步IO操作的系统调用函数之一,它可以用于等待一个或多个文件描述符上的事件发生,并在事件发生时通知用户进程。该函数与epoll_create和epoll_ctl一起使用来管理非阻塞I/O文件描述符。
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
参数说明:

  • epfd:表示要监控的epoll实例标识符。
  • events:指向存储返回事件的结构体数组。
  • maxevents:表示最大监听事件数。
  • timeout:超时时间,以毫秒为单位,如果timeout为负数则表示永久等待。

当有事件到达时,epoll_wait会将所有就绪的事件存放在传入的events数组中,并返回就绪事件数量。对于每个就绪的文件描述符,需要通过判断events[i].events字段中位设置情况来确定具体是哪种类型的事件(例如可读、可写等)。同时,在处理完就绪事件后,需要将相应文件描述符重新加入到epoll监听队列中。

4、epoll_event

epoll_event是一个结构体,用于描述一个文件描述符上的事件。其定义如下:struct epoll_event { uint32_t events; // 表示监听的事件类型 epoll_data_t data; // 用户数据,可以是指针或者整数值 };
其中events字段表示需要监听的事件类型,取值如下:

  • EPOLLIN:表示该文件描述符上有可读数据。
  • EPOLLOUT:表示该文件描述符上可以写数据。
  • EPOLLRDHUP:表示对端关闭连接或者半关闭连接,即收到FIN包。
  • EPOLLHUP:表示该文件描述符被挂起,可能是对端进程崩溃或者其他错误情况导致的。
  • EPOLLERR:表示出错。

5、边缘触发和水平触发)

另外,epoll还提供了ET(边缘触发)和LT(水平触发)两种工作模式。

  • 水平触发模式
    在水平触发模式下,如果文件描述符上的事件没有被处理完毕,epoll 会持续通知应用程序该文件描述符上仍有事件待处理。在这种情况下,如果应用程序不及时响应并读取数据,则 epoll 会一直通知应用程序该文件描述符上有数据可读取。
  • 边沿触发模式
    在边沿触发模式下,只要文件描述符上出现新的事件(例如数据可读或连接建立),epoll 就会通知应用程序。但是,在通知之后,如果应用程序没有立即响应并读取所有数据,则 epoll 不会再次通知该文件描述符上有新的数据可读。

总体来说,边沿触发模式相比于水平触发模式更为高效,并且可以避免由于重复监听导致 CPU 占用率过高的问题,一般用于数据量很大,需要分批次接收的时候。但是,在使用边沿触发模式时需要注意及时读取所有数据,并确保每个事件都得到了正确处理。
需要注意的是,EPOLLET模式下,并不会丢失数据。即如果数据未全部接收,此时又发送新的数据,接收的时候将先接收上一次的数据。并且,epoll默认是EPOLLLT。
在这里插入图片描述

五、区别对比

1、select和poll

select和poll都是用于多路复用I/O的系统调用函数,可以同时监控多个文件描述符上的事件。它们的主要区别如下:

  • 可以处理的文件描述符数量不同
    select支持最大1024个文件描述符,而poll没有限制。
  • select采用轮询方式,poll采用链表方式
    select将所有待检测的文件描述符放在一个fd_set集合中,每次轮询时需要遍历整个集合;而poll将所有待检测的文件描述符放在一个链表中,每次检查时只需要遍历该链表即可。
  • select支持几乎所有操作系统,poll仅支持部分操作系统
    select是标准POSIX接口,在几乎所有操作系统上都能使用;而poll则不是标准接口,在一些老旧的操作系统上可能无法使用。
  • select对于返回状态码不够清晰明了,而poll更加直观
    select返回后需要使用FD_ISSET宏来判断哪些文件描述符已经就绪;而poll返回后直接通过revents字段来判断哪些文件描述符已经就绪。
  • select效率较低,因为每次都要重新设置fd_set集合;poll效率较高
    由于select内核实现有许多缺陷,所以每次使用前都需要重新设置fd_set集合;而poll没有这个问题,所以效率更高。

总体来说,poll比select更加灵活、可靠,而且效率也更高。但由于select是标准接口,在一些特殊的情况下还是有其用武之地。

2、poll和epoll

poll和epoll都是用于I/O多路复用的系统调用,可以同时监视多个文件描述符是否有数据可读或可写。但是它们有以下区别:

  • 处理方式不同
    poll采用轮询的方式扫描所有的文件描述符,每次扫描时需要遍历整个被监控的文件描述符集合。如果被监控的文件描述符集合很大,那么就会带来较大的开销。
  • 而epoll采用事件通知机制,只有在发生事件时才对该事件进行处理。这样可以大幅减少轮询带来的开销,提高效率。
  • 监听对象数量不同
    poll可以监听的文件描述符数量受限于操作系统中一个进程能打开的最大文件数目。如果要监听更多的文件描述符,则需要增加进程打开文件数目限制,但是这会占用更多系统资源。
  • 而epoll没有监听对象数量上限,因为它采用基于事件驱动模式,在处理完一个事件后,并不删除该事件对应的结构体,所以支持万级别甚至百万级别以上并发连接。
  • 内核与用户空间交互方式不同
    poll每次调用都需要将所有监控的fd集合从用户空间拷贝到内核空间,而epoll只需要一次拷贝,然后在内核中对其进行操作,避免了多次拷贝的开销。

3、select和epoll

select和epoll都是用于I/O多路复用的系统调用,主要用于同时处理多个文件描述符的输入输出事件。但是它们之间存在一些不同点:

  • 操作系统支持程度:select是POSIX标准中定义的函数,可以在大多数操作系统上使用,而epoll只能在Linux操作系统上使用。
  • 处理方式:select采用轮询方式来检查文件描述符的状态变化,每次调用都需要将所有待监控的文件描述符从用户空间拷贝到内核空间,并且每次返回时需要遍历整个集合。而epoll通过回调机制,在文件描述符就绪时直接通知应用程序。
  • 所监视的文件描述符数量:select所能监视的文件描述符数量是有限制的,通常为1024或2048个。而epoll没有此限制,可以监视大量的文件描述符。
  • 内存开销:由于select需要将所有待监控的文件描述符从用户空间拷贝到内核空间,并且每次返回时需要遍历整个集合,因此会产生较大的内存开销。而epoll只需将被触发事件的fd放入一个链表中即可,因此内存开销较小。

总体来说,相比select函数,epoll具有更高效、更灵活、更强大等优势,在高并发场景下性能更佳。

六、事件驱动reactor

对于普通函数调用的流程:
程序调用某函数 → \to 函数执行 → \to 程序等待 → \to 函数将结果和控制权返回给程序 → \to 程序继续处理。

而reactor是一种事件驱动机制。其和普通函数调用的不同之处在于:应用程序不是主动的调用某个 API 完成处理,而是恰恰相反,应用程序需要提供相应的接口并注册到 reactor上,如果相应的事件发生,reactor将主动调用应用程序注册的接口,这些接口又称为“回调函数”。

reactor模式是处理并发I/O比较常见的模式。主要是思想是一下几个:
1、将所有要处理的I/O绑定到一个I/O多路复用器上,并且在此阻塞主线程/进程。
2、一旦有事件触发(可以是文件描述符或者socket的可读可写),多路复用器将事先准备好的相应I/O事件回调函数分发到对应的处理器中。
据此,reactor模型有三个重要的组件:
1、多路复用器:由操作系统提供,在 linux 上一般是 select, poll, epoll 等系统调用。
2、事件分发器:将多路复用器中返回的就绪事件分到对应的处理函数中。
3、事件处理器:负责处理特定事件的处理函数。

优点:
将网络 I/O 事件与处理这些事件的业务逻辑分开处理,从而实现高效、可扩展的网络编程。具体来说,当有 I/O 事件发生时,reactor 会立即通知相应的处理程序,并由处理程序进行相关操作。这种方式可以保证在系统中有大量并发连接的情况下,每个连接都能得到及时响应,并且不会阻塞其他连接。并且可以使得代码结构更加清晰、易于维护。

使用epoll 与 reactor 相结合能够提高网络编程应用程序的性能和可扩展性,并且可以更好地满足高并发请求的需求。对于简单的epll和reactor,
驱动的事件有两个:EPOLLIN 和 EPOLLOUT;
回调函数:对于listen fd,将调用accept_cb;而对于client fd将调用recv_cb和send_cb。

数据存储结构图:
数据存储结构
网络I/O与开发分离示意图:
在这里插入图片描述
事件触发示意图:
在这里插入图片描述

七、代码

子不语

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

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

相关文章

【SSH】在VScode远程开发 使用SSH远程连接服务器

文章目录 前言视频教程1、安装OpenSSH2、vscode配置ssh3. 局域网测试连接远程服务器4. 公网远程连接4.1 ubuntu安装cpolar内网穿透4.2 创建隧道映射4.3 测试公网远程连接 5. 配置固定TCP端口地址5.1 保留一个固定TCP端口地址5.2 配置固定TCP端口地址5.3 测试固定公网地址远程 转…

实训小总结

1. Web概述 1.1 Web和JavaWeb的概念 Web是全球广域网&#xff0c;也成为万维网&#xff0c;能够通过浏览器访问网站。 在我们日常的生活中&#xff0c;经常会使用浏览器去访问 百度 、 京东 、等这些网站&#xff0c;这些网站统称为Web网站。我们知道了什么是Web&#xff0c;…

超详细的HTML学习笔记:语法特点、骨架结构、基本标签及其属性(附代码示例)

作者想先说一点废话&#xff1a;这篇文章是作者通过学习动力节点、黑马程序员的两套不同的THML课程整理出的学习笔记&#xff0c;融合了两套课程的知识点&#xff0c;耗费了372行代码&#xff08;加代码注释&#xff09;&#xff0c;希望能帮到大家。HTML只是前端学习的开端&am…

【Redis】聊一下Redis切片集群的原理

背景 在分布式领域中&#xff0c;为了提高系统的稳定性&#xff0c;一般会采用数据复制/镜像的方式&#xff0c;同时部署多个相同功能的节点提供服务&#xff0c;也就是A B C存储的相同的数据。有一个主节点提供读写服务&#xff0c;另外两个节点进行数据的复制&#xff0c;在…

chatgpt赋能python:Python去重-如何高效地处理重复数据

Python去重 - 如何高效地处理重复数据 在数据处理过程中&#xff0c;重复数据可能会导致很多问题&#xff0c;如降低计算效率、影响数据质量等。因此&#xff0c;数据去重是一个非常重要的任务&#xff0c;特别是在大数据处理中更是如此。Python作为一种流行的编程语言&#x…

make xxx_deconfig过程

在uboot中&#xff0c;所写的shell脚本&#xff1a;mx6ull_alientek_emmc.sh的内容如下&#xff1a; #!/bin/bash2 make ARCHarm CROSS_COMPILEarm-linux-gnueabihf- distclean3 make ARCHarm CROSS_COMPILEarm-linux-gnueabihf- mx6ull_14x14_ddr512_emmc_defconfig4 make V1…

[数据结构习题]队列——用栈实现队列

[数据结构习题]队列——用栈实现队列 &#x1f449;知识点导航&#x1f48e;&#xff1a;【数据结构】栈和队列 &#x1f449;[王道数据结构]习题导航&#x1f48e;&#xff1a; p a g e 85.3 page85.3 page85.3 本节为栈和队列的综合练习题 题目描述&#xff1a; &#x1f…

chatgpt赋能python:Python实现CSV文件只取某两列的方法详解

Python实现CSV文件只取某两列的方法详解 介绍 CSV是一种常见的数据格式&#xff0c;通常使用逗号或分号分隔不同的字段。在处理CSV文件时&#xff0c;我们经常需要只提取其中的某些列&#xff0c;以便进行进一步的分析或处理。使用Python语言&#xff0c;可以很方便地实现这一…

MTK 相机功耗分析流程

和你一起终身学习&#xff0c;这里是程序员Android 经典好文推荐&#xff0c;通过阅读本文&#xff0c;您将收获以下知识点: 一、硬件功耗二、相机软件功耗三、参考文档 一、硬件功耗 1.1 硬件信息 以下硬件信息最好提前获取到 模块备注平台MTK or Qcom or sprdCPU频率大中小核…

【Python】Python系列教程-- Python3 列表(十二)

文章目录 前言访问列表中的值更新列表删除列表元素Python列表截取与拼接嵌套列表列表比较Python列表函数&方法 前言 往期回顾&#xff1a; Python系列教程–Python3介绍&#xff08;一&#xff09;Python系列教程–Python3 环境搭建&#xff08;二&#xff09;Python系列…

第一次使用Arduino IDE(mac os) 配置合宙ESP32C3(9.9包邮)且烧录代码的历程

目录 Arduino 配置ESP32 1. Arduino 请更新至最新版 2.科学上网 3.添加开发板管理URL 配置 1.连接开发板 2.Arduino IDE 的配置 3.烧录代码 Arduino 配置ESP32 1. Arduino 请更新至最新版 2.科学上网 3.添加开发板管理URL 首选项&#xff0c;编辑并添加 https://…

医疗实施-DRG基本知识

医疗实施-DRG基本知识 DRG常见名词定义&#xff1a; DRG&#xff08;Diagnosis Related Groups&#xff09;:疾病诊断相关分组&#xff0c;是用于衡量医疗服务质量效率以及进行医保支付的一个重要工具。DRG 实质上是一种病例组合分类方案&#xff0c;即根据年龄、疾病诊断、合…

网络安全学术顶会——NDSS 2023 议题清单、摘要与总结(下)

51、Let Me Unwind That For You: Exceptions to Backward-Edge Protection 通过堆栈缓冲区溢出进行反向边控制流劫持是软件利用的终极目标。直接控制关键的堆栈数据和劫持目标使得攻击者特别喜欢这种利用策略。因此&#xff0c;社区已经部署了强大的反向边保护&#xff0c;如影…

Executor框架的成员

Executor框架的成员 &#xff08;1&#xff09;ThreadPoolExecutorThreadPoolExecutor通常使用工厂类Executors来创建。Executors可以创建3种类型的ThreadPoolExecutor&#xff1a;SingleThreadExecutor、FixedThreadPool和CachedThreadPool。1&#xff09; FixedThreadPool 。…

chatgpt赋能python:Python取某几行-掌握技巧提高效率

Python取某几行-掌握技巧提高效率 Python是一种简单易学、高效编程的语言。它也是一种非常强大的语言&#xff0c;适用于许多不同领域的应用程序。在处理文本文件和数据集时&#xff0c;Python的优势变得尤为突出。在这篇文章中&#xff0c;我们将重点介绍如何使用Python取某几…

Linux【网络编程】之深入理解UDP协议

Linux【网络编程】之深入理解UDP协议 一、传输层二、再谈端口号2.1 端口号划分 三、查看网络状态---netstat四、查看服务器进程ID---pidof五、UDP协议端格式5.1 理解报头 六、UDP的特点七、UDP的缓冲区问题八、常见基于UDP的应用层协议 在前面的几篇文章中我主要偏向于应用层介…

30分钟!从0到1,用ChatGPT+Python做一个AI起名网!

坚持6年&#xff0c;第629篇原创 现在利用ChatGPT可以做很多很多事情&#xff0c;而对于我们程序员来说&#xff0c;是机会也是挑战&#xff01;因为原来很多工种&#xff0c;很多技术问题现在可以用非常廉价的技术去获取&#xff0c;成本更低了&#xff01; 打个比方&#xff…

【LeetCode】《LeetCode 101》第十章:神奇的位运算

文章目录 10.1 常用技巧10.2 位运算基础问题461. 汉明距离&#xff08;简单&#xff09;190. 颠倒二进制位&#xff08;简单&#xff09;136. 只出现一次的数字&#xff08;简单&#xff09; 10.3 二进制特性342. 4的幂&#xff08;简单&#xff09;318. 最大单词长度乘积&…

Vue+springboot舞蹈基础课程视频学习分享平台的实现和开发

基于java语言设计并实现了舞蹈基础数据平台。该系统基于B/S即所谓浏览器/服务器模式&#xff0c;应用Springboot框架&#xff0c;选择MySQL作为后台数据库。系统主要包括首页、个人中心、用户管理、舞蹈类型管理、舞蹈视频管理、用户留言、管理员管理、系统管理等功能模块。 重…

springboot+vue+elementui计算机专业课程选课管理系统vue

本系统的主要任务就是负责对学生选课。主要用户为老师、学生,其中,学生可对自己的信息进行查询,可以进行选课,也可以进行删除已选课程,教师可对学生和课程的信息进行查询&#xff0c;教师拥有所有的权限,可以添加删除学生信息。系统提供界面,操作简单。 为实现这些功能,系统一个…