Erlang gen_tcp:recv(Socket, Length) semantics

2019-04-15 10:56发布

问题:

After reading this answer, I want to understand if the same applies to the calls to gen_tcp:recv(Socket, Length). My understanding of the documentation is that this if more than Length bytes are available in the buffer, they remain there; if there is less than Length bytes, the call blocks until enough is available or connection closes.

In particular, this should work when packets are prefixed by 2 bytes holding packet length in little-endian order:

receive_packet(Socket) ->
  {ok, <<Length:16/integer-little>>} = gen_tcp:recv(Socket, 2),
  gen_tcp:recv(Socket, Length).

Is this correct?

回答1:

Yes (or No, see comments for details).

Consider:

Shell 1:

1> {ok, L} = gen_tcp:listen(8080, [binary, {packet, 0}, {active, false}]).
{ok,#Port<0.506>}
2> {ok, C} = gen_tcp:accept(L). %% Blocks
...

Shell 2:

1> {ok, S} = gen_tcp:connect("localhost", 8080, [binary, {packet, 0}]).
{ok,#Port<0.516>}
2> gen_tcp:send(S, <<0,2,72,105>>).
ok
3>

Shell 1 cont:

...
{ok,#Port<0.512>}
3> {ok, <<Len:16/integer>>} = gen_tcp:recv(C, 2).
{ok,<<0,2>>}
4> Len.
2
5> {ok, Data} = gen_tcp:recv(C, Len).
{ok,<<"Hi">>}
6>

However this is useful if you only want to confirm the behaviour. In reality you would change the {packet, N} option to define how many bytes that should be the packet length (on big-endian systems).

Same as before but without extracting length explicitly (note packet length = 2 in shell 1):

Shell 1:

1> {ok, L} = gen_tcp:listen(8080, [binary, {packet, 2}, {active, false}]).
{ok,#Port<0.506>}
2> {ok, C} = gen_tcp:accept(L). %% Blocks
...

In this case Erlang will strip the first 2 bytes and recv/2 will block until as many bytes it needs. In this case read-length must be 0 in recv/2.

Shell 2:

1> {ok, S} = gen_tcp:connect("localhost", 8080, [binary, {packet, 0}]).
{ok,#Port<0.516>}
2> gen_tcp:send(S, <<0,2,72,105>>).
ok
3>

Shell 1:

...
{ok,#Port<0.512>}
3> {ok, Data} = gen_tcp:recv(C, 0).
{ok,<<"Hi">>}

In this case I don't specify the {packet, N} option in shell 2 just to show the idea but normally it is not 0. If the packet option is set then gen_tcp will automatically append/strip that many bytes from the package.

If you specify packet 0 then you must do a recv/2 with a length >= 0 and the behaviour is the same as in C. You can simulate non-blocking receives by giving a short time out when doing the receive and this will return {error, timeout} in that case.

More on that can be read here: http://www.erlang.org/doc/man/gen_tcp.html http://www.erlang.org/doc/man/inet.html#setopts-2

Hope this clears things up.