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

acruukt9  于 2022-12-08  发布在  Erlang
关注(0)|答案(1)|浏览(150)

我有一个Erlang SSL TCP套接字,它与其他方有一个永久的TCP连接,我们使用一个类似于ISO 8583协议的协议,第四个字节是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,并等待数据包的其余部分,如果数据包中有多个msg,我会多次调用read_iso packet,如果我所做的是如下所示的简短示例:

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是否同步?
inn6fuwd

inn6fuwd1#

Question 1: Your packet protocol doesn't fit with erlang's packet protocol, so I think you need to read from the socket in raw mode by specifying {packet, raw} or equivalently {packet, 0} , see https://erlang.org/doc/man/inet.html#packet.

I'm not sure how you are using handle_info() to read from the socket. Are you setting {active, true} so that data sent to the socket lands in the genserver's mailbox? If so, I don't think that will work because {active, true} tells erlang to automatically read N bytes from the socket, where N is specfied by {packet, N} when you open the socket. In your case, N will be 4. Erlang then uses the integer contained in those 4 bytes, let's call it MsLen , to read MsLen bytes from the socket. Erlang then combines all the chunks it reads from the socket into one complete message and places the complete message in the genserver's mailbox. However, your MsLen will be wrong because it's not an unsigned integer, rather it's an ascii encoded integer. Therefore, I think you need to open the socket in passive mode, {active, false} , read the first four bytes using gen_tcp:recv() , decode to get the integer length, then call gen_tcp:recv() again to read that many bytes from the socket.
Or, you could specify {active, true} and {packet, raw} so that any data sent to the socket will land in the genserver's mailbox. In that case, a message will consist of whatever size chunk happened to be sent to the socket by the underlying transport mechanism. So, you would need to use a loop around a receive block to keep extracting messages from the mail box until you got enough bytes for a complete message.

Question 2: When you open a socket in active mode, {active, true} , erlang automatically reads N number of bytes from the socket, where N is specified in {packet, N} , then erlang combines the chunks into a complete message and places the message in the process mailbox, which can only be read by a receive clause. Calling gen_tcp:recv() reads from the socket, which is of no help in that case. See details here: Erlang client-server example using gen_tcp is not receiving anything .

Specifying {active, once} tells erlang to open the socket with {active, true} for one message, then the socket switches to {active, false} or passive mode. In passive mode, a process needs to read directly from the socket by calling gen_tcp:recv() . You can specify {active, once} when you want to prevent a hostile actor from flooding the socket with millions of messages, which in turn would fill up the process mailbox and cause the process to crash. Will a hostile actor be able to flood the socket with millions of messages?

Question: 3 Synchronous with what? When you use ! to send a message to a genserver process, the message send is asynchronous because the process that sent the message does not wait for any type of response from the genserver process, so execution continues unabated in the sending process. With a genserver, we don't ask whether handle_call() is asynchronous, instead we ask whether the process that calls gen_server:call() is asynchronous with the genserver process. The process that calls gen_server:call() stops execution and waits for a response from the genserver process, so we say that the process that called gen_server:call() is synchronous with the genserver process.

Is a process that calls gen_tcp:send() asynchronous with the genserver process? gen_tcp:send() returns ok or an error message, so it does not wait for a reply from the genserver process, so a process that calls gen_tcp:send() is asynchronous with the genserver process.

相关问题