Epoll使用简介

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

简单介绍epoll在网络套接字上的使用。

更新: 若不设置套接字为非阻塞的,则水平触发(只要有读/写数据就触发)下每次需要读取一定长度的数据。此时若返回值小于给定长度,则表示此次只能读取这么多。若返回值等于给定长度则需要留到下次读否则可能阻塞。
更更新: 对于stream套接字来说,使用EPOLLRDHUP监听对端的断开连接(需要手动监听该事件)。这样就不需要通过read()返回值为0来确定对端断开(对端可能发送0长度的数据?)。

首先是epoll的使用,主要步骤

  1. 创建epoll实例 - epoll_create1(0)
  2. 调用epoll_ctl添加、修改或者删除监听套接字。需要使用 epoll_event对象
    struct epoll_event tmpEvent;
    tmpEvent.events = EPOLLIN, tmpEvent.data.fd = fd;
    
  3. 使用epoll_wait监听

那么如果要使用epoll监听TCP连接,可以按照如下步骤:

  1. socket() + bind() + setsockopt() + listen()得到套接字sd
  2. 使用epoll_create创建epoll实例,得到epFd
  3. 设置tempEvent === {data.fd = fd, events = EPOLLIN},使用epoll_ctl(epFd, EPOLL_CTL_ADD, sd, &tempEvent)将sd假如到监听队列中
  4. 创建一个epoll_event数组eventList,调用epoll_wait监听队列,根据返回值ret
    1. -1:出错
    2. 0:无事发生
    3. >0:则eventList前ret个对象为事件以及对应的描述符
    4. 对于 sd 上的EPOLLIN,表示可以accept
      1. accept得到childSd,设置其为非阻塞O_NONBLOCK
      2. 使用epoll_ctl(epFd, EPOLL_CTL_ADD, childSd, tempEvent)加入到epoll中,事件为EPOLLIN
      3. 之所以不监听EPOLLOUT是因为套接字的写操作总是可行,故会一直触发事件。应当在读取到请求后才触发可写的监听
    5. 对于其他 sd
      1. 首先判断是否 (EPOLLHUP | EPOLLRDHUP),表示出错
      2. 否则,若EPOLLIN,则读取数据
        • 若读取到EOF(返回值为0),则表示连接已断开
        • EWOULDBLOCK or EAGAIN,表示没有数据可读,设置套接字的EPOLLOUT事件监听
      3. 否则,若EPOLLOUT,则写入数据,并取消套接字的EPOLLOUT事件监听

上述读写以及epoll_wait都有可能呗信号打断,需要处理一下。

代码实例:

#if !defined(EPOLL_TEST_H)
#define EPOLL_TEST_H

#include <fcntl.h>
#include <sys/epoll.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <arpa/inet.h>
#include <errno.h>
#include <cstdio>
#include <cstdlib>
#include <cstring>

// extern int epoll_ctl (int __epfd, int __op, int __fd, struct epoll_event *__event) __THROW;

const char resp[] = "HTTP/1.1 200\r\n\
Content-Type: application/json\r\n\
Content-Length: 13\r\n\
Date: Thu, 13 Aug 2020 08:02:00 GMT\r\n\
Keep-Alive: timeout=60\r\n\
Connection: keep-alive\r\n\r\n\
[HELLO WORLD]\r\n\r\n";
void errExit() {
    fprintf(stderr, "ERROR! errno: %s\n", strerror(errno));
    exit(1);
}

static epoll_event tempEvent;

void setTempEvent(int fd, int events) {
    memset(&tempEvent, 0, sizeof(tempEvent));
    tempEvent.data.fd = fd, tempEvent.events = events;
}

