C++网络编程实践:使用C++11基于epoll技术实现一个超大并发TCP服务器

理解epoll

epoll 是一种 I/O 复用技术,它允许一个线程有效地管理多个文件描述符(在本例中为套接字),而无需为每个连接创建单独的线程。这是通过事件驱动的方式来实现的,epoll 能够通知你哪些套接字已经准备好进行读写操作。

在传统的基于线程的模型中,每当有一个新的连接,服务器就会创建一个新的线程来处理这个连接。这种方法在连接数量较少时工作良好,但在高并发场景下会遇到线程上下文切换开销大、系统资源消耗过多等问题。

相比之下,epoll 允许你在一个线程中处理数千甚至数百万个连接,而不会因为线程管理带来额外的开销。epoll 通过注册事件(如 EPOLLIN 表示可读,EPOLLOUT 表示可写)来监控多个套接字。当某些套接字准备好时,epoll_wait 函数会立即返回这些套接字的列表,然后服务器可以依次处理这些事件,而无需创建新线程。

使用epoll的优势

使用 epoll 进行网络编程相比于传统的 selectpoll 方法具有显著的优势,尤其在处理大量并发连接的场景下更为明显

  1. 高效率epoll 使用更高效的数据结构和算法,例如基于红黑树的实现,使得它在处理大量文件描述符时效率远高于 selectpoll。它能够快速查找和更新事件状态,减少了系统调用的次数和上下文切换的开销。

  2. 扩展性epoll 支持水平触发(Level Triggered, LT)和边缘触发(Edge Triggered, ET)两种模式。ET 模式下,epoll 只在事件首次发生时报告,这可以减少不必要的事件报告,提高效率。LT 模式则允许重复报告事件直到被处理。

  3. 节省资源epoll 不需要为每一个文件描述符维护一个内核数据结构,而是使用一个文件描述符来管理多个连接,这样减少了内存使用和系统资源的消耗。

  4. 无连接数限制select 的最大限制是受 FD_SETSIZE 的约束,而 epoll 没有硬性的连接数限制,理论上可以处理成千上万乃至更多数量的并发连接。

  5. 精确的超时控制epoll 提供了更精确的超时控制,这对于需要高精度超时的网络应用尤为重要。

  6. 低延迟和高吞吐量epoll 的事件通知机制允许快速响应网络事件,降低延迟并提高整体吞吐量。

  7. 事件驱动模型epoll 基于事件驱动,这意味着只有当套接字上的事件真正发生时才会有通知,这避免了轮询所有连接所带来的开销。

  8. 支持多路复用epoll 可以同时处理多个套接字的读写事件,使得服务器可以有效地处理大量并发请求,而无需为每个连接创建额外的线程或进程。

  9. 低内存拷贝开销: 在处理事件时,epoll 可以直接访问内核中的数据结构,减少了用户空间和内核空间之间不必要的内存拷贝。

  10. 灵活的通知机制epoll 允许应用程序注册不同类型的事件,包括读事件、写事件等,以及错误条件和挂起事件。

代码样例 

下面用epoll技术实现一个TCP服务器,具体方法和思路看注释

