在 Erlang 主动模式下从套接字接收数据包,ASCII 数据包大小 header

Recieve packet from socket in Erlang active mode with ASCII packet size header

我有 Erlang SSL TCP 套接字,它与另一方有永久的 TCP 连接, 我们使用类似于 ISO8583 协议的协议,四个第一个字节是一个数据包大小,它是 ASCII 编码的。 基于 Erlang inet 文档 (https://erlang.org/doc/man/inet.html) 它只支持包大小的无符号整数。

The header length can be one, two, or four bytes, and containing an unsigned integer in big-endian byte order.

现在我使用 gen_server handle_info,一旦我收到一个数据包,我会读取前四个字节,然后将其与二进制大小进行比较,如果二进制大小很小,我什么都不做,然后放将二进制文件接收到 LastBin 并等待其余的数据包,如果数据包中有多个消息,我会多次调用 read_iso 数据包,如果我的操作是这样的,则简短示例:

handle_info({ConnType, _Socket, Bin}, {BSSl, BProtocol, Psize, OSocket, LastBin})
    when ConnType =:= ssl; ConnType =:= tcp ->
    logger:debug("mp_back:Got response from backend~w", [Bin]),
          read_iso_packet(Bin, {BSSl, BProtocol, Psize, OSocket, LastBin})
    end.
read_iso_packet(Bin, {BSSl, BProtocol, Psize, Socket, LastBin})
    when size(<<LastBin/binary, Bin/binary>>) < 5 ->
    {noreply, {BSSl, BProtocol, Psize, Socket, <<LastBin/binary, Bin/binary>>}};
read_iso_packet(Bin, {BSSl, BProtocol, Psize, Socket, LastBin})
    when size(<<LastBin/binary, Bin/binary>>) > 4 ->
    Packe_Size = get_packet_size(binary:part(<<LastBin/binary, Bin/binary>>, 0, 4)),
    logger:debug("mp_back:packet_size==~w", [Packe_Size]),
    logger:debug("mp_back:bin_size==~w", [size(Bin)]),
    read_iso_packet(Packe_Size + 4 - size(<<Bin/binary, LastBin/binary>>),
                    Packe_Size,
                    <<LastBin/binary, Bin/binary>>,
                    {BSSl, BProtocol, Psize, Socket, LastBin}).

read_iso_packet(0, _Packe_Size, Bin, {BSSl, BProtocol, Psize, Socket, _LastBin}) ->
    do_somthing(server_response, CSocket, Bin),
    {noreply, {BSSl, BProtocol, Psize, Socket, <<>>}};
read_iso_packet(SS, Packe_Size, Bin, {BSSl, BProtocol, Psize, Socket, _LastBin})
    when SS < 0 ->
    do_somthing(server_response, CSocket, [binary:part(Bin, 0, Packe_Size + 4)]),        
    read_iso_packet(binary:part(Bin, Packe_Size + 4, byte_size(Bin) - (Packe_Size + 4)),
                    {BSSl, BProtocol, Psize, Socket, <<>>});
read_iso_packet(SS, _Packe_Size, Bin, {BSSl, BProtocol, Psize, Socket, _LastBin})
    when SS > 0 ->
    logger:debug("mp_back: Small data going to read next~w", [Bin]),
    {noreply, {BSSl, BProtocol, Psize, Socket, Bin}}.

get_packet_size(Bin) ->
    {ok, [A], _} = io_lib:fread("~16u", binary_to_list(binary:part(Bin, 0, 1))),
    {ok, [B], _} = io_lib:fread("~16u", binary_to_list(binary:part(Bin, 1, 1))),
    {ok, [C], _} = io_lib:fread("~16u", binary_to_list(binary:part(Bin, 2, 1))),
    {ok, [D], _} = io_lib:fread("~16u", binary_to_list(binary:part(Bin, 3, 1))),
    A * 1000 + B * 100 + C * 10 + D.

我的问题是:

  1. 有更好的方法吗?上次我犯了一个错误并且不止一次地阅读了一些消息(我修复了那个错误,但还没有在生产中测试我的代码,但在测试中似乎没问题)。当数据包大小是无符号整数时,Erlang 可以为我处理这个问题,但 ASCII 编码的数据包大小没有成功。
  2. 我尝试使用 {active, once} 和 gen_tcp:recv 但它对我来说效果不佳,这是更安全的方法吗?
  3. gen_server:handle_info是同步的吗?

问题1:你的数据包协议不符合erlang的数据包协议,所以我认为你需要通过指定raw模式从套接字读取{packet, raw} 或等效的 {packet, 0},参见 https://erlang.org/doc/man/inet.html#packet

我不确定您是如何使用 handle_info() 从套接字读取数据的。您是否设置 {active, true} 以便发送到套接字的数据到达 genserver 的邮箱?如果是这样,我认为这不会起作用,因为 {active, true} 告诉 erlang 自动从套接字读取 N 字节,其中 N{packet, N} 在您打开时指定插座。在你的例子中,N 将是 4。Erlang 然后使用这 4 个字节中包含的整数,我们称之为 MsLen,从套接字读取 MsLen 字节。 Erlang 然后将从套接字中读取的所有块组合成一个完整的消息,并将完整的消息放入 genserver 的邮箱中。但是,您的 MsLen 将是错误的,因为它不是无符号整数,而是 ascii 编码的整数。因此,我认为你需要以被动模式打开套接字,{active, false},使用gen_tcp:recv()读取前四个字节,解码得到整数长度,然后再次调用gen_tcp:recv()读取那个来自套接字的许多字节。

或者,您可以指定 {active, true}{packet, raw},以便发送到套接字的任何数据都将到达 genserver 的邮箱。在这种情况下,一条消息将由底层传输机制恰好发送到套接字的任何大小的块组成。因此,您需要围绕接收块使用循环来不断从邮箱中提取消息,直到您获得足够的字节来完成一条消息。

问题2:当你以active模式打开套接字时,{active, true},erlang自动从socket,其中N{packet, N}中指定,然后erlang将chunk组合成complete消息,并将消息放入进程邮箱中,只能读取通过接收子句。调用 gen_tcp:recv() 从套接字读取,在这种情况下没有帮助。在此处查看详细信息:.

指定 {active, once} 告诉 erlang 为一条消息打开带有 {active, true} 的套接字,然后套接字切换到 {active, false} 或被动模式。在被动模式下,进程需要通过调用 gen_tcp:recv() 直接从套接字读取。当您想要防止恶意行为者用数百万条消息淹没套接字时,您可以指定 {active, once},这反过来会填满进程邮箱并导致进程崩溃。恶意行为者是否能够用数百万条消息淹没套接字?

问题:3与什么同步?当您使用 ! 向 genserver 进程发送消息时,消息发送是异步的,因为发送消息的进程不等待来自 genserver 进程的任何类型的响应,因此执行在发送过程中有增无减.对于 genserver,我们不问 handle_call() 是否异步,而是询问调用 gen_server:call() 的进程是否与 genserver 进程异步。调用gen_server:call()的进程停止执行,等待genserver进程的响应,所以我们说调用gen_server:call()的进程与genserver进程是同步的。

调用gen_tcp:send()的进程是否与genserver进程异步? gen_tcp:send() returns ok 还是一个错误信息,所以它不等待genserver进程的回复,所以调用gen_tcp:send()的进程与genserver进程是异步的。