Search code examples
tcperlangclient-serverpacket

Erlang: {packet,N} option works for N = 0 but not 1,2,4?


The following code does work for the {packet,0} option in the gen_tcp:connect() function call but not for 1 , 2 and 4 (although I only tested it for 4 and I'm assuming 1 and 2 doesn't work either). The question I have is why not and is it important to use one over the other? Basically the Erlang documentation does not address a detailed explanation of the subject regarding the packet options and Programming Erlang by Joe Armstrong doesn't give any great detail either; he just explains that the packets aren't reassembled in order although I always thought tcp packets are received as they are sent, unlike UDP. One interesting note I have is that the client-server on this page has {packet,4} as the option and it works fine and its very similar to this code below. Heres the server shell output for {packet,4} option used in the code below.

Erlang R16A (erts-5.10) [smp:8:8] [async-threads:10]

Eshell V5.10  (abort with ^G)
1> cd("c:/erlang").
c:/erlang
ok
2> c(cp3).
{ok,cp3}
3> cp3:server().
Started Server:
<0.41.0>
Accept Server:
Pid <0.43.0>
Connection accepted 
Accept Server:
Loop Server:
Error on socket #Port<0.2256> reason: einval

This is the client side shell output.

Erlang R16A (erts-5.10) [smp:8:8] [async-threads:10]

Eshell V5.10  (abort with ^G)
1> cd("c:/erlang").
c:/erlang
ok
2> cp3:client().
exit
3> 

and this is the code used,

-module(cp3).
-export([client/0, server/0,start/0,accept/1,enter_loop/1,loop/1]).

client() ->
    {ok, Socket} =  gen_tcp:connect("localhost", 4001,[binary, {packet, 4}]),
    ok = gen_tcp:send(Socket, "packet"),
    receive
        {tcp,Socket,String} ->
            io:format("Client received = ~p~n",[String]),       
            io:format("Client result = ~p~n",[String]),
            gen_tcp:close(Socket)
        after 1000 ->
            exit        
    end.

server() -> 
    Pid = spawn(fun()-> start() end),
    Pid.

start() ->  
    io:format("Started Server:~n"),
    {ok, Socket} = gen_tcp:listen(4001, [binary, {packet, 4},{reuseaddr, true},{active, false}]),
    accept(Socket).

accept(ListenSocket) ->
    io:format("Accept Server:~n"),
    case gen_tcp:accept(ListenSocket) of
        {ok, Socket} ->
            Pid = spawn(fun() ->
                io:format("Connection accepted ~n", []),
                enter_loop(Socket)
            end),
            io:format("Pid ~p~n",[Pid]),
            gen_tcp:controlling_process(Socket, Pid),
            Pid ! ack,
            accept(ListenSocket);
        Error ->
            exit(Error)
    end.

enter_loop(Socket) ->
    %% make sure to acknowledge owner rights transmission finished
    receive ack -> ok end,
    loop(Socket).

loop(Socket) ->   
    io:format("Loop Server:~n"),
    case gen_tcp:recv(Socket, 6) of
    {ok, Data} ->  
        case Data of
            <<"packet">> ->                 
                io:format("Server replying = ~p~n",[Data]),   
                gen_tcp:send(Socket, Data),
                loop(Socket)                  
        end;
    {error, Reason} ->      
        io:format("Error on socket ~p reason: ~p~n", [Socket, Reason])          
    end.

Solution

  • Your code is nearly ok, just modify in the loop(Socket) the line

    case gen_tcp:recv(Socket, 6) of

    by

    case gen_tcp:recv(Socket, 0) of

    see documentation:

    recv(Socket, Length) -> {ok, Packet} | {error, Reason} recv(Socket,Length, Timeout) -> {ok, Packet} | {error, Reason}

    Types: Socket = socket() Length = integer() >= 0 Timeout = timeout() Packet = string() | binary() | HttpPacket Reason = closed | inet:posix() HttpPacket = term() See the description of HttpPacket in erlang:decode_packet/3.

    This function receives a packet from a socket in passive mode. A closed socket is indicated by a return value {error, closed}.

    The Length argument is only meaningful when the socket is in raw mode and denotes the number of bytes to read. If Length = 0, all available bytes are returned. If Length > 0, exactly Length bytes are returned, or an error; possibly discarding less than Length bytes of data when the socket gets closed from the other side.

    The optional Timeout parameter specifies a timeout in milliseconds. The default value is infinity.

    With this modification it works, but as the client is closing the socket after each call, you get an error message, I am not sure it is useful to close the socket from the client. You can change this behavior by modifying the client this way:

    client() ->
        {ok, Socket} =  gen_tcp:connect("localhost", 4001,[binary, {packet, 4}]),
        ok = gen_tcp:send(Socket, "packet"),
        receive
            {tcp,Socket,String} ->
                io:format("Client received = ~p~n",[String]),       
                io:format("Client result = ~p~n",[String])
                %gen_tcp:close(Socket)
            after 1000 ->
                exit        
        end.