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

Linux下基于C++11的socket网络编程(Epoll)个人总结版

写完了发现好像没什么用,看看八股其实就够了,我真乐了。但是也都写了写完吧
在select中我们提到

select结束后,我传入的内容会变成一个类似(001110010101的数)是没有办法直接知道哪个位置是1的,只能一个一个的遍历。

这样很麻烦,但由于select用的是位掩码,所以只能这样一个个遍历。那优化的想法就来了:我只把就绪的传给应用程序行不行?没就绪的我也不传0了,我直接不传了。于是epoll出现了。

接着上八股:
epoll是Linux特有的IO多路复用机制,它使用一个内核事件表来管理和监听多个IO事件的就绪状态。应用程序需要将需要监听的文件描述符添加到内核事件表中,然后调用epoll_wait函数进行监听。当有文件描述符就绪时,epoll_wait函数会返回,并告知哪些文件描述符已经准备好进行读取或写入操作。与select和poll不同的是,epoll不需要应用程序遍历事件列表。epoll_wait 返回的数组中仅包含已就绪的事件,应用程序只需遍历该数组(时间复杂度为 O(k),k 为就绪事件数)。
水平触发(默认):
● 只要文件描述符处于就绪状态(如可读),就会持续触发事件(意思就是变为就绪状态)。
● 编程简单,但可能导致频繁唤醒。
边缘触发(需显式设置EPOLLET):
● 仅在文件描述符状态变化时触发一次(如从不可读到可读)。
● 必须处理完所有数据(如读完read()返回EAGAIN),否则不会再次触发。
● 性能更高,但编程复杂度增加

1.涉及到的函数

首先内核维护的结构是int event_fd,应用程序维护的结构是epoll_event event。这些函数的作用就是把内容加到event_fd中,或者把event_fd中的内容传给event,方便应用程序操作。

1.1epoll_event

struct epoll_event {uint32_t     events;      // 事件类型掩码(如 EPOLLIN、EPOLLOUT)epoll_data_t data;        // 用户数据(通过 union 存储)
};

epoll_event的结构如上图所示,events存event_fd传给它的实际类型(是读还是写,是水平触发还是边缘触发).data存已经就绪的fd是什么
1.2epoll_create()
功能:创建一个epoll实例,返回文件描述符event_fd。
1.3epoll_ctl()
int epoll_ctl(int epfd, int op, int fd, struct epoll_event event);
功能:控制 epoll 实例,添加 / 修改 / 删除监听事件。也就是说向内核控制的epfd中,进行op操作,操作fd套接字。是
event模式
也就是说是水平触发还是边缘触发,是监听接收缓冲区还是发送缓冲区都是通过epoll_ctl函数,从event传给event_fd的
● epfd:epoll 实例的文件描述符。
● op:操作类型:
○ EPOLL_CTL_ADD:添加监听。
○ EPOLL_CTL_MOD:修改监听事件。
○ EPOLL_CTL_DEL:删除监听(此时event参数可为NULL)。
● fd:要监听的目标文件描述符(如 socket)。
● event:指定监听的事件类型和关联数据:下面是常用的事件类型
○ EPOLLIN:文件描述符可读(如套接字接收缓冲区有数据)。
○ EPOLLOUT:文件描述符可写(如套接字发送缓冲区有空闲空间)。
○ EPOLLRDHUP(Linux 2.6.17+):对方关闭连接,或关闭了写操作(适用于 TCP 连接)。
○ EPOLLHUP:连接被挂断。
○ EPOLLET:边缘触发模式(区别于默认的水平触发)。
○ EPOLLONESHOT:一次触发后,事件自动移除,需重新注册
1.4epoll_wait()
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
功能:等待事件发生,返回就绪的文件描述符列表。
● epfd:epoll 实例的文件描述符。
● events:用于存储就绪事件的数组。
● maxevents:数组大小(必须大于 0)。
● timeout:超时时间(毫秒):
○ -1:阻塞直到有事件发生。
○ 0:立即返回(非阻塞)。
○ >0:指定超时时间。
● 成功:返回就绪事件的数量;超时:返回0;错误:返回-1(如被信号中断)
用epoll_create()在内核中创建一个 eventpoll 对象,包含红黑树和就绪链表。返回的int epoll_fd 是指向这个eventpoll对象(因为万物皆对象)。如果有需要添加的会用epoll_ctl()函数把需要添加的套接字放到红黑树里,如果内核检测到有就绪的套接字,会放到就绪链表里。应用程序这边调用epoll_wait()函数,若就绪链表为空,进程进入休眠;否则直接返回链表中的就绪事件,把就绪事件转移到我们定义好的epoll_event event中。应用程序就可以进行处理了。
看完这段之前提到的水平触发和边缘触发中的触发,应该也可以理解了,就是加到了就绪链表中。

