epoll的LT、ET模式和EPOLLONESHOT事件实例介绍

LT、ET模式

LT(Level Trigger)电平触发模式 -> 默认的, 相当于效率较高的poll

epoll_wait检测到事件发生后通知了应用程序 -> 应用程序可以不立即处理该事件。
下一次调用epoll_wait时,还会再次向应用程序通知此事件,直到该事件被处理

ET(Edge Trigger)边沿触发模式 epoll的高效工作模式(需向epoll内核事件表中注册一fd上的EPOLLET事件)

epoll_wait检测到事件发生后通知了应用程序 ->应用程序必须立即处理该事件,因为后续的epoll_wait调用将不再向应用程序通知这一事件。

注:
我的ubuntu是12.04,查看/usr/include/sys/epoll.h为:

struct epoll_event
{
    uint32_t events;           /*Epoll events*/
    epoll_data_t data;         /*User data variable*/
}__attribute__((__packed__));

所以不能像书里写的那样直接使用epoll_event,而要使用struct epoll_event

代码示例:

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <assert.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
#include <stdlib.h>
#include <sys/epoll.h>
#include <pthread.h>

#define MAX_EVENT_NUMBER 1024
#define BUFFER_SIZE 10

int setnonblocking( int fd )
{
    int old_option = fcntl( fd, F_GETFL );
    int new_option = old_option | O_NONBLOCK;
    fcntl( fd, F_SETFL, new_option );
    return old_option;
}

void addfd( int epollfd, int fd, bool enable_et )
{
    struct epoll_event event;
    event.data.fd = fd;
    event.events = EPOLLIN;
    if( enable_et )
    {
        event.events |= EPOLLET;
    }
    epoll_ctl( epollfd, EPOLL_CTL_ADD, fd, &event );
    setnonblocking( fd );
}

void lt(struct 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 );
            int ret = recv( sockfd, buf, BUFFER_SIZE-1, 0 );
            if( ret <= 0 )
            {
                close( sockfd );
                continue;
            }
            printf( "get %d bytes of content: %s\n", ret, buf );
        }
        else
        {
            printf( "something else happened \n" );
        }
    }
}

void et(struct 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 );
                int 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 );
                }
            }
        }
        else
        {
            printf( "something else happened \n" );
        }
    }
}

int main( int argc, char* argv[] )
{
    if( argc <= 2 )
    {
        printf( "usage: %s ip_address port_number\n", basename( argv[0] ) );
        return 1;
    }
    const char* ip = argv[1];
    int port = atoi( argv[2] );

    int ret = 0;
    struct sockaddr_in address;
    bzero( &address, sizeof( address ) );
    address.sin_family = AF_INET;
    inet_pton( AF_INET, ip, &address.sin_addr );
    address.sin_port = htons( port );

    int listenfd = socket( PF_INET, SOCK_STREAM, 0 );
    assert( listenfd >= 0 );

    ret = bind( listenfd, ( struct sockaddr* )&address, sizeof( address ) );
    assert( ret != -1 );

    ret = listen( listenfd, 5 );
    assert( ret != -1 );

    struct epoll_event events[ MAX_EVENT_NUMBER ];
    int epollfd = epoll_create( 5 );
    assert( epollfd != -1 );
    addfd( epollfd, listenfd, true );

    while( 1 )
    {
        int ret = epoll_wait( epollfd, events, MAX_EVENT_NUMBER, -1 );
        if ( ret < 0 )
        {
            printf( "epoll failure\n" );
            break;
        }

        lt( events, ret, epollfd, listenfd );
        //et( events, ret, epollfd, listenfd );
    }

    close( listenfd );
    return 0;
}

客户端测试代码:

#include <sys/socket.h>
#include <netinet/in.h>
#include <assert.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <fcntl.h>
#include <sys/epoll.h>
#include <stdlib.h>
#include <errno.h>
#include <time.h>

#include <pthread.h>