void workEpollTest() {
    // freopen("./foo.txt", "wt", stderr);
    const int port = 2333;
    int sd, ret;
    sd = socket(AF_INET, SOCK_STREAM, 0);
    fprintf(stderr, "created socket\n");
    if (sd == -1)
        errExit();
    int opt = 1;
    if (setsockopt(sd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(int)) == -1)
        errExit();
    fprintf(stderr, "socket reuse set\n");
    sockaddr_in addr;
    addr.sin_family = AF_INET, addr.sin_port = htons(port);
    addr.sin_addr.s_addr = INADDR_ANY;
    if (bind(sd, (sockaddr *)&addr, sizeof(addr)) == -1)
        errExit();
    fprintf(stderr, "socket binded\n");
    if (listen(sd, 1024) == -1)
        errExit();
    fprintf(stderr, "socket listen start\n");

    // ----------------------------------------------- epoll创建实例
    int epFd = epoll_create1(0);
    if (epFd == -1)
        errExit();
    fprintf(stderr, "epoll instance created\n");
    setTempEvent(sd, EPOLLIN);
    ret = epoll_ctl(epFd, EPOLL_CTL_ADD, sd, &tempEvent);
    if (ret == -1)
        errExit();
    fprintf(stderr, "listen socket add to epoll\n");
    // ----------------------------------------------- 开始接收连接
    epoll_event *eventList = new epoll_event[100];
    while (true) {
        ret = epoll_wait(epFd, eventList, 100, -1);
        fprintf(stderr, "epoll returned with value: %d\n", ret);
        if (ret == -1) {
            if (errno == EINTR)
                fprintf(stderr, "epoll interrupted by signal, ignoring\n");
            else
                errExit();
        } else if (ret == 0) {
            fprintf(stderr, "no events on socket\n");
        } else {
            // ----------------------------------------------- 检查有事件的描述符
            fprintf(stderr, "checking fds\n");
            for (int i = 0; i < ret; i++) {
                if (eventList[i].data.fd == sd) {
                    if (eventList[i].events & EPOLLIN) {
                        sockaddr_in addr;
                        socklen_t addrLen;
                        int childSd = accept(sd, (sockaddr *)&addr, &addrLen);
                        fprintf(stderr, "client connection got, childSd: %d\n", childSd);
                        if (childSd == -1)
                            errExit();
                        ret = fcntl(childSd, F_SETFL, O_NONBLOCK);
                        if (ret == -1)
                            errExit();
                        fprintf(stderr, "child sd set to NON BLOCK\n");
                        setTempEvent(childSd, EPOLLIN);
                        ret = epoll_ctl(epFd, EPOLL_CTL_ADD, childSd, &tempEvent);
                        if (ret == -1)
                            errExit();
                        fprintf(stderr, "childSd add to epoll list\n");
                    } else {
                        fprintf(stderr, "Unknown events on sd!\n");
                        close(epFd);
                        errExit();
                    }
                } else {
                    if (eventList[i].events & (EPOLLHUP | EPOLLRDHUP | EPOLLERR)) {
                        // close
                        fprintf(stderr, "events hup on sd, closing\n");
                        close(eventList[i].data.fd);
                        epoll_ctl(epFd, EPOLL_CTL_DEL, eventList[i].data.fd, nullptr);
                    } else if (eventList[i].events & EPOLLIN) {
                        char buffer[1024] = {};
                        while (1) {
                            ret = read(eventList[i].data.fd, buffer, 1024);
                            fprintf(stderr, "read on: %d returned with value: %d\n",
                                    eventList[i].data.fd, ret);
                            if (ret == 0) {
                                fprintf(stderr, "read return EOF, connection closed\n");
                                close(eventList[i].data.fd);
                                epoll_ctl(epFd, EPOLL_CTL_DEL, eventList[i].data.fd, nullptr);
                                break;
                            }
                            if (ret == -1) {
                                const int tmpErrno = errno;
                                if (tmpErrno == EWOULDBLOCK || tmpErrno == EAGAIN) {
                                    fprintf(stderr, "read would block, stop reading\n");
                                    // read is over
                                    // http pipe line? need to put resp into a queue
                                    setTempEvent(eventList[i].data.fd,
                                                 EPOLLIN | EPOLLOUT | EPOLLHUP);
                                    epoll_ctl(epFd, EPOLL_CTL_MOD, eventList[i].data.fd,
                                              &tempEvent);
                                    break;
                                } else {
                                    errExit();
                                }
                            }
                        }
                    } else if (eventList[i].events & EPOLLOUT) {
                        ret = write(eventList[i].data.fd, resp, sizeof(resp));
                        fprintf(stderr, "write on: %d returned with value: %d\n", i, ret);
                        if (ret == -1)
                            // may be interrupted by signal
                            errExit();
                        setTempEvent(eventList[i].data.fd, EPOLLIN);
                        epoll_ctl(epFd, EPOLL_CTL_MOD, eventList[i].data.fd, &tempEvent);
                    }
                }
            }
        }
    }
}

#endif // EPOLL_TEST_H

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值