I have an Erlang SSL TCP socket which has a permanent TCP connection to the other party. We use a protocol similar to the ISO8583 protocol where the four first bytes are the packet size, which is ASCII encoded.
Based on Erlang inet documentation (https://erlang.org/doc/man/inet.html) it only supports unsigned integer for packet size.
The header length can be one, two, or four bytes, and containing an unsigned integer in big-endian byte order.
Right now I use :gen_server.handle_info
, as soon as I receive a packet I read the first four bytes and compare it to Binary size and if the binary size is small I do nothing and put received binary to LastBin and wait for rest of packet and if more than one msg is in packet I call read_iso
packet several times, short sample of what I do is like this:
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.
My questions are:
- Is there a better way to do this? Last time I made a mistake and read some messages more than once (I fixed that bug, but did not test my code in production yet, but it seems OK in tests). When packet size is an unsigned integer Erlang can handle this for me but no success with ASCII encoded packet size.
- I tried to use
{active, once}
andgen_tcp:recv
but it did not work properly for me. Is this a safer approach? - Is
gen_server:handle_info
synchronous?