【问题标题】:Erlang gen_tcp:recv(Socket, Length) semanticsErlang gen_tcp:recv(Socket, Length) 语义
【发布时间】:2011-04-16 06:23:12
【问题描述】:

阅读this answer 后,我想了解是否同样适用于对gen_tcp:recv(Socket, Length) 的调用。我对文档的理解是,如果缓冲区中有超过Length 字节可用,它们将保留在那里;如果少于Length 字节,调用会阻塞,直到有足够的可用或连接关闭。

特别是,当数据包以 2 个字节为前缀以小端顺序保持数据包长度时,这应该可以工作:

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

这是正确的吗?

【问题讨论】:

    标签: sockets tcp erlang


    【解决方案1】:

    是(或否,详见 cmets)。

    考虑:

    外壳 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
    ...
    

    外壳 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>
    

    外壳 1 续:

    ...
    {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>
    

    但是,如果您只想确认行为,这很有用。实际上,您可以更改 {packet, N} 选项来定义数据包长度应该有多少字节(在大端系统上)。

    与之前相同,但没有显式提取长度(注意 shell 1 中的数据包长度 = 2):

    外壳 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
    ...
    

    在这种情况下,Erlang 将删除前 2 个字节,recv/2 将阻塞直到它需要的字节数。在这种情况下,recv/2 中的读取长度必须为 0。

    外壳 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>
    

    外壳 1:

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

    在这种情况下,我没有在 shell 2 中指定 {packet, N} 选项只是为了展示这个想法,但通常它不是 0。如果设置了 packet 选项,那么 gen_tcp 将自动附加/删除那么多包中的字节数。

    如果您指定数据包 0,那么您必须执行长度 >= 0 的recv/2,其行为与 C 中的行为相同。您可以通过在执行接收和在这种情况下,这将返回 {error, timeout}。

    更多信息可以在这里阅读: http://www.erlang.org/doc/man/gen_tcp.html http://www.erlang.org/doc/man/inet.html#setopts-2

    希望这能解决问题。

    【讨论】:

    • 您引用的文档说 Length 参数仅在套接字处于原始模式时才有意义。根据 OP 引用的线程和我曾经使用过的所有其他 TCP API,我得出结论,您的答案不正确,正确答案是“否”。
    • 我重新阅读了这个问题,我可能误解了这个问题。这个问题看起来更像是“我必须做 recv/2 直到我到达流的末尾”,如果你使用除 0 以外的数据包选项,Erlang 中的答案是否定的。这是对问题的正确解释吗?我真的没有看到任何其他理由让我错了,请澄清。
    • “实际上,您可以更改 {packet, N} 选项来定义应为数据包长度的字节数。”在这种情况下不是,因为在我的协议中数据包长度是小端,但{packet, N} 需要长度是大端。
    • 然而,当给定{packet, 2} 选项时,调用gen_tcp:recv(C, 0) 会产生误导。
    • 读取 TCP 流时,read 方法可以返回从 1 到缓冲区大小的任意数字。它可以在非阻塞模式下返回零。否则它会阻塞直到至少有一个字节可用。然后它将现在可用的任何字节复制到用户的缓冲区中,直到其长度。它不会进一步尝试填充缓冲区。你引用的那篇文章不适用。你要一直读下去,直到你拿到EOS,你要注意实际的返回值。
    猜你喜欢
    • 2012-08-14
    • 2023-04-07
    • 2012-08-20
    • 2011-09-25
    • 2011-08-08
    • 2015-08-20
    • 2016-08-01
    • 2012-02-05
    • 2015-02-22
    相关资源
    最近更新 更多