欢迎光临
我们一直在努力

Linux学习笔记:poll与epoll

接着上一篇,IO多路复用的另外两种实现:poll和epoll。select之前整理过了,这里主要讲poll和epoll的特点,以及epoll的三个核心函数。

 

一、poll

 

poll的特点:

 

1. 使用链表保存文件描述符集合,允许监测的文件描述符没有上限限制

2. 创建的文件描述符集合在应用层,需要应用层和内核层的反复数据拷贝

3. 应用层对于返回的事件结果需要遍历查找

4. poll只能工作在水平触发模式(低速模式),无法工作在边沿触发模式(高速模式)

 

简单说就是poll解决了select有fd数量上限的问题,但本质上和select差不多,还是要在用户态和内核态之间来回拷贝,返回后还要自己遍历找哪个fd就绪了,而且只有水平触发。

 

二、epoll

 

epoll的特点:

 

1. 使用树形结构(红黑树)保存文件描述符集合,没有文件描述符上限限制,并且查找效率高

2. 文件描述符集合创建在内核层,避免了应用层和内核层的反复数据拷贝

3. 直接返回到达事件的文件描述符集合

4. epoll可以工作在水平触发模式(低速模式),也可以工作在边沿触发模式(高速模式)

 

epoll比poll和select都高效,核心原因就是集合放在内核里,不用每次调用都拷贝一遍,而且直接返回就绪的fd,不用自己遍历全部。

 

三、epoll三个核心函数

 

1. epoll_create — 创建文件描述符集合

 

int epoll_create(int size);

 

功能:创建epoll的文件描述符集合。

 

参数:

 

– size:允许监测的文件描述符的个数

 

返回值:

 

– 成功:返回文件描述符句柄

– 失败:-1

 

2. epoll_ctl — 添加/修改/删除文件描述符

 

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

 

 

功能:对文件描述符集合中的文件描述符做操作。

 

参数:

 

– epfd:epoll创建的文件描述符集合

– op:操作方法

– EPOLL_CTL_ADD:添加操作

– EPOLL_CTL_MOD:修改操作

– EPOLL_CTL_DEL:删除操作

– fd:要操作的文件描述符

– event:epoll事件结构体

 

事件结构体定义:

 

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:读事件

– EPOLLOUT:写事件

 

返回值:

 

– 成功:0

– 失败:-1

 

3. epoll_wait — 通知内核开始监测事件

 

int epoll_wait(int epfd, struct epoll_event *events,

               int maxevents, int timeout);

 

 

功能:通知内核开始监测事件。

 

参数:

 

– epfd:监测的文件描述符句柄

– events:保存epoll返回的到达事件的集合

– maxevents:最多监测的事件个数

– timeout:设置超时时间,-1表示不设置超时,一直阻塞等待

 

返回值:

 

– 成功:实际到达的事件的个数

– 失败:-1

– 0:超时时间到达,却没有事件返回时

 

四、epoll TCP服务器示例

 

#include <stdio.h>

#include <sys/types.h>

#include <sys/socket.h>

#include <arpa/inet.h>

#include <netinet/in.h>

#include <unistd.h>

#include <string.h>

#include <sys/epoll.h>

 

#define MAX_EVENTS 20

 

int main(void)

{

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

    if (-1 == sockfd) {

        perror("socket error");

        return -1;

    }

 

    struct sockaddr_in seraddr;

    seraddr.sin_family = AF_INET;

    seraddr.sin_port = htons(50000);

    seraddr.sin_addr.s_addr = inet_addr("192.168.0.119");

 

    if (bind(sockfd, (struct sockaddr *)&seraddr, sizeof(seraddr)) == -1) {

        perror("bind error");

        close(sockfd);

        return -1;

    }

 

    if (listen(sockfd, 5) == -1) {

        perror("listen error");

        close(sockfd);

        return -1;

    }

 

    // 1. 创建epoll集合

    int epfd = epoll_create(MAX_EVENTS);

    if (-1 == epfd) {

        perror("epoll_create error");

        close(sockfd);

        return -1;

    }

 

    // 2. 把监听套接字加入epoll

    struct epoll_event ev;

    ev.events = EPOLLIN;

    ev.data.fd = sockfd;

    epoll_ctl(epfd, EPOLL_CTL_ADD, sockfd, &ev);

 

    struct epoll_event events[MAX_EVENTS];

    char buff[1024] = {0};

 

    while (1) {

        // 3. 等待事件

        int nready = epoll_wait(epfd, events, MAX_EVENTS, -1);

        if (-1 == nready) {

            perror("epoll_wait error");

            break;

        }

 

        for (int i = 0; i < nready; i++) {

            int fd = events[i].data.fd;

 

            if (fd == sockfd) {

                // 有新连接

                struct sockaddr_in cliaddr;

                socklen_t addrlen = sizeof(cliaddr);

                int connfd = accept(sockfd, (struct sockaddr *)&cliaddr, &addrlen);

                if (-1 == connfd) {

                    perror("accept error");

                    continue;

                }

                printf("客户端 [%s:%d] 接入\\n", inet_ntoa(cliaddr.sin_addr), ntohs(cliaddr.sin_port));

 

                // 把新客户端加入epoll

                ev.events = EPOLLIN;

                ev.data.fd = connfd;

                epoll_ctl(epfd, EPOLL_CTL_ADD, connfd, &ev);

            } else {

                // 客户端发数据

                memset(buff, 0, sizeof(buff));

                ssize_t size = recv(fd, buff, sizeof(buff) – 1, 0);

                if (size <= 0) {

                    printf("客户端断开\\n");

                    epoll_ctl(epfd, EPOLL_CTL_DEL, fd, NULL);

                    close(fd);

                    continue;

                }

                printf("收到: %s\\n", buff);

                send(fd, buff, size, 0);

            }

        }

    }

 

    close(epfd);

    close(sockfd);

    return 0;

}

 

整体流程就是epoll_create创建集合,epoll_ctl把fd加进去,epoll_wait等事件,返回后直接遍历就绪的fd处理就行,比select简洁高效很多。

赞(0)
未经允许不得转载:171主机测评 » Linux学习笔记:poll与epoll
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址