c、golang、ruby、pythonで簡単なサーバを書いて比べてみる

c以外は手軽に書けることは間違いないんだけど、ちょっとやってみますかということで。 サーバの処理としてはtcp port 9999で待ち受けて、クライアントからアクセスがあったら"hello, world"を返すだけのもの。 実装方法は完全に同じじゃなくて、例えばcだとaccept()のあとはfork(2)をしているし、rubyはthreadを使ったりという違いはありますが、その辺りは置いておいてどんなもんかというところを見てみたいと。

まずはc言語版。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
#include <arpa/inet.h>

int main(int argc, char **argv)
{
    int sock = socket(AF_INET, SOCK_STREAM, 0);
    if (sock < 0) {
        perror("socket");
        exit(-1);
    }

    struct sockaddr_in addr = {  
        .sin_family = AF_INET,
        .sin_port = htons(9999),
        .sin_addr.s_addr = htonl(INADDR_ANY)
    };

    if (bind(sock, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
        perror("bind");
        exit(-1);
    }

    if (listen(sock, 10) < 0) {
        perror("listen");
        exit(-1);
    }

    while (1) {
        socklen_t len = sizeof(addr);
        int conn = accept(sock, (struct sockaddr *) &addr, &len);
        if (conn < 0) {
            perror("accept");
            exit(-1);
        }

        pid_t pid = fork();
        if (!pid) {
            close(sock);
            char *msg = "hello, world\n";
            if (send(conn, msg, strlen(msg), 0) < 0) {
                perror("send");
                _exit(-1);
            }
            close(conn);
            _exit(0);
        } else if (pid < 0) {
            perror("fork");
            exit(-1);
        }
        close(conn);
    }
    return 0;
}

次にgolang版。

package main

import (
    "net"
    "os"
)

func main() {
    listener, err := net.Listen("tcp", "0.0.0.0:9999")
    if err != nil {
        panic("failed to listen")
    }

    for {
        conn, err := listener.Accept()
        if err != nil {
            panic("Accept() failed")
            os.Exit(-1)
        }
        go handleHello(conn)
    }
}

func handleHello(conn net.Conn) {
    defer conn.Close()

    msg := "hello, world\n"
    conn.Write([]byte (msg))
}

次がruby版。

#!/usr/bin/env ruby

require 'socket'


if __FILE__ == $0
    server = TCPServer.open("0.0.0.0", 9999)

    while true
        Thread.start(server.accept) do |conn|
            conn.write("hello, world\n")
            conn.close
        end
    end
end

最後にpython版。

#!/usr/bin/env python

import socketserver
import codecs

class HelloServer(socketserver.BaseRequestHandler):
    def handle(self):
        self.request.send(codecs.encode("hello, world\n"))
        self.request.close()

if __name__ == "__main__":
    server = socketserver.TCPServer(("0.0.0.0", 9999), HelloServer)
    server.serve_forever()

cは言わずもがなで、socket(2)でsocketを作ってbind(2)でアドレスの割当して、listen(2)で接続を待ってaccept(2)で接続要求を受け付けてってことをする必要がありますね。 あと、ネットワークバイトオーダーも書き手が気にしないといけないというところとか。

では、golangはというと、net.Listen()がcのsocket(2)、bind(2)、listen(2)も行ってくれるような感じ。で、あとはAccept()で接続要求を受け付ける。 rubyTCPServerクラスを使ってまして、これのopen()がgolangのListen()に近い感じですかね。あとはこのクラスのaccept()で接続要求を受け付けて〜という流れ。 最後のpythonはというと、TCPServerコンストラクタでListen()に近いことをして、serve_forever()でaccept()ということかな。

cは色々めんどいのでユーザーランドのコードを書くには使いたくないですが、golangrubypythonはどれを選んでも良さ気。golangはdeferがあるからsocketのクローズ漏れが抑えられそうか。異常終了とかの時とか。 (´-`).oO(個人的にはgolangはcっぽさがあって好きというのはあるんだけど。あとDial()みたいなPlan9っぽさとか。