io多路复用之epoll

 epoll模型函数接口


       #include <sys/epoll.h>

       int epoll_create(int size);
       int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
       int epoll_wait(int epfd, struct epoll_event *events,
                      int maxevents, int timeout);

op:

       EPOLL_CTL_ADD
              Register the target file descriptor fd on the epoll instance referred to by the file descriptor epfd and associate the event event with  the  internal
              file linked to fd.

       EPOLL_CTL_MOD
              Change the event event associated with the target file descriptor fd.

       EPOLL_CTL_DEL
              Remove  (deregister)  the  target  file descriptor fd from the epoll instance referred to by epfd.  The event is ignored and can be NULL (but see BUGS
              below).

event.events:

EPOLLIN: 注册读事件

EPOLLOUT: 注册写事件

EPOLLHUP:触发双端关闭

EPOLLRDHUP:触发读端关闭

EPOLLET:  注册边缘触发模式, epoll默认是水平触发,

EPOLLERR:表示对应的文件描述符发生错误;

返回值:

-1: 失败,并设置errno

0: 成功

           typedef union epoll_data {
               void        *ptr;
               int          fd;
               uint32_t     u32;
               uint64_t     u64;
           } epoll_data_t;

           struct epoll_event {
               uint32_t     events;      /* Epoll events */
               epoll_data_t data;        /* User data variable */
           };

epoll 在内核中的实现略图

 当调用 epoll_wait 函数是, 会将就绪事件从内核的双向链表拷贝用户态

示例代码:

int main(int argc, char **argv) 
{
    int listenfd, connfd, n;                                                                                                                                             
    struct sockaddr_in servaddr;
    char buff[MAXLNE];
 
    if ((listenfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
        printf("create socket error: %s(errno: %d)\n", strerror(errno), errno);
        return 0;
    }   
 
    memset(&servaddr, 0, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    servaddr.sin_port = htons(9999);

    int reuse = 1;
    if (setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) == -1) {
        return -1; 
    }   
 
    if (bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) == -1) {
        printf("bind socket error: %s(errno: %d)\n", strerror(errno), errno);
        return 0;
    }   
 
    if (listen(listenfd, 10) == -1) {
        printf("listen socket error: %s(errno: %d)\n", strerror(errno), errno);
        return 0;
    }  


    int epfd = epoll_create(1);

    struct epoll_event events[POLL_SIZE] = {0};
    struct epoll_event ev;

    ev.events = EPOLLIN;
    ev.data.fd = listenfd;

    epoll_ctl(epfd, EPOLL_CTL_ADD, listenfd, &ev);
                                                                                                                                                                         
    while (1) {
        int nready = epoll_wait(epfd, events, POLL_SIZE, 0);
        if (nready == -1) {
            perror("epoll_wait error. ");
            continue;
        }

        int i = 0;
        for (i=0; i < nready; i++) {
            int clientfd = events[i].data.fd;
            if (clientfd == listenfd) {
                struct sockaddr_in client;
                socklen_t len = sizeof(client);
                if ((connfd = accept(listenfd, (struct sockaddr *)&client, &len)) == -1) {
                    printf("accept socket error: %s(errno: %d)\n", strerror(errno), errno);
                    return 0;
                }

                printf("===>>> accept. \n");
                ev.events = EPOLLIN;
                ev.data.fd = connfd;
                epoll_ctl(epfd, EPOLL_CTL_ADD, connfd, &ev);
            } else if (events[i].events & EPOLLIN) {

                printf("===>>>recv. \n");
                n = recv(clientfd, buff, MAXLNE, 0);
                if (n > 0) {
                    buff[n] = '\0';
                    printf("recv msg from client: %s\n", buff);

                    send(clientfd, buff, n, 0);
                }
                else if (n == 0) {
                    ev.events = EPOLLIN;
                    ev.data.fd = clientfd;

                    epoll_ctl(epfd, EPOLL_CTL_DEL, clientfd, &ev);
                    close(clientfd);
                }
            }
        }

    }

    close(listenfd);
    return 0;
}

epoll的 LT 水平触发和 ET边沿触发

LT水平触发:当epoll_wait检查到有事件发送并通知应用程序,如果数据没有操作完成就会一直被触发直到操作完成;

比如read/recv操作,本次没有一次性接收完成,还有数据没有被读取,那么读事件就会被继续触发,直到读缓冲区中的数据被完全读取;

ET边沿触发: 当epoll_wait检查到有事件发送并通知应用程序,如果本次通知没有完成整个数据的处理,则也不会再次触发该事件;

比如read/recv操作,本次对端发送了56个字节,但是本次读取的时候只读了10个字节,还有 56-10=46 个字节没有被读取,那么剩余的没有被读取的字节不会再次触发读事件,所以剩下的46个字节就一直在读缓存区中;

当下次再次触发读事件时发送56个字节,在读缓冲区中的数据会被继续发送(缓冲区中数据的长度:46+56=102字节),所以本次读取10个字节数据(上次剩余46字节的前10个字节本读取,46-10+56=92个字节的数据没有被读取).

好像有点绕,慢慢体会吧。

LE/ET 的使用场景:

LT,一般用在accept callback 对listenfd 处理的时候,就是对新建连接速度要求比较高,建议使用LT水平触发,以免漏掉没有来得及处理的listenfd.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值