Unixドメインソケットの確認

#Unixドメインソケット
プロセス間通信といえば、pipeや共有メモリ、Unixドメインソケットを使用することが多いと思います。
あまり参考にはならないかもしれませんが、Unixドメインソケットについて少し実演してみたいと思います。

##Unixドメインソケットとは
同一ホスト内でのプロセス間通信に使用できる。
一般的に通常のTCP接続よりもパフォーマンスがいいとされている。
リバースプロキシ、DB接続などでよく使われています。

##サーバー

```c
#define UNIXDOMAIN_PATH "/tmp/server.sock"

int main(int argc, char *argv[]){
    int clifd, lsnfd;
    struct sockaddr_un cliaddr, srvaddr;
    struct pollfd fds[1] = {0,};

    lsnfd = socket(AF_UNIX, SOCK_STREAM, 0);
    if(lsnfd < 0){
        fprintf(stderr, "socket error errno[%d]\n", errno);
        exit(-1);
    }

    unlink(UNIXDOMAIN_PATH);
    memset(&srvaddr, 0, sizeof(struct sockaddr_un));
    srvaddr.sun_family = AF_UNIX;
    strcpy(srvaddr.sun_path, UNIXDOMAIN_PATH);
    if(bind(lsnfd, (struct sockaddr *)&srvaddr, sizeof(struct sockaddr_un)) < 0){
        fprintf(stderr, "bind error errno[%d]\n", errno);
        exit(-1);
    }
    
    if(listen(lsnfd, 5) < 0){
        fprintf(stderr, "listen error errno[%d]\n", errno);
        exit(-1);
    }
    
    fds[0].fd = lsnfd;
    fds[0].events = POLLIN;

    poll(fds, 1, -1);

    if(fds[0].revents & POLLIN){
        char recvbuf[2048] = "";
        memset(&cliaddr, 0, sizeof(struct sockaddr_un));
        socklen_t addrlen = sizeof(struct sockaddr_un);
        if((clifd = accept(lsnfd, (struct sockaddr *)&cliaddr, &addrlen)) < 0){
            fprintf(stderr, "accept error errno[%d]\n", errno);
            exit(-1);
        }
        int len = read(clifd, recvbuf, sizeof(recvbuf));
        recvbuf[len] = 0;
        fprintf(stdout, "%s\n", recvbuf);
    }else{
        fprintf(stderr, "error errno[%d]\n", errno);
        exit(-1);
    }

    close(clifd);
    close(lsnfd);
    return 0;
}

```

##クライアント

```c
#define UNIXDOMAIN_PATH "/tmp/server.sock"

int main(int argc, char *argv[]){
    struct sockaddr_un addr;
    int srvfd;

    srvfd = socket(AF_UNIX, SOCK_STREAM, 0);
    if(srvfd < 0){
        fprintf(stderr, "socket error errno[%d]\n", errno);
        exit(-1);
    }

    memset(&addr, 0, sizeof(struct sockaddr_un));
    addr.sun_family = AF_UNIX;
    strcpy(addr.sun_path, UNIXDOMAIN_PATH);

    if(connect(srvfd, (struct sockaddr *)&addr, sizeof(struct sockaddr_un)) < 0){
        fprintf(stderr, "connect error errno[%d]\n", errno);
        exit(-1);
    }

    if(write(srvfd, "hello world", strlen("hello world")) < 0){
        fprintf(stderr, "write error errno[%d]\n", errno);
        exit(-1);
    }

    close(srvfd);

    return 0;
}

```

こんな感じで作成してみました。
※各エラー処理はあまり厳密にチェックしていません。

まずはビルドします。

```bash
$ gcc -g -std=c99 -o server server.c
$ ls -l
合計 20
-rwxrwxr-x 1 dev dev 13214 11月 25 23:45 2014 server
-rw-rw-r-- 1 dev dev  1684 11月 25 23:26 2014 server.c

$ gcc -g -std=c99 -o client client.c
$ ls -l
合計 16
-rwxrwxr-x 1 dev dev 11128 11月 25 23:47 2014 client
-rw-rw-r-- 1 dev dev   920 11月 25 22:53 2014 client.c
```

これで準備が整いました。
サーバーを起動してみます。

```bash
$ ./server

$ ls -l /tmp/server.sock 
srwxrwxr-x 1 dev dev 0 11月 25 23:49 2014 /tmp/server.sock
$ file /tmp/server.sock
/tmp/server.sock: socket

```

socketファイルが作成されました。
これに対してクライアントを起動してみます。

```bash
$ ./client

#サーバー側のターミナル
$ ./server
hello world

#straceでトレースしていた結果
$ strace -tt -s 1024 -p 1624
Process 1624 attached - interrupt to quit
23:51:57.153781 restart_syscall(<... resuming interrupted call ...>) = 1
23:52:08.640343 accept(3, {sa_family=AF_FILE, NULL}, [2]) = 4
23:52:08.640432 read(4, "hello world", 2048) = 11
23:52:08.640512 fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 1), ...}) = 0
23:52:08.640554 mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f98b0ab9000
23:52:08.640728 write(1, "hello world\n", 12) = 12
23:52:08.640761 close(4)                = 0
23:52:08.640785 close(3)                = 0
23:52:08.640814 exit_group(0)           = ?
Process 1624 detached
```

