从带有 ASCII 数据包大小标头的 Erlang 活动模式的套接字接收数据包

Posted

技术标签:

【中文标题】从带有 ASCII 数据包大小标头的 Erlang 活动模式的套接字接收数据包【英文标题】:Recieve packet from socket in Erlang active mode with ASCII packet size header 【发布时间】:2021-04-10 06:57:25 【问题描述】:

我有 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.

我的问题是:

    有没有更好的方法来做到这一点?上次我犯了一个错误并多次阅读了一些消息(我修复了那个错误,但还没有在生产中测试我的代码,但在测试中似乎还可以)。当数据包大小为无符号整数时,Erlang 可以为我处理这个问题,但对 ASCII 编码的数据包大小没有成功。 我尝试使用 active, once 和 gen_tcp:recv 但它对我来说不能正常工作,这是一种更安全的方法吗? gen_server:handle_info 是否同步?

【问题讨论】:

【参考方案1】:

问题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, truepacket, raw,以便发送到套接字的任何数据都将到达genserver 的邮箱。在这种情况下,消息将由底层传输机制恰好发送到套接字的任何大小的块组成。因此,您需要围绕接收块使用循环来不断从邮箱中提取消息,直到您获得足够的字节来接收完整的消息。

问题2:当你在active模式下打开一个socket时,active, true,erlang会自动从socket中读取N的字节数,其中N在@987654345中指定@,然后erlang将这些块组合成一个complete消息,并将该消息放入进程邮箱中,该邮箱只能通过receive子句读取。调用 gen_tcp:recv() 从套接字读取,在这种情况下没有帮助。在此处查看详细信息:Erlang client-server example using gen_tcp is not receiving anything。

指定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() 返回ok 或错误消息,因此它不会等待来自 genserver 进程的回复,因此调用gen_tcp:send() 的进程与 genserver 进程是异步的。

【讨论】:

我使用 packet, 0,同步是指如果程序没有完成对第一个数据包的处理并且数据包 2 和 3 到达 Erlang VM 是否等待 handle_info 返回?是否有可能丢失数据包序列?据我所知,handle_call 是同步的,而 handle_cast 在 gen_server 中是异步的。

以上是关于从带有 ASCII 数据包大小标头的 Erlang 活动模式的套接字接收数据包的主要内容,如果未能解决你的问题,请参考以下文章

UDP数据包字节读取粒度?

带有区分大小写的标头的 Redshift Unload

如何跟踪erlang中的子进程?

自定义 TCP 标头/从 Wireshark 数据包复制 TCP 标头

如何从 pcap 文件中提取所有数据包的 TCP 标头?

从 C# 调用带有 C++ 标头的 dll