我有一个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
,但它不能正常工作。这是一个更安全的方法吗?
gen_server:handle_info
是否同步?
1条答案
按热度按时间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进程异步。