[erlang-questions] gen_tcp:recv and hairloss, easy to run example here

Kevin <>
Sat Nov 8 20:45:15 CET 2008


I distilled my bug down to two easy to run and understand files in 
appreciation for anybody who will take a look.

Two files, cut at lines.

compile with:
erlc *.erl

run with:
erl -s ...._gen_server start_link

telnet to:
localhost 8000

type anything and hit return to echo,
type "sssh" to change to inner recv, error message should echo back

The structure of having an inner recv might seem strange, but it makes 
sense because the
outer loop processes commands in text mode, and then switches down into 
a tight loop
that handles an unlimited stream of binary until the end.  The FSM 
controls this, and the main
loop is above the FSM, so the FSM only knows when to take over the 
streaming.
The example code does a bad job justifying the use of a FSM and the 
inner loop though :)

Can't wait until somebody points out how clueless I am  :-)


%%-- cut 
here------------------------------------------------------------------
-module(...._gen_server).
-behaviour(gen_server).
-export([start_link/0]).
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
     terminate/2, code_change/3]).
-record(state, {lsocket, port}).
-export([acceptor/1]).

-define(TCP_OPTS, [binary,
                   {packet, raw},
           {active, false}, % get data from gen_tcp:recv/2
                   {reuseaddr, true}]).

start_link() ->
    gen_server:start_link({local, ?MODULE}, ?MODULE, #state{}, []).

init(State) ->
    case gen_tcp:listen(8000, ?TCP_OPTS) of
        {ok, LSocket} ->
            {ok, spawn_acceptor(State#state{lsocket=LSocket})};
        {error, Reason} ->
            erlang:display(Reason),
            {stop, Reason}
    end.

handle_cast({accepted}, State) ->
    {noreply, spawn_acceptor(State)}.

spawn_acceptor(State = #state{lsocket=LSocket}) ->
    proc_lib:spawn_link(?MODULE, acceptor, [{self(), LSocket}]),
    State.

acceptor({Server, LSock}) ->
    {ok, Sock} = gen_tcp:accept(LSock),
    gen_server:cast(Server, {accepted}),
    {ok, Fsm} = ...._fsm:start_link(),
    gen_tcp:send(Sock, "hello, I will echo, until you type sssh\r\n"),
    loop(Sock, Fsm).

loop(Sock, Fsm) ->
    case gen_tcp:recv(Sock, 0) of
    {ok, Request} ->
            ok = gen_fsm:send_event(Fsm, {Request, Sock}),
            loop(Sock, Fsm);
    {error, Reason} ->
            io:format("gen_server recv error: ~p~n", [Reason])
    end.

handle_call(_Request, _From, State) ->
    {reply, ok, State}.

handle_info(_Info, State) ->
    {noreply, State}.

code_change(_OldVsn, State, _Extra) ->
    {ok, State}.

terminate(_Reason, _State) ->
    ok.

%%------------------------------------------------------------------------------


%%-- cut 
here------------------------------------------------------------------
-module(...._fsm).

-behaviour(gen_fsm).

-compile(export_all).

%% gen_fsm stuf to stop the complaining
-export([init/1, handle_info/3, code_change/4,
         handle_sync_event/4, terminate/3, handle_event/3]).

-record(state, {}).


start_link() ->
    gen_fsm:start_link(?MODULE, [], []).

init([]) ->
    {ok, start_state, #state{}}.

start_state({Request, Sock}, State) ->
    case Request of
        <<"sssh\r\n">> -> %% silent mode
            gen_tcp:send(Sock, "silent until you type nosssh\r\n"),
            sssh_loop(Sock),
            {next_state, start_state, State};
        _ ->
            gen_tcp:send(Sock, Request),
            {next_state, start_state, State}
    end.

sssh_loop(Sock) ->
    case gen_tcp:recv(Sock, 0) of
        {ok, Request} ->
            case Request of
                <<"nosssh\r\n">> -> %% silent mode
                    nil;
                _ ->
                    sssh_loop(Sock)
            end;
        {error, Reason} ->
            gen_tcp:send(Sock,
                         "*** Sorry no silent mode, I keep getting error 
\""
                         ++ atom_to_list(Reason) ++ "\"\r\n")
    end.

handle_event(stop, _StateName, State) ->
    {stop, normal, State}.

terminate(_Reason, _StateName, _State) ->
    ok.

handle_sync_event(_Event, _From, StateName, State) ->
    {reply, ok, StateName, State}.

handle_info(_Info, StateName, State) ->
    {next_state, StateName, State}.

code_change(_OldVsn, StateName, State, _Extra) ->
    {ok, StateName, State}.

%%------------------------------------------------------------------------------




More information about the erlang-questions mailing list