【Linux/多路复用】poll和epoll的使用

poll


复制代码
参数:
1.fds是一个poll函数监听的结构列表. 每一个元素中, 包含了三部分内容: 文件描述符, 监听的事件集合, 返回的事件集合.
2.nfds表示fds数组的长度.
3.timeout表示poll函数的超时时间, 单位是毫秒(ms).

events和revents的取值:

poll的返回结果:

复制代码
返回值小于0, 表示出错;
返回值等于0, 表示poll函数等待超时;
返回值大于0, 表示poll由于监听的文件描述符就绪而返回.

poll的优点

1.pollfd结构包含了要监视的event和发生的event,不再使用select"参数-值"传递的方式. 接口使用比select更方便.

2.poll并没有最大数量限制 (但数量过大后性能也会下降).
poll的缺点

复制代码
1.和select函数一样,poll返回后,需要轮询pollfd来获取就绪的描述符.
2.每次调用poll都需要把大量的pollfd结构从用户态拷贝到内核中.
3.同时连接的大量客户端在一时刻可能只有很少的处于就绪状态, 因此随着监视的描述符数量的增长, 其效
率也会线性下降. 

例:使用poll监控标准输入

cpp 复制代码
#include <unistd.h>
#include <stdio.h>
#include <poll.h>
int main()
{
    struct pollfd poll_fd;
    poll_fd.fd = 0;
    poll_fd.events = POLLIN;

    for (;;)
    {
        int ret = poll(&poll_fd, 1, 1000);
        if (ret < 0)
        {
            perror("poll");
            continue;
        }
        if (ret == 0)
        {
            printf("poll timeout\n");
            continue;
        }
        if (poll_fd.revents == POLLIN)
        {
            char buf[1024] = {0};
            read(0, buf, sizeof(buf) - 1);
            printf("stdin:%s", buf);
        }
    }
}

epoll

epoll_create

复制代码
int epoll_create(int size);
创建一个epoll的句柄.自从linux2.6.8之后,size参数是被忽略的. 
用完之后, 必须调用close()关闭.

epoll_ctl:epoll的事件注册函数.

复制代码
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
它不同于select()是在监听事件时告诉内核要监听什么类型的事件, 而是在这里先注册要监听的事件类型. 
第一个参数是epoll_create()的返回值(epoll的句柄).
第二个参数表示动作,用三个宏来表示. 
第三个参数是需要监听的fd. 
第四个参数是告诉内核需要监听什么事. 
第二个参数的取值:
EPOLL_CTL_ADD :注册新的fd到epfd中;
EPOLL_CTL_MOD :修改已经注册的fd的监听事件;
EPOLL_CTL_DEL :从epfd中删除一个fd;

events可以是以下几个宏的集合:

复制代码
EPOLLIN : 表示对应的文件描述符可以读 (包括对端SOCKET正常关闭); 
EPOLLOUT : 表示对应的文件描述符可以写;
EPOLLPRI : 表示对应的文件描述符有紧急的数据可读 (这里应该表示有带外数据到来);
EPOLLERR : 表示对应的文件描述符发生错误;
EPOLLHUP : 表示对应的文件描述符被挂断;
EPOLLET : 将EPOLL设为边缘触发(Edge Triggered)模式, 这是相对于水平触发(Level Triggered)来说的.
EPOLLONESHOT:只监听一次事件, 当监听完这次事件之后, 如果还需要继续监听这个socket的话, 需要
再次把这个socket加入到EPOLL队列里. 

epoll_wait:收集在epoll监控的事件中已经发生的事件.

复制代码
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
参数events是分配好的epoll_event结构体数组. 
epoll将会把发生的事件赋值到events数组中 (events不可以是空指针,内核只负责把数据复制到这个events数组中,不会去帮助我们在用户态中分配内存). 
maxevents告知内核这个events有多大,这个 maxevents的值不能大于创建epoll_create()时的size. 
参数timeout是超时时间 (毫秒,0会立即返回,-1是永久阻塞). 
如果函数调用成功,返回对应I/O上已准备好的文件描述符数目,如返回0表示已超时, 返回小于0表示函数失败. 

epoll的优点

复制代码
1.接口使用方便: 虽然拆分成了三个函数, 但使用起来更方便高效. 不需要每次循环都设置关注的文件描述符, 也做到了输入输出参数分离开.
2.数据拷贝轻量: 只在合适的时候调用 EPOLL_CTL_ADD 将文件描述符结构拷贝到内核中, 这个操作并不频繁(而select/poll都是每次循环都要进行拷贝).
3.事件回调机制: 避免使用遍历, 而是使用回调函数的方式, 将就绪的文件描述符结构加入到就绪队列中, epoll_wait 返回直接访问就绪队列就知道哪些文件描述符就绪. 这个操作时间复杂度O(1). 即使文件描述符数目很多, 效率也不会受到影响. 
4.没有数量限制: 文件描述符数目无上限.

示例:

Epoller.hpp

cpp 复制代码
#pragma once
#include "nocopy.hpp"
#include "log.hpp"
#include <cerrno>
#include <cstring>
#include <sys/epoll.h>

