【Linux】多路转接之epoll
摘要 本文主要探讨了epoll模型相对于传统poll/select的性能优化点。首先分析了poll存在的两个主要开销问题:频繁的用户态-内核态数据拷贝(每次调用需传输整个pollfd数组)和O(n)时间复杂度的事件遍历。然后详细介绍了epoll的解决方案:通过epoll_ctl/epoll_wait分离监听与等待机制,利用红黑树和就绪队列高效管理事件,仅返回就绪事件而非全部fd。文章还提供了epo
优化poll进行拷贝的开销
poll开销过大
- 将整个 pollfd 数组拷贝到内核态,以便内核检查 fd 是否就绪(从用户态 → 内核态)。
- 内核检查 fd 状态,并填充 revents。
- 将 pollfd 数组从内核态拷贝回用户态,让应用程序可以读取 revents 里的就绪状态(从内核态 → 用户态)。
这个拷贝过程涉及 所有 pollfd 结构体,而 pollfd 结构体的大小通常是 8~16字节(取决于架构),如果监听 fd 数量很大(例如 上千个),拷贝数据量会很大,导致 系统调用的开销上升。
优化遍历开销
- 优化 poll 遍历查询空位置的问题:poll 需要遍历整个 pollfd 数组来找到空闲位置,以便管理 fd,当 fd 数量庞大时,维护成本很高。
- 优化遍历查询就绪 fd 的问题:poll 在返回时,仍然需要遍历整个 pollfd 数组来寻找就绪的 fd,时间复杂度为 O(n)。
epoll 的优化点(epoll_ctl + epoll_wait 分离)
操作 | 说明 |
---|---|
epoll_ctl |
只在你要添加/修改/删除监听 fd 时才调用一次,相当于“注册监听列表” |
epoll_wait |
每次只等待事件,不关心你监听了哪些 fd,内核直接返回“就绪事件” |
认识epoll的接口
poll 和select 都通过一个接口完成“注册 + 等待”,每次调用都要传递和检查全部 fd,效率低;
而epoll 将监听与等待分离,通过epoll_ctl 注册事件,通过epoll_wait 等待事件,减少了不必要的遍历和数据拷贝
epoll_create()
作用
创建一个 epoll 实例,并返回一个 epoll 文件描述符(epfd),该 epfd 用于后续的 epoll_ctl() 和 epoll_wait() 操作。
函数原型
int epoll_create(int size);这个参数已经进行废弃了,但是为了进行向前兼容没有将该参数进行删除。
返回值
- 成功:返回 epfd(epoll 实例的文件描述符)。
- 失败:返回 -1,errno 指示错误类型。
epoll_ctl()
作用
管理 epoll 监控的文件描述符,对epoll模型进行操作,包括添加、修改、删除操作。
函数原型
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
参数
- epfd:由 epoll_create() 生成的 epoll 句柄。
- op:操作类型,支持以下三种:
EPOLL_CTL_ADD:添加一个新的 FD 到 epoll 实例。
EPOLL_CTL_MOD:修改已有的 FD 监听的事件。
EPOLL_CTL_DEL:从 epoll 实例中删除 FD。
- fd:需要监听的文件描述符。
- event:指向 struct epoll_event 结构体的指针,设置监听的事件类型。
返回值
- 成功:返回 0。
- 失败:返回 -1,errno 指示错误类型。
epoll_wait()
作用
等待被监听的文件描述符发生事件,并返回就绪的文件描述符。
函数原型
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
参数
- epfd:epoll_create() 生成的 epoll 句柄。
- events:用于存储发生事件的文件描述符,用户需提供足够的空间。
- maxevents:events 数组的大小,建议大于 0。
- timeout:超时时间(毫秒):
0:立即返回(非阻塞)。
-1:永远阻塞,直到有事件发生。
>0:等待指定时间。
返回值
- 成功:返回就绪的文件描述符数量(nfds)。
- 失败:返回 -1,errno 指示错误类型。
epoll的原理
epoll服务器实现的框架
socket套接编程的封装
#pragma once
#include <iostream>
#include <string>
#include <cstring>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "log.hpp"
#include "err.hpp"
class Sock
{
const static int backlog = 32;
public:
static int Socket()
{
// 1. 创建socket文件套接字对象
int sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock < 0)
{
logMessage(FATAL, "create socket error");
exit(SOCKET_ERR);
}
logMessage(NORMAL, "create socket success: %d", sock);
int opt = 1;
setsockopt(sock, SOL_SOCKET, SO_REUSEADDR|SO_REUSEPORT, &opt, sizeof(opt));
return sock;
}
static void Bind(int sock, int port)
{
// 2. bind绑定自己的网络信息
struct sockaddr_in local;
memset(&local, 0, sizeof(local));
local.sin_family = AF_INET;
local.sin_port = htons(port);
local.sin_addr.s_addr = INADDR_ANY;
if (bind(sock, (struct sockaddr *)&local, sizeof(local)) < 0)
{
logMessage(FATAL, "bind socket error");
exit(BIND_ERR);
}
logMessage(NORMAL, "bind socket success");
}
static void Listen(int sock)
{
// 3. 设置socket 为监听状态
if (listen(sock, backlog) < 0) // 第二个参数backlog后面在填这个坑
{
logMessage(FATAL, "listen socket error");
exit(LISTEN_ERR);
}
logMessage(NORMAL, "listen socket success");
}
static int Accept(int listensock, std::string *clientip, uint16_t *clientport)
{
struct sockaddr_in peer;
socklen_t len = sizeof(peer);
int sock = accept(listensock, (struct sockaddr *)&peer, &len);
if (sock < 0)
logMessage(ERROR, "accept error, next");
else
{
logMessage(NORMAL, "accept a new link success, get new sock: %d", sock); // ?
*clientip = inet_ntoa(peer.sin_addr);
*clientport = ntohs(peer.sin_port);
}
return sock;
}
};
epoll模型的封装
epollServer.hpp
#include <iostream>
#include<unistd.h>
#include<sys/epoll.h>
#include<functional>
#include<string>
#include"sock.hpp"
#include"log.hpp"
using namespace std;
using func_t = function<string(const string&)>;
#define SIZE 1024
const static int defultvalue=-1;
const static int defultnum=64;
class EpollServer
{
public:
EpollServer(int port,func_t func,int num=defultnum)
:_listen_sockfd(defultvalue)
,_port(port)
,_num(num)
,_revs(nullptr)
,_func(func)
{
}
void serverInit()
{
//1、创建套接字
_listen_sockfd=Sock::Socket();
//2、进行bind绑定
Sock::Bind(_listen_sockfd,_port);
//3、设置监听状态
Sock::Listen(_listen_sockfd);
//4、创建epoll模型
//4、1 创建epoll模型实例
_epfd=epoll_create(1);
if(_epfd<0)
{
logMessage(FATAL,"创建epoll实例失败 :%s",strerror(errno));
exit(EPOLL_CREATE_ERROR);
}
//4、2 管理epoll进行监控的文件描述符
struct epoll_event events;
events.events=EPOLLIN;
events.data.fd=_listen_sockfd;
epoll_ctl(_epfd,EPOLL_CTL_ADD,_listen_sockfd,&events);
//5、进行开辟就是事件的空间
_revs=new struct epoll_event[_num];
logMessage(NORMAL,"进行epoll服务器初始化成功");
}
void serverStart()
{
int timeout=-1;
for(;;)
{
// 进行等待管理的文件描述符发生事件
int n = epoll_wait(_epfd,_revs,_num,timeout);
switch (n)
{
case -1:
logMessage(ERROR,"epoll_wait等待文件描述符发生事件失败");
break;
case 0:
logMessage(NORMAL,"outtime....");
break;
default:
//一定有事件进行就绪
HandlerEven(n); //将有多少个就绪的事件进行传入
break;
}
}
}
void HandlerEven(int readyNum)
{
for(int i=0;i<readyNum;i++)
{
int sockfd=_revs[i].data.fd;
uint32_t events=_revs[i].events;
//处理监听套接字---listen套接字就绪
if(sockfd==_listen_sockfd && events&EPOLLIN)
{
string clienip;
uint16_t port;
int fd=Sock::Accept(sockfd,&clienip,&port);
if(fd<0)
{
logMessage(FATAL,"accept 获取链接失败");
continue;;
}
//建立连接成功,我们可以直接进行读取吗??????? 不可以!!!!
//交给epoll
struct epoll_event ev;
ev.events=EPOLLIN;
ev.data.fd=fd;
epoll_ctl(_epfd,EPOLL_CTL_ADD,fd,&ev);
}
//处理普通套接字---普通套接字就绪
else if(events&EPOLLIN)
{
//进行读取客户端的消息
char buffer[SIZE];
ssize_t n=recv(sockfd,buffer,sizeof(buffer)-1,0);
if(n>0)
{
buffer[n]=0;
logMessage(NORMAL,"client# %s",buffer);
//通过回调方法进行处理客户端的消息
string resp=_func(buffer);
//将处理过后的消息进行返回
send(sockfd,resp.c_str(),resp.size(),0);
}
else if(n==0)
{
epoll_ctl(_epfd,EPOLL_CTL_DEL,sockfd,nullptr);
close(sockfd);
logMessage(NORMAL,"客户端退出");
}
else
{
//细节:
epoll_ctl(_epfd,EPOLL_CTL_DEL,sockfd,nullptr);
close(sockfd);
logMessage(ERROR,"进行读取客户端消息失败");
}
}
else
{
}
}
}
~EpollServer()
{
if(_listen_sockfd!=defultvalue)
{
close(_listen_sockfd);
}
if(_epfd!=defultvalue)
{
close(_epfd);
}
if(_revs)
{
delete[] _revs;
}
}
private:
int _listen_sockfd;
int _port;
int _epfd;
struct epoll_event* _revs;
int _num;
func_t _func;
};
更多推荐
所有评论(0)