优化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;
};

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