epoll

目录

epoll

epoll是linux下多路复用I/O接口select和poll的增强版本, 他能显著提高程序在大量并发连接中只有少量活跃的情况下的系统CPU利用率, 因为它会复用文件描述符集合来传递结果, 而不用迫使开发者每次等待事件之前都必须重新准备要被监听的文件描述符集合. 另一点原因就是获取事件的时候, 它无须遍历整个被监听的描述符集, 只要遍历那些被内核IO事件异步唤醒二加入Read列的描述符集合就可以

目前epoll是linux大规模并发网络程序中的热门首选模型, 适用于连接描述符多, 监听描述符少的情况

epoll处理提供select和poll那种IO事件的电平触发(Level Triggerd)外, 还提供了边沿触发(Edge Triggered), 这就使得用户空间出现有可能缓冲IO状态, 减少epoll_wait和epoll_wait的调用, 提高应用程序效率

查看一个进程可以打开socket描述符上限, 可通过修改配置文件/etc/security/limits.conf的方式修改上限

[zyb@server ~]$ cat /proc/sys/fs/file-max
180122

基础API

头文件: #include <sys/epoll.h>

(1)int epoll_create(int size);
参数:
  size: 现在并不起作用, 只是给内核一个提示, 告诉它事件表需要多大
返回值:
  成功: 返回文件描述符将用作其他所有epoll系统调用的第一个参数, 以指定要访问的内核事件表
  失败: -1, errno被设置为合适的值
  

(2)int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
参数:
  epfd: epoll_create的句柄
  op: 表示动作, EPOLL_CTL_ADD-->注册新的fd到epfd中, EPOLL_CTL_MOD-->修改已注册到epfd中的事件, EPOLL_CTL_DEL-->删除epfd中的fd事件
  event: 内核要监听的事件
返回值:
  成功: 0
  失败: -1, errno被设置为合适的值

struct epoll_event {
   uint32_t     events;      /* Epoll events */
   epoll_data_t data;        /* User data variable */
};
// events: 
// EPOLLIN:  表示对应的文件描述符可以读(包括对端SOCKET正常关闭)
// EPOLLOUT: 表示对应的文件描述符可以写
// EPOLLERR: 表示对应的文件描述符发送错误
// EPOLLET:  将EPOLL设置问边沿触发模式

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

  
(3)int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
等待所监控的文件描述符上有事件发生, 类似select调用
参数:
  epfd:
  events: 用来存储内核得到事件的集合
  maxevents: 告之这个events的大小, 不能大于epoll_create时的size
  timeout: 超时时间
    -1: 阻塞
    0: 立即返回
    >0: 指定毫秒数
返回值:
  成功: 描述符就绪数目, 时间到则返回0
  失败: -1, erron设置合适的值

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <arpa/inet.h>
#include <sys/epoll.h>
#include <errno.h>
#include <ctype.h>

#include "wrap.h"

#define MAXLINE     8192
#define SERV_PORT   8000
#define OPEN_MAX    5000

