И так: программа многопоточна, и журналирование также происходит в отдельном потоке для обеспечения целостности многострочных дампов.
Про Эрланг. После многократных и пока полностью неуспешных заходов на Хаскелл и после все еще неудачных попыток на Lisp или Scheme написать что-то более менее реальное и жизненное, Эрланг был реальным прорывом для меня.
Удивительно, невозможность изменять переменные (представьте, что программируя на С++ надо все переменные делать const) является фантастическим способом борьбы с опечатками при cut-and-paste. Также когда делаешь циклы через хвостовую рекурсию, сразу осознаешь, как эффективно работать со списками, чтобы их не копировать, а всегда "таскать" за хвост или голову.
Ну а концепция легких потоков и обмена сообщениями между ними (как в Go), приправленная глобальной иммутабельностью, позволяет легко писать надежные многотопочные программы.
Например, истересен способ реализации многопотокового TCP/IP сервера. Обычно при его программировании есть распростраенный прием: один главный поток, принимающий соединения, и когда соединение принято, создается новый поток-исполнитель, который обрабатывает соединение и после этого умирает.
В Эрланге можно сделать иначе (функция acceptor()). Поток, ожидающий входящего соединения, после его получения рождает свой клон для ожидания следующего соединения и затем сам обрабабатывает запрос.
Для меня это было немного необычно.
-module(tcp_proxy).
-define(WIDTH, 16).
main([ListenPort, RemoteHost, RemotePort]) ->
ListenPortN = list_to_integer(ListenPort),
start(ListenPortN, RemoteHost, list_to_integer(RemotePort));
main(_) -> usage().
usage() ->
io:format("~ntcp_proxy.erl local_port remote_port remote_host~n~n", []),
io:format("Example:~n~n", []),
io:format("tcp_proxy.erl 50000 google.com 80~n~n", []).
start(ListenPort, CalleeHost, CalleePort) ->
io:format("Start listening on port ~p and forwarding data to ~s:~p~n",
[ListenPort, CalleeHost, CalleePort]),
{ok, ListenSocket} = gen_tcp:listen(ListenPort, [binary, {packet, 0},
{reuseaddr, true},
{active, true}]),
io:format("Listener socket is started ~s~n", [socket_info(ListenSocket)]),
spawn(fun() -> acceptor(ListenSocket, CalleeHost, CalleePort, 0) end),
register(logger, spawn(fun() -> logger() end)),
wait().
% Infinine loop to make sure that the main thread doesn't exit.
wait() -> receive _ -> true end, wait().
format_socket_info(Info) ->
{ok, {{A, B, C, D}, Port}} = Info,
lists:flatten(io_lib:format("~p.~p.~p.~p:~p", [A, B, C, D, Port])).
peer_info(Socket) -> format_socket_info(inet:peername(Socket)).
socket_info(Socket) -> format_socket_info(inet:sockname(Socket)).
acceptor(ListenSocket, RemoteHost, RemotePort, ConnN) ->
case gen_tcp:accept(ListenSocket) of
{ok, LocalSocket} ->
spawn(fun() -> acceptor(ListenSocket, RemoteHost, RemotePort, ConnN + 1) end),
LocalInfo = peer_info(LocalSocket),
logger ! {message, "~4.16.0B: Incoming connection from ~s~n", [ConnN, LocalInfo]},
case gen_tcp:connect(RemoteHost, RemotePort, [binary, {packet, 0}]) of
{ok, RemoteSocket} ->
RemoteInfo = peer_info(RemoteSocket),
logger ! {message, "~4.16.0B: Connected to ~s~n", [ConnN, RemoteInfo]},
exchange_data(LocalSocket, RemoteSocket, LocalInfo, RemoteInfo, ConnN, 0),
logger ! {message, "~4.16.0B: Finished~n", [ConnN]};
{error, Reason} ->
logger ! {message, "~4.16.0B: Unable to connect to ~s:~s (error: ~p)~n",
[ConnN, RemoteHost, RemotePort, Reason]}
end;
{error, Reason} ->
logger ! {message, "Socket accept error '~w'~n", [Reason]}
end.
exchange_data(LocalSocket, RemoteSocket, LocalInfo, RemoteInfo, ConnN, PacketN) ->
receive
{tcp, RemoteSocket, Bin} ->
logger ! {received, ConnN, Bin, RemoteInfo, PacketN},
gen_tcp:send(LocalSocket, Bin),
logger ! {sent, ConnN, LocalInfo, PacketN},
exchange_data(LocalSocket, RemoteSocket, LocalInfo, RemoteInfo, ConnN, PacketN + 1);
{tcp, LocalSocket, Bin} ->
logger ! {received, ConnN, Bin, LocalInfo, PacketN},
gen_tcp:send(RemoteSocket, Bin),
logger ! {sent, ConnN, RemoteInfo, PacketN},
exchange_data(LocalSocket, RemoteSocket, LocalInfo, RemoteInfo, ConnN, PacketN + 1);
{tcp_closed, RemoteSocket} ->
logger ! {message, "~4.16.0B: Disconnected from ~s~n", [ConnN, RemoteInfo]};
{tcp_closed, LocalSocket} ->
logger ! {message, "~4.16.0B: Disconnected from ~s~n", [ConnN, LocalInfo]}
end.
logger() ->
receive
{received, Pid, Msg, From, PacketN} ->
io:format("~4.16.0B: Received (#~p) ~p byte(s) from ~s~n",
[Pid, PacketN, byte_size(Msg), From]),
dump_bin(Pid, Msg),
logger();
{sent, Pid, ToSocket, PacketN} ->
io:format("~4.16.0B: Sent (#~p) to ~s~n", [Pid, PacketN, ToSocket]),
logger();
{message, Format, Args} ->
io:format(Format, Args),
logger()
end.
dump_list(Prefix, L, Offset) ->
{H, T} = lists:split(lists:min([?WIDTH, length(L)]), L),
io:format("~4.16.0B: ", [Prefix]),
io:format("~4.16.0B: ", [Offset]),
io:format("~-*s| ", [?WIDTH * 3, dump_numbers(H)]),
io:format("~-*s", [?WIDTH, dump_chars(H)]),
io:format("~n", []),
if length(T) > 0 -> dump_list(Prefix, T, Offset + 16); true -> [] end.
dump_numbers(L) when (is_list(L)) ->
lists:flatten([io_lib:format("~2.16.0B ", [X]) || X <- L]).
dump_chars(L) ->
lists:map(fun(X) ->
if X >= 32 andalso X < 128 -> X;
true -> $.
end
end, L).
dump_bin(Prefix, Bin) ->
dump_list(Prefix, binary_to_list(Bin), 0).
Вывод: Эрланг - прекрасный вариант для начала функциональной карьеры.