多路复用I/O--epoll

多路复用I/O–epoll

epoll定义

epoll只有epoll_create,epoll_ctl,epoll_wait 3个系统调用。

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);

1. int epoll_create(int size);

创建一个epoll的句柄。自从linux2.6.8之后,size参数是被忽略的。需要注意的是,当创建好epoll句柄后,它就是会占用一个fd值,在linux下如果查看/proc/进程id/fd/,是能够看到这个fd的,所以在使用完epoll后,必须调用close()关闭,否则可能导致fd被耗尽。

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

epoll的事件注册函数,它不同于select()是在监听事件时告诉内核要监听什么类型的事件,而是在这里先注册要监听的事件类型。

  • 第一个参数是epoll_create()的返回值。

  • 第二个参数表示动作,用三个宏来表示:

    EPOLL_CTL_ADD:注册新的fd到epfd中;

    EPOLL_CTL_MOD:修改已经注册的fd的监听事件;

    EPOLL_CTL_DEL:从epfd中删除一个fd;

  • 第三个参数是需要监听的fd。

  • 第四个参数是告诉内核需要监听什么事,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 */
};

events可以是以下几个宏的集合:

说明
EPOLLIN 表示对应的文件描述符可以读(包括对端SOCKET正常关闭)
EPOLLOUT 表示对应的文件描述符可以写
EPOLLPRI 表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来)
EPOLLERR 表示对应的文件描述符发生错误
EPOLLHUP 表示对应的文件描述符被挂断
EPOLLET 将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)来说的
EPOLLONESHOT 只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里

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

  • epfd是收集在epoll监控的事件中已经发送的事件。
  • 参数events是分配好的epoll_event结构体数组,epoll将会把发生的事件赋值到events数组中(events不可以是空指针,内核只负责把数据复制到这个events数组中,不会去帮助我们在用户态中分配内存)。

  • maxevents告之内核这个events有多大,这个 maxevents的值不能大于创建epoll_create()时的size。

  • 参数timeout是超时时间(毫秒,0会立即返回,-1将不确定,也有说法说是永久阻塞)。如果函数调用成功,返回对应I/O上已准备好的文件描述符数目,如返回0表示已超时。

实例讲解

下面已一个例子来演示epoll的用法。

我们有两个程序,epoll.c和write_fifo.c。

epoll.c中,我们循环监听了两个描述符,STDIN_FILENO标准输入描述符和命名管道fd。不同的fd上有数据反馈,在显示器上会打印不同的输出。

write_fifo.c中,每隔5秒向命名管道fd写入”this is for test”。

/**
 * epoll.c
 * 演示epoll的用法
 */

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

#define fifo_filename "test_fifo"

int main(int argc, char *argv[])
{
    int ret;
    int fd;

    ret = mkfifo(fifo_filename, 0666);
    if (ret != 0) {
        perror("mkfifo error");
    }

    fd = open(fifo_filename, O_RDWR);
    if (fd < 0) {
        perror("open error");
        exit(-1);
    }

    ret = 0;
    struct epoll_event event;
    struct epoll_event wait_event;

    int epfd = epoll_create(10);
    if (epfd == -1) {
        perror("epoll_create error");
        exit(-1);
    }

    event.data.fd = 0;
    event.events = EPOLLIN;

    ret = epoll_ctl(epfd, EPOLL_CTL_ADD, 0, &event);
    if (ret == -1) {
        perror("epoll_ctl error");
        exit(-1);
    }

    event.data.fd = fd;
    event.events = EPOLLIN;

    ret = epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &event);
    if (ret == -1) {
        perror("epoll_ctl error");
        exit(-1);
    }

    ret = 0;

    while (1) {
        ret = epoll_wait(epfd, &wait_event, 2, -1);

        if (ret == -1) {
            close(epfd);
            perror("epoll_wait error");
        } else if (ret > 0) {
            char buf[100] = {0};

            if ((wait_event.data.fd == 0) && (wait_event.events & EPOLLIN == EPOLLIN)) {
                read(0, buf, sizeof(buf));
                printf("stdin buf = %s\n", buf);
            } else if ((wait_event.data.fd == fd) && (wait_event.events & EPOLLIN == EPOLLIN)) {
                read(fd, buf, sizeof(buf));
                printf("fifo buf = %s\n", buf);
            }
        } else if (ret == 0) {
            printf("time out\n");
        }
    }

    exit(0);
}
/**
 * write_fifo.c
 * 给命名管道发送信息
 */

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <fcntl.h>

#define fifo_filename "test_fifo"

int main(int argc, char *argv[])
{
    int ret = 0;
    int fd;

    ret = mkfifo(fifo_filename, 0666);
    if (ret != 0) {
        perror("mkfifo error");
    }

    fd = open(fifo_filename, O_RDWR);
    if (fd < 0) {
        perror("open error");
        exit(-1);
    }

    while (1) {
        char *str = "this is for test";
        write(fd, str, strlen(str));
        printf("after write to fifo\n");
        sleep(5);
    }

    exit(0);
}

编译两个程序

/myblog/myblog/source/epoll# gcc epoll.c -o epoll
/myblog/source/epoll# gcc write_fifo.c -o write_fifo

启动select程序,如下:

/myblog/source/epollt# ./epoll
fifo buf = this is for test
fifo buf = this is for test
fifo buf = this is for test
hello
stdin buf = hello

fifo buf = this is for test
fifo buf = this is for test
^C

启动write_fifo程序,如下:

/myblog/source/epoll# ./write_fifo
mkfifo error: File exists
after write to fifo
after write to fifo
after write to fifo
after write to fifo
after write to fifo
^C

从程序运行结果可以看出,当write_fifo不断往命名管道写入”this is for test”时,select监听到命名管道fd已准备好,从fd上读出数据并打印到显示器上;当我们从标准输入中打出hello时,select监听到STDIN_FILENO(描述符为0)已准备好,从描述符0中读出数据并打印到显示器上。

猜你喜欢

转载自blog.csdn.net/teffi/article/details/76565317