epoll示例

epoll示例

linux下的非阻塞網絡編程必定離不開epoll。epoll的使用一共有三個函數:

int epoll_create(int size);

函數產生一個epoll句柄,其中size參數在linux內核2.6.8會被忽略,但一定要大約0。

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

epoll的事件註冊函數,第一個參數是 epoll_create() 的返回值,第二個參數取值如下:

EPOLL_CTL_ADD    //註冊新的fd到epfd中;
EPOLL_CTL_MOD    //修改已經註冊的fd的監聽事件;
EPOLL_CTL_DEL    //從epfd中刪除一個fd;

當我們關閉一個fd時,epoll會自動刪除該socket相關的事件,因此我們可以忽略EPOLL_CTL_DEL
第三個參數fd,第四個參數指定相關事件,使用方式如下:

    struct epoll_event ev;
    memset(&ev, 0, sizeof(ev));
    ev.events = EPOLLIN|EPOLLOUT;
    ev.data.fd = fd;

events中常見的幾個宏如下
EPOLLIN //可讀
EPOLLOUT //可寫
EPOLLERR //fd出錯,出現在epoll_wait的返回事件中

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

返回處於ready狀態的fd。其中events和maxevents指定了一個接受ready事件的event數組,timeout指定若沒有fd可讀寫,等待多久返回,0表示立刻返回,-1表示永久等待,其它表示等待的毫秒數。

使用epoll的大致過程是(注意所有epoll管理的fd都應當爲NONBLOCK):
1. 創建epoll句柄
2. 監聽端口,並且把fd交給epoll管理
3. 調用epoll_wait,處理返回的fd,若listen的fd可讀則accept連接並把連接交個epoll管理,其它連接可讀則讀取數據

下面給出可以編譯運行的代碼github.com/yedf/epoll-example

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

#define exit_if(r, ...) if(r) {printf(__VA_ARGS__); printf("error no: %d error msg %s\n", errno, strerror(errno)); exit(1);}

void setNonBlock(int fd) {
    int flags = fcntl(fd, F_GETFL, 0);
    exit_if(flags<0, "fcntl failed");
    int r = fcntl(fd, F_SETFL, flags | O_NONBLOCK);
    exit_if(r<0, "fcntl failed");
}

void updateEvents(int efd, int fd, int events, int op) {
    struct epoll_event ev;
    memset(&ev, 0, sizeof(ev));
    ev.events = events;
    ev.data.fd = fd;
    printf("%s fd %d events read %d write %d\n",
           op==EPOLL_CTL_MOD?"mod":"add", fd, ev.events & EPOLLIN, ev.events & EPOLLOUT);
    int r = epoll_ctl(efd, op, fd, &ev);
    exit_if(r, "epoll_ctl failed");
}

void handleAccept(int efd, int fd) {
    struct sockaddr_in raddr;
    socklen_t rsz = sizeof(raddr);
    int cfd = accept(fd,(struct sockaddr *)&raddr,&rsz);
    exit_if(cfd<0, "accept failed");
    sockaddr_in peer, local;
    socklen_t alen = sizeof(peer);
    int r = getpeername(cfd, (sockaddr*)&peer, &alen);
    exit_if(r<0, "getpeername failed");
    printf("accept a connection from %s\n", inet_ntoa(raddr.sin_addr));
    setNonBlock(cfd);
    updateEvents(efd, cfd, EPOLLIN|EPOLLOUT, EPOLL_CTL_ADD);
}

void handleRead(int efd, int fd) {
    char buf[4096];
    int n = 0;
    while ((n=::read(fd, buf, sizeof buf)) > 0) {
        printf("read %d bytes\n", n);
        int r = ::write(fd, buf, n); //寫出讀取的數據
        //實際應用中,寫出數據可能會返回EAGAIN,此時應當監聽可寫事件,當可寫時再把數據寫出
        exit_if(r<=0, "write error");
    }
    if (n<0 && (errno == EAGAIN || errno == EWOULDBLOCK))
        return;
    exit_if(n<0, "read error"); //實際應用中,n<0應當檢查各類錯誤,如EINTR
    printf("fd %d closed\n", fd);
    close(fd);
}

void handleWrite(int efd, int fd) {
    //實際應用應當實現可寫時寫出數據,無數據可寫才關閉可寫事件
    updateEvents(efd, fd, EPOLLIN, EPOLL_CTL_MOD);
}

void loop_once(int efd, int lfd, int waitms) {
    const int kMaxEvents = 20;
    struct epoll_event activeEvs[100];
    int n = epoll_wait(efd, activeEvs, kMaxEvents, waitms);
    printf("epoll_wait return %d\n", n);
    for (int i = 0; i < n; i ++) {
        int fd = activeEvs[i].data.fd;
        int events = activeEvs[i].events;
        if (events & (EPOLLIN | EPOLLERR)) {
            if (fd == lfd) {
                handleAccept(efd, fd);
            } else {
                handleRead(efd, fd);
            }
        } else if (events & EPOLLOUT) {
            handleWrite(efd, fd);
        } else {
            exit_if(1, "unknown event");
        }
    }
}

int main() {
    short port = 99;
    int epollfd = epoll_create(1);
    exit_if(epollfd < 0, "epoll_create failed");
    int listenfd = socket(AF_INET, SOCK_STREAM, 0);
    exit_if(listenfd < 0, "socket failed");
    struct sockaddr_in addr;
    memset(&addr, 0, sizeof addr);
    addr.sin_family = AF_INET;
    addr.sin_port = htons(port);
    addr.sin_addr.s_addr = INADDR_ANY;
    int r = ::bind(listenfd,(struct sockaddr *)&addr, sizeof(struct sockaddr));
    exit_if(r, "bind to 0.0.0.0:%d failed %d %s", port, errno, strerror(errno));
    r = listen(listenfd, 20);
    exit_if(r, "listen failed %d %s", errno, strerror(errno));
    printf("fd %d listening at %d\n", listenfd, port);
    setNonBlock(listenfd);
    updateEvents(epollfd, listenfd, EPOLLIN, EPOLL_CTL_ADD);
    for (;;) { //實際應用應當註冊信號處理函數,退出時清理資源
        loop_once(epollfd, listenfd, 10000);
    }
    return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章