実装
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、再登場」を参考にさせてもらいました。