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博客

相关推荐
小Ti客栈2 分钟前
Netty 零拷贝原理与实战
运维·服务器·网络
星恒随风27 分钟前
Linux网络编程:应用层协议究竟是什么?从网络计算器到TCP Socket抽象基类完整实现
linux·网络·笔记·tcp/ip
SDWAN_Cheap1 小时前
物理服务器跟云服务器有什么区别,使用于什么场景
运维·服务器
Smoothcloud润云1 小时前
GPU算力平台推荐:复现实验时,如何判断租用资源是否合适?
服务器·人工智能·深度学习·gpu算力·gpu
Fuyo_11191 小时前
C++ AVL树保姆级详解:从平衡因子到四大旋转机制
java·c++·算法
jktantan1 小时前
每个节点 41 字节:把一棵百万节点树拆进 14 个 typed array
前端·javascript·vue.js·算法·typescript
揽星河..2 小时前
【C语言】猜数字游戏
c语言·算法·游戏
LabVIEW开发2 小时前
CTM-3 红外测温仪 + LabVIEW 温度怎么测?
算法·labview·测试·labview程序
叼烟扛炮2 小时前
[LangChain] 学习笔记(02):LCEL 链式调用与动态模型配置实战
服务器·开发语言·网络·笔记·学习·langchain
纪念 2292 小时前
C++算法(六)
开发语言·c++·算法