从字节缓冲区中提取数据包

时间:2013-04-04 13:07:16

标签: c# arrays linq list extract

我有一个长度为256的缓冲区,它接收来自蓝牙的字节序列。我需要提取的实际数据包是开头和结尾,带有字节126。我想使用LINQ在缓冲区中提取最新的数据包。

我现在正在做的是检查126的最后一个索引,然后向后计数,直到我到达另一个126。还有一些陷阱,例如,两个相邻的数据包可能导致两个字节126相邻。

以下是缓冲区示例:

126   6 0   5   232 125 93  126 126 69  0 
0   1   0   2   2   34  6   0   5   232 125 
93  126 126 69  0   0   1   0   2   2   34 
6   0   5   232 125 93  126 126 69  0   0 
1   0   2   2   34  6   0   5   232 125 93 
126 126 69  0   0

所以我的信息是:

  • 数据包以字节值126
  • 开始和结束
  • 起始索引的值为69
  • 后的下一个字节
  • 126的结束字节右边的3个最后一个字节是我知道如何计算的整个数据包的CRC,所以在提取数据包之后我可以检查这个CRC以查看我是否有正确的数据包

所以最后我想要一个包含正确数据包的数组或列表。例如:

126 69  0  0   1   0   2   2   34  6   0   5   232 125 93 126

你能给我一个从缓冲区中提取这个数据包的快速解决方案吗?

这是我到目前为止所尝试的......它失败了,因为它无法真正返回我正在寻找的正确数据包:

var data = ((byte[])msg.Obj).ToList(); //data is the buffer 

byte del = 126; //delimeter or start/end byte
var lastIndex = data.LastIndexOf(del);
var startIndex = 0;
List<byte> tos = new List<byte>(); //a new list to store the result (packet)    

//try to figure out start index                            
if(data[lastIndex - 1] != del)
{
    for(int i = lastIndex; i > 0; i--)
    {
        if(data[i] == del)
        {
            startIndex = i;
        }
    }

    //add the result in another list
    for(int i = 0; i <= lastIndex - startIndex; i++)
    {
        tos.Add(data[i]);
    }

    string shit = string.Empty;

    foreach (var b in tos)
        shit += (int)b + ", ";

   //print result in  a textbox
    AddTextToLogTextView(shit + "\r\n");
}

5 个答案:

答案 0 :(得分:3)

使用LINQ,如果可以将以下两个规则应用于缓冲区,则可以在一行代码中完成:

  • 缓冲区包含至少一个被包围的完整包 给定分隔符。
  • 每个数据包至少包含一个字节的数据。

以下是代码:

var data = (byte[])msg.Obj;
byte delimiter = 126;

var packet = data.Reverse()
                 .SkipWhile(b => b != delimiter)
                 .SkipWhile(b => b == delimiter)
                 .TakeWhile(b => b != delimiter)
                 .Reverse();

(好吧,这不仅仅是一行,因为我将它分成多行以提高可读性。)

编辑:删除了对Take(1)的调用,因为它总是会返回一个空序列。但结果不包含这种分隔符。


以下是它的工作原理:

由于我们想要找到最后一个数据包,我们可以反转数据:

var reversed = data.Reverse();

缓冲区可以以尚未完成的数据包结束。所以让我们跳过这个:

reversed = reversed.SkipWhile(b => b != delimiter);

reversed现在为空或以delimiter开头。由于我们假设缓冲区始终包含至少一个完整的数据包,因此我们已经可以为结果获取下一个字节,因为我们知道它是分隔符:

var packet = reversed.Take(1);

在序列中我们现在可以跳过一个字节。如果我们发现的分隔符实际上是新数据包的开始,则剩余的序列将以另一个分隔符开始,因此我们也必须跳过它:

reversed = reversed.Skip(1);
if (reversed.First() == delimiter)
{
    reversed.Skip(1);
}

由于我们知道数据包不能为空,因为它包含3个字节的CRC,我们可以编写:

reversed = reversed.SkipWhile(b => b == delimiter);

现在实际数据如下:

packet = packet.Concat(reversed.TakeWhile(b => b != delimiter));
reversed = reversed.SkipWhile(b => b != delimiter);

下一个字节是标记数据包开始的分隔符:

packet = packet.Concat(reversed.Take(1));

最后要做的是再次反转结果:

packet = packet.Reverse();

也许你想把它放到一个方法中:

public IEnumerable<byte> GetPacket(byte[] data, byte delimiter)
{
    yield return delimiter;

    foreach (byte value in data.Reverse()
                               .SkipWhile(b => b != delimiter)
                               .SkipWhile(b => b == delimiter)
                               .TakeWhile(b => b != delimiter))
    {
        yield return value;
    }

    yield return delimiter;
}

您必须在此方法的返回值上调用Reverse。