サーバー側で「hello world」が受信できているのが確認できると思います。

##gunicornで確認してみる
ここ最近gunicornを使う機会がありましたので、gunicornのUnixドメインソケットに先ほどのclient.cでGETリクエストを送信してみたいと思います。

Djangoでプロジェクト作成

```bash
$ django-admin startproject unix_domain
$ cd unix_domain
$ ls -l
合計 8
-rwxr-xr-x 1 dev dev  254 11月 26 00:10 2014 manage.py
drwxrwxr-x 2 dev dev 4096 11月 26 00:10 2014 unix_domain
```

[gunicornの設定ファイルはリンクを参照](https://github.com/benoitc/gunicorn/blob/master/examples/example_config.py)

```py3
#bindの部分のみ変更
bind = 'unix:/tmp/gunicorn.sock'
```

```bash
#gunicorn起動
$ gunicorn -c conf/config.py unix_domain.wsgi:application
[2014-11-26 00:34:12 +0900] [1779] [INFO] Starting gunicorn 19.1.1
[2014-11-26 00:34:12 +0900] [1779] [INFO] Listening at: unix:/tmp/gunicorn.sock (1779)
[2014-11-26 00:34:12 +0900] [1779] [INFO] Using worker: sync
[2014-11-26 00:34:12 +0900] [1779] [INFO] Server is ready. Spawning workers
[2014-11-26 00:34:12 +0900] [1782] [INFO] Booting worker with pid: 1782
[2014-11-26 00:34:12 +0900] [1782] [INFO] Worker spawned (pid: 1782)

#socketファイル確認
$ ls -l /tmp/gunicorn.sock;file /tmp/gunicorn.sock
srwxrwxrwx 1 dev dev 0 11月 26 00:34 2014 /tmp/gunicorn.sock
/tmp/gunicorn.sock: socket
```

先ほどのclient.cを編集

```c
#define UNIXDOMAIN_PATH "/tmp/gunicorn.sock"
#define GET_MSG "GET / HTTP/1.0\r\n\r\n"

int main(int argc, char *argv[]){
    struct sockaddr_un addr;
    int srvfd;

    srvfd = socket(AF_UNIX, SOCK_STREAM, 0);
    if(srvfd < 0){
        fprintf(stderr, "socket error errno[%d]\n", errno);
        exit(-1);
    }

    memset(&addr, 0, sizeof(struct sockaddr_un));
    addr.sun_family = AF_UNIX;
    strcpy(addr.sun_path, UNIXDOMAIN_PATH);

    if(connect(srvfd, (struct sockaddr *)&addr, sizeof(struct sockaddr_un)) < 0){
        fprintf(stderr, "connect error errno[%d]\n", errno);
        exit(-1);
    }

    if(write(srvfd, GET_MSG, strlen(GET_MSG)) < 0){
        fprintf(stderr, "write error errno[%d]\n", errno);
        exit(-1);
    }

    char recvbuf[2048] = "";
    int recvlen = read(srvfd, recvbuf, sizeof(recvbuf));
    if(recvlen < 0){
        fprintf(stderr, "read error errno[%d]\n", errno);
        exit(-1);
    }
    recvlen = (recvlen >= 2048)?    2047:recvlen;
    recvbuf[recvlen] = 0;
    fprintf(stdout, "msg[\n%s]\n", recvbuf);

    close(srvfd);

    return 0;
}
```

実行します。

```bash
$ ./client
msg[
HTTP/1.0 200 OK
Server: gunicorn/19.1.1
Date: Tue, 25 Nov 2014 15:36:43 GMT
Connection: close
Content-Type: text/html
X-Frame-Options: SAMEORIGIN
Vary: Cookie


<!DOCTYPE html>
<html lang="en"><head>
  <meta http-equiv="content-type" content="text/html; charset=utf-8">
  <meta name="robots" content="NONE,NOARCHIVE"><title>Welcome to Django</title>
  <style type="text/css">
    html * { padding:0; margin:0; }
    body * { padding:10px 20px; }
    body * * { padding:0; }
    body { font:small sans-serif; }
    body>div { border-bottom:1px solid #ddd; }
    h1 { font-weight:normal; }
    h2 { margin-bottom:.8em; }
    h2 span { font-size:80%; color:#666; font-weight:normal; }
    h3 { margin:1em 0 .5em 0; }
    h4 { margin:0 0 .5em 0; font-weight: normal; }
    table { border:1px solid #ccc; border-collapse: collapse; width:100%; background:white; }
    tbody td, tbody th { vertical-align:top; padding:2px 3px; }
    thead th { padding:1px 6px 1px 3px; background:#fefefe; text-align:left; font-weight:normal; font-size:11px; border:1px solid #ddd; }
    tbody th { width:12em; text-align:right; color:#666; padding-right:.5em; }
    #summary { background: #e0ebff; }
    #summary h2 { font-weight: normal; color: #666; }
    #explanation { background:#eee; }
    #instructions { background:#f6f6f6; }
    #summary table { border:none; background:transparent; }
  </style>
</head>

<body>
<div id="summary">
  <h1>It worked!</h1>
  <h2>Congratulations on your first Django-powered page.</h2>
</div>

<div id="instructions">
  <p>
    Of course, you haven't actually done any work yet.
    Next, start your first app by running <code>python manage.py startapp [app_label]</code>.
  </p>
</div>

<div id="explanation">
  <p>
    You're seeing this message because you have <code>DEBUG = True</code> in your
    Django settings file and you haven't configured any URLs. Get to work!
  </p>
</div>
</body></html>
]
```

ちゃんとレスポンスを取得できました。
client.cのreadの処理が微妙なので、ヘッダの部分しか取得できない時もあります。
気になる方は修正してください。

```bash
#straceの結果
00:36:40.737350 select(9, [5], [], [7 8], {15, 0}) = 1 (in [5], left {12, 109034})
00:36:43.628447 fchmod(6, 01)           = 0
00:36:43.628549 accept(5, {sa_family=AF_FILE, NULL}, [2]) = 9
00:36:43.628772 fcntl(9, F_GETFL)       = 0x2 (flags O_RDWR)
00:36:43.628802 fcntl(9, F_SETFL, O_RDWR) = 0
00:36:43.628845 fcntl(9, F_GETFD)       = 0
00:36:43.628886 fcntl(9, F_SETFD, FD_CLOEXEC) = 0
00:36:43.629029 recvfrom(9, "GET / HTTP/1.0\r\n\r\n", 8192, 0, NULL, NULL) = 18
00:36:43.629364 gettimeofday({1416929803, 629386}, NULL) = 0
00:36:43.629443 getsockname(5, {sa_family=AF_FILE, path="/tmp/gunicorn.sock"}, [21]) = 0
00:36:43.630946 clock_gettime(CLOCK_REALTIME, {1416929803, 630970662}) = 0
00:36:43.631078 sendto(9, "HTTP/1.0 200 OK\r\nServer: gunicorn/19.1.1\r\nDate: Tue, 25 Nov 2014 15:36:43 GMT\r\nConnection: close\r\nContent-Type: text/html\r\nX-Frame-Options: SAMEORIGIN\r\nVary: Cookie\r\n\r\n", 168, 0, NULL, 0) = 168
00:36:43.631162 sendto(9, "\n<!DOCTYPE html>\n<html lang=\"en\"><head>\n  <meta http-equiv=\"content-type\" content=\"text/html; charset=utf-8\">\n  <meta name=\"robots\" content=\"NONE,NOARCHIVE\"><title>Welcome to Django</title>\n  <style type=\"text/css\">\n    html * { padding:0; margin:0; }\n    body * { padding:10px 20px; }\n    body * * { padding:0; }\n    body { font:small sans-serif; }\n    body>div { border-bottom:1px solid #ddd; }\n    h1 { font-weight:normal; }\n    h2 { margin-bottom:.8em; }\n    h2 span { font-size:80%; color:#666; font-weight:normal; }\n    h3 { margin:1em 0 .5em 0; }\n    h4 { margin:0 0 .5em 0; font-weight: normal; }\n    table { border:1px solid #ccc; border-collapse: collapse; width:100%; background:white; }\n    tbody td, tbody th { vertical-align:top; padding:2px 3px; }\n    thead th { padding:1px 6px 1px 3px; background:#fefefe; text-align:left; font-weight:normal; font-size:11px; border:1px solid #ddd; }\n    tbody th { width:12em; text-align:right; color:#666; padding-right:.5em; }\n    #summary { background: #e0ebff; }\n    #s"..., 1759, 0, NULL, 0) = 1759
00:36:43.631286 gettimeofday({1416929803, 631298}, NULL) = 0
00:36:43.631352 gettimeofday({1416929803, 631371}, NULL) = 0
00:36:43.631643 clock_gettime(CLOCK_REALTIME, {1416929803, 631659664}) = 0
00:36:43.631787 write(2, "b'' - - [25/Nov/2014:15:36:43 +0000] \"GET / HTTP/1.0\" 200 - \"-\" \"-\"\n", 68) = 68
00:36:43.632249 close(9)                = 0
00:36:43.632309 getppid()               = 1779
00:36:43.632367 fchmod(6, 0)            = 0
00:36:43.632437 select(9, [5], [], [7 8], {15, 0}) = 0 (Timeout)
```

straceの結果を見ても、リクエストを受信してレスポンンスを返しているのが確認できると思います。
straceの結果は１回目はDjangoモジュールのロードなどかなりのシステムコールを実行していて長くなるので２回目に実行した結果を貼付けています。

今回はクライアントとサーバーをCで書きましたが、Pythonなどで書いても面白いと思います。
ちなみに動作確認したOSはCentOS6.5になります。

間違っている箇所やこうすればもっと面白い等ありましたら、遠慮なく指摘してください。