int main(int argc, char **argv)
{
    int connectfd;
    struct sockaddr_in servaddr;
    char sendline[20] = "hello world!";
    int i;

    int port = atoi(argv[2]);

    if(argc != 3)
        printf("usage: tcpcli <IP addresss> <port number>\n");

    bzero(&servaddr, sizeof(servaddr));

    connectfd = socket(AF_INET, SOCK_STREAM, 0);

    servaddr.sin_family = AF_INET;
    servaddr.sin_port = htons(port);
    inet_pton(AF_INET, argv[1], &servaddr.sin_addr);

    printf("start connect\n");

    connect(connectfd, (struct sockaddr *)&servaddr, sizeof(servaddr) );
    if(errno == EINTR)
        printf("connect is interupted\n");
    if(errno == ETIMEDOUT)
        printf("connect is interupted2\n");


    for(i = 0; i < 4; i++)
    {
        sleep(1);
        send(connectfd, sendline, strlen(sendline), 0);
    }
    exit(0);
}

执行结果:

kip@dou:~/Desktop/ServerCoding$ ./dou_cli 127.0.0.1 9877
start connect

lt模式下:

kip@dou:~/Desktop/ServerCoding$ ./epoll_et_lt 127.0.0.1 9877
event trigger once
get 9 bytes of content:hello wor
event trigger once
get 9 bytes of content:ld!hello 
event trigger once
get 9 bytes of content:world!hel
event trigger once
get 9 bytes of content:lo world!
event trigger once
get 9 bytes of content:hello wor
event trigger once
get 3 bytes of content:ld!
event trigger once

et模式下:

kip@dou:~/Desktop/ServerCoding$ ./epoll_et_lt 127.0.0.1 9877
event trigger once
get 9 bytes of content:hello wor
get 3 bytes of content:ld!
read later
event trigger once
get 9 bytes of content:hello wor
get 3 bytes of content:ld!
read later
event trigger once
get 9 bytes of content:hello wor
get 3 bytes of content:ld!
read later
event trigger once
get 9 bytes of content:hello wor
get 3 bytes of content:ld!
read later
event trigger once

EPOLLONESHOT事件

一线程读完某socket上数据后开始处理这些数据,此时该socket上又有新数据可读(即EPOLLIN再次被触发) -> 另一线程被唤醒读新的数据 =>造成2个线程同时操作一个socket的局面
EPOLLONESHOT事件:保证一个socket连接在任一时刻只被一个线程处理
注册了EPOLLONESHOT事件的文件描述符,操作系统最多触发其上注册的一个可读、可写/异常事件。
只触发一次,除非使用epoll_ctl重置该fd上注册的EPOLLONESHOT事件。=>所以,注册了EPOLLONESHOT事件的socket一旦被某个线程处理完,该线程应立即重置该fd上注册的EPOLLONESHOT事件,以确保这个socket下次可读时,其EPOLLIN事件能被触发。

示例代码如下:
客户端代码同上,服务器端代码:

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <assert.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/epoll.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <pthread.h>

#define MAX_EVENT_NUMBER 10
#define BUFFER_SIZE 10


typedef struct fds_t
{
    int epollfd;
    int sockfd;
}fds;

void reset_oneshot(int epollfd, int fd)
{
    struct epoll_event event;
    event.data.fd = fd;
    event.events = EPOLLIN | EPOLLET | EPOLLONESHOT;
    epoll_ctl(epollfd, EPOLL_CTL_MOD, fd, &event);
}

int setnobloking(int fd)
{
    int old_opt = fcntl(fd, F_GETFL);
    int new_op = old_opt | O_NONBLOCK;
    fcntl(fd, F_SETFL, new_op);
    return old_opt;
}

void addfd(int epollfd, int fd, int flag)
{
    struct epoll_event event;
    event.data.fd = fd;
    event.events = EPOLLIN | EPOLLET;
    if(flag)
    {
        event.events |= EPOLLONESHOT;
    }
    epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &event);
    setnobloking(fd);
}

void *worker(void *arg)
{
    int sockfd = ((fds*)arg)->sockfd;
    int epollfd = ((fds*)arg)->epollfd;

    printf("start new thread to receive data on fd:%d\n", sockfd);

    char buf[BUFFER_SIZE];
    memset(buf, '\0', BUFFER_SIZE);
    while(1)
    {
        int ret = recv(sockfd, buf, BUFFER_SIZE-1, 0);
        if(ret < 0)
        {
            if(EAGAIN == errno)
            {
                reset_oneshot(epollfd, sockfd);
                printf("read later\n");
                break;
            }
        }
        else if(0 == ret)
        {
            close(sockfd);  
            printf("foreiner closed the connection\n");
            break;      
        }
        else
        {
            printf("get %d bytes of content on:%s\n", ret, buf);    
            sleep(5);     //模拟处理事件所消耗的时间
        }
    }
    printf("end thread receiving data on fd:%d\n", sockfd);
}

