Rhythm & Biology

Engineering, Science, et al.

libeventでechoサーバをつくってみた

memcachedで使われていることで有名なlibeventを試してみました。
以前libevを試したことがあるのですが、libeventの方が少し書きやすいという印象です。パフォーマンスに関してはlibevのほうが上という噂ですが。

libeventやlibevに関して少し説明しておくと、これらは非同期IOを実現するライブラリです。他にもシグナルやタイマー処理といったこともできるらしいです(まだ詳しく調べていません)。
非同期IOのAPIはOSごとに独自のもの(epoll, kqueueなど)があるのですが、libeventなどを利用するとその違いを隠蔽してくれるため、移植性が高まります。freebsdで開発してlinuxで動かすということだって出来ますね。

インストールに関しては、macの場合にはmacportsで簡単に入れられますし、linuxであればyumやapt-getで簡単に導入できます。

そしてechoサーバのコードです。エラー対策は全くしていません。

/* echoserver_libevent.c */
#include <stdio.h>                                                                                  
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include <event.h>

#define PORT 1986
#define BACKLOG -1
#define BUFSIZE 256

void accept_handler(int listener, short event, void *arg);
void recv_handler(int client, short event, void *arg);

int main(int argc, const char* argv[])
{
  struct event ev;
  struct sockaddr_in sin;
  int listener;

  listener = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);

  memset(&sin, 0, sizeof(struct sockaddr_in));
  sin.sin_family = AF_INET;
  sin.sin_addr.s_addr = htonl(INADDR_ANY);
  sin.sin_port = htons(PORT);

  bind(listener, (struct sockaddr*)&sin, sizeof(struct sockaddr));
  listen(listener, BACKLOG);

  event_init();
  event_set(&ev, listener, EV_READ | EV_PERSIST, accept_handler, &ev);
  event_add(&ev, NULL);
  event_dispatch();

  return 0;
}

void accept_handler(int listener, short event, void *arg)
{
  struct event *ev;
  struct sockaddr_in addr;
  int client;
  socklen_t addrlen = sizeof(addr);

  if (event & EV_READ) {
    client = accept(listener, (struct sockaddr*)&addr, &addrlen);
    ev = (struct event*)malloc(sizeof(struct event));
    event_set(ev, client, EV_READ | EV_PERSIST, recv_handler, ev);
    event_add(ev, NULL);
    printf("connection accepted\n");
  }
}

void recv_handler(int client, short event, void *arg)                                               
{
  char buf[BUFSIZE];
  struct event *ev = (struct event*)arg;
  ssize_t recvlen;

  if (event & EV_READ) {
    if ((recvlen = recv(client, buf, BUFSIZE-1, 0)) <= 0) {
      event_del(ev);
      free(ev);
      close(client);
      printf("connection closed\n");
    } else {
      send(client, buf, recvlen, 0);
    }
  }
}

基本の流れは、

  1. struct eventを作って
  2. event_setでコールバックなどの設定を行って
  3. event_addで監視させ始める

となります。

重要な点は、

というところでしょうか。

コンパイルして動かして、telnetなり自作のクライアントなりでデータを送るとエコーバックします。

$ gcc echoserver_libevent.c -levent
$ ./a.out
$ telnet localhost 1986
aaa
aaa
test
test
^]
telnet> ^D