epoll两种模式区别-ET-LT

概述

epoll有两种工作模式:

  1. 边缘触发模式Edge Trigger,ET)
  2. 水平触发模式Level Trigger,LT) 默认模式

这两中模式用电平来表示,

LT模式:

1. 低电平 => 高电平
2. 处于高电平状态

ET模式:

1. 低电平 => 高电平

socket可读事件LT触发条件:

1. socket上无数据 => socket上有数据
2. socket处于有数据状态

socket可读事件ET触发条件:

1. socket上无数据 => socket上有数据
2. socket又新来一次数据

socket可写事件LT触发条件:

1. socket可写   => socket可写
2. socket不可写 => socket可写

socket可写事件ET触发条件:

1. socket不可写 => socket可写

代码实例

该链接epoll LT模式和ET模式详解 分别给出了以上四种情况的实例,在这里给出建议代码:

socket可读事件LT触发条件:

服务器大致代码如下:

// 将监听套接字注册到epoll实例中
epoll_event listen_fd_event;
listen_fd_event.data.fd = listenfd;
listen_fd_event.events = EPOLLIN;

//将监听sokcet绑定到epollfd上去
if (epoll_ctl(epollfd, EPOLL_CTL_ADD, listenfd, &listen_fd_event) == -1)
{
    std::cout << "epoll_ctl error" << std::endl;
    close(listenfd);
    return -1;
}
 
 // epoll_wait返回各种事件
 n = epoll_wait(epollfd, epoll_events, 1024, 1000);
 ....
 for (size_t i = 0; i < n; ++i) {
     if (epoll_events[i].events & EPOLLIN) {
         if (epoll_events[i].data.fd == listenfd) {
             // 将连接套接字注册到epoll实例中,注册可读事件
             ....
         } else {
         	// 处理连接套接字上的可读事件,注意,每次只读一个字节
             int m = recv(epoll_events[i].data.fd, &ch, 1, 0);
             // 针对m的逻辑处理, 将接受到的一个字符输出
             std::cout << "recv from client:" << ch << std::endl;
         }
     } else if (epoll_events[i].events & EPOLLERR) {
         // 处理错误
     }
 }

如果次数客户端,对该服务器发送abcd,那么服务端会输出,

recv from client:a
recv from client:b
recv from client:c
recv from client:d

socket可读事件ET触发条件:

// 将监听套接字注册到epoll实例中
epoll_event listen_fd_event;
listen_fd_event.data.fd = listenfd;
listen_fd_event.events = EPOLLIN;
listen_fd_event.events |= EPOLLET;

//将监听sokcet绑定到epollfd上去
if (epoll_ctl(epollfd, EPOLL_CTL_ADD, listenfd, &listen_fd_event) == -1)
{
    std::cout << "epoll_ctl error" << std::endl;
    close(listenfd);
    return -1;
}
 
 // epoll_wait返回各种事件
 n = epoll_wait(epollfd, epoll_events, 1024, 1000);
 ....
 for (size_t i = 0; i < n; ++i) {
     if (epoll_events[i].events & EPOLLIN) {
         if (epoll_events[i].data.fd == listenfd) {
             // 将连接套接字注册到epoll实例中,注册可读事件
             ....
         } else {
         	// 处理连接套接字上的可读事件,注意,每次只读一个字节
             int m = recv(epoll_events[i].data.fd, &ch, 1, 0);
             // 针对m的逻辑处理, 将接受到的一个字符输出
             std::cout << "recv from client:" << ch << std::endl;
         }
     } else if (epoll_events[i].events & EPOLLERR) {
         // 处理错误
     }
 }

如果次数客户端,对该服务器发送abcd,那么服务端会输出,

recv from client:a

此时注意啊,et模式下只会触发一次事件,当客户端再此发送123,服务端输出:

recv from client:b

socket可写事件LT触发条件:

// 将监听套接字注册到epoll实例中
epoll_event listen_fd_event;
listen_fd_event.data.fd = listenfd;
listen_fd_event.events = EPOLLIN;
//listen_fd_event.events |= EPOLLET;

//将监听sokcet绑定到epollfd上去
if (epoll_ctl(epollfd, EPOLL_CTL_ADD, listenfd, &listen_fd_event) == -1)
{
    std::cout << "epoll_ctl error" << std::endl;
    close(listenfd);
    return -1;
}
 
 // epoll_wait返回各种事件
 n = epoll_wait(epollfd, epoll_events, 1024, 1000);
 ....
 for (size_t i = 0; i < n; ++i) {
     if (epoll_events[i].events & EPOLLIN) {
         if (epoll_events[i].data.fd == listenfd) {
             // 将连接套接字注册到epoll实例中,注册可读可写事件
             ....
         } else {
         	// 处理连接套接字上的可读事件,注意,每次只读一个字节
             int m = recv(epoll_events[i].data.fd, &ch, 1, 0);
             // 针对m的逻辑处理, 将接受到的一个字符输出
             std::cout << "recv from client:" << ch << std::endl;
         }
     }else if (epoll_events[i].events & EPOLLOUT) {
          std::cout << "EPOLLOUT triggered,clientfd:"<< std::endl;
     } 
     else if (epoll_events[i].events & EPOLLERR) {
         // 处理错误
     }
 }

