I am starting to learn Erlang in the hopes of creating a game server to real-time multiplayer games. Currently, I am trying to estimate the amount of work and headache Erlang would cause vs. Scala. So, to start, I am creating a simple Erlang server process. I found a nice tutorial by Jesse Farmer which I have modified to learn more. My modified code is meant to be similar to his echo server, except it takes in English words and simply returns the Lojban equivalent. However, only the wildcard case is ever selected. Here is the code:
-module(translate).
-export([listen/1]).
-import(string).
-define(TCP_OPTIONS, [binary, {packet, 0}, {active, false}, {reuseaddr, true}]).
% Call echo:listen(Port) to start the service.
listen(Port) ->
{ok, LSocket} = gen_tcp:listen(Port, ?TCP_OPTIONS),
accept(LSocket).
% Wait for incoming connections and spawn the echo loop when we get one.
accept(LSocket) ->
{ok, Socket} = gen_tcp:accept(LSocket),
spawn(fun() -> loop(Socket) end),
accept(LSocket).
% Echo back whatever data we receive on Socket.
loop(Socket) ->
case gen_tcp:recv(Socket, 0) of
{ok, Data} ->
case Data of
"Hello" -> gen_tcp:send(Socket, "coi\n");
"Hello\n" -> gen_tcp:send(Socket, "coi\n");
'Hello' -> gen_tcp:send(Socket, "coi\n");
<<"Hello">> -> gen_tcp:send(Socket, "coi\n");
<<"Hello\n">> -> gen_tcp:send(Socket, "coi\n");
_ -> gen_tcp:send(Socket, "I don't understand")
end,
loop(Socket);
{error, closed} ->
ok
end.
My current test is to open two terminal windows and execute
[CONSOLE 1]
erl
c(translate).
translate:listen(8888).
[CONSOLE 2]
telnet localhost 8888
whatever
Hello
And the output becomes:
I don't understand
I don't understand
How can I parse the incoming data? This style of pattern matching seems to be failing completely. Thanks!
Try this one:
case binary_to_list(Data) of
"Hello\r\n" -> gen_tcp:send(Socket, "this will be good variant\n");
_ -> gen_tcp:send(Socket, "I don't understand")
end,
Or without explicit convert:
case Data of
<<"Hello\r\n">> -> gen_tcp:send(Socket, "this will be good variant\n");
_ -> gen_tcp:send(Socket, "I don't understand")
end,
Updated from comments
To work with more complicated matching remove "\r\n" suffix first:
Content = list_to_binary(lists:subtract(binary_to_list(Data), "\r\n")),
case Content of
<<"Hello">> -> gen_tcp:send(Socket, <<"Good day!\n">>);
<<"My name is, ", Name/binary>> -> gen_tcp:send(Socket, <<"Hello ", Name/binary, "!\n">>);
_ -> gen_tcp:send(Socket, "I don't understand")
end,
Related
I'm fairly new to Erlang, and I'm trying to set up a simple multi-client chat server to learn more about the language. However whenever I try and run it I get a socket closed error from the call of gen_tcp:accept on the listening socket. I've tried a number of different port numbers to no avail. What am I missing?
The code is below:
-define(TCP_OPTIONS, [binary, {packet, 2}, {active, false}, {reuseaddr, true}]).
listen(Portno, DictPid) ->
case gen_tcp:listen(Portno, ?TCP_OPTIONS) of
{ok, ListenSocket} ->
spawn_link(fun() -> accept_connections(ListenSocket, DictPid) end),
io:format("Listening on ~p~n", [ListenSocket]);
{error, Error} ->
io:format("Listen Error: ~w~n", [Error])
end.
accept_connections(ListenSocket, DictPid) ->
case gen_tcp:accept(ListenSocket) of
{ok, ClientSocket} ->
io:format("Accepting:~w~n", [ClientSocket]),
gen_tcp:send(ClientSocket, "Welcome! Enter your name~n"),
ClientPid = spawn(fun() -> io:format("Client connected"),
setup_user(ClientSocket, DictPid) end),
gen_tcp:controlling_process(ClientSocket, ClientPid),
accept_connections(ListenSocket, DictPid);
{error, Error} ->
io:format("Accept Error: ~w~n", [Error])
end.
setup_user(ClientSocket, DictPid) ->
{ok, Username} = gen_tcp:recv(ClientSocket, 0),
DictPid ! {add_new_pair, ClientSocket, Username},
ClientDict = get_dict(DictPid),
broadcast_message(dict:fetch_keys(ClientDict), "[" ++ Username ++ "has entered the chat]"),
client_loop(ClientSocket, Username, DictPid).
[rest of program]
The issue here is that the controller of the ListenSocket terminates, which causes the ListenSocket to be closed.
I'm reading the "Using TCP" chapter of "Software for a Concurrent World", and the parallel TCP server confuses me.
Rather than looping and spawning a new handler process for each connection, it spawns a new acceptor process – i.e. it continuously spawns copies of itself.
%% Parallel TCP Server (From the book)
par_start(Port) ->
{ok, Sock} = gen_tcp:listen(Port, [{packet, line}]),
spawn(fun() -> par_connect(Sock) end).
par_connect(Sock) ->
{ok, Conn} = gen_tcp:accept(Sock),
% Spawn a new process to accept more connections
spawn(fun() -> par_connect(Sock) end),
handle(Conn).
handle(Conn) ->
receive
{tcp, Socket, Bin} ->
io:format("Data: ~p~n", [Bin]),
gen_tcp:send(Socket, Bin),
handle(Conn);
{error, closed} ->
io:format("Connection closed: ~p~n", [Conn])
end.
I find this bit confusing:
par_connect(Sock) ->
{ok, Conn} = gen_tcp:accept(Sock),
% Spawn a new process to accept more connections
spawn(fun() -> par_connect(Sock) end),
handle(Conn).
Is there a benefit to handling the connection in the process that accepts it, rather than spawning a new handler for each connection?
Spawning new handler for each connection:
connect(Sock) ->
{ok, Conn} = gen_tcp:accept(Sock),
% Handle connection in different process
Handler = spawn(fun() -> handle(Conn) end),
gen_tcp:controlling_process(Conn, Handler),
% Loop to accept more connections
connect(Sock).
It does avoid a call to gen_tcp:controlling_process(..). Is it faster to skip this, and instead spawn a new process?
I just do a testing with gen_tcp. One simple echo server, and one client.
But client started and closed, server accept two connection, and one is ok, the other is bad.
Any issue of my demo script, and how to explain it?
server
-module(echo).
-export([listen/1]).
-define(TCP_OPTIONS, [binary, {packet, 0}, {active, false}, {reuseaddr, true}]).
listen(Port) ->
{ok, LSocket} = gen_tcp:listen(Port, ?TCP_OPTIONS),
accept(LSocket).
accept(LSocket) ->
{ok, Socket} = gen_tcp:accept(LSocket),
spawn(fun() -> loop(Socket) end),
accept(LSocket).
loop(Socket) ->
timer:sleep(10000),
P = inet:peername(Socket),
io:format("ok ~p~n", [P]),
case gen_tcp:recv(Socket, 0) of
{ok, Data} ->
gen_tcp:send(Socket, Data),
loop(Socket);
{error, closed} ->
ok;
E ->
io:format("bad ~p~n", [E])
end.
Demo Server
1> c(echo).
{ok,echo}
2> echo:listen(1111).
ok {ok,{{192,168,2,184},51608}}
ok {error,enotconn}
Client
> spawn(fun() -> {ok, P} = gen_tcp:connect("192.168.2.173", 1111, []), gen_tcp:send(P, "aa"), gen_tcp:close(P) end).
<0.64.0>
```
But client started and closed, server accept two connection, and one is ok, the other is bad.
Actually, your server only accepted one connection:
Enter loop/1 upon accepting the connection from the client
inet:peername/1 returns {ok,{{192,168,2,184},51608}} because the socket is still open
gen_tcp:recv/2 returns <<"aa">> which was sent by the client
gen_tcp:send/2 sends the data from 3 to the client
Enter loop/1 again
inet:peername/1 returns {error,enotconn} because the socket was closed by the client
gen_tcp:recv/2 returns {error,closed}
The process exits normally
So in reality, your echo server is functioning just fine, however there are some improvements that can be made that are mentioned in the comment made by #zxq9.
Improvement 1
Hand off control of the accepted socket to the newly spawned process.
-module(echo).
-export([listen/1]).
-define(TCP_OPTIONS, [binary, {packet, 0}, {active, false}, {reuseaddr, true}]).
listen(Port) ->
{ok, LSocket} = gen_tcp:listen(Port, ?TCP_OPTIONS),
accept(LSocket).
accept(LSocket) ->
{ok, CSocket} = gen_tcp:accept(LSocket),
Ref = make_ref(),
To = spawn(fun() -> init(Ref, CSocket) end),
gen_tcp:controlling_process(CSocket, To),
To ! {handoff, Ref, CSocket},
accept(LSocket).
init(Ref, Socket) ->
receive
{handoff, Ref, Socket} ->
{ok, Peername} = inet:peername(Socket),
io:format("[S] peername ~p~n", [Peername]),
loop(Socket)
end.
loop(Socket) ->
case gen_tcp:recv(Socket, 0) of
{ok, Data} ->
io:format("[S] got ~p~n", [Data]),
gen_tcp:send(Socket, Data),
loop(Socket);
{error, closed} ->
io:format("[S] closed~n", []);
E ->
io:format("[S] error ~p~n", [E])
end.
Improvement 2
Wait on the client side for the echo server to send back the data before closing the socket.
spawn(fun () ->
{ok, Socket} = gen_tcp:connect("127.0.0.1", 1111, [binary, {packet, 0}, {active, false}]),
{ok, Peername} = inet:peername(Socket),
io:format("[C] peername ~p~n", [Peername]),
gen_tcp:send(Socket, <<"aa">>),
case gen_tcp:recv(Socket, 0) of
{ok, Data} ->
io:format("[C] got ~p~n", [Data]),
gen_tcp:close(Socket);
{error, closed} ->
io:format("[C] closed~n", []);
E ->
io:format("[C] error ~p~n", [E])
end
end).
Example
The server should look something like this:
1> c(echo).
{ok,echo}
2> echo:listen(1111).
[S] peername {{127,0,0,1},57586}
[S] got <<"aa">>
[S] closed
The client should look something like this:
1> % paste in the code from Improvement 2
<0.34.0>
[C] peername {{127,0,0,1},1111}
[C] got <<"aa">>
Recommendations
As #zxq9 mentioned, this is not OTP style code and probably shouldn't be used for anything beyond educational purposes.
A better approach might be to use something like ranch or gen_listener_tcp for the server side listening and accepting of connections. Both projects have examples of echo servers: tcp_echo (ranch) and echo_server.erl (gen_listener_tcp).
Consider the following (based on sockserv from LYSE)
%%% The supervisor in charge of all the socket acceptors.
-module(tcpsocket_sup).
-behaviour(supervisor).
-export([start_link/0, start_socket/0]).
-export([init/1]).
start_link() ->
supervisor:start_link({local, ?MODULE}, ?MODULE, []).
init([]) ->
{ok, Port} = application:get_env(my_app,tcpPort),
{ok, ListenSocket} = gen_tcp:listen(
Port,
[binary, {packet, 0}, {reuseaddr, true}, {active, true} ]),
lager:info(io_lib:format("Listening for TCP on port ~p", [Port])),
spawn_link(fun empty_listeners/0),
{ok, {{simple_one_for_one, 60, 3600},
[{socket,
{tcpserver, start_link, [ListenSocket]},
temporary, 1000, worker, [tcpserver]}
]}}.
start_socket() ->
supervisor:start_child(?MODULE, []).%,
empty_listeners() ->
[start_socket() || _ <- lists:seq(1,20)],
ok.
%%%-------------------------------------------------------------------
%%% #author mylesmcdonnell
%%% #copyright (C) 2015, <COMPANY>
%%% #doc
%%%
%%% #end
%%% Created : 06. Feb 2015 07:49
%%%-------------------------------------------------------------------
-module(tcpserver).
-author("mylesmcdonnell").
-behaviour(gen_server).
-record(state, {
next,
socket}).
-export([start_link/1]).
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, code_change/3, terminate/2]).
-define(SOCK(Msg), {tcp, _Port, Msg}).
-define(TIME, 800).
-define(EXP, 50).
start_link(Socket) ->
gen_server:start_link(?MODULE, Socket, []).
init(Socket) ->
gen_server:cast(self(), accept),
{ok, #state{socket=Socket}}.
handle_call(_E, _From, State) ->
{noreply, State}.
handle_cast(accept, S = #state{socket=ListenSocket}) ->
{ok, AcceptSocket} = gen_tcp:accept(ListenSocket),
kvstore_tcpsocket_sup:start_socket(),
receive
{tcp, Socket, <<"store",Value/binary>>} ->
Uid = kvstore:store(Value),
send(Socket,Uid);
{tcp, Socket, <<"retrieve",Key/binary>>} ->
case kvstore:retrieve(binary_to_list(Key)) of
[{_, Value}|_] ->
send(Socket,Value);
_ ->
send(Socket,<<>>)
end;
{tcp, Socket, _} ->
send(Socket, "INVALID_MSG")
end,
{noreply, S#state{socket=AcceptSocket, next=name}}.
handle_info(_, S) ->
{noreply, S}.
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
terminate(normal, _State) ->
ok;
terminate(_Reason, _State) ->
lager:info("terminate reason: ~p~n", [_Reason]).
send(Socket, Bin) ->
ok = gen_tcp:send(Socket, Bin),
ok = gen_tcp:close(Socket),
ok.
I'm unclear on how each tcpserver process is terminated? Is this leaking processes?
I don't see any place that you are terminating the owning process.
I think what you are looking for are four cases:
The client terminates the connection (you receive tcp_closed)
The connection goes wonky (you receive tcp_error)
The server receives a system message to terminate (this could, of course, just be the supervisor killing it, or a kill message)
The client sends a message telling the server its done and you want to do some clean up other than just reacting to tcp_closed.
The most common case is usually the client just closes the connection, and for that you want something like:
handle_info({tcp_closed, _}, State) ->
{stop, normal, State};
The connection getting weird is always a possibility. I can't think of any time I want to have the owning process or the socket stick around, so:
%% You might want to log something here.
handle_info({tcp_error, _}, State) ->
{stop, normal, State};
And any case where the client tells the server its done and you need to do cleanup based on the client having done something successful (maybe you have resources open that should be written to first, or a pending DB transaction open, or whatever) you would want to expect a success message from the client that closes the connection the way your send/2 does, and returns {stop, normal, State} to halt the process.
The key here is making sure you identify the cases where you want to end the connection and either have the server process killed or (better) return {stop, Reason, State}.
As written above, if you intend send/2 to be a single response and a clean exit (or really, that every accept cast should result in a single send/2 and then termination), then you want:
handle_cast(accept, S = #state{socket=ListenSocket}) ->
{ok, AcceptSocket} = gen_tcp:accept(ListenSocket),
kvstore_tcpsocket_sup:start_socket(),
receive
%% stuff that results in a call to send/2 in any case.
end,
{stop, normal, S}.
The case LYSE demonstrates is one where the connection is persistent and there is ongoing back-and-forth between a client and server. In the case above you are handling a single request, spawning a new listener to re-fill the listener pool, and should be exiting because you have no plan of this gen_server doing any further work.
I'm currently trying to create a simple chat server with socket.io-erlang. I just started learning Erlang so I have a few problems adapting their demo so it works with modules. Hope you can help me, here's what I have so far. It shouldn't have any features yet, this time I only want to make it work (I get a few crash reports after starting it, you can read them if you want).
App
-module(echat_app).
-behaviour(application).
-export([start/2, stop/1]).
start(_StartType, _StartArgs) ->
echat_sup:start_link().
stop(_State) ->
ok.
Supervisor
-module(echat_sup).
-behaviour(supervisor).
-export([start_link/0]).
-export([init/1]).
-define(CHILD(I, Type), {I, {I, start_link, []}, permanent, 5000, Type, [I]}).
start_link() ->
supervisor:start_link({local, ?MODULE}, ?MODULE, []).
init([]) ->
application:start(sasl),
application:start(gproc),
application:start(misultin),
application:start(socketio),
{ok, Pid} = socketio_listener:start([{http_port, 7878}, {default_http_handler,echat_http_handler}]),
EventManager = socketio_listener:event_manager(Pid),
ok = gen_event:add_handler(EventManager, echat_socketio_handler,[]),
receive _ ->
io:format("sub received something"),
{ok, {
{one_for_one, 5, 10},
[]
}}
end.
Socket.IO Event Handler
-module(echat_socketio_handler).
-behaviour(gen_event).
-include_lib("socketio/include/socketio.hrl").
-export([init/1, handle_event/2, handle_call/2, handle_info/2, terminate/2, code_change/3]).
init([]) ->
{ok, undefined}.
handle_event({client, Pid}, State) ->
io:format("Connected: ~p~n",[Pid]),
EventManager = socketio_client:event_manager(Pid),
ok = gen_event:add_handler(EventManager, ?MODULE,[]),
{ok, State};
handle_event({disconnect, Pid}, State) ->
io:format("Disconnected: ~p~n",[Pid]),
{ok, State};
handle_event({message, Client, Msg=#msg{content=Content}}, State) ->
io:format("Got a message: ~p from ~p~n",[Msg, Client]),
socketio_client:send(Client, #msg{ content = "hello!" }),
socketio_client:send(Client, #msg{ content = [{<<"echo">>, Content}], json = true}),
{ok, State};
handle_event(_E, State) ->
{ok, State}.
handle_call(_, State) ->
{reply, ok, State}.
handle_info(_, State) ->
{ok, State}.
terminate(_Reason, _State) ->
ok.
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
HTTP Request Handler
-module(echat_http_handler).
-export([handle_request/3]).
handle_request(_Method, _Path, Req) ->
Req:respond(200).
just a few things, no a specific answer to your question.
First, in general you start the dependencies before starting your app, rather than on the init of some supervisor.
Regarding the errors:
{error,{{noproc,{gen_server,call,
[socketio_listener_sup_sup,
{start_child,[[{http_port,7878},
{default_http_handler,echat_http_handler}]]},
infinity]}},
{echat_app,start,[normal,[]]}}}
This is the first one, it means that the code tried to call a gen_server named 'socketio_listener_sup_sup' , but that process did not exist. Given the name of it, I guess that is something that should be started by the socket.io-erlang application itself, so maybe it is not starting correctly for some reason. You can check that easily by checking the results:
ok = application:start(sasl),
ok = application:start(gproc),
ok = application:start(misultin),
ok = application:start(socketio),