从带有 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, true
和packet, 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 活动模式的套接字接收数据包的主要内容,如果未能解决你的问题,请参考以下文章