我有一个长度为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 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");
}
答案 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中完整地编写它,您可能需要尝试以下操作。您甚至不需要使用List
,packetArray
直接为您提供一个字节数组。
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;
}
结果: