epollでechoサーバ

epollとlibevをそれぞれ使って、echoサーバを実装すると以下のようになります。
ubuntu 10.10で実行しました。

  • epoll版 echoサーバ
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <sys/socket.h>
#include <sys/types.h>
#include <sys/epoll.h>
#include <unistd.h>
#include <fcntl.h>
#include <netinet/in.h>

#define SERVER_PORT 8012
#define MAX_BACKLOG 10
#define MAX_EVENTS 100

// 異常終了
void die(const char* msg) {
    perror(msg);
    exit(EXIT_FAILURE);
}

// ノンブロッキングI/Oに設定する
void setnonblocking(int sock) {
    int flag = fcntl(sock, F_GETFL, 0);
    fcntl(sock, F_SETFL, flag | O_NONBLOCK);
}

// ECHOサーバの設定
int setup_socket() {
    int sock;
    struct sockaddr_in sin;
    if ((sock = socket(PF_INET, SOCK_STREAM, 0)) < 0) {
        die("socket");
    }
    memset(&sin, 0, sizeof sin);

    sin.sin_family = AF_INET;
    sin.sin_addr.s_addr = htonl(INADDR_ANY);
    sin.sin_port = htons(SERVER_PORT);

    if (bind(sock, (struct sockaddr *) &sin, sizeof sin) < 0) {
        close(sock);
        die("bind");
    }
    if (listen(sock, MAX_BACKLOG) < 0) {
        close(sock);
        die("listen");
    }
    return sock;
}

// クライアントからのイベントを処理する
void event_client(int epfd, int client, struct epoll_event ev) {
    char buffer[1024];
    int n = read(client, buffer, sizeof buffer);
    if (n < 0) {
        perror("read");
        epoll_ctl(epfd, EPOLL_CTL_DEL, client, &ev);
        close(client);
    } else if (n == 0) {
        epoll_ctl(epfd, EPOLL_CTL_DEL, client, &ev);
        close(client);
    } else {
        write(client, buffer, n);
    }
}

// サーバへの接続要求イベントを処理する
void event_server(int epfd, int listener, struct epoll_event ev) {
    struct sockaddr_in client_addr;
    socklen_t client_addr_len = sizeof client_addr;

    int client = accept(listener, (struct sockaddr *) &client_addr, &client_addr_len);
    if (client < 0) {
        die("accept");
    }
    setnonblocking(client);
    memset(&ev, 0, sizeof ev);
    ev.events = EPOLLIN | EPOLLET;
    ev.data.fd = client;
    epoll_ctl(epfd, EPOLL_CTL_ADD, client, &ev);
}

// メイン関数
int main() {
    int listener, epfd;
    struct epoll_event ev;
    struct epoll_event events[MAX_EVENTS];

    // サーバ起動
    listener = setup_socket();

    // epollの初期化
    if ((epfd = epoll_create(MAX_EVENTS)) < 0) {
        die("epoll_create");
    }
    memset(&ev, 0, sizeof ev);
    ev.events = EPOLLIN;
    ev.data.fd = listener;
    epoll_ctl(epfd, EPOLL_CTL_ADD, listener, &ev);

    // 無限ループ
    while (1) {
        int i;
        // 接続があるまで待つ
        int nfd = epoll_wait(epfd, events, MAX_EVENTS, -1);
        // 接続されているクライアント数分、処理を行う
        for (i = 0; i < nfd; i++) {
            // 新規接続の場合
            if (events[i].data.fd == listener) {
            	event_server(epfd, listener, ev);
            } else {
                event_client(epfd, events[i].data.fd, ev);
            }
        }
    }

    return 0;
}

epoll()を使う場合、自前で無限ループをつくり、その中で、epoll_wait()を呼び出し、クライアントからの接続を待つ必要があります。epoll_wait()の最後のパラメータにマイナスの値を設定すると、タイムアウトせずにひたすら待ちます。