epoll学习笔记1

3 篇文章 0 订阅
3 篇文章 0 订阅

简介

epoll是同poll相似的,用于同时监听多个fd上的相关I/O事件。epoll相较于poll而言有边缘触发和水平触发两种事件触发模式,poll只有水平触发一种事件触发模式。

事件触发模式介绍

水平触发

水平触发(Level-Triggered)是当被监控的fd上有可读写事件发生时,epoll_wait会通知程序去读写。如果程序在这次操作没有将数据全部读写完(可能由于缓冲区不够),那么下次调用epoll_wait时仍然会通知程序去读写,直到数据被读写完为止。
优点: 开发时每次读写操作只需要读写想要的数据量即可,这样可以每次事件触发执行一个操作。
缺点: 如果系统中有大量不需要读写的fd时,每次epoll_wait都会返回,效率会很低。

边缘触发

边缘触发(Edge-Triggered)是当被监控的fd上有可读写事件发生时,epoll_wait会通知程序去读写。如果程序在这次操作没有将数据全部读写完(可能由于缓冲区不够),那么下次调用epoll_wait时将不会再通知程序去读写(即:只通知一次)。直到该被监控fd上第二次出现可读写事件时,epoll_wait才会通知程序去读写。
优点: 如果系统中有大量不需要读写fd时,不用每次epoll_wait返回时都会返回,相比水平触发效率更高。
缺点: 如果没有一次性将数据全部读写完,则不会再收到事件通知。

相关结构体介绍

struct epoll_event

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 */
};

event事件

EPOLLIN: 和fd相关联的读操作事件。
EPOLLOUT: 和fd关联的写操作事件。
EPOLLRDHUP: 和fd关联的读挂起事件。在流式socket的通信对端关闭了连接,或关闭了一半的写操作。
EPOLLPRI: 和fd关联的紧急数据的读操作事件。
EPOLLERR: 和fd关联的错误事件。
EPOLLHUP: 和fd关联的挂起事件。
EPOLLET: 设置fd触发方式为边缘触发。默认为水平触发。
EPOLLONESHOT: 设置fd的事件只触发一次。在一次epoll_wait报告该fd关联事件后,系统将禁止报告该fd关联的事件,必须通过epoll_ctl的EPOLL_CTL_MOD方法重新设置fd的事件触发。

依赖头文件

#include <sys/epoll.h>

相关函数介绍

epoll_create

int epoll_create(int size);

创建一个epoll实例,返回标识该epoll实例的fd。该fd将用于epoll_wait/epoll_pwait/epoll_ctl等操作的第一个参数。
参数
size: linux2.6.8后该参数将会被忽略,但是必须大于0。
返回值
成功返回一个非负的fd,该fd将于epoll_wait等接口。失败返回-1,错误码在errno中。

epoll_create1

 int epoll_create1(int flags);

创建一个epoll实例,返回标识该epoll实例的fd。该fd将用于epoll_wait/epoll_pwait/epoll_ctl等操作的第一个参数。
参数
flags: 为0,则效果等同于epoll_create;为EPOLL_CLOEXEC,则会将fd设置为close-on-exec(即:在fd上设置FD_CLOEXEC标识)。
返回值
成功返回一个非负的fd,该fd将于epoll_wait等接口。失败返回-1,错误码在errno中。

epoll_ctl

int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);

对epoll实例执行相应的控制操作。
参数
epfd: 和epoll实例关联的fd。
op: 操作的操作码。取值有:EPOLL_CTL_ADD、EPOLL_CTL_MOD、EPOLL_CTL_DEL。
fd: 执行操作应用的fd。
event: 执行操作应用fd对应的事件信息。
返回值
成功返回0。失败返回-1,错误码在errno中。

epoll_wait

int epoll_wait(int epfd, struct epoll_event *events,
               int maxevents, int timeout);

等待epoll实例中关联的fd上的相关事件被触发,或超时时返回。
参数
epfd: 和epoll实例关联的fd。
events: struct epoll_event结构体数组,用于接收epoll关联fd的事件信息。
maxevents: events的数组大小。
timeout: 等待超时时间,单位毫秒。等于-1表示一直等待。

epoll_pwait

int epoll_pwait(int epfd, struct epoll_event *events,
                int maxevents, int timeout, 
                const sigset_t *sigmask);

作用同epoll_wait,只是可以设置允许捕捉的信号。
参数
epfd: 和epoll实例关联的fd。
events: struct epoll_event结构体数组,用于接收epoll关联fd的事件信息。
maxevents: events的数组大小。
timeout: 等待超时时间,单位毫秒。等于-1表示一直等待。
sigmask: 信号信息掩码。

示例

代码

服务端

源文件名称:epoll_server.c

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

static void
epoll_addfd_read(int efd, int fd)
{
    int flags = fcntl(fd, F_GETFL);
    fcntl(fd, F_SETFL, flags | O_NONBLOCK);
    struct epoll_event ep_event;
    //ep_event.events = EPOLLIN;
    ep_event.events = EPOLLIN | EPOLLET;
    ep_event.data.fd = fd;
    epoll_ctl(efd, EPOLL_CTL_ADD, fd, &ep_event);
}

