公司动态
Epoll模型
Epoll模型一、底层核心原理每一个epoll模型内部都维护了一棵红黑树当调用epoll_ctl并执行EPOLL_CTL_ADD操作时会将新增的文件描述符fd及关联数据构造成一个新结点存入这棵红黑树中。此外epoll模型中还维护了一个就绪队列当文件描述符上有事件就绪时底层会触发中断操作系统会将该文件描述符对应的红黑树结点放入就绪队列中。当执行epoll_wait时就能直接拿到所有已经就绪的事件对应的fd由程序员自行处理。二、为什么用红黑树存储 fd 数据因为epoll模型可监测的 fd 数量没有硬上限当存在大量 fd 时使用红黑树既可以对 fd 进行高效管理也能实现快速查找与增删操作。三、事件结点如何加入就绪队列红黑树的每一个结点中存储的不只有 fd还包含其他指针字段。当某个结点有事件就绪时不需要重新拷贝整个结点再添加到就绪队列只需要通过结点内的就绪队列指针将结点链入就绪队列即可头插或尾插方式全程无需拷贝结点数据。四、epoll IO 多路转接的整体使用逻辑epoll 是 IO 多路转接模型的一种实现相比 select/poll 效率更高。使用 epoll 的大致流程为调用epoll_create创建一个 epoll 模型获取该模型的句柄本质也是一个文件描述符调用epoll_ctl将需要监听的文件描述符添加到 epoll 模型中进行事件检测在事件循环中调用epoll_wait接口等待就绪的文件描述符五、核心接口详细介绍1.epoll_create功能创建一个 epoll 模型实例返回该实例的句柄文件描述符。说明该函数的参数在新版本内核中已被弃用传入一个大于 0 的数字即可。函数原型#include sys/epoll.h int epoll_create(int size);使用样例// 1.创建epoll模型 _epfd epoll_create(128); if(_epfd 0) { std::cout epoll create error, strerror(errno) std::endl; exit(-1); }2.epoll_ctl功能通过 epoll 句柄对被监听的文件描述符执行增、删、改操作。成功返回 0失败返回 - 1。函数原型#include sys/epoll.h int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);参数说明epfdepoll 模型的句柄op操作类型EPOLL_CTL_ADD将 fd 添加到 epoll 模型中进行监听EPOLL_CTL_MOD修改 fd 对应的监听事件EPOLL_CTL_DEL将 fd 从 epoll 模型中移除fd目标文件描述符event需要监听的事件结构体其中epoll_event结构体定义如下events字段用于设置需要监听的事件常用的有EPOLLIN可读事件、EPOLLOUT可写事件data字段用户自定义数据一般将 fd 存入该字段的fd成员中struct epoll_event { uint32_t events; /* Epoll events */ epoll_data_t data; /* User data variable */ }; typedef union epoll_data { void *ptr; int fd; uint32_t u32; uint64_t u64; }epoll_data_t;使用样例// 2.将listen套接字加入到epoll模型中 // 先构建一个事件结构体 struct epoll_event ev; ev.events | EPOLLIN; ev.data.fd _socket-GetSockfd(); int n epoll_ctl(_epfd, EPOLL_CTL_ADD, _socket-GetSockfd(), ev); // 成功返回0 if(n ! 0) { std::cout epoll control error, strerror(errno) std::endl; exit(-1); }3.epoll_wait功能检查 epoll 模型中是否有文件描述符的事件就绪。如果有就绪事件会将这些就绪的 epoll 事件放到events缓冲区中成功时返回就绪事件的个数超时返回 0出错返回 - 1。函数原型#include sys/epoll.h int epoll_wait(int epfd, struct epoll_event* events, int maxevents, int timeout);参数说明epfdepoll 模型的句柄events存放就绪事件的输出缓冲区maxevents缓冲区最多能容纳的事件个数timeout超时时间单位是毫秒使用样例// 定义常量表示就绪事件缓冲区的最大容量 const static int gnum 64; // 定义存放就绪事件的缓冲区 struct epoll_event _events[gnum]; // 调用epoll_wait进行等待超过1秒就会返回继续向下执行 int n epoll_wait(_epfd, _events, gnum, 1000); if(n 0) { // 没有事件就绪超时返回 std::cout time out... std::endl; } else if(n 0) { // 有n个事件就绪开始处理事件 HandlerEvents(n); } else { if(errno EINTR) continue; std::cout epoll wait fail, strerror(errno) std::endl; exit(-1); }代码样例——EpollServerDemoEpollServer.hpp#pragma once #include iostream #include memory #include TcpSocket.hpp #include unordered_map #include unistd.h #include sys/epoll.h class EpollServer { private: const static int Nsizeof(fd_set)*8; const static int defaultfd-1; const static int gnum64; public: EpollServer(uint16_t port) :_port(port) ,_socket(std::make_uniqueTcpSocket(port)) ,_epfd(defaultfd) { // 1.创建epoll模型 _epfdepoll_create(128); if(_epfd0) { std::coutepoll create error, strerror(errno)std::endl; exit(-1); } // 2.将listen套接字加入到epoll模型中 // 先构建一个事件结构体 struct epoll_event ev; ev.events|EPOLLIN; ev.data.fd_socket-GetSockfd(); int n epoll_ctl(_epfd, EPOLL_CTL_ADD, _socket-GetSockfd(), ev); // 成功返回0 if(n!0) { std::coutepoll control error, strerror(errno)std::endl; exit(-1); } } void HandlerEvents(int num) { std::cout有Numnum个事件就绪了std::endl; } void Loop() { while(true) { int nepoll_wait(_epfd, _events, gnum, 1000); if(n0) { // 事件没有就绪 std::couttime out...std::endl; } else if(n0) { // 有n个事件就绪了 HandlerEvents(n); } else { if(errnoEINTR) continue; std::coutepoll wait fail, strerror(errno)std::endl; exit(-1); } } } private: int _epfd; uint16_t _port; std::unique_ptrTcpSocket _socket; struct epoll_event _events[gnum]; };TcpSocket.hpp#pragma once #include iostream #include sys/socket.h #include arpa/inet.h #include netinet/in.h #include cstring class InetAddr { public: InetAddr() {} InetAddr(const std::stringip, uint16_t port) :_ip(ip) ,_port(port) { memset(_sockaddr, 0, sizeof(_sockaddr)); _sockaddr.sin_familyAF_INET; _sockaddr.sin_porthtons(port); _sockaddr.sin_addr.s_addrinet_addr(_ip.c_str()); } std::string Ip() { return _ip; } uint16_t Port() { return _port; } // 将addr里面的数据拷贝给自己 void GetInetAddr(sockaddr_in*addr) { memcpy(_sockaddr, addr, sizeof(*addr)); _portntohs(addr-sin_port); _ipinet_ntoa(addr-sin_addr); } private: struct sockaddr_in _sockaddr; std::string _ip; uint16_t _port; }; class TcpSocket { public: TcpSocket(uint16_t port) :_port(port) { _listensocketsocket(AF_INET, SOCK_STREAM, 0); if(_listensocket0) { std::cout监听套接字失败std::endl; exit(-1); } // 设置地址复用 int opt1; setsockopt(_listensocket, SOL_SOCKET, SO_REUSEADDR, opt, sizeof(opt)); // bind struct sockaddr_in local; memset(local, 0, sizeof(local)); local.sin_familyAF_INET; local.sin_porthtons(port); local.sin_addr.s_addrINADDR_ANY; int nbind(_listensocket, (struct sockaddr*)local, sizeof(local)); if(n0) { std::cout绑定失败std::endl; exit(-1); } // 开启监听 n::listen(_listensocket, _defaultBacklog); if(n0) { std::coutTCP监听失败std::endl; exit(-1); } } int Accept(InetAddr*addr) { struct sockaddr_in peer; socklen_t lensizeof(peer); int sockfdaccept(_listensocket, (struct sockaddr*)peer, len); addr-GetInetAddr(peer); return sockfd; } int GetSockfd() { return _listensocket; } private: uint16_t _port; int _listensocket; // 类内常量所有类对象共享一份 const static int _defaultBacklog16; };Main.cc#include iostream #include EpollServer.hpp int main() { std::unique_ptrEpollServer serverstd::make_uniqueEpollServer(8080); server-Loop(); return 0; }makefiletest_server:Main.cc g -g -o $ $^ -stdc14 .PHONY:clean clean: rm -f test_server