Linux-计算机网络-epoll的LT,ET模式

一.epoll的LT和ET模式介绍

epol 对文件描述符有两种操作模式:LT(Level Trigger,电平触发)模式和 ET(EdgeTrigger,边沿触发)模式。LT模式是默认的工作模式。当往epol 内核事件表中注册一个文件描述符上的 EPOLLET 事件时,epoll将以高效的 ET 模式来操作该文件描述符。

epoll的LT和ET模式区别示意图

二.epoll的ET模式如何处理

1.epoll的ET模式编程读取数据的处理方式

将描述符设置为非阻塞模式 +循环读取数据

也就是ET模式下的描述符必须是非阻塞的

2.将描述符设置为非阻塞模式的方法

cpp 复制代码
//ser_epo11_ET.C
#include <fcntl.h>
void setnonblock(int fd)
{
int oldfl=fcntl(fd,F_GETFL);
int newfl=oldfl=O_NONBLOCK;

if(fcntl(fd,F SETFL,newfl)==-1)
perror("fcntl error\n");
}

三.ET模式的完整代码

cpp 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <assert.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/epoll.h>
#include<fcntl.h>
#include<errno.h>
#define MAXFD 10

void setnoblock(int fd)
{
    int oldfl=fcntl(fd,F_GETFL);
    int newfl=oldfl|O_NONBLOCK;
    if(fcntl(fd,F_SETFL,newfl)==-1)
    {
        perror("fcntl error!\n");
    }


}
int create_socket()
 {
     int sockfd=socket(AF_INET,SOCK_STREAM,0);
     if(sockfd==-1)
     {
         return -1;
     }

     struct sockaddr_in saddr;
     memset(&saddr,0,sizeof(saddr));
     saddr.sin_family=AF_INET;
     saddr.sin_port=htons(6000);
     saddr.sin_addr.s_addr=inet_addr("127.0.0.1");

     int res=bind(sockfd,(struct sockaddr *)&saddr,sizeof(saddr));
     if(res==-1)
     {
         return -1;
     }

     res=listen(sockfd,5);
     if(res==-1)
     {
         return -1;
     }

     return sockfd;
 }
void epoll_add(int epfd,int fd)
{
    struct epoll_event ev;
    ev.events=EPOLLIN|EPOLLET;
    ev.data.fd=fd;

    setnoblock(fd);
  if(epoll_ctl(epfd,EPOLL_CTL_ADD,fd,&ev)==-1)
  {
      perror("epoll ctl add err");
  }
}

void epoll_del(int epfd,int fd)
{
if(epoll_ctl(epfd,EPOLL_CTL_DEL,fd,NULL)==-1)
    {
        perror("epoll ctl add err\n");
  }

}

int main()
{
    int sockfd=create_socket();
    assert(sockfd!=-1);

   int epfd=epoll_create(MAXFD);
   assert(epfd!=-1);

   epoll_add(epfd,sockfd);
   struct epoll_event evs[MAXFD];
   while(1)
   {
    int n=epoll_wait(epfd,evs,MAXFD,5000);
    if(n==-1)
    {
        perror("epoll_wait error\n");
continue;
    }
    else if(n==0)
    {
        printf("time out\n");
        continue;
    }
    else
    {

       for(int i=0;i<n;i++)
       {
           int fd=evs[i].data.fd;
         if(evs[i].events & EPOLLIN)
         {
           if(fd==sockfd)
           {
//accept
             struct sockaddr_in caddr;
             int len=sizeof(caddr);
             int c=accept(fd,(struct sockaddr *)&caddr,&len);
             if(c<0)
             {
             continue;
             }
             printf("accept c=%d\n",c);
             epoll_add(epfd,c);

           }
           else
           {
          // recv
             while(1)
             {
                 char buff[128]={0};
                 int num=recv(fd,buff,1,0);
                 if(num==-1)
                 {
                     if(errno!=EAGAIN && errno!=EWOULDBLOCK)
{
                         perror("recv err!");
                     }
                     else
                     {
                         send(fd,"ok",2,0);
                     }
                     break;
                 }
                 else if(num==0)
                 {
                     epoll_del(epfd,fd);
                     close(fd);
                     printf("client close\n");
                     break;
                 }
                 else
                 {
                     printf("buff(%d)=%s\n",fd,buff);
 }

             }


           }
         }

       }
    }
   }
   exit(0);




}