//TcpServer.cpp
#include "TcpServer.h"#include <vector>TcpServer::TcpServer(){serv_socks.clear();client_socks.clear();
}TcpServer::~TcpServer(){CloseServerSock();CloseClientSock();
}bool TcpServer::InitServer(const std::vector<std::string>& ports){//先判断服务端socket是否已开启,如果开启全部关闭CloseServerSock();//创建服务器中的服务端socketfor (const auto& port: ports) {int serv_sock= socket(PF_INET , SOCK_STREAM , 0);if(serv_sock == -1){cout<<"服务器创建监听套接字失败,socket() error!"<<endl;return false;}struct sockaddr_in addr;//绑定服务端的socket到相应的网络地址结构中memset(&addr , 0 , sizeof(addr));addr.sin_family = AF_INET;addr.sin_addr.s_addr = htonl(INADDR_ANY);addr.sin_port = htons(atoi(port.c_str()));if(bind(serv_sock , (struct sockaddr*)& addr , sizeof(addr)) == -1){cout<<"服务器绑定套接字失败,bind() error!"<<port<<endl;//关闭服务端的socketclose(serv_sock);continue;;}//开始监听服务端的套接字if(listen(serv_sock , 5) == -1){cout<<"服务端监听套接字失败,listen() error!"<<port<<endl;//关闭服务端的socketclose(serv_sock);continue;;}serv_socks.push_back(serv_sock);ser_epoll.AddEpollEvent(serv_sock);cout<<"服务端成功监听端口"<<port<<endl;}return !serv_socks.empty();
}int TcpServer::Accept(int serv_fd){//判断服务端是否已经开启了socketif(serv_fd == -1){cout<<"服务器中未开启监听的socket,Accept() error!"<<endl;return -1;}struct sockaddr_in clnt_addr;socklen_t clnt_addr_len = sizeof(clnt_addr);int clnt_sock = accept(serv_fd, (struct sockaddr*)&clnt_addr, &clnt_addr_len);if (clnt_sock == -1) {cout << "accept() failed: " << strerror(errno) << endl;return -1;}client_socks.push_back(clnt_sock);return clnt_sock;
}void TcpServer::StartEpoll(){while(1){//开始使用epoll监视事件的发生int event_cnt = ser_epoll.EpollWait(ep_events);if(event_cnt == -1){//创建epoll_wait()函数失败cout<<"epoll_wait() error!"<<endl;break ;}for(int i = 0 ; i < event_cnt ; ++i){int _fd = ep_events[i].data.fd;bool is_listening_socket=false;for (int listen_fd : serv_socks) {if (listen_fd == _fd) {is_listening_socket = true;break;}}if(is_listening_socket){//有新的连接int accept_res = Accept(_fd);if(accept_res <= 0){cout<<"accept() error!"<<endl;continue ;}ser_epoll.AddEpollEvent(accept_res);cout<<"new client【"<<accept_res<<"】connect..."<<endl;}else{//出现了读写操作string result="";if(!TcpBaseRead(_fd , result)){//可能是对端关闭了连接cout<<"client【"<<_fd<<"】断开了连接!"<<endl;ser_epoll.DeleteEpollEvent(_fd);close(_fd);}else{cout<<"客户端【"<<_fd<<"】说:"<<result<<endl;if(!TcpBaseWrite(_fd , result)){//关闭cout<<"client【"<<_fd<<"】断开了连接!"<<endl;ser_epoll.DeleteEpollEvent(_fd);close(_fd);}}}}}
}// 关闭指定客户端连接
void TcpServer:: CloseSingleClientSock(int fd) {ser_epoll.DeleteEpollEvent(fd);for (auto it = client_socks.begin(); it != client_socks.end(); ++it) {if (*it == fd) {client_socks.erase(it);break; // 找到后立即退出循环}}if (fd > 0) {std::cout << "关闭客户端连接 fd=" << fd << std::endl;close(fd);}
}void TcpServer::CloseServerSock(){for (int fd : serv_socks) {if (fd > 0) {close(fd);}}serv_socks.clear();
}void TcpServer::CloseClientSock(){for (int fd : client_socks) {if (fd > 0) {ser_epoll.DeleteEpollEvent(fd);close(fd);}}client_socks.clear();
}
//TcpServerEpoll.cpp
//
// Created by root on 2025/6/30.
//#include "TcpServerEpoll.h"
TcpServerEpoll::TcpServerEpoll(){//创建一个epoll,大小位EPOLL_SIZE,这个函数返回的是一个文件描述符epoll_fd = epoll_create(EPOLL_SIZE);}int TcpServerEpoll::AddEpollEvent(const int sock_fd){event.events = EPOLLIN;event.data.fd = sock_fd;if ( epoll_ctl(epoll_fd , EPOLL_CTL_ADD , sock_fd , &event)) {return -1; // 添加失败}monitored_fds.insert(sock_fd); // 成功添加,记录到集合return 0;
}int TcpServerEpoll::DeleteEpollEvent(const int sock_fd){if (epoll_ctl(epoll_fd , EPOLL_CTL_DEL , sock_fd , NULL)) {return -1;}monitored_fds.erase(sock_fd); // 成功添加,记录到集合return 0;
}int TcpServerEpoll::EpollWait(epoll_event* events){int event_cnt = epoll_wait(epoll_fd , events , EPOLL_SIZE , -1);return event_cnt;
}TcpServerEpoll::~TcpServerEpoll(){close(epoll_fd);
}
//
// Created by root on 2025/6/25.
//#include "TcpServer.h"int main(int argc ,char* argv[]){//创建服务端的socketTcpServer tcp_server;//初始化服务器std::vector<std::string> ports={"8080", "8081","8082"};for (const auto& port : ports)std::cout << "即将监听端口号"<<port << " ";bool init_res = tcp_server.InitServer(ports);if(!init_res){cout<<"服务器初始化失败"<<endl;return -1;}for (const auto& port : ports)std::cout << "已监听端口号"<<port << " ";//开启服务端的I/O功能tcp_server.StartEpoll();std::cout << "服务器已正常关闭" << std::endl;return 0;
}
http://www.dtcms.com/a/265916.html

相关文章:

  • ssh挂载拷贝
  • Java 大视界 -- Java 大数据机器学习模型在自然语言处理中的跨语言信息检索与知识融合(331)
  • 汽车ECU产线烧录和检测软件怎么做?
  • 云计算中的tap口、bond口、qr口:它们究竟有何玄机?
  • 【JS笔记】JS 和 noodjs 的常见操作(十)
  • 数据库10:MySQL的数据类型与约束和属性设置,数据模式
  • EXCEL 基础函数
  • JavaScript的初步学习
  • 未来之窗冥界调试工具—东方仙盟
  • java分页插件| MyBatis-Plus分页 vs PageHelper分页:全面对比与最佳实践
  • 【Bug Recod】更新中...
  • 可执行脚本
  • 08-three.js Textures
  • day15——Java常用API(二):常见算法、正则表达式与异常处理详解
  • 【机器学习深度学习】AI 项目开发流程:从需求到部署的五大阶段
  • Springboot3整合ehcache3缓存--XML配置和编程式配置
  • 移除 Java 列表中的所有空值
  • 一天两道力扣(1)
  • Linux多线程(十二)之【生产者消费者模型】
  • “Payload document size is larger than maximum of 16793600.“问题解决(MongoDB)
  • Kettle数据抽取(十一)作业-邮件
  • 什么是码率?剪映中如何选择适合的视频码率
  • C++(std::sort)
  • js-cookie详细介绍
  • Node.js与Webpack
  • 2025年6月:技术探索与生活平衡的协奏曲
  • 目标检测:从基础原理到前沿技术全面解析
  • 架构师的“降维打击”:用桥接模式,把 N*M 的问题变成 N+M
  • Matplotlib 安装使用教程
  • 【Git】同时在本地使用多个github账号进行github仓库管理