5epoll

[TOC]

epoll系列函数

#include
int epoll_create(int size);

  • size指epoll实例的大小
  • 成功时返回epoll文件描述符,失败时返回-1

调用epoll_create函数时创建的fd保存空间称为“epoll例程”
通过size参数决定例程的大小,但这只是向操作系统提的“建议”(Linux2.6.8后该参数将被完全忽略)

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

  • epfd用于注册监视对象的epoll例程的fd
  • op用于指定监视对象的添加、删除或更改等操作
    • EPOLL_CTL_ADD将fd注册到epoll例程
    • EPOLL_CTL_DEL从epoll例程中删除fd
    • EPOLL_CTL_MOD更改注册的fd的关注事件发生情况
  • fd需要注册的监视对象的fd
  • event监事对象的事件类型
  • 例子:epoll_ctl(A, EPOLL_CTL_ADD, B, C);“epoll例程A中注册文件描述符B,主要目的时监视参数C中的事件”
  • 成功时返回0,失败时返回-1

    struct epoll_event
    {
    __uint32_t      event;
    epoll_data_t    data;
    }
    typedef union epoll_data
    {
        void*       ptr;
        int         fd;
        __uint32_t  u32;
        __uint64_t  u64;
    }epoll_data_t;
    
    //调用
    struct epoll_event event;
    ......
    event.events = EPOLLIN;
    event.data.fd=sockfd;
    epoll_ctl(epfd, EPOLL_CTL_ADD, sockfd, &event);
    

其中,epoll_event.events的选项有:

  • EPOLLIN需要读取数据的情况
  • EPOLLOUT输出缓冲为空,可用立即发送数据的情况
  • EPOLLPRI收到OOB数据
  • EPOLLDHIP断开连接或半关闭的情况,这在边缘触发方式下非常有用
  • EPOLLERR发生错误
  • EPOLLET以边缘触发的方式得到事件通知
  • EPOLLONESHOT发生一次事件后,相应的fd不再收到事件通知。因此需要向epoll_ctl函数的第二个参数传递EPOLL_CTL_MOD,再次设置事件
  • 可以通过位运算同时传递多个上述参数:|

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

  • epfd事件发生监视范围的epoll例程的fd
  • events保存发生事件的文件描述符集合的地址
  • maxevents第二个参数中可以保存的最大事件数
  • timeout以1/1000秒为单位的等待事件,传递-1时,一直等待直到发生事件
  • 成功时返回发生事件的文件描述符数,失败时返回-1
  • 第二个参数所指缓冲需要动态分配

    int event_cnt;
    struct epoll_event* ep_events;
    ......
    ep_events = malloc(sizeof(struct epoll_event)*EPOLL_SIZE);//EPOLL_SIZE是宏常量
    ......
    event_cnt = epoll_wait(epfd, ep_events, EPOLL_SIZE, -1);
    

epoll服务器端示例代码

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

#define BUF_SIZE 100
#define EPOLL_SIZE 50
void error_handling(char* buf);

int main(int argc, char const *argv[])
{
    int serv_sock, clnt_sock;
    struct sockaddr_in serv_adr, clnt_adr;//socket.h
    socklen_t adr_sz;
    int str_len;
    char buf[BUF_SIZE];

    struct epoll_event* ep_events;//epoll.h
    struct epoll_event event;
    int epfd, event_cnt;

    if(argc!=2){
        printf("Usage : %s <port>\n", argv[0]);
        exit(1);
    }

    serv_sock=socket(PF_INET, SOCK_STREAM, 0);
    memset(&serv_adr, 0, sizeof(serv_adr));
    serv_adr.sin_family=AF_INET;
    serv_adr.sin_addr.s_addr=htonl(INADDR_ANY);
    serv_adr.sin_port=htons(atoi(argv[1]));

    if(bind(serv_sock, (struct sockaddr*)&serv_adr, sizeof(serv_adr))==-1)
        error_handling("bind() error");
    if(listen(serv_sock, 5)==-1)
        error_handling("listen() error");

    epfd=epoll_create(EPOLL_SIZE);
    ep_events=malloc(sizeof(struct epoll_event)*EPOLL_SIZE);

    event.events=EPOLLIN;
    event.data.fd=serv_sock;
    epoll_ctl(epfd, EPOLL_CTL_ADD, serv_sock, &event);

    while(1)
    {
        event_cnt=epoll_wait(epfd, ep_events, EPOLL_SIZE, -1);
        if(event_cnt==-1)
        {
            puts("epoll_wait() error");
            break;
        }

        for (int i = 0; i < event_cnt; ++i)
        {
            if(ep_events[i].data.fd==serv_sock)
            {
                adr_sz=sizeof(clnt_adr);
                clnt_sock=accept(serv_sock, (struct sockaddr*)&clnt_adr, &adr_sz);
                event.events=EPOLLIN;
                event.data.fd=clnt_sock;
                epoll_ctl(epfd, EPOLL_CTL_ADD, clnt_sock, &event);
                printf("connected client: %d \n", clnt_sock);
            }
            else
            {
                str_len=read(ep_events[i].data.fd, buf, BUF_SIZE);//?
                if(str_len==0)//close request
                {
                    epoll_ctl(epfd, EPOLL_CTL_DEL, ep_events[i].data.fd, NULL);
                    close(ep_events[i].data.fd);
                    printf("closed client: %d \n",ep_events[i].data.fd);
                }
                else
                {
                    write(ep_events[i].data.fd, buf, str_len);//echo
                }
            }
        }
    }
    close(serv_sock);
    close(epfd);
    return 0;
}

void error_handling(char* msg)
{
    fputs(msg, stderr);
    fputc('\n', stderr);
    exit(1);
}


条件触发与边缘触发

条件触发:每当收到客户端数据时,都会注册该事件,并多次调用epoll_wait()

边缘触发:接收数据时仅注册一次该事件
为了实现边缘触发,需要

  • 通过errno变量验证错误原因
  • 为了完成非阻塞IO,更改套接字特性

为了在发生错误时获得额外的信息,Linux申明了这个全局变量
#include
int errno
每种函数发生错误时,保存到errno中的值都不同,没有必要记住所有可能的值
此处需要知道的是:read()发现输入缓冲中没有数据可读时返回-1,同时在errno中保存EAGAIN常量

将套接字改为非阻塞方式的函数
#include
int fcntl(int filedes, int cmd, ...);

  • filedes需要修改的fd
  • cmd函数调用的目的
  • 成功时返回cmd参数相关值,失败时返回-1
  • 如果向第二个参数传递F_GETFL,可用获得第一个参数所指的fd属性(int型)
  • 如果传递F_SETFL,可以更改fd属性。若希望将fd改为非阻塞模式,需要:
    • int flag = fcntl(fd, F_GETFL, 0);//获得属性
    • fcntl(fd, F_SETFL, flag|O_NONBLOCK);//添加O_NONBLOCK属性
编辑于2025-04-08
关于我
zinkt
西南大学2019级本科生
zinkt@foxmail.com
前端基于bootstrap5
后端使用gink&ginkorm
基于它们构建的一个个人博客系统
目录