如果客户端对服务器发起连接,由于套接字输出缓存可写,一直触发可写事件,因此输出:

EPOLLOUT triggered,clientfd:
EPOLLOUT triggered,clientfd:
EPOLLOUT triggered,clientfd:
......

socket可写事件ET触发条件:

// 将监听套接字注册到epoll实例中
epoll_event listen_fd_event;
listen_fd_event.data.fd = listenfd;
listen_fd_event.events = EPOLLIN;
listen_fd_event.events |= EPOLLET;

//将监听sokcet绑定到epollfd上去
if (epoll_ctl(epollfd, EPOLL_CTL_ADD, listenfd, &listen_fd_event) == -1)
{
    std::cout << "epoll_ctl error" << std::endl;
    close(listenfd);
    return -1;
}
 
 // epoll_wait返回各种事件
 n = epoll_wait(epollfd, epoll_events, 1024, 1000);
 ....
 for (size_t i = 0; i < n; ++i) {
     if (epoll_events[i].events & EPOLLIN) {
         if (epoll_events[i].data.fd == listenfd) {
             // 将连接套接字注册到epoll实例中,注册可读可写事件
             ....
         } else {
         	// 处理连接套接字上的可读事件,注意,这里每次可输出1024个字节
             int m = recv(epoll_events[i].data.fd, &ch, 1024, 0);
             // 针对m的逻辑处理, 将接受到的一个字符输出
             std::cout << "recv from client:" << ch << std::endl;
         }
     }else if (epoll_events[i].events & EPOLLOUT) {
          std::cout << "EPOLLOUT triggered,clientfd:"<< std::endl;
     } 
     else if (epoll_events[i].events & EPOLLERR) {
         // 处理错误
     }
 }

ET模式下,可写事件只会触发一次,刚连接时服务端输出

EPOLLOUT triggered,clientfd:

如果需要再次触发,客户端需要再次发送消息,比如客户端发送abc,此时服务端会激发写事件和读事件,因此输出

recv from client:abc
EPOLLOUT triggered,clientfd:

LT与ET模式应用场景

首先给出结论,

  1. ET模式适用高流量的情景下,比如nginx就是使用et模式,比如发送大文件et模式较为使用。
  2. 一般情况下使用LT模式,比如redis就是LT模式。

原因:使用ET模式,特定场景下会比LT更快,因为它可以便捷的处理EPOLLOUT事件,省去打开与关闭EPOLLOUT的epoll_ctl(EPOLL_CTL_MOD)调用。从而有可能让你的性能得到一定的提升。

LT模式下处理请求流程

LT代码

代码大概信息如下:

  1. 当epoll实例监听套接字有读事件触发时,将新产生的 连接套接字注册到epoll实例中,事件类型为EPOLLIN

  2. 当连接套接字有请求时,激发读事件,此时我们读取套接字内容并进行处理,接着发送一个reponsd

  3. 注意,这里就需要根据是否一次性发完respond进行区别:

    if (一次性发完) {
    	// 只注册读事件
        updateEvents(efd, fd, EPOLLIN, EPOLL_CTL_MOD);
    } else {
        updateEvents(efd, fd, EPOLLIN | EPOLLOUT, EPOLL_CTL_MOD);
    }
    
  4. 像比如ET模式,效率就出现再最后一个updateEvents上,如果文件特别大,是不是这个语句执行的次数就更多,因此大文件适用于ET模式。

ET模式下处理请求流程

ET代码

代码大概信息如下:

  1. 当epoll实例监听套接字有读事件触发时,将新产生的 连接套接字注册到epoll实例中,事件类型为EPOLLIN | EPOLLOUT | EPOLLRT
  2. 当连接套接字有请求时,激发读事件,此时我们读取套接字内容并进行处理,接着发送一个reponsd
  3. 这里就不不需要改变注册套接字的事件类型。

从上述流程可以看出,像比如ET模式,效率就出现再最后一个updateEvents上,如果文件特别大,是不是这个语句执行的次数就更多,因此大文件适用于ET模式。

参考

  1. epoll LT模式和ET模式详解
  2. epoll的边沿触发模式(ET)真的比水平触发模式(LT)快吗?(当然LT模式也使用非阻塞IO,重点是要求ET模式下的代码不能造成饥饿)
  3. epoll LT/ET深入剖析
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值