#include <iostream>
#include <string.h> 
#include <unistd.h> 
#include <sys/socket.h>  // socket, bind, listen, accept
#include <netinet/in.h>  // sockaddr_in
#include <arpa/inet.h>   // inet_addr
#include <fcntl.h>       // fcntl
#include <errno.h>   
#include <sys/epoll.h>   // epoll
#include <unordered_map>
#include <string>
#include <vector>#define MAX_EVENTS 3000class TCPServer {
public://构造函数TCPServer(int port) : listenSocket(-1), port(port){}~TCPServer() {if (listenSocket != -1)close(listenSocket);}void start() {initSocket();setupEpoll();listen(listenSocket, SOMAXCONN);std::cout << "Server started on port " << port << std::endl;run();}private:int listenSocket;int epollFd;int port;int msgCount;/*** 初始化监听套接字* 该函数创建一个监听套接字,并将其绑定到指定的IP地址和端口上。* 如果创建或绑定套接字时发生错误,程序将输出错误信息并退出。*/void initSocket() {// 创建一个流式套接字listenSocket = socket(AF_INET, SOCK_STREAM, 0);if (listenSocket == -1) {perror("Error creating socket");exit(EXIT_FAILURE);}// 设置套接字选项,允许重复使用地址// 这在重启服务时特别有用,可以避免因为地址还处于TIME_WAIT状态而无法绑定int optval = 1;setsockopt(listenSocket, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval));// 初始化sockaddr_in结构体,用于指定套接字要绑定的地址和端口struct sockaddr_in addr;memset(&addr, 0, sizeof(addr));addr.sin_family = AF_INET;addr.sin_port = htons(port);addr.sin_addr.s_addr = htonl(INADDR_ANY);// 将套接字绑定到指定的地址和端口上if (bind(listenSocket, (struct sockaddr*)&addr, sizeof(addr)) == -1) {perror("Error binding socket");close(listenSocket);exit(EXIT_FAILURE);}}/*** 演示epoll的使用* 初始化epoll监听结构体* 本函数用于创建epoll实例,并将监听socket添加到epoll中,以非阻塞方式监听客户端连接请求。* * @note 如果创建epoll实例或添加监听socket到epoll中失败,程序将退出。*/void setupEpoll() {// 创建epoll实例epollFd = epoll_create1(0);if (epollFd == -1) {// 如果创建失败,输出错误信息,关闭监听socket,并退出程序perror("Error creating epoll instance");close(listenSocket);exit(EXIT_FAILURE);}struct epoll_event ev;// 设置epoll事件类型为可读事件和边缘触发ev.events = EPOLLIN | EPOLLET;// 将监听socket关联到epoll事件中ev.data.fd = listenSocket;if (epoll_ctl(epollFd, EPOLL_CTL_ADD, listenSocket, &ev) == -1) {// 如果添加监听socket到epoll失败,输出错误信息,关闭监听socket和epoll实例,并退出程序perror("Error adding listen socket to epoll");close(listenSocket);close(epollFd);exit(EXIT_FAILURE);}// 将监听socket设置为非阻塞模式fcntl(listenSocket, F_SETFL, O_NONBLOCK);}/*** 主循环,负责监听和处理客户端连接。* 该循环将持续运行,直到发生无法恢复的错误。* 它通过epoll_wait监控客户端连接和已建立的连接上的活动。*/void run() {while (true) {// 准备接收epoll_wait返回的事件。std::vector<struct epoll_event> events(MAX_EVENTS);// 调用epoll_wait阻塞,直到有事件发生或超时。超时时间为-1,表示无限等待。int numEvents = epoll_wait(epollFd, events.data(), MAX_EVENTS, -1);// 检查epoll_wait调用是否失败。if (numEvents == -1) {perror("Error in epoll_wait");break;}// 遍历发生的事件,区分监听套接字和客户端套接字的事件。for (int i = 0; i < numEvents; ++i) {// 如果事件来源是监听套接字,则处理新的客户端连接请求。if (events[i].data.fd == listenSocket) {handleNewConnection();}else {// 如果事件来源是已建立的客户端套接字,则处理客户端的请求或数据。handleClient(events[i].data.fd);}}}}/*** 接收新连接*/void handleNewConnection() {int clientSocket = accept(listenSocket, nullptr, nullptr);if (clientSocket == -1) {if (errno != EAGAIN && errno != EWOULDBLOCK) {perror("Error accepting new connection");}return;}//添加到epoll中struct epoll_event ev;ev.events = EPOLLIN | EPOLLET;ev.data.fd = clientSocket;if (epoll_ctl(epollFd, EPOLL_CTL_ADD, clientSocket, &ev) == -1) {perror("Error adding client socket to epoll");close(clientSocket);return;}//设置非阻塞模式fcntl(clientSocket, F_SETFL, O_NONBLOCK);struct sockaddr_in addr;socklen_t len = sizeof(addr);getpeername(clientSocket, (struct sockaddr*)&addr, &len);std::string address = inet_ntoa(addr.sin_addr);std::cout << "Client connected " << address << std::endl;}/*** 处理客户端消息*/void handleClient(int clientSocket) {ssize_t bytesRead = read(clientSocket, buffer, sizeof(buffer) - 1);if (bytesRead == -1) {if (errno != EAGAIN && errno != EWOULDBLOCK) {perror("Error reading from client");}return -1;}else if(bytesRead == 0){close(clientSocket);epoll_ctl(epollFd, EPOLL_CTL_DEL, clientSocket, nullptr);std::cout << "Client Disconnected "  << std::endl;return 0;}std::cout << "Received data " << bytesRead  << std::endl;//回复一些数据//write(clientSocket, buffer, len);}
};int main(int argc, char* argv[]) {int PORT = 8080;if (argc >= 2) {PORT = std::atoi(argv[1]);}TCPServer server(PORT);server.start();return 0;
}

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

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

