如何在 Queue<byte> 中搜索字节并提取数据包?

4
我正在通过COM端口接收数据包。每个数据包以{0xFF,0xFF}开头,以{0xFE,OxFE}结尾。所有接收到的字节都排队在Queue<byte>中,在每个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<byte>中的任何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);
}

有没有其他解决方案或建议可以完成这个任务?

1个回答

0

尝试以下方法:

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

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

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

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

您需要创建一个适合此目的的搜索算法。针和干草堆都是字节数组(byte[])。您可以使用Boyer-Moore字符串搜索算法来实现此目的。

这是一个简单的C#实现Boyer-Moore算法的示例,仅实现了坏字符规则。如果您还想实现好后缀规则,可以在维基百科上阅读相关内容。

该算法通常用于字符串,但我修改了它以适应字节数组。在本地使用IP摄像头测试过,可以提取接收到的JPEG图像。

请查看维基百科文章以获取更多信息。它包含一个完整的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个字节,并且仅包含开始和结束标记之间的字节。现在,您可以触发一个事件并将数据包作为事件参数传递。

备注:从COM端口接收数据是一个持续的事件。您需要不断监视它。不断追加接收到的数据并检查开始和索引标记,提取数据包等等。注意您的缓冲区不要溢出。您需要在那里实现一些清理工作。

希望对您有所帮助。请查看AForge实现连续读取传入数据的MJPEGStream的示例。

总之:

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

您需要实现一些清理工作,以确保缓冲区不会溢出(> 4096字节)。例如,一旦找到数据包,您可以清理缓冲区直到最后接收到的结束标记。


你的方法很好,直到DataReceived事件上没有整个数据包出现。如果只有部分数据包,即头部和一些数据没有'0xfe, oxfe'尾巴,会在第二个DataReceived事件中传输其余数据,之前接收到的数据将被覆盖并丢失。这就是为什么我使用了队列和出队方法。 - PawelZ
我不希望整个数据包一次性到达。当你从端口读取数据时,将其添加到缓冲区中,该缓冲区应声明为实例变量,而不是事件处理程序的局部变量。更新了我的答案。 - Christophe Geers

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接