LoginSignup
10
11

More than 5 years have passed since last update.

Erlangでechoサーバを作る

Last updated at Posted at 2014-03-21

実装

echo_server.erl
%% TCP echo server demo
-module(echo_server).
-export([start/1]).

start(Port) ->
    Pid = spawn(fun() ->
        {ok, ListenSocket} = gen_tcp:listen(Port, [binary, {active, false}, {reuseaddr, true}, {backlog, 64}]),
        spawn(fun() ->
            accept(ListenSocket)
        end),
        timer:sleep(infinity)
    end),
    {ok, Pid}.

accept(ListenSocket) ->
    {ok, Socket} = gen_tcp:accept(ListenSocket),
    spawn(fun() ->
        accept(ListenSocket)
    end),
    trace("connect ~p~n", [Socket]),
    loop(Socket).

loop(Socket) ->
    gen_tcp:send(Socket, "> "),
    inet:setopts(Socket, [{active, once}]),
    receive
        {tcp, Socket, <<"bye", _/binary>>} ->
            gen_tcp:send(Socket, "bye bye!\n"),
            gen_tcp:close(Socket);
        {tcp, Socket, Message} ->
            gen_tcp:send(Socket, Message),
            loop(Socket);
        {tcp_closed, Socket} ->
            trace("close ~p~n", [Socket]),
            gen_tcp:close(Socket);
        {tcp_error, Socket, Reason} ->
            trace("Handle error ~p on ~p~n", [Reason, Socket])
    end.

trace(Message, Args) ->
    io:format(Message, Args).

echoサーバ起動

erl
Erlang R16B03-1 (erts-5.10.4) [source] [64-bit] [smp:4:4] [async-threads:10] [hipe] [kernel-poll:false] [dtrace] 

Eshell V5.10.4  (abort with ^G)
1> c(echo_server).
{ok,echo_server}
2> echo_server:start(8022).
{ok,<0.40.0>}
connect #Port<0.2341>
3> 

telnetクライアントで接続してみる

% telnet localhost 8022
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
> hello
hello
> bye
bye bye!
Connection closed by foreign host.

ちょっと負荷をかけてみる

client.rb
require "socket"

def main host, port, count
    tx = []
    (1..count).each do
        tx << Thread.new do
            sock = TCPSocket.open(host, port)
            test(sock, "hello\n", "hello\n")
            test(sock, "bye\n", "bye bye!\n")
            sock.close()
        end
    end
    tx.each do |t|
        t.join()
    end
    puts "done"
end

def test sock, send, recv
    check(sock, "> ")
    sock.write(send)
    check(sock, recv)
end

def check sock, except
    fact = sock.recv(except.size)
    raise "fail #{ except } != #{ fact }" if fact != except
end

if $0 == __FILE__
    host = ARGV[0] || "localhost"
    port = ARGV[1] || 8022
    count = ARGV[2] || 1000
    main(host, port.to_i, count.to_i)
end

うまくいった!

% time ruby client.rb
done
ruby client.rb  0.63s user 0.98s system 134% cpu 1.191 total

しかし何度か実行すると失敗する事も…

time ruby client.rb
client.rb:26:in `recv': Connection reset by peer - recvfrom(2) (Errno::ECONNRESET)
    from client.rb:26:in `check'
    from client.rb:20:in `test'
    from client.rb:8:in `block (2 levels) in main'
ruby client.rb  0.70s user 0.92s system 26% cpu 6.231 total

まとめ

erlangでサーバを作るのは難しくない。
今回の素朴な実装では負荷をかけると(Errno::ECONNRESET)が発生する事がある。

参考:http://www.ymotongpoo.com/works/lyse-ja/ja/26_buckets_of_sockets.html
「26.6. Sockserv、再登場」を参考にさせてもらいました。

10
11
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
10
11