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

C++网络编程之IO多路复用(三)

概述

        在前两篇文章中,我们介绍了如何使用select和poll进行IO多路复用。select通常有一个固定的文件描述符数量上限(通常是1024),poll虽然没有严格的文件描述符数量限制,但在实际使用中也可能受到系统资源的限制。相比之下,epoll支持非常大的文件描述符数量(理论上可以达到系统文件描述符的最大值),因此更适合高并发场景。在本篇中,我们将重点介绍epoll。

epoll

        epoll是Linux特有的IO多路复用接口,专为大规模并发场景设计。epoll相比于select和poll有更高的性能,因为它可以处理大量的文件描述符,并且在获取事件时不需要遍历整个文件描述符集合。epoll的内部实现中使用了红黑树和就绪链表两种数据结构,以便高效地管理和查询事件。

        epoll的核心思想是:创建一个事件表,这个事件表会将所有需要监控的文件描述符和它们对应的事件关联起来。当某个文件描述符上的事件发生时,内核会将该事件添加到就绪列表中。用户程序只需要从就绪列表中读取事件即可,而不需要像select或poll那样每次都需要遍历整个文件描述符集来查找哪些事件已经就绪。

        与epoll相关的系统API主要有三个:epoll_create、epoll_ctl、epoll_wait,下面分别进行介绍。

        1、epoll_create函数用于创建一个epoll实例,并返回一个新的文件描述符。在较新的Linux版本中,推荐使用epoll_create1函数。epoll_create1是epoll_create的改进版,允许指定标志位。

int epoll_create(int size);
int epoll_create1(int flags);

        size:这个参数在现代内核中已经不再使用,但在调用时仍需要传递一个大于零的值。它是历史遗留下来的,用于向后兼容。

        flags:可以是0或EPOLL_CLOEXEC。EPOLL_CLOEXEC标志表示执行exec函数族时,关闭此文件描述符。

        返回值:成功时返回新的文件描述符(非负整数),失败时返回-1,并设置errno。

        2、epoll_ctl函数用于向epoll实例添加、修改或删除感兴趣的文件描述符。

int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);

        epfd:由epoll_create或epoll_create1创建的epoll文件描述符。

        op:操作类型,可以是以下之一:

        (1)EPOLL_CTL_ADD:将文件描述符fd添加到epoll实例中。

        (2)EPOLL_CTL_MOD:修改文件描述符fd在epoll实例中的事件。

        (3)EPOLL_CTL_DEL:从epoll实例中移除文件描述符fd。

        fd:要操作的文件描述符。

        event:指向epoll_event结构体的指针,定义了监听的事件类型和关联的数据。

        返回值:成功时返回0,失败时返回-1,并设置errno。

        3、epoll_wait函数用于等待并获取就绪的IO事件。

int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);

        epfd:由epoll_create或epoll_create1创建的epoll文件描述符。

        events:指向epoll_event数组的指针,用来存放发生的事件。

        maxevents:events数组的最大长度。

        timeout:等待时间,单位为毫秒。-1表示无限等待,0表示立即返回,不阻塞。

        返回值:成功时返回发生的事件数量,失败时返回-1,并设置errno。

边缘触发模式

        epoll支持边缘触发(Edge-Triggered,即ET)模式,select和poll不支持,它们仅支持水平触发模式(Level-Triggered,即LT)。边缘触发模式是一种高效的IO事件通知机制,它与水平触发模式有所不同。在边缘触发模式下,epoll只会在文件描述符的状态发生变化时通知应用程序一次。这意味着,如果应用程序没有处理完所有数据,内核将不会再次通知该事件,直到新的数据到达或状态再次发生变化。

        epoll的边缘触发模式具有以下几个特点。

        1、仅在状态变化时通知。当文件描述符从不可读变为可读,或者从不可写变为可写时,epoll会通知应用程序。如果应用程序没有完全读取或写入所有数据,那么剩余的数据将不会再次触发事件,直到有新的数据到来或状态再次变化。

        2、非阻塞IO。在边缘触发模式下,必须使用非阻塞IO。这是因为当read或write调用返回EAGAIN或EWOULDBLOCK时,表示当前没有更多数据可以读取或写入,但并不意味着文件描述符已经不可读或不可写。应用程序需要继续尝试读取或写入,直到所有数据处理完毕。

        3、更高的性能。由于边缘触发模式减少了内核和用户空间之间的上下文切换次数,因此通常比水平触发模式更高效,特别是在高并发场景中。

        4、更复杂的编程模型。使用边缘触发模式需要更加小心地处理IO操作,以确保不会遗漏任何数据。

实战代码

        在下面的示例代码中,我们使用epoll函数实现了TCP服务器的IO多路复用。

        首先,我们创建一个监听套接字listen_sock,将其绑定到指定的端口8888,并开始监听连接请求。

        然后,我们使用epoll_create1创建一个epoll实例,并检查是否成功。接着,将监听套接字listen_sock添加到 epoll实例中,并设置为边缘触发模式(EPOLLET)和可读事件(EPOLLIN)。

        在主循环中,我们使用epoll_wait函数等待IO事件的发生。epoll_wait会阻塞,直到有事件发生或超时,返回值nfds是就绪事件的数量。遍历events数组中的每一个就绪事件,检查每个事件的文件描述符fd是否为监听套接字listen_sock。

        如果事件的文件描述符是监听套接字listen_sock,则表示有新的连接请求。调用accept接受新连接,并创建一个新的客户端套接字conn_sock。将新连接的套接字添加到epoll实例中,并设置为边缘触发模式(EPOLLET)和可读事件(EPOLLIN)。

        如果事件的文件描述符不是监听套接字listen_sock,则表示该文件描述符上有数据可读。调用read函数读取数据,并回显给客户端。如果读取到0字节,表示客户端已断开连接。如果读取失败,也认为客户端断开连接。在这两种情况下,关闭套接字,并从epoll实例中移除该套接字。

        最后,当程序退出时,关闭所有打开的套接字和epoll实例。

