栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > C/C++/C#

C++网络编程快速入门(四):EPOLL模型使用

C/C++/C# 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

C++网络编程快速入门(四):EPOLL模型使用

目录
  • 基本使用方法
    • step1:创建epollfd
    • step2:将fd绑定到epollfd
    • step3:调用epoll_wait检测事件
    • epoll_wait与poll、select区别所在
  • 水平触发与边缘触发

基本使用方法 step1:创建epollfd
  • 创建一个epollfd,若epoll_create调用成功,则返回一个非负值的epollfd,否则返回-1
extern int epoll_create (int __size) __THROW;
step2:将fd绑定到epollfd

有了epollfd之后,我们有三种需求:
1、将需要检测事件的其他fd绑定到这个epollfd上
2、修改一个已经绑定到epollfd的fd的事件类型
3、在不需要的时候将fd从epollfd上解绑
都需要依托函数epoll_ctl完成:

extern int epoll_ctl (int __epfd, int __op, int __fd,
		      struct epoll_event *__event) __THROW;

__epfd:即epollfd
__op:操作类型,有三种EPOLL_CTL_ADD、EPOLL_CTL_MOD、EPOLL_CTL_DEL。分别对应着在epollfd上添加、修改、移除fd,当为EPOLL_CTL_DEL时,__event参数忽略,置NULL
__fd:需要备操作的fd
__event:一个epoll_event结构体的地址
具体结构如下:

// 在64位操作系统下,大小为8 byte
typedef union epoll_data
{
  void *ptr;
  int fd;
  uint32_t u32;
  uint64_t u64;
} epoll_data_t;

struct epoll_event
{
  uint32_t events;	
  epoll_data_t data;	
} __EPOLL_PACKED;

返回值:
调用成功,返回0;
调用失败,返回-1,通过errno错误码可以获取具体的错误原因。

step3:调用epoll_wait检测事件
extern int epoll_wait (int __epfd, struct epoll_event *__events,
		       int __maxevents, int __timeout);

__events:一个epoll_event结构数组的首地址,是一个输出参数,在函数调用成功后,在events中存放的是与就绪事件相关的epoll_event结构体数组。
__maxevents:数组元素个数
__timeout:超时时间,单位为ms
返回值:调用成功返回有事件的fd数量,若返回0,表示超时。若返回-1,表示调用失败。
使用示例如下:

    while (true) {
        epoll_event epollEvents[1024];
        int n = epoll_wait(epollfd, epollEvents, 1024, 1000);
        if (n < 0) {
            if (errno == EINTR) {
                // 被信号中断 重试
                continue;
            } else {
                // 出错 退出
                break;
            }
        } else if (n == 0) {
            // 超时,继续重试
            continue;
        } else {
            // 处理事件
            for (size_t i = 0; i < n; i++) {
                if (epollEvents[i].events & EPOLLIN) {
                    // 处理可读事件
                } else if (epollEvents[i].events & EPOLLOUT) {
                    // 处理可写事件
                } else if (epollEvents[i].events & EPOLLERR) {
                    // 处理出错事件
                }
            }
        }
    }
epoll_wait与poll、select区别所在

在第二讲中演示了select的基本使用方式:C++网络编程快速入门(二):Linux下使用select演示简单服务端程序
select和epoll底层机制一样,所以这里只看select。
可以发现调用完select之后,需要在原来的clientfds数组中遍历,然后加条件判断是否是有事件的。
而epoll_wait调用完之后是直接返回一个筛选过后的有事件的events数组。
所以:
在fd数量比较多但是某段时间内的就绪事件fd数量较少时,epoll_wait函数更加高效。
也就是epoll模型更适合用在socket连接数量较大而活跃的连接较少的情景下

水平触发与边缘触发

epoll具有两种模式:边缘触发模式(Edge Trigger,ET)和水平触发模式(Level Trigger,LT)。
区别在于:
1、LT:一个事件只要有,就会一直触发
2、ET:一个事件从无到有,才会触发
以socket读事件为例:
水平模式下,只要socket上有未读完的数据,就会一直产生EPOLLIN事件。
边缘模式下,socket上每新来一次数据就会触发一次,如果上一次触发后未将socket上的数据读完,也不会再触发,除非再新来一次数据。
以socket写事件为例:
水平模式下,只要socket上TCP窗口一直不饱和,就会一直触发EPOLLOUT事件。
边缘模式下,只有TCP窗口由不饱和变成饱和 或者 再一次变成不饱和,才会触发EPOLLOUT事件。
这对于编程的启示是:
1、对于非阻塞socket,如果epoll使用边缘模式检测事件可读,那么一旦触发,一定要一次性把socket上数据收取干净,即循环调用recv函数直到recv出错

bool recvEtMode() 
{
    // 每次只收取256个字节
    char buf[256];
    while (true) {
        int nRecv = ::recv(clientfd, buf, 256, 0);
        if (nRecv == -1) {
            if (errno == EWOULDBLOCK) {
                return true;
            } else if (errno == EINTR) {
                continue;
            } else {
                return false;
            }
        }
        else if (nRecv == 0) {
            // 对端关闭了socket
            return false;
        } else {
            inputBuffer.add(buf, (size_t)nRecv);
        }
    }
    return true;
}

2、如果是水平模式,可以根据业务一次性收取固定字节数
下面总结一下两者在编码上需要注意的地方:
1、LT模式下,读事件触发后可以按需收取想要的字节数,不用把本次数据收取干净;
ET模式下,读事件必须把数据收取干净,因为我们不一定再有机会收取数据了。
2、LT模式下,不需要写事件时一定要及时移除,避免不必要地触发且浪费CPU资源。
ET模式下,写事件触发后,如果还需要下一次的写事件触发来驱动任务(例如发送上次剩余的数据),则我们需要继续注册一次检测可写事件
3、LT会导致多次触发,ET优点是触发次数少

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/384287.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号