static void
epoll_removefd(int efd, int fd)
{
    epoll_ctl(efd, EPOLL_CTL_DEL, fd, NULL);
    close(fd);
}

static void
do_read(int efd, int fd)
{
    char buf[1024];
    ssize_t rlen;
    int trycnt = 0;
    while(1) {
        rlen = read(fd, buf, sizeof(buf));
        if (rlen >= 0) {
            if (rlen == 0) {
                printf("connection already close by peer. fd:%d\n", fd);
                epoll_removefd(efd, fd);
                break;
            }
            printf("recv len:%ld. fd:%d\n", rlen, fd);
            trycnt = 0;
            /*
            if (rlen < sizeof(buf)) {
                break;
            }
            */
        } else {
            if (errno == EAGAIN || errno == EWOULDBLOCK) {
                if (trycnt >= 5) {
                    printf("try read again trycnt >= 5 break out, fd:%d\n", fd);
                    break;
                }
                printf("need read again. fd:%d, trycnt:%d\n", fd, ++trycnt);
            } else {
                printf("read failed. errno:%d, errstr:%s, fd:%d\n", errno, strerror(errno), fd);
                if (errno == ECONNRESET || errno == ENOTCONN) {
                    printf("connection already close by peer. fd:%d\n", fd);
                    epoll_removefd(efd, fd);
                }
                break;
            }
        }
    }
}

static int
do_process(int efd, int listenfd)
{
    struct epoll_event events[8], *event;
    int fd;
    int num = epoll_wait(efd, events, sizeof(events), 1000);
    if (num < 0) {
        printf("epoll_wait failed. errstr:%s\n", strerror(errno));
        return -1;
    } else if (num == 0) {
        printf("epoll_wait timeout\n");
        return 0;
    }
    int i;
    for (i = 0; i < num; ++i) {
        event = &events[i];
        fd = event->data.fd;
        if (fd == listenfd) {
            struct sockaddr_storage caddr;
            socklen_t caddrlen = sizeof(caddr);
            int fd = accept(listenfd, (struct sockaddr *)&caddr, &caddrlen);
            if (fd > 0) {
                epoll_addfd_read(efd, fd);
                if (caddr.ss_family != AF_INET && caddr.ss_family != AF_INET6) {
                    printf("Unsupport family:%d\n", caddr.ss_family);
                } else {
                    char caddr_str[128] = {0};
                    uint16_t port;
                    if (caddr.ss_family == AF_INET) {
                        struct sockaddr_in *addr4 = (struct sockaddr_in *)&caddr;
                        inet_ntop(AF_INET, (const void *)&(addr4->sin_addr.s_addr), caddr_str, sizeof(caddr_str));
                        port = ntohs(addr4->sin_port);
                    } else {
                        struct sockaddr_in6 *addr6 = (struct sockaddr_in6 *)&caddr;
                        inet_ntop(AF_INET6, (const void *)&(addr6->sin6_addr), caddr_str, sizeof(caddr_str));
                        port = ntohs(addr6->sin6_port);
                    }
                    printf("A new connection(%d) from %s#%u\n", fd, caddr_str, port);
                }
            } else {
                printf("accept call failed. errstr:%s, listenfd:%d\n", strerror(errno), listenfd);
            }
        } else {
            do_read(efd, fd);
        }
    }
    return 0;
}

static int
create_tcp_server(const char *ip, const uint16_t port, const bool ipv6)
{
    const struct sockaddr *addr;
    socklen_t addrlen;
    struct sockaddr_in addr4;
    struct sockaddr_in6 addr6;
    int family;
    if (ipv6) {
        memset(&addr6, 0, sizeof(addr6));
        addr6.sin6_family = AF_INET6;
        addr6.sin6_port = htons(port);
        if (inet_pton(AF_INET6, ip, &addr6.sin6_addr) == -1) {
            printf("inet_pton failed for ipv6. ip:%s, port:%u, errstr:%s\n", ip, port, strerror(errno));
            return -1;
        }
        addr = (const struct sockaddr *)&addr6;
        addrlen = sizeof(addr6);
        family = AF_INET6;
    } else {
        memset(&addr4, 0, sizeof(addr4));
        addr4.sin_family = AF_INET;
        addr4.sin_port = htons(port);
        if (inet_pton(AF_INET, ip, &addr4.sin_addr.s_addr) == -1) {
            printf("inet_pton failed for ipv4. ip:%s, port:%u, errstr:%s\n", ip, port, strerror(errno));
            return -1;
        }
        addr = (const struct sockaddr *)&addr4;
        addrlen = sizeof(addr4);
        family = AF_INET;
    }
    int fd = socket(family, SOCK_STREAM, 0);
    if (fd == -1) {
        printf("socket failed. errstr:%s\n", strerror(errno));
        return -1;
    }
    int reuse = 1;
    if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) == -1) {
        close(fd);
        printf("setsockopt for SO_REUSEADDR failed. errstr:%s\n", strerror(errno));
        return -1;
    }
    if (bind(fd, addr, addrlen) == -1) {
        close(fd);
        printf("bind failed. errstr:%s\n", strerror(errno));
        return -1;
    }
    if (listen(fd, 5) == -1) {
        close(fd);
        printf("listen failed. errstr:%s\n", strerror(errno));
        return -1;
    }
    return fd;
}

