gen_server, gen_tcp question

Kent Boortz kent@REDACTED
Wed Jul 17 09:46:42 CEST 2002


Eric Merritt <cyberlync@REDACTED> writes:
>  As some of you may know I am fairly new to erlang. I
> have been poking around a bit getting my feet wet,
> messing with yaws, mnesia, etc. Well I am finally
> getting around to coding a fairly heft application as
> a learning exercise and I have hit a stumbling block.
> I am just begining to write the tcp comm part of the
> application, I would love to follow the otp practices
> and make use of gen_server. There even seems to be
> some arcane way for gen_tcp and gen_server to work
> together for a module. I can't, for the life of me,
> figure it out. The documentation is pretty sparse in
> this area as well. 

If you talk about creating an application that listen to a port and
accept an connection then I think they don't fit together as well as
they should do. Some OTP applications bypass the gen_server module and
use the sys module to use gen_tcp and still fit into the supervision
model of Erlang/OTP. You can look at the source of ssl or the P9
rshell application for an example of this. There is an ascii drawing
in "$ERL_TOP/lib/rshell/src/rshell.erl" that show the supervisor
structure.

One problem is that we can't sit and wait for a connection in the
blocking call gen_tcp:accept() because the application may get a code
change system message. One way around this is to give a timeout of one
second to gen_tcp:accept() to leave the call and pick up system
messages if any and redo the accept call.

A second problem that gen_tcp solves in an unusual way (a bit ugly in
my opinion) is that after an accept of a connection in active mode
messages with TCP data may be received to the message queue of the
process calling gen_tcp:accept() before it has started a process to
handle the specific connection. There is a function
gen_tcp:controlling_process() that redirect these messages to a new
process and, from what I have been told, actually move the TCP
messages from the message queue of the first process to the new
controlling process,

kent


init(Parent, Options) ->
    process_flag(trap_exit, true),

    Deb = sys:debug_options(?DBG_OPTS ++ Options),
    Port = get_port(Options),
    AcceptTimeout =
        case application:get_env(rshell, accept_timeout) of
            {ok, AcceptT} ->
                AcceptT;
            undefined ->
                1000                            % XXX: Create a macro!!!
        end,

    Opts =
        [
         {nodelay, true},
         {packet,raw},
         {reuseaddr,true}
        ],

    % We crash if we can't open the socket to listen
    {ok, ListenSocket} = gen_tcp:listen(Port, Opts),

    loop(Parent, Deb, ListenSocket, AcceptTimeout).


loop(Parent, Deb, ListenSocket, AcceptTimeout) ->
    receive
        {system, From, Request} ->
            State = {ListenSocket, AcceptTimeout},
            sys:handle_system_msg(Request, From, Parent, ?MODULE, Deb, State);
        What ->
            NewDeb = sys:handle_debug(Deb, {?MODULE, write_debug}, rshell_listener,
                                      {in, What}),
            loop(Parent, NewDeb, ListenSocket, AcceptTimeout)
            case gen_tcp:accept(ListenSocket, AcceptTimeout) of
                {ok, Sock} ->
                    case supervisor:start_child(rshell_connection_sup,
                                                [Sock]) of
                        {ok,Pid} when pid(Pid) ->
                            gen_tcp:controlling_process(Sock, Pid),
                            loop(Parent, Deb, ListenSocket, AcceptTimeout);
                        Other ->
                            NewDeb = sys:handle_debug(Deb,
                                                      {?MODULE, write_debug},
                                                      rshell_listener,
                                                      {start_child_result,
                                                       Other}),
                            gen_tcp:close(Sock),
                            loop(Parent, NewDeb, ListenSocket, AcceptTimeout)
                    end;
                {error, timeout} ->
                    %% This is what we want to enable system events
                    loop(Parent, Deb, ListenSocket, AcceptTimeout);
                Other ->
                    NewDeb = sys:handle_debug(Deb,
                                              {?MODULE, write_debug},
                                              rshell_listener,
                                              {accept_result,Other}),
                    loop(Parent, NewDeb, ListenSocket, AcceptTimeout)
            end
    end.



More information about the erlang-questions mailing list