Epoll水平触发(Level Triggered)工作模式和边缘触发(Edge Triggered)工作模式区别

8 篇文章 0 订阅
5 篇文章 0 订阅
注意:此文章适合对Epoll有初步了解的同行观看,如果还没了解epoll工作模式的同行,建议看一下链接:

Epoll多路I/O复用技术


LT模式(默认方式)

LT模式即Level Triggered工作模式。
    与ET模式不同的是,以LT方式调用epoll接口的时候,它就相当于一个速度比较快的poll,无论后面的数据是否被使用。
    LT(level triggered):LT是缺省的工作方式,并且同时支持block和no-block socket。在这种做法中,内核告诉你一个文件描述符是否就绪了,然后你可以对这个就绪的fd进行IO操作。如果你不作任何操作,内核还是会继续通知你的,所以,这种模式编程出错误可能性要小一点。传统的select/poll都是这种模型的代表。
水平触发图例:

图1-1

图1-2


ET模式

ET(edge-triggered):ET是高速工作方式,只支持no-block socket。
    在这种模式下,当描述符从未就绪变为就绪时,内核通过epoll告诉你。然后它会假设你知道文件描述符已经就绪,并且不会再为那个文件描述符发送更多的就绪通知。请注意,如果一直不对这个fd作IO操作(从而导致它再次变成未就绪),内核不会发送更多的通知(only once)。
边缘触发图例:

图2-1

图2-2


总结:

    水平触发,是相对比较安全的,因为当内核有事件被唤醒的时候,linux系统就会将被唤醒的事件拷贝到用户态,让用户对被唤醒的事件进行处理,如果该事件没有处理,那么下一次等待中,linux内核依旧会拷贝到用户态中,保证每一次事件都能抵达用户态。
    边缘触发,只会在内核被唤醒事件从无到有的那一刻,才会将事件拷贝给用户态,虽然它减少了linux内核拷贝到用户态的次数,但带来的后果有可能在linux中部分事件已经被唤醒,但是没有被获取得到。

epoll 水平触发代码:

#include "../common.h"

int set_NonBlock(int fd )
{
    int flags = fcntl(fd, F_GETFL);
    flags |= O_NONBLOCK;
    fcntl(fd , F_SETFL, flags);
    return 0;
}


int main()
{
    int fd = socket(AF_INET, SOCK_STREAM, 0);

    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port = htons(9999);
    addr.sin_addr.s_addr = 0;

    int ret = bind(fd ,(struct sockaddr*)&addr, sizeof(addr));

    if (ret != 0)
    {
        perror("bind");
        close(fd);    
        return ret;
    }

    ret = listen(fd, 1024);
    if (ret != 0)
    {
        perror("listen");
        close(fd);    
        return ret;
    }

    set_NonBlock(fd);

    int epfd = epoll_create(1024);
    struct epoll_event event;
    event.data.fd = fd;
    event.events = EPOLLIN;

    ret = epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &event);
    if (ret != 0)
    {
        perror("epoll_ctl");
        close(fd);
        return 0;
    }

    while ( 1 )
    {
        struct epoll_event ev[8];
        ret = epoll_wait(epfd, ev, 8, 5000);
        if (ret != 0)
        {
            if (errno == EINTR)
                continue;
        }
        for (int i = 0 ; i < ret ; i++)
        {
            int newfd;
            if (ev[i].data.fd == fd)
            {
                //socket fd
                newfd = accept(ev[i].data.fd,NULL,NULL);
                event.data.fd = newfd;
                event.events = EPOLLIN;
                ret = epoll_ctl(epfd, EPOLL_CTL_ADD, newfd, &event);
                if (ret != 0)
                {
                    perror("epoll_ctl");
                    close(newfd);
                    close(fd);    
                    return -1;
                }
            }
            else
            {
                int connectfd = ev[i].data.fd;
                char buf[1024] = {0};
                if (read( connectfd, buf,sizeof(buf)) > 0)
                {
                    printf("recv buf:%s\n",buf);
                }
                else 
                {
                    close(connectfd);
                }
            }
        }

    }
    return 0;
}

epoll 边缘触发代码:

#include <sys/types.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/epoll.h>
#include <arpa/inet.h>
#include <sys/wait.h>
#include <stdlib.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <stdio.h>
#include <signal.h>
void sig_handle(int sig)
{
    printf("recv signal :%d\n",sig);
}
int main(int argc, char * argv[])
{
    signal(SIGPIPE,sig_handle);
    if (argc<2)
    {
        printf("usage:%s + [count]\n",argv[0]);
        return 0;
    }
    unlink("dbg.txt");
    int dbg = open("dbg.txt",O_CREAT|O_APPEND|O_RDWR,0666);
    int count = atoi(argv[1]);
    int fd = socket(AF_INET,SOCK_STREAM,0);
    struct sockaddr_in addr;
    memset(&addr,0,sizeof(addr));
    addr.sin_family = AF_INET;
    addr.sin_port = htons(9988);
    int ret = bind(fd,(struct sockaddr*)&addr,sizeof(addr));
    if (ret ==-1)
    {
        perror("bind");
        return 0;
    }
    listen(fd,250);
    int is_child_process = 0;//判断在哪个进程中,父进程0,子进程1
    for (int i = 0 ; i < count ; i++)
    {
        pid_t pid = fork();
        if (pid==0)
        {
            is_child_process = 1;
            break;
        }
    }

    struct epoll_event ev;
    ev.events = EPOLLIN|EPOLLET;
    ev.data.fd = fd;
    int epfd = epoll_create(1024);//建立epfd的描述符
    int flags = fcntl(fd,F_GETFL);
    flags |= O_NONBLOCK;
    fcntl(fd,F_SETFL,flags);

    epoll_ctl(epfd,EPOLL_CTL_ADD,fd,&ev);
    while (1)
    {
        struct epoll_event evs[10];
        int process_count = epoll_wait(epfd,evs,10,5000);
        if (process_count == 0) continue;//如果监听的进程都没有事件产生,则再次进入循环,继续监听
        for (int i = 0 ; i < process_count ;i++)
        {
            if (evs[i].data.fd == fd)
            {
                //当进程中的socket描述符是server的socket本身时候,则accept否则就直接操作
                int ret = accept(evs[i].data.fd,NULL,NULL);
                if (ret == -1)
                {
                    printf("errno:%s",strerror(errno));
                    //其他错误,直接exit
                    break;
                }
                ev.data.fd = ret;
                epoll_ctl(epfd,EPOLL_CTL_ADD,ret,&ev);
            }
            else
            {
                //read or write
                char buf[1024];

                int ret = read(evs[i].data.fd,buf,sizeof(buf));
                if (ret == -1)
                {
                    perror("read");
                    if (errno == EINTR)
                        break;
                    exit(0);
                }
                else if (ret == 0)
                {
                    //normal exit
                    close(evs[i].data.fd);
                    break;
                }
                //printf("recv data %s from pid:%d\n",buf,getpid());
                write(dbg,"1",1);
            }
        }
    }
    if (!is_child_process)
    {
        for (int i = 0 ; i < count; i ++)
        {
            wait(NULL);//等待所有的子进程退出为止
        }
    }
    return 0;
}

个人感觉:
其实我使用了很久ET和LT两种模式,但是呢,ET是否就会高效率过LT?这个也不好说,其实如果读者是一个有心人的话,那么你们也可以去看看libevent的开源库,你会发现,其实他们底层的epoll,也是采用LT模式而已,所以呢,他们两者的差别具体在哪里。真不好说,希望有大牛指导指导!

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值