Recieve packet from socket in Erlang active mode with ASCII packet size header
zxq9
zxq9@REDACTED
Tue Jan 5 15:51:59 CET 2021
Hi, George.
Reply is below.
On 2021/01/04 22:40, George Hope wrote:
> I have Erlang SSL TCP socket which has a permanent TCP connection to
> other party,
> we use a protocol similar to ISO8583 protocol four first byte is a
> 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 first four byte then compare it to Binary size and if the binary
> size is small I do nothing and put recieved 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 if what I do is like this:
This sounds ISO-8583 -ish. Note that 8583 allows several ways to encode
a message's length, not just as ASCII text in the first 4 bytes -- so
just make sure you know what kind of data your receiving.
Anyway, your solution is headed in the right direction, but it can be
expressed a bit more concisely and also needs to account for cases where
you have two messages joined up (so the tail of one message arrives in
the same message as the beginning of the next message), as well as
handling cases where you receive less than 4 bytes of the beginning of a
message (which usually occurs when the network has joined up the border
of two messages).
Here is one way of approaching that (note the following code is untested
and might have typos or whatever, but hopefully it expresses the idea):
handle_info({ssl, Socket, Bin}, State = #s{socket = Socket}) ->
NewState = rx(Bin, State),
{noreply, NewState};
handle_info({tcp, Socket, Bin}, State = #s{socket = Socket}) ->
NewState = rx(Bin, State),
{noreply, NewState}.
rx(Bin = <<ASCII:4/binary, _/binary>>, State = #s{left = none, acc =
none}) ->
Size = binary_to_integer(ASCII),
Received = byte_size(Bin),
if
Received == Size ->
ok = do_something(Bin),
State;
Received > Size ->
<<Message:Size/binary, Rest/binary>> = Bin,
ok = do_something(Message),
rx(Rest, State);
Received < Size ->
Left = Size - Received,
State#s{left = Left, acc = Bin}
end;
rx(Bin, State = #s{left = none, acc = none}) ->
State#s{left = none, acc = Bin};
rx(Bin, State = #s{left = none, acc = Acc}) ->
NewBin = <<Acc/binary, Bin/binary>>,
rx(NewBin, State#s{acc = none});
rx(Bin, State = #s{left = Left, acc = Acc}) when Left == byte_size(Bin) ->
Message = <<Acc/binary, Bin/binary>>,
ok = do_something(Message),
State#s{left = none, acc = none};
rx(Bin, State = #s{left = Left, acc = Acc}) ->
Received = byte_size(Bin),
case Left - Received of
NewLeft when NewLeft > 0 ->
NewAcc = <<Acc/binary, Bin/binary>>,
State#s{left = NewLeft, acc = NewAcc};
NewLeft when NewLeft < 0 ->
<<Tail:Left/binary, Rest/binary>> = Bin,
Message = <<Acc/binary, Tail/binary>>,
ok = do_something(Message),
rx(Rest, State#s{left = none, acc = none})
end.
The only reason for the mess is handling the border case between
messages and the annoying reality of it being possible to receive less
than the first 4 bytes of a message.
In the above example the leading 4 bytes is included in the message
passed to do_something/1 and the message size value in the header
includes the header itself. Changing details like that around is a
fairly trivial matter.
Hopefully this explains more than it confuses.
-Craig
More information about the erlang-questions
mailing list