Search code examples
socketserlanggen-tcperlang-ports

Does gen_tcp:recv/3 closes the socket if the timeout is reached?


I currently have a server that handles multiple connections from clients, and client that connects to the server using two connections. My client has two processes that handle respectively sending and receiving to and from the server, but not both. The problem I currently have is when I want to close the socket, my reading process is stuck on the gen_tcp:recv/2 block. If I put a timeout, the socket is closed when the timeout has been reached. My question is, is it possible to have gen_tcp:recv/3 call that doesn't closes the socket.
This is how my reading process looks like.

read(Socket, Control) ->
    Control ! ok,
    receive
        read ->
            case gen_tcp:recv(Socket, 0) of
                {ok, Data} ->
                %% handling for messages;
                Other ->
                    io:format(Other)
                end,
                read(self()), %% this sends "read" to itself with "!"
                read(Socket, Control);
                {error, Reason} ->
                    io:format(Reason)
            end;
        close ->
            io:format("Closing Reading Socket.~n"),
            gen_tcp:close(Socket)
    end.

As you can see here, the process will never be able to receive a close if recv/2 doesn't read anything.


Solution

  • Sure, gen_tcp:recv/3 with timeout set to infinity will not close the socket :) See the official documentation.

    Edit:

    From the documentation:

    This function receives a packet from a socket in passive mode.

    Check the documentation for setopts/2 to understand the difference between passive and active modes. In particular:

    If the value is false (passive mode), the process must explicitly receive incoming data by calling gen_tcp:recv/2,3.

    Your process can only do one thing at a time - either listen for the close message from another process or wait for the TCP packet. You could try to use the gen_tcp:controlling_process/2 but I don't know the details. Another solution would be to handle the recv/3 in a separate (third) linked process and kill the process when the close is received.

    The better way would be to use an active socket instead, see the Examples section in the official documentation for some guide how to do that.

    The best way in my opinion would be to use the OTP gen_server to handle both, the close message and incoming TCP packets in the same process. There is an excellent tutorial in the Erlang and OTP in Action book on how to implement that and here is the code example on Github.