如果性能很重要,您可以在底层阵列上使用相同的算法。这样它的速度将快20倍:

int end = data.Length - 1;
while (data[end] != delimiter)
    end--;

while (data[end] == delimiter)
    end--;

int start = end;
while (data[start] != delimiter)
    start--;

byte[] result = new byte[end - start + 2];  // +2 to include delimiters
Array.Copy(data, start, result, 0, result.Length);

答案 1 :(得分:3)

解决方案

我准备了三个可能的解决方案,从输入buffor获取最后一个数据包:

使用LINQ

public static byte[] GetLastPacketUsingLINQ(byte[] input, byte delimiter)
{
    var part = input.Reverse()
                    .SkipWhile(i => i != delimiter)
                    .SkipWhile(i => i == delimiter)
                    .TakeWhile(i => i != delimiter)
                    .Reverse();

    return (new byte[] { delimiter }).Concat(part).Concat(new byte[] { delimiter }).ToArray();
}

使用string.Split

public static byte[] GetLastPacketUsingString(byte[] input, byte delimiter)
{
    var encoding = System.Text.Encoding.GetEncoding("iso-8859-1");
    string inputString = encoding.GetString(input);
    var parts = inputString.Split(new[] { (char)delimiter }, StringSplitOptions.RemoveEmptyEntries);

    return encoding.GetBytes((char)delimiter + parts[parts.Length - 2] + (char)delimiter);
}

使用while循环和索引器

public static byte[] GetLastPacketUsingIndexers(byte[] input, byte delimiter)
{
    int end = input.Length - 1;
    while (input[end--] != delimiter) ;

    int start = end - 1;
    while (input[start--] != delimiter) ;

    var result = new byte[end - start];
    Array.Copy(input, start + 1, result, 0, result.Length);
    return result;
}

性能

我还进行了一些非常简单的性能测试。结果如下:

LINQ version result:
126 69 0 0 1 0 2 2 34 6 0 5 232 125 93 126

String version result:
126 69 0 0 1 0 2 2 34 6 0 5 232 125 93 126

Indexers version result:
126 69 0 0 1 0 2 2 34 6 0 5 232 125 93 126

LINQ version time: 64ms (106111 ticks)
String version time: 2ms (3422 ticks)
Indexers version time: 1ms (2359 ticks)

结论

正如你所看到的,最简单的一个也是最好的一个。

您可能认为LINQ是每个问题的答案,但有时候,手动编写更简单的解决方案而不是使用LINQ方法更好。

答案 2 :(得分:1)

实际上有各种方式来解决您的问题,最简单的想法是检测双126(0x7e),并且无关紧要像CRC。

这个概念的基本实现就像这样

  • 代码简单

    var list=new List<byte[]>();
    int i=0, j=0;
    for(; i<data.Length; ++i)
        if(i>0&&0x7e==data[i]&&0x7e==data[i-1]) {
            list.Add(data.Skip(j).Take(i-j).ToArray());
            j=i;
        }
    list.Add(data.Skip(j).Take(i-j).ToArray());
    

根据我对Konami Code in C#的旧回答,它甚至用来解决这个问题:Double characters shown when typing special characters while logging keystrokes in c#

  • 带序列检测器的代码

    public partial class TestClass {
        public static void TestMethod() {
            var data=(
                new[] { 
                        126, 6, 0, 5, 232, 125, 93, 126, 
                        126, 69, 0, 0, 1, 0, 2, 2, 34, 6, 0, 5, 232, 125, 93, 126, 
                        126, 69, 0, 0, 1, 0, 2, 2, 34, 6, 0, 5, 232, 125, 93, 126, 
                        126, 69, 0, 0, 1, 0, 2, 2, 34, 6, 0, 5, 232, 125, 93, 126, 
                        126, 69, 0, 0 
                    }).Select(x => (byte)x).ToArray();
    
            var list=new List<List<byte>>();
    
            foreach(var x in data) {
                if(list.Count<1||SequenceCapturer.Captured((int)x))
                    list.Add(new List<byte>());
    
                list.Last().Add(x);
            }
    
            foreach(var byteList in list)
                Debug.Print("{0}", byteList.Select(x => x.ToString("x2")).Aggregate((a, b) => a+"\x20"+b));
        }
    }
    
    public class SequenceCapturer {
        public int Count {
            private set;
            get;
        }
    
        public int[] Sequence {
            set;
            get;
        }
    
        public bool Captures(int value) {
            for(var i=Sequence.Length; i-->0; ) {
                if(Sequence[i]!=value) {
                    if(0==i)
                        Count=0;
    
                    continue;
                }
    
                if(Count!=i)
                    continue;
    
                ++Count;
                break;
            }
    
            var x=Sequence.Length==Count;
            Count=x?0:Count;
            return x;
        }
    
        public SequenceCapturer(int[] newSequence) {
            Sequence=newSequence;
        }
    
        public SequenceCapturer()
            : this(new[] { 0x7e, 0x7e }) {
        }
    
        public static bool Captured(int value) {
            return Instance.Captures(value);
        }
    
        public static SequenceCapturer Instance=new SequenceCapturer();
    }
    

