在Elixir中,我如何模式匹配通过TCP在分解的数据包中到达的二进制流

时间:2016-05-20 14:20:18

标签: elixir

背景:

我有一个串口到以太网适配器,它接收状态更新(报警面板)并通过家庭网络上的IP / PORT提供这些更新。每个数据包都是整个消息的一部分,协议是二进制的。

问题:

如何将单个数据包和二进制数据组合到一个可以模式匹配的缓冲区中。我感兴趣的数据与其他我没有的数据混在一起。没有真正的分隔符,但我感兴趣的消息将始终以0x01 0x06开头,然后消息是固定长度(其他消息是可变长度)

我知道如何连接到IP /端口并接收数据,一次一个字节,但我仍然坚持如何收集它们并对数据进行滑动窗口匹配,直到找到数据包我感兴趣的信息。

我希望从数据流中提取的示例数据包:

数据包将包含一个校验和,计算为所有先前字节的总和。

  • 01H - 表示此数据包是实时状态消息
  • 06H - 包中包含校验和的总字节数的字节数
  • arg1 -Status描述符(通常是Area#)
  • arg2 - 主要状态描述符。
  • arg3 - 状态描述符(通常是区域#)
  • cksum - 所有先前字节的总和(模256)

1 个答案:

答案 0 :(得分:1)

示例数据包的模式匹配可能是这样的:

<<real_time_status_message :: bytes-size(1),
  total_bytes :: bytes-size(1),
  status_descriptor_area :: bytes-size(1),
  main_status_descriptor :: bytes-size(1),
  status_descriptor_zone :: bytes-size(1),
  checksum :: bytes-size(1)>> = binary

但是,在应用模式匹配之前,您需要找到消息的开头。您不必实际接收字节后的输入。你可以在一个循环中获得可变长度的块。

#parser reads tcp data and maintains state of unparsed input (leftovers)
def parse(), do: parse("")
def parse(leftovers) do
  chunk = #receive binary from tcp
  binary_to_scan = leftovers <> chunk
  {:messages, msgs, rest} = scan_all(binary_to_scan)
  do_something_with_msgs(msgs)
  parse(rest)
end

# there might be more than one msg in binary,
# so we need recursive function with accumulator to collect them
def scan_all(binary), do: scan_all(binary, [])
def scan_all(binary, acc) do
  case scan(binary) do
    {:found, _previous, msg, rest} ->
      scan_all(rest, [msg | acc])
    {:not_found, binary} ->
      {:messages, Enum.reverse(acc), binary}
  end
end

# actual sliding window uses pattern matching with bytes-size
def scan(binary), do: scan(binary, byte_size(binary), 0)
def scan(binary, size, n) when size == (n - 2) do
  {:not_found, binary}
end
def scan(binary, size, n) do
  case binary do
    << part1 :: bytes-size(n), 1, 6, msg :: bytes-size(4), rest :: binary >> ->
      {:found, <<part1>>, <<1, 6, msg :: binary>>, rest}
    binary ->
      scan(binary, size, n+1)
  end
end

我手动输入此代码,因此可能存在错误或语法问题,但您可以将其视为实现扫描算法的伪代码。