class Epoller : public nocopy 
{
    static const int size=128;
public:
Epoller()
{
    _epfd=epoll_create(size);//
    if(_epfd==-1)
    {
        lg(Error,"epoll create error:%s",strerror(errno));
    }
    else
    {
        lg(Info,"epoll create success:%d",_epfd);
    }
}
int EpollerWait(struct epoll_event revents[],int num)
{
    int n=epoll_wait(_epfd,revents,num,-1);
    return n;
}
int EpollerUpdate(int oper,int sock,uint32_t event)
{
    int n=0;
    if(oper==EPOLL_CTL_DEL)
    {
        n=epoll_ctl(_epfd,oper,sock,nullptr);
        if(n!=0)
        {
            lg(Error,"epoll_ctl delete error!");
        }
    }
    else
    {
        //修改或添加
        struct epoll_event ev;
        ev.events=event;
        ev.data.fd=sock;//方便我们后期得知是哪一个fd就绪了。

        n=epoll_ctl(_epfd,oper,sock,&ev);
        if(n!=0)
        {
            lg(Error,"epoll_ctl error!");
        }
    }
    return n;
}
~Epoller()
{
    if(_epfd>=0)
    close(_epfd);
}
private:
    int _epfd;
    int _timeout{3000};
};

EpollServer.hpp

cpp 复制代码
#pragma once
#include <iostream>
#include <memory>
#include "Socket.hpp"
#include "log.hpp"
#include "nocopy.hpp"
#include "Epoller.hpp"

using namespace std;

uint32_t EVENT_IN = (EPOLLIN);
uint32_t EVENT_OUT = (EPOLLOUT);

class EpollServer : public nocopy
{
    static const int num = 64;

public:
EpollServer(uint16_t port)
:_port(port),_listsocket_ptr(new Sock()),_epoller_ptr(new Epoller())
{}
void Init()
{
    _listsocket_ptr->Socket();
    _listsocket_ptr->Bind(_port);
    _listsocket_ptr->Listen();

    lg(Info,"create listensocket success:%d\n",_listsocket_ptr->Fd());
}
void Accepter()
{
    std::string clientip;
    uint16_t clientport;
    int sock=_listsocket_ptr->Accept(clientip,clientport);
    if(sock>0)
    {
        _epoller_ptr->EpollerUpdate(EPOLL_CTL_ADD,sock,EVENT_IN);
        lg(Info,"get a new link,client info@ %s:%d",clientip.c_str(),clientport);
    }
}
void Recver(int fd)
{
    char buffer[4096];
    ssize_t n=read(fd,buffer,sizeof(buffer)-1);
    if(n>0)
    {
        buffer[n]=0;
        cout<<"client say@ "<<buffer<<endl;
        string echo_string="server echo: ";
        echo_string+=buffer;
        write(fd,echo_string.c_str(),echo_string.size());
    }
    else if(n==0)
    {
        cout<<"client quit,me too!"<<endl;
        _epoller_ptr->EpollerUpdate(EPOLL_CTL_DEL,fd,0);
        close(fd);
    }
    else
    {
        cout<<"read error,fd: "<<fd<<endl;
        _epoller_ptr->EpollerUpdate(EPOLL_CTL_DEL,fd,0);
        close(fd);
    }
}
void Dispatcher(struct epoll_event revs[],int num)
{
    for(int i=0;i<num;i++)
    {
        uint32_t events=revs[i].events;
        int fd=revs[i].data.fd;
        if(events&EVENT_IN)
        {
            if(fd==_listsocket_ptr->Fd())
            {
                Accepter();
            }
            else
            {
                Recver(fd);
            }
        }
        else if(events&EVENT_OUT)
        {

        }
        else
        {}
    }
}
void Start()
{
    _epoller_ptr->EpollerUpdate(EPOLL_CTL_ADD,_listsocket_ptr->Fd(),EVENT_IN);
    struct epoll_event revs[num];//接收就绪事件结果的数组。
    while(true)
    {
        int n=_epoller_ptr->EpollerWait(revs,num);
        if(n>0)
        {
            lg(Debug,"event happened,fd is:%d",revs[0].data.fd);
            Dispatcher(revs,n);
        }
        else if(n==0)
        {
            lg(Info,"time out...");
        }
        else
        {
            lg(Error,"epoll wait error!");
        }
    }

}
~EpollServer()
{
   _listsocket_ptr->Close(); 
}
private:
std::shared_ptr<Sock> _listsocket_ptr;
std::shared_ptr<Epoller> _epoller_ptr;
uint16_t _port;
};
相关推荐
倔强的石头1062 小时前
【Linux指南】基础IO系列(七):“一切皆文件” 底层实现 ——struct file 与统一 IO 接口的魔法
linux·运维·服务器
cpp_25012 小时前
P2430 严酷的训练
数据结构·c++·算法·动态规划·洛谷·背包dp
网络小白不怕黑2 小时前
1.1 VMware部署Rocky Linux 9 (GPT分区表,最小化安装)
linux·服务器·gpt
Counter-Strike大牛2 小时前
SpringBoot中使用POI+EasyExcel批量导出主子表信息,以箱单为例
windows·spring boot·后端
医疗信息化王工2 小时前
基于ASP.NET Core的医院不良事件管理系统的架构设计
后端·asp.net
小谢小哥2 小时前
53-熔断降级详解
java·后端·架构
克莱因3582 小时前
思科Cisco 静态NAT
服务器·网络·思科
tankeven2 小时前
动态规划专题(06):树形动态规划(未完待续)
c++·算法·动态规划
恒创科技HK2 小时前
Windows香港云服务器新开注意事项(含远程连接教程)
运维·服务器·windows