int main(int argc, const char *argv[]) {
    int i, ret; 
    int n, num = 0;
    char buf[MAXLINE], str[OPEN_MAX];

    int connfd;

    struct sockaddr_in cliaddr;             // 客户端
    socklen_t clilen = sizeof(cliaddr);     // 客户端大小

    int listenfd;
    listenfd = Socket(AF_INET, SOCK_STREAM, 0);
    int opt = 1;
    setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));   // 端口复用

    struct sockaddr_in servaddr;
    bzero(&servaddr, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    servaddr.sin_port = htons(SERV_PORT);

    Bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr));

    Listen(listenfd, 20);
    
    ssize_t efd;
    efd = epoll_create(OPEN_MAX);
    if (efd == -1)
        perr_exit("epoll_create error");

    struct epoll_event tep;                                 // 创建epoll模型, efd指向红黑树根节点
    tep.events = EPOLLIN; tep.data.fd = listenfd;           // 将lfd的监听事件设置为读
    ret = epoll_ctl(efd, EPOLL_CTL_ADD, listenfd, &tep);    // 将lfd及对应的结构体设置到树上
    if (ret == -1) 
        perr_exit("epoll_ctl");

    // 用于epoll_wait返回时的存储
    struct epoll_event ep[OPEN_MAX];
    ssize_t nready;
    int readfd;     // 存储返回事件集中的套接字描述符
    while (1) {
        nready = epoll_wait(efd, ep, OPEN_MAX, -1);     // 设置为阻塞监听
        if (nready == -1)
            perr_exit("epoll_wait error");

        for (i = 0; i < nready; i++) {
            if (!(ep[i].events & EPOLLIN))      // 不为读事件继续循环
                continue;

            if (ep[i].data.fd == listenfd) {    // 判断满足的事件是否为listenfd
                connfd = Accept(listenfd, (struct sockaddr *)&cliaddr, &clilen);

                printf("received frome %s at port %d \n",
                       inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)),
                       ntohs(cliaddr.sin_port));
                printf("cfd %d----client %d\n", connfd, num++);

                tep.events = EPOLLIN; tep.data.fd = connfd;
                ret = epoll_ctl(efd, EPOLL_CTL_ADD, connfd,&tep);
                if (ret == -1)
                    perr_exit("epoll_ctl error");
            }
            else {
                readfd = ep[i].data.fd;
                n = read(readfd, buf, MAXLINE);

                if (n == 0) {
                    ret = epoll_ctl(efd, EPOLL_CTL_DEL, readfd, NULL);
                    if (ret == -1)
                        perr_exit("epoll_ctl error");
                    Close(readfd);
                    printf("client[%d] colsed connection\n", readfd);
                }
                else if (n < 0) {
                    perror("read n < 0 error: ");
                    ret = epoll_ctl(efd, EPOLL_CTL_DEL, readfd, NULL);
                    Close(readfd);
                }
                else {
                    for (i = 0; i < n; i++) 
                        buf[i] = toupper(buf[i]);
                    Write(STDOUT_FILENO, buf, n);
                    Writen(readfd, buf, n);
                }
            }
        }
    }

    return 0;
}

epoll进阶

epoll有两种事件模型:
  Edge Triggered(ET): 边缘触发, 只有暑假到来时才触发, 不管缓存区中是否还有数据
  Level Triggered(LT): 水平触发, 只要缓存区中有数据就会触发, 默认模型

基于管道, 边缘触发时父进程一次显示4个字符, 触发时一次显示全部字符

#include <stdio.h>
#include <stdlib.h>
#include <sys/epoll.h>
#include <errno.h>
#include <unistd.h>

#define MAXLINE 10

int main(int argc, const char *argv[]) {
    int i;
    char buf[MAXLINE], ch = 'a';

    int pfd[2];
    pipe(pfd);

    pid_t pid;
    pid = fork();

    if (pid == 0) {     // 子写
        close(pfd[0]);  // 关闭读管道
        while (1) {
            for (i = 0; i < MAXLINE / 2; i++) 
                buf[i] = ch;
            buf[i - 1] = '\n';
            ch++;
            for (;i < MAXLINE; i++) 
                buf[i] = ch;
            buf[i - 1] = '\n';
            ch++;
            write(pfd[1], buf, sizeof(buf));
            sleep(5);
        }
        close(pfd[1]);
    }
    else if (pid > 0) {     // 父读
        close(pfd[1]);
        int ret, len;

        int efd;
        efd = epoll_create(10);

        struct epoll_event event;
        // 一次只读4个字符
        event.events = EPOLLIN | EPOLLET;   // ET边沿触发
        // 一次读所有字符
        //event.events = EPOLLIN;             // 默认LT水平触发
        event.data.fd = pfd[0];
        epoll_ctl(efd, EPOLL_CTL_ADD, pfd[0], &event);

        struct epoll_event resevent[10];
        while (1) {
            ret = epoll_wait(efd, resevent, 10, -1);         // 阻塞
            printf("ret = %d\n", ret);
            if (resevent[0].data.fd == pfd[0]) {
                len = read(pfd[0], buf, MAXLINE / 2);
                write(STDOUT_FILENO, buf, len);
            }
        }
        close(pfd[0]);
        close(efd);
    }

    return 0;
}

阻塞epoll

服务端

#include <stdio.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <signal.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/epoll.h>
#include <unistd.h>

#define MAXLINE 10
#define SERV_PORT 9000

