libevでechoサーバ

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

#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>

#include <ev.h>

#define SERVER_PORT 8011
#define MAX_BACKLOG 10
#define MAX_EVENTS 100
#define RCVBUFSIZE 256

// 異常終了
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(EV_P_ struct ev_io *w, int revents) {
    char buf[RCVBUFSIZE + 1];
    size_t n = recv(w->fd, buf, RCVBUFSIZE, 0);
    if (n < 0) {
    	perror("recv");
    }
    if (n <= 0) {
        close(w->fd);
        ev_io_stop(EV_A_ w);
        free(w);
    } else {
        buf[n] = '\0';
        send(w->fd, buf, n, 0);
    }
}

// サーバへの接続要求イベントを処理する
void event_server(EV_P_ struct ev_io *w, int revents) {
    struct sockaddr_in client_addr;
    socklen_t client_addr_len = sizeof(client_addr);
    struct ev_loop *l;
    ev_io *client_watcher;

    int client = accept(w->fd, (struct sockaddr *) &client_addr, &client_addr_len);
    if (client < 0) {
	    if (EINTR == errno) { return; }
          die("accept");
    }
    setnonblocking(client);
    client_watcher = calloc(1, sizeof(ev_io));
    l = w->data;
    // ev_ioの初期化と開始(クライアントのイベントを監視)
    ev_io_init(client_watcher, event_client, client, EV_READ);
    ev_io_start(l, client_watcher);
}

// メイン関数
int main() {
    struct ev_loop *loop;
    ev_io watcher;

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

    // イベントループの初期化
    loop = ev_default_loop(0);
    watcher.data = loop;

    // ev_ioの初期化と開始(サーバへの接続要求を監視)
    ev_io_init(&watcher, event_server, listener, EV_READ);
    ev_io_start(loop, &watcher);

    // イベントループ開始
    ev_loop(loop, 0);
    close(listener);
    return 0;
}

ビルドするには、libevをインストールしておく必要があります。libeioのソースコードは、Node.jsのdeps配下にあります。
※ちなみにNode.jsは、deps配下にあるライブラリをstaticリンクするため、libev等を事前にインストールする必要はありません。

  • libevをインストール /usr/local/libにインストールされます。
$ cd libev
$ ./configure
$ make
$ make install
  • 共有ライブラリのパスに「/usr/local/lib」を追加する。
$ sudo vi /etc/ld.so.conf
include /etc/ld.so.conf.d/*.conf
# 1行追加
/usr/local/lib
$ sudo ldconfig

libevを使用する場合は、無限ループを作成する必要がありません。ev_loop()で、イベントループとよばれる、イベントを監視するループが実行されます。イベントループに関数を設定して、イベントを監視します。