libevを使った低パフォーマンスなCOMETサーバ(試作)

「これはひどい」だ…orz。

my_commetd.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <alloca.h>

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

#include <errno.h>

#include <ev.h>

#define PORT 10080
#define MAX_BACKLOG 5
#define LINEBUFSIZE 1025
#define MAX_CONNS 1024

#define die(s) do { fprintf(stderr, "%s\n", (s)); exit(1); } while(0)
#define die_with_err(s) do { perror((s)); exit(1); } while(0)

#ifdef DEBUG
#define debug(...) do { fprintf(stderr,  __VA_ARGS__); } while(0)
#else
#define debug(...) do {} while(0)
#endif

struct wait_socket {
  struct ev_loop *loop;
  struct ev_io *w;
  FILE *fin;
  FILE *fout;
};

struct wait_socket *wait_sockets[MAX_CONNS];

enum cmd_commet { CMD_HOOK = 0, CMD_SHOOT };

enum cmd_commet get_cmd(FILE *fin) {
  char buf[LINEBUFSIZE];
  char *cmd;

  if (fgets(buf, LINEBUFSIZE, fin) == NULL) {
    return -1;
  }

  debug("request line: %s\n", buf);

  if ((cmd = strchr(buf, '?')) == NULL) {
    return CMD_HOOK;
  }

  cmd++;

  if (strncmp(cmd, "hook", 4) == 0) {
    return CMD_HOOK;
  } else if (strncmp(cmd, "shoot", 5) == 0) {
    return CMD_SHOOT;
  } else {
    return CMD_HOOK;
  }
}

int skip_header(FILE *fin) {
  char buf[LINEBUFSIZE];
  int len = -1;

  while(1) {
    if (fgets(buf, LINEBUFSIZE, fin) == NULL) {
      break;
    }

    debug("header: %s\n", buf);

    if (strncasecmp(buf, "Content-Length:", 15) == 0) {
      len = atoi(buf + 15);
    }

    if (buf[0] == '\n' || strcmp(buf, "\r\n") == 0) {
      break;
    }
  }

  debug("content length: %d\n", len);
  return len;
}

char *read_body(FILE *fin, char *buf, int len) {
  if (fgets(buf, len, fin) == NULL) {
    return NULL;
  }

  debug("body: %s\n", buf);

  return buf;
}

void service_hook(struct ev_loop *loop, struct ev_io *w, FILE *fin, FILE *fout) {
  struct wait_socket *ws;

  debug("cmd: hook\n");

  ws = malloc(sizeof(struct wait_socket));
  ws->loop = loop;
  ws->w = w;
  ws->fin = fin;
  ws->fout = fout;

  wait_sockets[w->fd] = ws;
}

void service_shoot(FILE *fout, char *body) {
  int i;

  debug("cmd: shoot\n");

  for (i = 0; i < MAX_CONNS; i++) {
    struct wait_socket *ws;

    if (wait_sockets[i] != NULL) {
      ws = wait_sockets[i];
      ev_io_stop(ws->loop, ws->w);
      fprintf(ws->fout, "HTTP/1.1 200 OK\r\n");
      fprintf(ws->fout, "Content-Type: text/plain\r\n\r\n");
      fprintf(ws->fout, "%s", body);
      fclose(ws->fin);
      fclose(ws->fout);
      close(ws->w->fd);
      free(ws->w);
      free(ws);
      wait_sockets[i] = NULL;
    }
  }

  fprintf(fout, "HTTP/1.1 200 OK\r\n\r\n");
}

void service_error(FILE *fout) {
  debug("cmd: error\n");
  fprintf(fout, "HTTP/1.1 500 Server Error\r\n\r\n");
}

void service(struct ev_loop *loop, struct ev_io *w, FILE *fin, FILE *fout, int *close_out) {
  char buf[LINEBUFSIZE];
  enum cmd_commet cmd;
  int len = -1;
  char *body;

  cmd = get_cmd(fin);
  len = skip_header(fin);

  switch (cmd) {
  case CMD_HOOK:
    service_hook(loop, w, fin, fout);
    *close_out = 0;
    break;

  case CMD_SHOOT:
    body = alloca(len + 1);
    fgets(body, len + 1, fin);
    service_shoot(fout, body);
    *close_out = 1;
    break;

  default:
    service_error(fout);
    *close_out = 1;
    break;
  }
}

void on_readable(struct ev_loop *loop, struct ev_io *w, int revents) {
  FILE *fin, *fout;
  int close_out;

  if (wait_sockets[w->fd] == NULL) {
    fin = fdopen(w->fd, "r");
    fout = fdopen(w->fd, "w");
    setvbuf(fout, NULL, _IONBF, 0);
  }

  service(loop, w, fin, fout, &close_out);

  if (close_out) {
    fclose(fin);
    fclose(fout);
    close(w->fd);
    ev_io_stop(loop, w);
    free(w);
  }
}

void on_request(struct ev_loop *loop, struct ev_io *w, int revents) {
  int sock, flags;
  struct sockaddr_in addr;
  socklen_t len = sizeof(addr);
  struct ev_loop *l;
  ev_io *client_watcher;

  if ((sock = accept(w->fd, (struct sockaddr*) &addr, &len)) < 0) {
    if (EINTR == errno) { return; }
    die_with_err("accept(2)");
  }

  debug("accepted.\n");

  if ((flags = fcntl(sock, F_GETFL, 0)) < 0 || fcntl(sock, F_SETFL, flags | O_NONBLOCK) < 0) {
    die_with_err("fcntl(2)");
  }

  client_watcher = calloc(1, sizeof(ev_io));
  l = w->data;
  ev_io_init(client_watcher, on_readable, sock, EV_READ);
  ev_io_start(l, client_watcher);
}

int main() {
  int sock, epfd, flags;
  struct sockaddr_in addr;
  struct ev_loop *loop;
  ev_io watcher;

  if ((sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) {
    die_with_err("socket(2)");
  }

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

  if (bind(sock, (struct sockaddr*) &addr, sizeof(addr)) < 0) {
    die_with_err("bind(2)");
  }

  if (listen(sock, MAX_BACKLOG) < 0) {
    die_with_err("listen(2)");
  }

  if ((flags = fcntl(sock, F_GETFL, 0)) < 0 || fcntl(sock, F_SETFL, flags | O_NONBLOCK) < 0) {
    die_with_err("fcntl(2)");
  }

  memset(wait_sockets, 0, sizeof(wait_sockets));

  loop = ev_default_loop(0);
  watcher.data = loop;
  ev_io_init(&watcher, on_request, sock, EV_READ);
  ev_io_start(loop, &watcher);
  ev_loop(loop, 0);

  close(sock);

  return 0;
}

使い方

いろいろ

とりあえず、考えないといけないのは…

  • ファイルディスクリプタの最大値がよく分からない
  • struct ev_ioって任意の値を保持できなかったっけ?
  • EV_Pをstruct ev_loop *loopに展開するのはまずいかも
    • というかこのマクロはちょっと…
  • デーモン化
  • SIGPIPEとか無効にしないとまずそう

それはさておき、シングルスレッド楽だなぁ。