在Erlang活动模式下从套接字接收数据包,并带有ASCII数据包大小报头

cqoc49vn  于 2023-03-27  发布在  Erlang
关注(0)|答案(1)|浏览(260)

我有一个Erlang SSL TCP套接字,它与另一方有一个永久的TCP连接。我们使用一个类似于ISO8583协议的协议,其中前四个字节是数据包大小,这是ASCII编码的。
根据Erlang inet文档(https://erlang.org/doc/man/inet.html),它只支持无符号整数作为数据包大小。
标头长度可以是一个、两个或四个字节,并且包含按大端字节顺序的无符号整数。
现在我使用:gen_server.handle_info,一旦我收到一个数据包,我就读取前四个字节并将其与二进制大小进行比较,如果二进制大小很小,我什么也不做,将接收到的二进制放入LastBin并等待数据包的其余部分,如果数据包中有多个msg,我会多次调用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.有没有更好的方法来做到这一点呢?上次我犯了一个错误,读取了一些消息不止一次(我修复了那个bug,但没有在生产环境中测试我的代码,但在测试中似乎没问题)。当包大小是一个无符号整数时,Erlang可以为我处理这个问题,但对于ASCII编码的包大小没有成功。
1.我尝试使用{active, once}gen_tcp:recv,但它不能正常工作。这是一个更安全的方法吗?

  1. gen_server:handle_info是否同步?
e5nqia27

e5nqia271#

**Question 1:**您的packet协议与erlang的packet协议不匹配,所以我认为您需要指定{packet, raw}或等效的{packet, 0},以raw模式从socket读取,请参阅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的邮箱中。在这种情况下,消息将由底层传输机制发送到套接字的任何大小的块组成。因此,你需要在receive块周围使用一个循环来保持从邮箱中提取消息,直到你得到足够的字节来完成一个完整的消息。

**问题2:**当您以active模式{active, true}打开套接字时,erlang会自动从套接字读取N字节数,其中N{packet, N}中指定,然后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进程异步。

相关问题