相关文章

国际网络专线的开通流程

1. 选择服务商&#xff1a;首先&#xff0c;您需要选择一个可靠的服务商来提供国际网络专线服务。确保服务商具有良好的声誉和专业知识&#xff0c;以便为您提供高质量的网络连接和支持。 2. 评估需求&#xff1a;在与服务商沟通之前&#xff0c;您需要明确自己的网络需求。这…

dp经典问题:LCS问题

dp&#xff1a;LCS问题 最长公共子序列&#xff08;Longest Common Subsequence, LCS&#xff09;问题 是寻找两个字符串中最长的子序列&#xff0c;使得这个子序列在两个字符串中出现的相对顺序保持一致&#xff0c;但不要求连续。 力扣原题链接 1.定义 给定两个字符串 S1…

Python快速搭建网站

使用 Python 快速搭建网站通常可以选择以下几种流行的框架&#xff1a; Flask&#xff1a;轻量级框架&#xff0c;适合快速原型开发。Django&#xff1a;功能强大的框架&#xff0c;适合构建复杂的大型应用。 下面将分别介绍如何使用 Flask 和 Django 快速搭建一个简单的网站…

猫狗识别—视频识别

猫狗识别—视频识别 1. 导入所需的库&#xff1a;2. 创建Tkinter主窗口并设置标题&#xff1a;3. 设置窗口的宽度和高度&#xff1a;4. 创建一个Canvas&#xff0c;它将用于显示视频帧&#xff1a;5. 初始化一个视频流变量cap&#xff0c;用于存储OpenCV的视频捕获对象&#xf…

C语言 给定半径和高,计算圆,球,圆柱各参数

设圆半径r1.5&#xff0c;圆柱高h3&#xff0c;求圆周长&#xff0c;圆面积&#xff0c;圆球表面积&#xff0c;圆球体积&#xff0c;圆柱体积。用scanf输入数据&#xff0c;输出计算结果&#xff0c;输出时要求有文字说明&#xff0c;取小数点后2位数字。 #include <stdio.…

【速速收藏】适用于Linux系统的五个优秀PDF编辑器

PDF (Portable Document Format) 是便携文档格式的缩写&#xff0c;这是一种用于电子共享文档的标准格式&#xff0c;广泛应用于各种文档类型的存储和分发。然而&#xff0c;有时我们可能需要对PDF文档进行更改和编辑。本文将介绍五款在Linux平台上广受欢迎的PDF编辑器。 ​​…

陀螺仪LSM6DSV16X与AI集成(8)----MotionFX库解析空间坐标

陀螺仪LSM6DSV16X与AI集成.8--MotionFX库解析空间坐标 概述视频教学样品申请源码下载开启CRC串口设置开启X-CUBE-MEMS1设置加速度和角速度量程速率选择设置FIFO速率设置FIFO时间戳批处理速率配置过滤链初始化定义MotionFX文件卡尔曼滤波算法主程序执行流程lsm6dsv16x_motion_fx…

Linux基础 - 常用命令

目录 零. 简介 一 . 常见 Ubuntu 命令 二. apt-get 下载 三. 网络命令 四. 常用命令的总结 零. 简介 在 Ubuntu 中&#xff0c;命令是用于与操作系统进行交互和执行各种操作的指令。通过在终端中输入命令&#xff0c;可以完成文件管理、系统配置、软件安装、进程管理等各种…

【分布式事务】Seata AT实战

目录 Seata 介绍 Seata 术语 Seata AT 模式 介绍 实战&#xff08;nacos注册中心&#xff0c;db存储&#xff09; 部署 Seata 实现 RM 实现 TM 可能遇到的问题 1. Seata 部署成功&#xff0c;服务启动成功&#xff0c;全局事务不生效 2. 服务启动报错 can not get …

