2013-04-04 10 views
6

Ho un buffer di lunghezza 256 che riceve sequenze di byte dal bluetooth. Il pacchetto effettivo che ho bisogno di estrarre è che inizia e finisce con il byte 126. Voglio estrarre l'ultimo pacchetto nel buffer usando LINQ.Estrazione del pacchetto di dati dal buffer di byte

Quello che sto facendo ora è controllare l'ultimo indice di 126 e poi contare all'indietro fino a raggiungere un altro 126. Ci sono anche alcuni problemi, ad esempio, due pacchetti adiacenti possono generare due byte di 126 uno accanto all'altro.

Ecco un esempio di tampone:

 
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 

Così le informazioni che ho è:

  • inizia a pacchetto e termina con il valore di byte di 126
  • il byte successivo dopo l'indice di partenza ha un valore di 69
  • il 3 ultimo byte a destra per il byte finale di 126 è un CRC dell'intero pacchetto che so come calcolare, quindi dopo aver estratto un pacchetto posso diamine questo CRC per vedere se ho il pacchetto giusto

Quindi alla fine voglio avere una matrice o un elenco che contenga il pacchetto corretto. per esempio:

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

Potete darmi un soloution veloce di estrarre questo pacchetto dal buffer?

Questo è ciò che I'v provato finora .... non riesce come posso davvero restituire il pacchetto corretto Cerco:

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

@KendallFrey I 'v ha aggiunto il mio approccio, non sembra proprio trovare il vero indice di inizio e fine di un pacchetto corretto :( –

+0

Puoi spiegare la tua logica? – ehudt

+0

Può esserci in questo buffer risultati parziali? Voglio dire che il byte di inizio viene inviato ma fino alla fine del buffer non c'è un marcatore finale perché fa parte di una risposta più ampia? In questo caso, qualsiasi soluzione presentata di seguito è comunque errata. –

risposta

3

Solutions

ho preparato tre possibili soluzioni di prendere l'ultimo pacchetto da ingresso buffor:

utilizzando 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(); 
} 

utilizzando 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); 
} 

Utilizzando while loop e indicizzatori

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; 
} 

prestazioni

Ho anche effettuato alcuni molto semplici test di performance. Ecco i risultati:

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) 

Conclusione

Come si può vedere, la più semplice è anche la migliore qui.

Si può pensare che LINQ sia una risposta per ogni problema, ma qualche volta è un'idea davvero migliore scrivere manualmente una soluzione più semplice invece di usare i metodi LINQ.

+0

+0 Tutti gli array di byte arbitrari non possono essere convertiti in/da stringa senza perdere alcuni byte. Quindi la tua versione per la stringa non funzionerebbe. – I4V

+0

La versione dell'indicizzatore ha esito negativo se il buffer termina esattamente con la fine dell'ultimo pacchetto. Rimuovi gli ultimi quattro byte dei dati di esempio e vedrai cosa intendo. – pescolino

+0

Beh, dopo tutto, l'indicizzazione con alcune modifiche è la migliore risposta che ho ottenuto finora. –

1

in realtà ci sono vari modi per risolvere la tua domanda , l'idea più semplice è rilevare il doppio 126 (0x7e) e non importa altre cose come CRC.

Il L'implementazione di base di questo concetto sarebbe stato così

  • codice semplice

    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()); 
    

Base sul mio vecchio risposta di Konami Code in C#, ed è anche utilizzato per risolvere questo problema : Double characters shown when typing special characters while logging keystrokes in c#.

  • codice con un rilevatore di sequenza

    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(); 
    } 
    

O se volete scriverlo pieno in LINQ, si potrebbe desiderare di provare quanto segue. Non è nemmeno necessario utilizzare List, packetArray fornisce una matrice di matrici di byte direttamente.

I let s sono intesi per suddividere il codice in linee, altrimenti sarebbe un'istruzione lungo estremo in una riga. Se si considera una linea è il migliore, quindi lo farò.

  • Codice di 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(); 
    
  • codice per l'uscita

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

Tuttavia, anche nello stesso concetto di soluzione, non ci sarebbero vari modi, come ho detto prima . Consiglio vivamente che non implicano condizioni aggiuntive come qualcosa su CRC, che potrebbe rendere le cose più complicate.

3

Utilizzando LINQ questo può essere fatto in una singola linea di codice se le seguenti due regole possono essere applicate al buffer:

  • Il buffer contiene almeno un pacchetto completo circondato dal proposta delimitatore.
  • Ogni pacchetto contiene almeno un byte di dati.

Ecco il codice: (. Ok, questo era più di una singola linea perché ho a spacco in più righe per una migliore leggibilità)

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(); 

EDIT: rimosso il chiama Take (1) perché restituisce sempre una sequenza vuota. Il risultato tuttavia non contiene delimitatore in questo modo.


Ed ecco come funziona:

Dal momento che vogliamo trovare l'ultimo pacchetto possiamo invertire i dati:

var reversed = data.Reverse(); 

Il buffer può terminare con un pacchetto che non è completa ancora. Quindi cerchiamo di saltare che:

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

reversed ora è vuoto o si comincia con delimiter. Poiché abbiamo ipotizzato che il buffer contiene sempre almeno un pacchetto completo possiamo già prendere il successivo byte per il nostro risultato perchè sappiamo che è il delimitatore:

var packet = reversed.Take(1); 

Nella sequenza ora possiamo saltare un byte.Se il delimitatore abbiamo trovato era effettivamente l'inizio di un nuovo pacchetto della sequenza rimanente inizia con un altro delimitatore quindi dobbiamo ignorare che anche:

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

Poiché sappiamo che un pacchetto non può essere vuoto perché contiene un 3 byte CRC avremmo potuto scrivere:

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

Ora i dati effettivi segue:

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

il byte successivo è il delimitatore, che segna l'inizio del pacchetto:

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

L'ultima cosa da fare è di invertire di nuovo il risultato:

packet = packet.Reverse(); 

Forse si vuole mettere questo in un metodo:

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; 
} 

Si dovrà chiama Reverse sul valore di ritorno di questo metodo.


Se le prestazioni sono importanti, è possibile utilizzare lo stesso algoritmo nell'array sottostante. In questo modo sarà circa 20 volte più veloce:

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); 
+0

Grazie per la risposta. E immagino che possiamo togliere il "Reverse" in qualche modo. –

+1

@KenKin: Ho aggiunto una versione che funziona direttamente sull'array sottostante invece di usare LINQ. Inoltre ho corretto un errore nella risposta originale: Calling Take (1) produce una sequenza con un singolo elemento che poi è stato saltato. Questo ha sempre portato a una sequenza vuota. – pescolino

0

Dato che stai cercando l'ultimo pacchetto, è molto più semplice invertire il byte [] e cercare il primo pacchetto. I due delimitatori del pacchetto non sono solo 126.Sono 126, 69 per l'inizio e 126, 126 per l'estremità a meno che la fine del pacchetto è ricevuto per l'ultimo byte, il che rende il delimitatore fine 126.

Vorrei suggerire utilizzando un metodo simular a questo:

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]; 
    } 

Non sono del tutto sicuro se gli indici della copia sono completamente corretti, ma questo dovrebbe essere il succo di ciò.

0

poiché è possibile ricevere dati incompleti, è necessario memorizzare l'ultimo buffer incompleto.

questo è caso esemplare, Primo ricezione:

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 

il secondo flusso:

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

e il codice:

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; 
    } 

risultato: data result

Problemi correlati