int main(void) {

    char buf[MAXLINE];
    char str[INET_ADDRSTRLEN];

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

    struct sockaddr_in servaddr;
    bzero(&servaddr, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    servaddr.sin_port = htons(SERV_PORT);

    bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr));

    listen(listenfd, 20);
    printf("Accepting connecting......\n");

    struct sockaddr_in cliaddr;
    socklen_t clilen = sizeof(cliaddr);
    int connfd = accept(listenfd, (struct sockaddr*)&cliaddr, &clilen);
    printf("received from %s at port %d\n",
           inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)),
           ntohs(cliaddr.sin_port));

    int efd = epoll_create(10);

    struct epoll_event event;
    bzero(&event, sizeof(event));
    event.data.fd = connfd;
    event.events = EPOLLIN | EPOLLET;       // 边缘触发
    //event.events = EPOLLIN;                 // 水平触发
    epoll_ctl(efd, EPOLL_CTL_ADD, connfd, &event);

    struct epoll_event resevent[10];

    int res;
    while (1) {
        res = epoll_wait(efd, resevent, 10, -1);
        
        printf("res %d \n", res);
        if (resevent[0].data.fd == connfd) {
            int len = read(connfd, buf, MAXLINE / 2);
            write(STDOUT_FILENO, buf, len);
        }
    }


    return 0;
}
/*
[zyb@server block_epoll]$ ./server
Accepting connecting......
received from 127.0.0.1 at port 52464
epoll_wait begin
epoll_wait end
res 1 
aaaa
epoll_wait begin
epoll_wait end
res 1 
bbbb
epoll_wait begin
epoll_wait end
res 1 
cccc
epoll_wait begin
^C
[zyb@server block_epoll]$
*/

非阻塞epoll

边缘触发并且非阻塞模式减少epoll_wait函数调用次数,

#include <stdio.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <signal.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/epoll.h>
#include <unistd.h>
#include <fcntl.h>

#define MAXLINE 10
#define SERV_PORT 9000

int main(void) {

    char buf[MAXLINE];
    char str[INET_ADDRSTRLEN];

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

    struct sockaddr_in servaddr;
    bzero(&servaddr, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    servaddr.sin_port = htons(SERV_PORT);

    bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr));

    listen(listenfd, 20);
    printf("Accepting connecting......\n");

    struct sockaddr_in cliaddr;
    socklen_t clilen = sizeof(cliaddr);
    int connfd = accept(listenfd, (struct sockaddr*)&cliaddr, &clilen);
    printf("received from %s at port %d\n",
           inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)),
           ntohs(cliaddr.sin_port));

    // 非阻塞模式读
    int flag = fcntl(connfd, F_GETFL);
    flag |= O_NONBLOCK;
    fcntl(connfd, F_SETFL, flag);

    int efd = epoll_create(10);

    struct epoll_event event;
    bzero(&event, sizeof(event));
    event.data.fd = connfd;
    event.events = EPOLLIN | EPOLLET;       // 边缘触发
    //event.events = EPOLLIN;                 // 水平触发
    epoll_ctl(efd, EPOLL_CTL_ADD, connfd, &event);

    struct epoll_event resevent[10];

    int res;
    while (1) {
        printf("epoll_wait begin\n");
        res = epoll_wait(efd, resevent, 10, -1);
        printf("epoll_wait end\n");
        
        printf("res %d \n", res);
        if (resevent[0].data.fd == connfd) {
            int len = read(connfd, buf, MAXLINE / 2);
            write(STDOUT_FILENO, buf, len);
        }
    }


    return 0;
}

/*
[zyb@server nonblock_epoll]$ ./server 
Accepting connecting......
received from 127.0.0.1 at port 52454
epoll_wait begin
epoll_wait end
res 1 
aaaa
epoll_wait begin
epoll_wait end
res 1 
bbbb
epoll_wait begin
epoll_wait end
res 1 
cccc
epoll_wait begin
^C
[zyb@server nonblock_epoll]$ 
*/

客户端

客户端固定一次发送10个字符

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <netinet/in.h>

#define MAXLINE     10
#define SERV_PORT   9000

int main(int argc, const char *argv[]) {
    int i;
    char buf[MAXLINE];
    char ch = 'a';

    int sockfd = socket(AF_INET, SOCK_STREAM, 0);

    struct sockaddr_in servaddr;
    bzero(&servaddr, sizeof(servaddr));
    servaddr.sin_family= AF_INET;
    inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr);
    servaddr.sin_port = htons(SERV_PORT);

    connect(sockfd, (struct sockaddr*)&servaddr, sizeof(servaddr));

    while (1) {
        for (i = 0; i < MAXLINE / 2; i++)
            buf[i] = ch;
        buf[i - 1] = '\n';
        ch++;
        for (; i < MAXLINE; i++) 
            buf[i] = ch;
        buf[i - 1] = '\n';
        ch++;
        write(sockfd, buf, sizeof(buf));
        sleep(5);
    }
    close(sockfd);

    return 0;
}
上一篇:IPC-本地套接字


下一篇:Linux Socket 编程简介 【转载】