How to properly send/close (TCP socket)?

Hello everyone! Late last night for some reason I decided to look at how I was sending last messages and closing sockets. I wanted to make sure it’s correct so back to the documentation I went…

To give you a bit of context, in one of my modules, I start the socket with the following options:

binary, {active, false}, {nodelay, true}, {reuseaddr, true}, {debug, false}, {backlog, 24}

Then in each client I do:

inet:setopts(Socket, [{active, once}])

And dealing with sends like so:

_ = gen_tcp:send(Socket, Data)

In one of the requests, I pretty much need to send one last message to my client and close the connection. For that, I was doing:

_ = gen_tcp:send(Socket, Data),
{stop, normal, State}.

But that doesn’t seem to be correct.

Reading the documentation, I found shutdown and a few additional flags/options (send_timeout, send_timeout_close and linger which I ended up not using it because according to the AI is not a reliable flag, not sure if that’s true or not…)

I ended up with the following:

send_packet(PID, Packet) ->
	gen_server:cast(PID, {send_packet, Packet}).

shutdown_socket(PID) ->
	gen_server:cast(PID, {shutdown_socket}).

init([custom_game, GamePID, Socket]) ->
	ok		= inet:setopts(Socket, [{send_timeout, 5000}]),
	State	= #state{socket = Socket, game_pid = GamePID},
	{ok, State};

handle_info(timeout, State) ->
	Socket = State#state.socket,
	gen_tcp:close(Socket),
	{stop, normal, State};

handle_cast({send_packet, Packet}, State) ->
	Socket = State#state.socket,
	case gen_tcp:send(Socket, Packet) of
		ok ->
			{noreply, State};
		{error, _Reason} ->
			gen_tcp:close(Socket),
			{stop, normal, State}
	end;
handle_cast({shutdown_socket}, State) ->
	Socket = State#state.socket,
	case gen_tcp:shutdown(Socket, write) of
		ok ->
			{noreply, State, 5000};
		{error, _Reason} ->
			gen_tcp:close(Socket),
			{stop, normal, State}
	end;

With this, let’s say I need to send one last message before terminating the gen_server, I can do:

send_packet(self(), Data),
shutdown_socket(self()),
{noreply, State}

My understanding is that:

  • By calling shutdown(write) we signal the client that we want to close the connection. When the client reads, it will eventually read 0 meaning, we need to close the connection.
  • In the ideal scenario, the client will call close on its end (my client uses Python) and this will result in handle_info({tcp_closed, _Socket}, State) being called
  • If the client hangs or simply disconnects, the client wont call close and that’s why I need to have a timer to eventually close if nothing happens after a few seconds

Would this be correct?

Thanks!