#include <iostream>
#include <sys/epoll.h>
#include <unistd.h>
#include <fcntl.h>
#include <cstring>
#include <sys/socket.h>
#include <netinet/in.h>using namespace std;#define MAX_EVENTS                  10int main()
{int listen_sock = socket(AF_INET, SOCK_STREAM, 0);if (listen_sock == -1){cout << "Create socket failed" << endl;return 1;}struct sockaddr_in server_addr;memset(&server_addr, 0, sizeof(server_addr));server_addr.sin_family = AF_INET;server_addr.sin_port = htons(8888);server_addr.sin_addr.s_addr = INADDR_ANY;if (bind(listen_sock, (struct sockaddr*)&server_addr, sizeof(server_addr)) == -1){cout << "Bind failed" << endl;close(listen_sock);return 1;}if (listen(listen_sock, 5) == -1){cout << "Listen failed" << endl;close(listen_sock);return 1;}// 创建epoll实例int epoll_fd = epoll_create1(0);if (epoll_fd == -1){cout << "Create epoll failed" << endl;close(listen_sock);return 1;}// 添加监听Socketstruct epoll_event ev, events[MAX_EVENTS];ev.events = EPOLLIN | EPOLLET;ev.data.fd = listen_sock;epoll_ctl(epoll_fd, EPOLL_CTL_ADD, listen_sock, &ev);while (true){// 等待并获取就绪的IO事件int nfds = epoll_wait(epoll_fd, events, MAX_EVENTS, -1);if (nfds == -1){cout << "Wait epoll failed" << endl;break;}for (int i = 0; i < nfds; ++i){if (events[i].data.fd == listen_sock){// 新连接struct sockaddr_in client_addr;socklen_t client_len = sizeof(client_addr);int conn_sock = accept(listen_sock, (struct sockaddr*)&client_addr, &client_len);if (conn_sock != -1){ev.events = EPOLLIN | EPOLLET;ev.data.fd = conn_sock;epoll_ctl(epoll_fd, EPOLL_CTL_ADD, conn_sock, &ev);}else{cout << "New connection" << endl;}}else{// 处理数据char buf[1024];ssize_t nread = read(events[i].data.fd, buf, sizeof(buf));if (nread > 0){// 接收并回显数据cout << "Received data: " << string(buf, nread) << endl;write(events[i].data.fd, buf, nread);}else if (nread == 0){// 客户端关闭连接epoll_ctl(epoll_fd, EPOLL_CTL_DEL, events[i].data.fd, nullptr);close(events[i].data.fd);}else{// 发生错误cout << "Read error" << endl;epoll_ctl(epoll_fd, EPOLL_CTL_DEL, events[i].data.fd, nullptr);close(events[i].data.fd);}}}}close(listen_sock);close(epoll_fd);return 0;
}


http://www.mrgr.cn/news/70293.html

相关文章:

  • 误删了照片,甚至对存储卡进行了格式化 都可以找到丢失的图片,并让您恢复它们 支持一键恢复或永久删除丢失的照片、视频、音乐、文档等-供大家学习研究参考
  • 废品买卖回收管理系统|Java|SSM|Vue| 前后端分离
  • 基于之前的秒杀功能的优化(包括Sentinel在SpringBoot中的简单应用)
  • selenium环境搭建详细过程
  • HP6心率血压传感器
  • Linux:文件管理(二)——文件缓冲区
  • 丹摩征文活动|快速上手 CogVideoX-2b:智谱清影 6 秒视频生成部署教程
  • Python和Geopandas进行地理数据可视化的实用指南
  • 如何对公司的打印进行记录?打印机打印记录的3个自查小妙招,手把手教会你!
  • MySQL面试必杀技!不会这些,面试官都要哭了,你还想找工作?
  • 【NOIP普及组】摆花
  • 【LeetCode】每日一题 2024_11_11 切棍子的最小成本(区间 DP,记忆化搜索)
  • 堆排序,学习笔记
  • EHOME视频平台EasyCVR宇视设备视频平台1000路监控ip地址如何规划?
  • 期权懂|国内期货期权交易有门槛吗?
  • mysql 配置文件 my.cnf 增加 lower_case_table_names = 1 服务启动不了的原因
  • Ubuntu 的 ROS2 操作系统turtlebot3环境搭建
  • 内网环境,基于k8s docer 自动发包
  • 【c++笔试强训】(第三篇)
  • .wslconfig:6 中的未知密钥 ‘boot.systemd‘ 问题解决
  • 机器学习——特征工程、正则化、强化学习
  • Python绘制爱心
  • 简易入手《SOM神经网络》的本质与原理
  • 企业网络转型:优势与挑战
  • 劳务争议调解平台(源码+文档+部署+讲解)
  • 使用Python的vn.py进行量化回测双均线策略