[erlang-questions] How to build a dynamic list of TCP clients ?

Jesper Louis Andersen jesper.louis.andersen@REDACTED
Fri Nov 19 19:35:15 CET 2010


On Fri, Nov 19, 2010 at 6:56 PM, info <info@REDACTED> wrote:

> In a TCP/IP gen_server I do:
> Pid = spawn_link(... process_accept ...)

I will assume that your gen server is a *listener* in the sense that
is has called gen_tcp:listen/2 on a socket. You then spawn_link other
processes to call gen_tcp:accept/1 on this ListenSocket and accept
connections. If I am wrong, please correct me.

> My first idea was to create a record and to insert the Pid just after the spawn_link:
> Pid = spawn_link(... process_accept ...),
> C = #clients(pid=Pid,name=0,s=Socket),   #name will be initialized by the process_accept
> [C | ListClients];
>
> Now I have my list of clients but a client can disappear (tcp_closed) and the list must be updated.
> Is it a good approach or do you have better !

Yes, you will need to do some bookkeeping when clients disappear. Your
current setup is that the cliens are spawn-linked to the listener. By
default, if you do not set the trap_exit process flag, it means that
if one of the clients errs, then your whole tree dies. I generally do
not recommend this approach since it isn't very robust. I don't
recommend you trap_exits as well, but rather add a client_pool
supervisor process running as simple_one_for_one to your supervisor
tree. This means that the death of a client won't affect your, but the
pool supervisor -- and supervisors are built to handle crashes of
processes.

The next part is that you call erlang:monitor/2 on the Pid you get
back from supervisor:start_child/2. In effect, if you lose a client
you will get a 'DOWN' message you can grab in ?MODULE:handle_info/2 of
your listening server and thus remove the client from your client
list.

In the long run, you want to eliminate the serialization on having to
go through the listen server on each client lookup. Let the listen
server maintain ListClients as a protected ETS table so other
processes can look up pid information without having to explicitly
call into the listener gen_server. A slightly more general variant use
Ulf Wigers Gproc process table for storing the processes.

> My objective is to give the possibility to send info to one client from another module by sending to the gen_server (handle_cast) the following command: send_to_client(Name,Message)
> Next gen_server shall find the corresponding process and send the message by gen_tcp:send(S,Message)

My mantra is to do this without involving the listener at all. Take
the ETS variant from above (if you really go down this path, GProc is
way easier in the long run). Then in the listener module (freely
written, not tested):

-module(listener).
-behaviour(gen_server).

[..]

send_to_client(Name, Msg) ->
  P = ets:lookup_element(?MODULE, Name, #client.pid), % Crash if Name
isn't there
  client:send_msg(P, Msg).

and then in module client:

-module(client).
-behaviour(gen_server).
[..]

send_msg(P, Msg) -> gen_server:cast(Pid, {msg, Msg}).

handle_cast({msg, M}, #state { socket = S } = State) ->
  gen_tcp:send(S, encode(M)),
  {noreply, State};
[..]
handle_cast(Unknown, State) ->
  error_logger:info_report({client_unknown_msg, Unknown}),
  {noreply, State}.

Ie, the client governs the socket completely and the server is never
in on the Socket. Notice that you can give away a socket to another
process through gen_tcp:controlling_process/2. This setup tend to be
vastly simpler than having a single server "demuxing" amongst several
connections. And it is faster too, since there is no lookup and demux
necessary anymore: Data goes straight to where it belongs.

If you have further questions, feel free to ask.



-- 
J.


More information about the erlang-questions mailing list