天天看点

举源码实例来说明epoll之LT和ET模式的区别

一、先来看看官方的说辞

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

       对于采用LT工作的文件描述符,当epolll_wait检测到其上有事件发生并将此事件通知应用程序后,应用程序可以不立即处理该事件。这样当应用程序下次调用epoll_wait时,epolll_wait还会再次向应用程序通知此事件,直到有该事件被处理。而对于采用ET模式的文件描述符,当epoll_wait检测当其上有事件发生时并将此事件通知应用程序后,应用程序必须立即处理该事件,因为后续的epolll_wait调用不再降此事件通知应用程序,可见,ET模式在很大程度上降底了同一个epoll事件被重复触发的次数,因此效率要比LT模式高。

二、再来举例,写代码验证

完整的源码工程下载:

https://download.csdn.net/download/libaineu2004/10888888

LT,我设置了静态变量num,来统计奇数偶数。偶数时,故意不处理IO事件,奇数时才处理,下断点可以观察LT的工作模式是怎么样的?

实践可以证明:对于采用LT工作的文件描述符,当epolll_wait检测到其上有事件发生并将此事件通知应用程序后,应用程序可以不立即处理该事件。这样当应用程序下次调用epoll_wait时,epolll_wait还会再次向应用程序通知此事件,直到有该事件被处理。

void lt( epoll_event* events, int number, int epollfd, int listenfd )
{
    char buf[ BUFFER_SIZE ];
    for ( int i = 0; i < number; i++ )
    {
        int sockfd = events[i].data.fd;
        if ( sockfd == listenfd )
        {
            struct sockaddr_in client_address;
            socklen_t client_addrlength = sizeof( client_address );
            int connfd = accept( listenfd, ( struct sockaddr* )&client_address, &client_addrlength );
            addfd( epollfd, connfd, false );
        }
        else if ( events[i].events & EPOLLIN )
        {
            printf( "event trigger once\n" );
            memset( buf, '\0', BUFFER_SIZE );
            static int num = 0;
            int ret = 0;
            if ((num % 2) == 0)
            {
                printf( "event null\n" );;
            }
            else
            {
                ret = recv( sockfd, buf, BUFFER_SIZE-1, 0 );
                if( ret <= 0 )
                {
                    close( sockfd );
                    continue;
                }
            }
            num++;
            printf( "get %d bytes of content: %s\n", ret, buf );
        }
        else
        {
            printf( "something else happened \n" );
        }
    }
}      

再来验证ET,我同样地设置了静态变量count,来统计奇数偶数。偶数时,故意不处理IO事件,奇数时才处理,下断点可以观察ET的工作模式是怎么样的?

实践可以证明:对于采用ET模式的文件描述符,当epoll_wait检测当其上有事件发生时并将此事件通知应用程序后,应用程序必须立即处理该事件,因为后续的epolll_wait调用不再降此事件通知应用程序。另外,源码例子中,当count偶数时,程序员故意不处理IO事件,数据并不会丢失,而是会缓存起来,等到下次接收到新数据,IO事件触发时,即count奇数时,epolll_wait再次通知,会和本次数据一起读出来。

所以,为了一次性把数据读完,ET模式必须加上while(1)循环。

void et( epoll_event* events, int number, int epollfd, int listenfd )
{
    char buf[ BUFFER_SIZE ];
    for ( int i = 0; i < number; i++ )
    {
        int sockfd = events[i].data.fd;
        if ( sockfd == listenfd )
        {
            struct sockaddr_in client_address;
            socklen_t client_addrlength = sizeof( client_address );
            int connfd = accept( listenfd, ( struct sockaddr* )&client_address, &client_addrlength );
            addfd( epollfd, connfd, true );
        }
        else if ( events[i].events & EPOLLIN )
        {
            printf( "event trigger once\n" );
            //while( 1 )
            {
                memset( buf, '\0', BUFFER_SIZE );
                static int count = 0;
                int ret = 0;
                if ((count % 2) == 0)
                {
                    printf( "event null\n" );
                }
                else
                {
                    ret = recv( sockfd, buf, BUFFER_SIZE-1, 0 );
                    if( ret < 0 )
                    {
                        if( ( errno == EAGAIN ) || ( errno == EWOULDBLOCK ) )
                        {
                            printf( "read later\n" );
                            break;
                        }
                        close( sockfd );
                        break;
                    }
                    else if( ret == 0 )
                    {
                        close( sockfd );
                    }
                    else
                    {
                        printf( "get %d bytes of content: %s\n", ret, buf );
                    }
                }
                count++;
            }
        }
        else
        {
            printf( "something else happened \n" );
        }
    }
}      

三、最后看看ET模式,因为有个while循环,epoll是在边缘触发的时候要把套接字中的数据读干净,那么当有多个套接字时,在读的套接字一直不停的有数据到达,如何保证其他套接字不被饿死?说白了就是:

ET源源不断来数据,造成 while(1)死循环怎么办?

问 2018/12/30 11:17:24

请问一下,epoll是在边缘触发的时候要把套接字中的数据读干净,那么当有多个套接字时,在读的套接字一直不停的有数据到达,如何保证其他套接字不被饿死?

答 2018/12/30 11:23:16

et模式是有数据只通知一次,如果不处理不会继续通知。

答 2018/12/30 11:24:36

就像饭店你知道有人来了  你就得服务  但是你不应该只为他服务   你可以先上一部分菜给他吃着  然后给其他顾客上菜

答 2018/12/30 11:25:00

这样子避免了其他顾客的等待

答 2018/12/30 11:26:02

当顾客吃完了再请他走就ok啦

答 2018/12/30 11:31:10

具体实现应该是给每个连接添加一个时间记录  记录上次处理的时间  当再次需要处理时判断时间差  是否符合间隔(当然如果没有其他连接在等待处理就不需要间隔啦),如果符合时间间隔才进行处理。

问 2018/12/30 11:32:59

et模式是有数据只通知一次,如果不处理不会继续通知。时间间隔到了,不处理就丢失了

答 2018/12/30 11:50:40

谁跟你说丢失了

答 2018/12/30 11:53:51

epollwait会监听 触发事件加入队列处理  如果考虑均匀服务  那么需要计时器io不一次性处理完  如果不考虑就是直接处理完所有io事件就移除了

答 2018/12/30 11:56:01

et模式,如果通知事件触发不处理就需要下次收到数据才会再次通知  数据保留在缓冲期

答 2018/12/30 11:56:30

这样子的特性可以让你延迟数据处理的时间点

答 2018/12/30 11:57:48

比如你收到A发来的数据时不处理  当收到完A B C的数据再处理然后将数据发回去给它们

补充说明:

无论是et还是lt,都建议把数据读干净,都建议开一个缓冲区buffer(读内存比网卡快),异步处理数据,这样不至于阻塞后面的套接字;

ET源源不断来数据,造成 while(1)死循环怎么办?使用超时时间吧,while循环超过了指定的时间间隔,就打断循环体。另外,也需要检查源源不断来的数据的合法性,不合法就及时断开tcp连接。

继续阅读