【问题标题】:How to search for bytes in Queue<byte> and extract packet?如何在 Queue<byte> 中搜索字节并提取数据包?
【发布时间】:2012-06-28 13:19:35
【问题描述】:

我正在通过 COM 端口接收数据包。每个数据包以 {0xFF, 0xFF} 开始,以 {0xFE, OxFE} 结束。所有接收到的字节都在Queue&lt;byte&gt; 中排队,在每个void port_DataReceived(object sender, SerialDataReceivedEventArgs e) 之后我正在处理该队列。 如果数据包中出现任何 0xFF 或 0xFE,则设备在其后添加 0x00。

  1. 如何提取每个数据包?
  2. 如何删除每个数据包中不必要的 0x00,其中包含头字节?

对于我遇到的第一个问题:

void port_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
    byte[] data = new byte[port.BytesToRead];
    try
    {
        port.Read(data, 0, data.Length);    
    }
    catch (Exception ex)
    {
        Debug.WriteLine(ex.Message);
    }
    data.ToList().ForEach(newByte => receivedData.Enqueue(newByte));
    processData();
}

private void processData()
{
    // Determine if we have a "packet" in the queue
    if (Enumerable.SequenceEqual(receivedData.Take(2), new List<byte> { 0xFF, 0xFF }))
    {
        // Beginning of new packet in the front of queue is ready!
        if (Enumerable.SequenceEqual(receivedData.Skip(Math.Max(0, receivedData.Count() - 2)).Take(2), new List<byte> { 0xFE, 0xFE }))
        {
            List<byte> tempPacket = new List<byte>();
            // Whole packet in the queue
            while(receivedData.Count > 0)
                tempPacket.Add(receivedData.Dequeue());
            tempPacket.TrimExcess();
            Packet pack = new Packet(tempPacket, PacketOrigin.Serial);
        }
    }
}

我正在尝试删除在Queue&lt;byte&gt; 中可以找到的任何 0xFE 和 0xFF 之后的所有 0x00,到目前为止我想出了:

List<byte> unconvertedPacket = new List<byte> { 0xFF, OxFF, 0x00, 0x00,0x4D, 0xFA 0xFE, 0x00, 0x01, 0x00, 0x03, 0xFE, 0xFE}
int index = 0;
while (index != null)
{
    unconvertedPacket.RemoveAt(index + 1);
    index = unconvertedPacket.IndexOf(0xFE);
}
while (index != null)
{
    unconvertedPacket.RemoveAt(index + 1);
    index = unconvertedPacket.IndexOf(0xFF);
}

有没有人有其他解决方案/建议?

【问题讨论】:

    标签: c# list serial-port


    【解决方案1】:

    尝试以下方法:

    在 DataReceived 事件处理程序中继续读取传入的数据并将其附加到缓冲区 (byte[])。

    首先您需要在接收数据的缓冲区中找到开始标记({0xFF, 0xFF})。您需要确定此标记在缓冲区中的索引。

    一旦有了起始索引,就需要不断地将传入数据附加到缓冲区并检查结束标记(0xFE、0xFE)是否已到达。捕获缓冲区内结束标记的索引。

    一旦有了开始和结束索引,就可以提取它们之间的数据包。没关系在它之后添加的额外 0x00 字节。您知道开始和结束标记的索引及其长度 (2)。只需提取它们之间的字节数组即可。

    您需要创建适合此目的的搜索算法。针和干草堆都是字节数组(byte[])。为此,您可以使用Boyer-Moore string search algorithm

    这是 Boyer-Moore 算法的简单 C# 实现,它仅实现 the bad character rule。如果您还想实现 good suffix rule,请阅读 Wikipedia。

    该算法通常用于字符串,但我对其进行了修改以使用字节数组。使用 IP 摄像机在本地对此进行了测试,以提取接收到的 JPEG 图像。

    查看 Wikipedia 文章了解更多信息。它包含一个完整的 Java 实现,您可以轻松地将其转换为 C#。

    public class BoyerMoore
    {
        public static int IndexOf(byte[] needle, byte[] haystack)
        {
            if (needle == null || needle.Length == 0)
                return -1;
    
            int[] charTable = CreateCharTable(needle);
            for (int i = needle.Length - 1, j; i < haystack.Length;)
            {
                for (j = needle.Length - 1; needle[j] == haystack[i]; i--, j--)
                {
                    if (j == 0)
                        return i;
                }
                i += charTable[haystack[i]];
            }
            return -1;
        }
    
        private static int[] CreateCharTable(byte[] needle)
        {
            const int ALPHABET_SIZE = 256;
            var table = new int[ALPHABET_SIZE];
            for (int i = 0; i < table.Length; i++)
            {
                table[i] = needle.Length;
            }
            for (int i = 0; i < needle.Length - 1; i++)
            {
                table[needle[i]] = needle.Length - 1 - i;
            }
            return table;
        }
    }
    

    示例用法:

    var haystack = new byte[] 
      {0xFF, 0xFF, 0x00, 0x00, 0x4D, 0xFA, 0xFE, 0x00, 0x01, 0x00, 0x03, 0xFE, 0xFE};
    
    var startIndexOf = BoyerMoore.IndexOf(new byte[] {0xFF, 0xFF}, haystack);
    var endIndexOf = BoyerMoore.IndexOf(new byte[] {0xFE, 0xFE}, haystack);
    
    var packet = new byte[endIndexOf - 2 - startIndexOf];
    for (int i = startIndexOf + 2, j = 0; i < endIndexOf - startIndexOf; i++, j++)
    {
        packet[j] = haystack[i];
    }
    

    瞧,这个例子中的数据包字节数组包含 9 个字节,并且只包含开始和结束标记之间的字节。例如,您现在可以触发事件并将数据包作为事件 arg 传递。

    备注:从 COM 口接收数据是一个连续的事件。你需要继续监控它。继续附加接收到的数据并继续检查开始和索引标记,提取包......等等。注意你的缓冲区不会溢出。您需要在那里进行一些内务管理。

    希望对您有所帮助。查看AForgeMJPEGStream 实现,了解持续读取传入数据的示例。

    总结一下:

    1. 声明一个实例变量来存储接收到的数据(例如 _buffer = new byte[4096])。
    2. 将传入数据附加到 DataReceived 事件处理程序中的缓冲区。
    3. 搜索开始标记。如果找到,请记住实例变量中的起始索引。
    4. 如果您已经知道开始标记的位置,则搜索结束标记的索引。
    5. 找到结束标记后,提取数据包并触发事件。将数据包用作事件的 EventArgs 的一部分。
    6. 清洗、漂洗、重复。

    您需要进行一些内务处理以确保缓冲区不会溢出(> 4096 字节)。例如,一旦你找到一个数据包,你就可以清理缓冲区直到最后收到的结束标记。

    【讨论】:

    • 您的方法很好,直到 DataReceived 事件中不会显示整个数据包。如果只有部分数据包,即头部和一些没有'0xfe,oxfe'尾部的数据会到来,并且在第二个dataReceived事件中rest会到来,之前接收到的数据将被覆盖并丢失。这就是我使用队列和出队方法的原因。
    • 我不希望整个数据包立即到达。当您从端口读取数据时,将其添加到缓冲区,您将其声明为实例变量,而不是事件处理程序的局部变量。更新了我的答案。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-01-11
    • 2014-05-10
    • 2013-03-26
    • 2011-07-21
    • 2023-03-24
    相关资源
    最近更新 更多