或者,如果您想在Linq中完整地编写它,您可能需要尝试以下操作。您甚至不需要使用ListpacketArray直接为您提供一个字节数组。

let用于将代码分解为行,否则它将是在一行中的极长语句。如果你认为一行最好,那么我会。

  • packetArray的代码

    var packetArray=(
        from sig in new[] { new byte[] { 0x7e, 0x7e } }
        let find=new Func<byte[], int, IEnumerable<byte>>((x, i) => x.Skip(i).Take(sig.Length))
        let isMatch=new Func<IEnumerable<byte>, bool>(sig.SequenceEqual)
        let filtered=data.Select((x, i) => 0==i||isMatch(find(data, i-1))?i:~0)
        let indices=filtered.Where(i => ~0!=i).Concat(new[] { data.Length }).ToArray()
        from index in Enumerable.Range(1, indices.Length-1)
        let skipped=indices[index-1]
        select data.Skip(skipped).Take(indices[index]-skipped).ToArray()).ToArray();
    
  • 输出代码

    foreach(var byteArray in packetArray)
        Debug.Print("{0}", byteArray.Select(x => x.ToString("x2")).Aggregate((a, b) => a+"\x20"+b));
    

然而,即使在相同的解决方案概念中,也会有各种方式,如前所述。我强烈建议不要涉及类似CRC的附加条件,这可能会使事情变得更复杂。

答案 3 :(得分:0)

由于您正在寻找最后一个数据包,因此反转字节[]并查找第一个数据包要容易得多。你的两个数据包分隔符不只是126.它们是126,69为开始,126,126为结尾,除非数据包的末尾是最后一个字节,这使得结束分隔符为126.

我建议使用类似的方法:

public static byte[] GetMessage(byte[] msg)
    {
        //Set delimiters
        byte delimit = 126;
        byte startDelimit = 69;

        //Reverse the msg so we can find the last packet
        List<byte> buf = msg.Reverse().ToList();

        //set indices to impossible values to check for failures
        int startIndex = -1;
        int endIndex = -1;
        //loop through the message
        for (int i = 0; i < buf.Count - 1; i++)
        {
            //find either a double 126, or 126 as the last byte (message just ended)
            if (buf[i] == delimit && (buf[i + 1] == delimit || i == 0))
            {
                if (i == 0)
                {
                    startIndex = i;
                    i++;
                }
                else
                {
                    startIndex = i + 1;
                    i += 2;
                }
                continue;
            }
            //Only process if we've found the start index
            if (startIndex != -1)
            {
                //check if the byte is 69 followed by 126
                if (buf[i] == startDelimit && buf[i + 1] == delimit)
                {
                    endIndex = i + 1;
                    break;
                }
            }
        }
        //make sure we've found a message
        if (!(startIndex == -1 || endIndex==-1))
        {
            //get the message and reverse it to be the original packet
            byte[] revRet = new byte[endIndex - startIndex];
            Array.Copy(buf.ToArray(), startIndex, revRet, 0, endIndex - startIndex);

            return revRet.Reverse().ToArray();
        }
        return new byte[1];
    }

我不完全确定副本的索引是否完全正确,但这应该是它的主旨。

答案 4 :(得分:0)

因为您可能收到不完整的数据,所以必须存储最后一个不完整的缓冲区。

这是示例案例,First Receive:

126,   6, 0,   5,  232, 125, 93,  126, 126, 69,  0, 
0,   1,   0,   2,   2,   34,  6  , 0 ,  5 ,  232, 125, 
93,  126, 126, 69,  0,   0,   1 ,  0,   2,   2,   34, 
6,   0,   5,   232, 125, 93,  126, 126, 69,  0,   0 ,
1,   0,  2,   2,   34,  6,   0,   5,   232, 125, 93, 
126, 126, 69,  0,   0

第二个流:

69,  0,   0 , 1,   0,  2,   2,   34,  6,   0, 126

和代码:

    List<byte> lastBuf = new List<byte>();

    List<byte[]> Extract(byte[] data, byte delim)
    {
        List<byte[]> result = new List<byte[]>();

        for (int i = 0; i < data.Length; i++)
        {
            if (lastBuf.Count > 0)
            {
                if(data[i] == delim)
                {
                    result.Add(lastBuf.ToArray());
                    lastBuf.Clear();
                }
                else
                {
                    lastBuf.Add(data[i]);
                }
            }
            else 
            { 
                if(data[i] != 126)
                {
                    lastBuf.Add(data[i]);
                }
            }
        }

        return result;
    }

结果:data result