四.总结

ET模式下我们都需要做哪些事情?一共三点:

(1)添加事件类型的时候一定要添加上EPOLLET,这叫开启ET模式

(2)描述符要设置成非阻塞模式:

(3)在IO函数返回以后,就是epoll wait返回以后,它提醒我们描述符上有读事件产生了,我们要循环去处理;直到把这个描述符上的数据处理完;然后再去处理下一个描述符;
所以就是说ET模式下的编程就要求我们的描述符必须是非阻塞模式,

如下图:

对于 LT 模式操作的文件描述符,当 epoll wait 检测到其上有事件发生并将此事件通知应用程序

后,应用程序可以不立即处理该事件。这样,当应用程序下一次调用epoll wait 时,还会再次向

应用程序通告此事件,直到该事件被处理。

对于 ET 模式操作的文件描述符,当 epoll wait 检测到其上有事件发生并将此事件通知 应用程

序后,应用程序必须立即处理该事件,因为后续的 epoll wait 调用将不再向应用程序通知这一事

件。所以 ET模式在很大程度上降低了同一个 epol 事件被重复触发的次数,因此效率比 LT 模式

高。

五.EPOLLONESHOT事件

即使我们使用 ET模式,一个socket上的某个事件还是可能被触发多次。这在并发程序

中就会引起一个问题。比如一个线程(或进程,下同)在读取完某个socket上的数据后开

始处理这些数据,而在数据的处理过程中该socket上又有新数据可读(EPOLLIN再次被触

发),此时另外一个线程被唤醒来读取这些新的数据。于是就出现了两个线程同时操作一个

socket的局面。这当然不是我们期望的。我们期望的是一个socket连接在任一时刻都只被-

个线程处理。这一点可以使用epoll的EPOLLONESHOT事件实现。

对于注册了EPOLLONESHOT事件的文件描述符,操作系统最多触发其上注册的一个可

读、可写或者异常事件,且只触发一次,除非我们使用cpollct函数重置该文件描述符上注

册的 EPOLLONESHOT 事件。这样,当一个线程在处理某个socket时,其他线程是不可能有

机会操作该 socket的。但反过来思考,注册了EPOLLONESHOT事件的socket一旦被某个

线程处理完毕,该线程就应该立即重置这个socket上的EPOLLONESHOT事件,以确保这个

socket 下一次可读时,其EPOLLIN事件能被触发,进而让其他工作线程有机会继续处理这

个 socket。

原因:不希望出现两个线程同时操作一个socket的局面;
使用EPOLLONESHOT事件之后,当一个线程在处理某个socket时,其他线程时不可能有机会操作该
socket的.

六.同步非同步,阻塞非阻塞

Linux-计算机网络-探索epoll是同步阻塞的还是异步非阻塞的-CSDN博客

相关推荐
我命由我12345几秒前
PDFBox - PDDocument 与 byte 数组、PDF 加密
java·服务器·前端·后端·学习·java-ee·pdf
zhong_kh26 分钟前
RHCSA 基础练习
linux
微笑尅乐40 分钟前
从递归到迭代吃透树的层次——力扣104.二叉树的最大深度
算法·leetcode·职场和发展
im_AMBER43 分钟前
Leetcode 28
算法·leetcode
让我们一起加油好吗1 小时前
【基础算法】多源 BFS
c++·算法·bfs·宽度优先·多源bfs
冷崖1 小时前
定时器的学习(二)
linux·c++·学习
B站计算机毕业设计之家1 小时前
深度学习实战:python动物识别分类检测系统 计算机视觉 Django框架 CNN算法 深度学习 卷积神经网络 TensorFlow 毕业设计(建议收藏)✅
python·深度学习·算法·计算机视觉·分类·毕业设计·动物识别
And_Ii1 小时前
LeetCode 3350. 检测相邻递增子数组 II
数据结构·算法·leetcode
想唱rap1 小时前
C++ string类的使用
开发语言·c++·笔记·算法·新浪微博
JAVA学习通1 小时前
Replication(下):事务,一致性与共识
大数据·分布式·算法