int main( int argc, char * argv[])
{
    int i;
    if(argc <= 2)
    {
        printf("usage: %s ip_address port_number\n", basename(argv[0]));
        return 1;
    }   
    const char *ip = argv[1];
    int port  = atoi(argv[2]);

    int ret = 0;
    struct sockaddr_in address;
    bzero(&address, sizeof(address));
    address.sin_family = AF_INET;
    inet_pton(AF_INET, ip, &address.sin_addr);
    address.sin_port = htons(port);

    int listenfd = socket(AF_INET, SOCK_STREAM, 0);
    assert(listenfd >= 0);

    ret = bind(listenfd, (struct sockaddr *)&address, sizeof(address));
    assert(ret != -1);

    ret = listen(listenfd, 5);
    assert(ret != -1);

    struct epoll_event events[MAX_EVENT_NUMBER];

    int epollfd = epoll_create(5);
    assert(epollfd != -1);

    addfd(epollfd, listenfd, 0);

    while(1)
    {
        int ret = epoll_wait(epollfd, events, MAX_EVENT_NUMBER, -1);
        if(ret < 0)
        {
            printf("epoll failure\n");
            break;
        }
    //  lt(events, ret, epollfd, listenfd);
        //et(events, ret, epollfd, listenfd);
        for(i = 0; i < ret; i++)
        {
            int sockfd = events[i].data.fd;
            if(sockfd == listenfd)
            {
                struct sockaddr_in cli_addr;
                socklen_t cli_addr_len = sizeof(cli_addr);

                int connfd = accept(listenfd, (struct sockaddr *)&cli_addr, &cli_addr_len);

                addfd(epollfd, connfd, 1);
            }
            else if(events[i].events & EPOLLIN)
            {
                pthread_t thread;
                fds fds_for_new_worker;
                fds_for_new_worker.epollfd = epollfd;
                fds_for_new_worker.sockfd = sockfd;
                pthread_create(&thread, NULL, worker, (void *)&fds_for_new_worker);
            }
            else
            {
                printf("sth else happened1\n");
            }
        }
    }
    close(listenfd);
    return 0;

}

addfd(epollfd, connfd, 0),即当不注册EPOLLONESHOT事件时,服务器端反应为:

kip@dou:~/Desktop/ServerCoding$ ./epoll_one_shot 127.0.0.1 9877start new thread to receive data on fd:5
get 9 bytes of content on:hello wor
start new thread to receive data on fd:5
get 9 bytes of content on:ld!hello 
start new thread to receive data on fd:5
get 9 bytes of content on:world!hel
start new thread to receive data on fd:5
get 9 bytes of content on:lo world!
start new thread to receive data on fd:5
get 9 bytes of content on:hello wor
get 3 bytes of content on:ld!lo wor
foreiner closed the connection
end thread receiving data on fd:5

addfd(epollfd, connfd, 1),对连接socket描述符注册EPOLLONESHOT事件时,服务器端反应为:

kip@dou:~/Desktop/ServerCoding$ ./epoll_one_shot 127.0.0.1 9877
start new thread to receive data on fd:5
get 9 bytes of content on:hello wor
get 9 bytes of content on:ld!hello 
get 9 bytes of content on:world!hel
get 9 bytes of content on:lo world!
get 9 bytes of content on:hello wor
get 3 bytes of content on:ld!lo wor
foreiner closed the connection
end thread receiving data on fd:5

可以看出明显区别,在一个线程处理事件未完成时,如果注册了EPOLLONESHOT,该连接socket有新数据到了,不会触发新的线程去读这些数据。而是处理完,本线程继续读,直至(errno==EAGAIN)无数据可读 -> 该线程才放弃为该socket服务,同时要重置该fd上的EPOLLONESHOT事件(reset_oneshot())

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值