int
main(int argc, char **argv)
{
    int svrfd = create_tcp_server("127.0.0.1", 5151, false);
    if (svrfd < 0) {
        printf("create_tcp_server failed.\n");
        return -1;
    }
    int efd = epoll_create(1024);
    if (efd == -1) {
        close(svrfd);
        printf("epoll_create failed. errstr:%s\n", strerror(errno));
        return -1;
    }
    epoll_addfd_read(efd, svrfd);
    int ret;
    while (1) {
        ret = do_process(efd, svrfd);
        if (ret != 0) {
            break;
        }
    }
    epoll_removefd(efd, svrfd);
    close(efd);
    return 0;
}

客户端

源文件名称:epoll_client.c

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

static void
do_write(int fd, char *data, size_t dlen)
{
    ssize_t wlen, awlen = 0;
    int trycnt = 0;
    while (awlen < dlen) {
        wlen = write(fd, (const void *)(data+awlen), dlen-awlen);
        if (wlen == -1) {
            if (errno == EAGAIN || errno == EWOULDBLOCK) {
                if (trycnt >= 5) {
                    printf("try write again trycnt >= 5 break out. fd:%d\n", fd);
                    break;
                }
                printf("need write again. fd:%d, trycnt:%d\n", fd, ++trycnt);
            } else {
                printf("write failed. errstr:%s, fd:%d\n", strerror(errno), fd);
                break;
            }
        } else {
            awlen += wlen;
            trycnt = 0;
            printf("write succeed. fd:%d, wlen:%ld, awlen:%ld, dlen:%ld\n", fd, wlen, awlen, dlen);
        }
    }
}

static int
create_tcp_client(const char *ip, const uint16_t port, const bool ipv6)
{
    const struct sockaddr *addr;
    socklen_t addrlen;
    struct sockaddr_in addr4;
    struct sockaddr_in6 addr6;
    int family;
    if (ipv6) {
        memset(&addr6, 0, sizeof(addr6));
        addr6.sin6_family = AF_INET6;
        addr6.sin6_port = htons(port);
        if (inet_pton(AF_INET6, ip, &addr6.sin6_addr) == -1) {
            printf("inet_pton failed for ipv6. ip:%s, port:%u, errstr:%s\n", ip, port, strerror(errno));
            return -1;
        }
        addr = (const struct sockaddr *)&addr6;
        addrlen = sizeof(addr6);
        family = AF_INET6;
    } else {
        memset(&addr4, 0, sizeof(addr4));
        addr4.sin_family = AF_INET;
        addr4.sin_port = htons(port);
        if (inet_pton(AF_INET, ip, &addr4.sin_addr.s_addr) == -1) {
            printf("inet_pton failed for ipv4. ip:%s, port:%u, errstr:%s\n", ip, port, strerror(errno));
            return -1;
        }
        addr = (const struct sockaddr *)&addr4;
        addrlen = sizeof(addr4);
        family = AF_INET;
    }
    int fd = socket(family, SOCK_STREAM, 0);
    if (fd == -1) {
        printf("socket failed. errstr:%s\n", strerror(errno));
        return -1;
    }
    if (connect(fd, addr, addrlen) == -1) {
        close(fd);
        printf("connect failed. errstr:%s\n", strerror(errno));
        return -1;
    }
    return fd;
}

int
main(int argc, char **argv)
{
    if (argc <= 1) {
        printf("please give send data\n");
        return -1;
    }
    int clifd = create_tcp_client("127.0.0.1", 5151, false);
    if (clifd < 0) {
        printf("create_tcp_client failed.\n");
        return -1;
    }
    int i;
    for (i = 1; i < argc; ++i) {
        do_write(clifd, argv[i], strlen(argv[i]));
    }
    close(clifd);
    return 0;
}

执行结果

客户端

xxxx:~$ ./epoll_client adfdf sdfsdfs sss aaaaaa sdf
write succeed. fd:3, wlen:5, awlen:5, dlen:5
write succeed. fd:3, wlen:7, awlen:7, dlen:7
write succeed. fd:3, wlen:3, awlen:3, dlen:3
write succeed. fd:3, wlen:6, awlen:6, dlen:6
write succeed. fd:3, wlen:3, awlen:3, dlen:3
xxxx:~$ 

服务端

xxxx:~$ ./epoll_server 
epoll_wait timeout
epoll_wait timeout
epoll_wait timeout
epoll_wait timeout
epoll_wait timeout
epoll_wait timeout
epoll_wait timeout
epoll_wait timeout
epoll_wait timeout
epoll_wait timeout
epoll_wait timeout
A new connection(5) from 127.0.0.1#55275
recv len:24. fd:5
connection already close by peer. fd:5
epoll_wait timeout
epoll_wait timeout
epoll_wait timeout
epoll_wait timeout
epoll_wait timeout
epoll_wait timeout
epoll_wait timeout
epoll_wait timeout
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值