[java]集合类stream的相关操作

1.对list中的map进行分组 下面例子中&#xff0c;根据高度height属性进行分组 List<Map<String, Float>>originalList new ArrayList<>();originalList.add(new HashMap<String,Float>() {{put("lng", 180.0f);put("lat",90f);…

C++使用Poco库封装一个FTP客户端类

0x00 Poco库中 Poco::Net::FTPClientSession Poco库中FTP客户端类是 Poco::Net::FTPClientSession , 该类的接口比较简单。 上传文件接口&#xff1a; beginUpload() , endUpload() 下载文件接口&#xff1a; beginDownload() , endDownload() 0x01 FTPCli类说明 FTPCli类…

CSS规则——font-face

font-face 什么是font-face&#xff1f; 想要让网页文字千变万化&#xff0c;仅靠font-family还不够&#xff0c;还要借助font-face&#xff08;是一个 CSS 规则&#xff0c;它允许你在网页上使用自定义字体&#xff0c;而不仅仅是用户系统中预装的字体。这意味着你可以通过提…

jemeter基本使用

后端关验签&#xff0c;设置请求头编码和token 配置编码和token

Linux安装minio及mc客户端(包含ARM处理器架构)

&#x1f353; 简介&#xff1a;java系列技术分享(&#x1f449;持续更新中…&#x1f525;) &#x1f353; 初衷:一起学习、一起进步、坚持不懈 &#x1f353; 如果文章内容有误与您的想法不一致,欢迎大家在评论区指正&#x1f64f; &#x1f353; 希望这篇文章对你有所帮助,欢…

练习实践:ubuntu18.04安装、配置Nginx+PHP环境,两种配置方式,多站点

参考来源&#xff1a; https://help.aliyun.com/document_detail/464753.html https://www.cnblogs.com/laosan007/p/12803287.html https://blog.csdn.net/qq_55364077/article/details/132207083 【安装同版本7.2的php】 需要知道对应php和nginx的安装版本 需要安装php-fpm…

<sa8650>QCX Usecase 使用详解—如何在 QCX 框架中添加新的自定义Usecase/Pipeline

<sa8650>QCX Usecase 使用详解—如何在 QCX 框架中添加新的自定义Usecase/Pipeline 一、前言二、为 Usecase/Pipeline 创建新文件夹三、Create Usecase XML四、为 camxAutoo_Test 管道创建拓扑五、添加Usecase/Pipeline编译六、 使用 Qcarcam_Test 应用程序运行Usecase一、前…

List of installed software and plug-ins

* Indicates lower priority real machine — install: *diskgenius *diskinfo *diskmark *IDM *zoomit *wiseprogramUnistaller *winxray *spaceSniffer vscode notepad winrar everything huorong firefox vmware inside — general: typora qbittorrent IDM zoomit x-mind…

【总线】AXI4第五课时:信号描述

大家好,欢迎来到今天的总线学习时间!如果你对电子设计、特别是FPGA和SoC设计感兴趣&#xff0c;那你绝对不能错过我们今天的主角——AXI4总线。作为ARM公司AMBA总线家族中的佼佼者&#xff0c;AXI4以其高性能和高度可扩展性&#xff0c;成为了现代电子系统中不可或缺的通信桥梁…

【华为OD机试】垃圾短信识别(C++ Java JavaScript Python)

题目 题目描述 大众对垃圾短信深恶痛绝,希望能对垃圾短信发送者进行识别,为此,很多软件增加了垃圾短信的识别机制。 经分析,发现正常用户的短信通常具备交互性,而垃圾短信往往都是大量单向的短信,按照如下规则进行垃圾短信识别: 本题中,发送者A符合以下条件之一的,则认…

【高性能计算笔记】

第1章 - 高性能计算介绍 1. 概念&#xff1a; 高性能计算(High performance computing&#xff0c;缩写HPC)&#xff1a; 指通常使用很多处理器&#xff08;作为单个机器的一部分&#xff09;或者某一集群中组织的几台计算机&#xff08;作为单个计算资源操作&#xff09;的…