50 kB zu viele Daten für Sockets?

  • C#
  • .NET (FX) 4.0

Es gibt 24 Antworten in diesem Thema. Der letzte Beitrag () ist von EaranMaleasi.

    50 kB zu viele Daten für Sockets?

    Servus leute,

    Ich hab hier eine Server-Client Konstruktion die auf der Socket-Klasse basiert, und sich die Async-Funktionen zu nütze macht.
    Dazu kommt noch eine RSA-AES verschlüsselung mitsamt HMAC. Für AES und HMAC wird diese implementation benutzt : AESThenHMAC

    Mein Problem ist es, dass nur jede dritte oder gar vierte Übertragung erfolgreich ist, alle anderen Nachrichten scheinen sich beim Weg durchs Lan (PC->Switch->Router->Switch->PC) verändert zu haben. Ich habe Testweise die BouncyCastle Implementation von Keccak(SHA3) auf beiden Seiten über die Verschlüsselten Daten laufen lassen und tatsächlich sind die Daten verändert. In den Fällen, in dem die Verschlüsselung Klappt, sind natürlich auch die Hashes exakt die selben.

    Meine "Pakete" bestehen aus einem Enum, dass eine Anweisung darstellt, und aus einer selbstgeschriebenen Klasse, die entsprechende Daten enthält.
    Alles wird über den BinaryFormatter zu bytes Serialisiert, die danach Verschlüsselt, und dann gesendet werden.
    Die Größe dieser "Pakete" kann bis zu 50 kb betragen, tendenz steigend. Wesentlich kürzere Pakete scheinen jedoch Problemlos anzukommen, da die Kommunikation, bevor solche "große" Pakete gesendet werden, einwandfrei funktioniert.
    Diese Pakete haben eine länge von meist weniger als einem Kilobyte.
    Muss ich eventuell diese Pakete splitten? Oder ist von Veränderungen bei dieser "Menge" von Daten grundsätzlich zu rechnen, trotz TCP?

    Edit:
    Es ist vielleicht noch zu erwähnen, dass Verbindungen von und zu dem selben PC (per LAN-IP nicht localhost) einwandfrei funktionieren.

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „EaranMaleasi“ ()

    probierma TcpClient zu verwenden - der gibt dir einen Networkstream.
    Damit hatte ich bei meinen Übungen keine solche Probleme.
    Aber ich habs auch nicht mit Verschlüsselung kombiniert - für CryptoStreams als "endliche Streams" muss man sich vmtl. was ausdenken, ums mit dem unendlichen NetworkStream kompatibel zu kriegen.
    Mit dem NetworkStream bin ich nicht wirklich gut befreundet, der hat mir bei meinen ersten Versuchen vor ein bis zwei Jahren extreme Probleme bereitet, vorallem im Zusammenhang mit Verschlüsselung.
    Testweise wäre dies möglich, doch ein "pain in the ass" wenn ich dass in mein Projekt Implementieren müsste. Ich werds versuchen, doch ich hoffe solange mal, dass Jemand doch noch eine andere Lösung hat.

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „EaranMaleasi“ ()

    ich hab jetzt den Link auf deine spezial-Verschlüsselung überflogen - das müsste eiglich leicht mit Networkstream kompatibel zu kriegen sein.
    Das entschlüsselt ja byte[], und solch kann man ja mit BinaryReader/Writer zuverlässig in einen (beliebigen, also auch:) Networkstream lesen und schreiben.

    EaranMaleasi schrieb:

    Muss ich eventuell diese Pakete splitten?


    Das macht Tcp schon von selber und genau das ist wahrscheinlich auch dein Problem. Bei Tcp empfängst du nicht garantiert genau die Daten länge die du gesendet hast, sondern kannst evtl. deine Daten in mehreren kleinen Teilen empfangen. Entweder arbeitest du mit einem NetworkStream oder packst eine Size Angabe mit in dein Array und beachtest diese Size Angabe beim Empfangen.

    Pinki schrieb:

    oder packst eine Size Angabe mit in dein Array und beachtest diese Size Angabe beim Empfangen.

    Meine pakete sind folgendermaßen Aufgebaut:
    LängePaket + Verschlüsselt(MengeNutzdatenObjektLängen + LängeNutzdatenObjekt1 + ... + LängeNutzdatenObjektN + NutzdatenObjekt1 + ... + NutzdatenObjektN)

    Die Längen betragen jeweils 4 Bytes, entsprechen also einem int. Die MengeNutzdatenObjektLängenist selbst ein int groß.
    Es wird zuerst gelesen wie lang das Paket ist, danach das Paket selbst. Danach wird Entschlüsselt, die Menge der Nutzdatenlängen gelesen. Danach schneide ich die Nutzdatenlängen heraus und finde anhand der Längen meine Serialisierten Objekte, die ich dann wieder Deserialisiere.

    Auf beiden Seiten (Client und Server) sind die Längen auch identisch, jedoch die Daten verändert.
    Wie schon gesagt. Ich teste noch vor der Entschlüsselung die Richtigkeit der Daten per HMAC. Schlägt dies Fehl, wurden die Daten unterwegs verändert, oder mit dem falschen Schlüssel gearbeitet.
    Testweise habe ich ja noch SHA3 also Keccak nach der Verschlüsselung und vor der Entschlüsselung eingebaut. Selbes Bild; die Hashes sind verschieden, wenn der HMAC fehlschlägt.
    Okay, hier mal der Code der für das Senden und Empfangen, sowie die Serialisierung, und Kodierung zuständig ist.
    Senden

    C#-Quellcode

    1. public void SendDataAsync(params object[] obj)
    2. {
    3. byte[] toSend = ObjectSerialization.AssembleByteArray(obj);
    4. byte[] encMessage = null;
    5. if(IsEncrypted)
    6. { encMessage = AESThenHMAC.SimpleEncryptWithPassword(toSend, RSAEH.EncodeToByte(AESPASS)); }
    7. else
    8. { encMessage = toSend; }
    9. Console.WriteLine(Alphabet.getK2(encMessage));
    10. byte[] wholeMessage = ObjectSerialization.AddLength(encMessage);
    11. SocketAsyncEventArgs saea = new SocketAsyncEventArgs() { UserToken = obj };
    12. saea.Completed += Data_Sent;
    13. saea.SetBuffer(wholeMessage, 0, wholeMessage.Length);
    14. ClientS.SendAsync(saea);
    15. }

    Empfangen

    C#-Quellcode

    1. /// <summary>
    2. /// Begins to receive data. Raises the DataReceived event upon completion.
    3. /// </summary>
    4. public void ReceiveDataAsync()
    5. {
    6. try
    7. {
    8. SocketAsyncEventArgs saeaShort = new SocketAsyncEventArgs();
    9. saeaShort.SetBuffer(new byte[4], 0, 4);
    10. saeaShort.Completed += Receive_Short_Completed;
    11. ClientS.ReceiveAsync(saeaShort);
    12. }
    13. catch(Exception ex)
    14. {
    15. OnOperationFailed(this, new TCPCSEventArgs() { Except = ex });
    16. }
    17. }

    C#-Quellcode

    1. private void Receive_Short_Completed(object sender, SocketAsyncEventArgs e)
    2. {
    3. try
    4. {
    5. e.Completed -= Receive_Short_Completed;
    6. int size = BitConverter.ToInt32(e.Buffer, 0);
    7. SocketAsyncEventArgs saea = new SocketAsyncEventArgs();
    8. saea.SetBuffer(new byte[size], 0, size);
    9. saea.Completed += Receive_Long_Completed;
    10. ClientS.ReceiveAsync(saea);
    11. }
    12. catch(Exception ex)
    13. { OnOperationFailed(this, new TCPCSEventArgs() { Except = ex, Message = "RSCFailed" }); }
    14. }

    C#-Quellcode

    1. private void Receive_Long_Completed(object sender, SocketAsyncEventArgs e)
    2. {
    3. try
    4. {
    5. e.Completed -= Receive_Long_Completed;
    6. byte[] result;
    7. Console.WriteLine(Alphabet.getK2(e.Buffer));
    8. if(IsEncrypted)
    9. { result = AESThenHMAC.SimpleDecryptWithPassword(e.Buffer, AESPASS); }
    10. else
    11. { result = e.Buffer; }
    12. OnDataReceived(this, new TCPCSEventArgs() { Data = ObjectSerialization.DisassembleByteArray(result) });
    13. }
    14. catch(Exception ex)
    15. { OnOperationFailed(this, new TCPCSEventArgs() { Except = ex, Message = "RLC Failed" }); }
    16. }

    Serialisierung

    C#-Quellcode

    1. using LicenceLib.Cryptographics;
    2. using System;
    3. using System.Collections.Generic;
    4. using System.IO;
    5. using System.Linq;
    6. using System.Net.Sockets;
    7. using System.Runtime.Serialization.Formatters.Binary;
    8. using System.Text;
    9. namespace LicenceLib.Util
    10. {
    11. public static class ObjectSerialization
    12. {
    13. private static byte[] ObjectToByteArray(object obj)
    14. {
    15. if(obj == null)
    16. return null;
    17. BinaryFormatter bf = new BinaryFormatter();
    18. MemoryStream ms = new MemoryStream();
    19. bf.Serialize(ms, obj);
    20. return ms.ToArray();
    21. }
    22. private static object ByteArrayToObject(byte[] arrBytes)
    23. {
    24. MemoryStream memStream = new MemoryStream();
    25. BinaryFormatter binForm = new BinaryFormatter();
    26. memStream.Write(arrBytes, 0, arrBytes.Length);
    27. memStream.Seek(0, SeekOrigin.Begin);
    28. object obj = binForm.Deserialize(memStream);
    29. return obj;
    30. }
    31. internal static byte[] AddLength(byte[] Data)
    32. {
    33. byte[] toSend = new byte[4 + Data.Length];
    34. GetBytes(Data.Length).CopyTo(toSend, 0);
    35. Data.CopyTo(toSend, 4);
    36. return toSend;
    37. }
    38. private static byte[] GetBytes(int i)
    39. {
    40. return BitConverter.GetBytes(i);
    41. }
    42. private static byte[] GlueObjects(object[] add)
    43. {
    44. List<byte[]> lstAdds = new List<byte[]>();
    45. byte[] adds = null;
    46. byte[] addsLengths = null;
    47. byte[] AmountLengths = null;
    48. byte[] sAdds = null;
    49. foreach(object item in add)
    50. { lstAdds.Add(ObjectToByteArray(item)); }
    51. List<int> aLengths = new List<int>();
    52. if(lstAdds.Count > 0)
    53. {
    54. long addsLength = 0;
    55. long addsLengthLength = 0;
    56. lstAdds.ForEach(x => addsLength += x.Length);
    57. lstAdds.ForEach(x => addsLengthLength += GetBytes(x.Length).Length);
    58. lstAdds.ForEach(x => aLengths.Add(x.Length));
    59. if(addsLength + addsLengthLength + 4 > int.MaxValue)
    60. { throw new ArgumentOutOfRangeException("add", string.Format("There are to many objects for a single message. A single message can only be 2^31 bytes long. This message would be {0} bytes long.", addsLength + addsLengthLength)); }
    61. adds = new byte[addsLength];
    62. addsLengths = new byte[addsLengthLength];
    63. AmountLengths = GetBytes(aLengths.Count);
    64. int addsWritten = 0;
    65. int lengthsWritten = 0;
    66. foreach(byte[] item in lstAdds)
    67. {
    68. Buffer.BlockCopy(item, 0, adds, addsWritten, item.Length);
    69. addsWritten += item.Length;
    70. byte[] toWrite = GetBytes(item.Length);
    71. Buffer.BlockCopy(toWrite, 0, addsLengths, lengthsWritten, toWrite.Length);
    72. lengthsWritten += toWrite.Length;
    73. }
    74. sAdds = new byte[addsLength + addsLengthLength + AmountLengths.Length];
    75. Buffer.BlockCopy(AmountLengths, 0, sAdds, 0, AmountLengths.Length);
    76. Buffer.BlockCopy(addsLengths, 0, sAdds, AmountLengths.Length, (int)addsLengthLength);
    77. Buffer.BlockCopy(adds, 0, sAdds, AmountLengths.Length + (int)addsLengthLength, (int)addsLength);
    78. }
    79. return sAdds;
    80. }
    81. /// <summary>
    82. /// returns all the objects that are in the byte[]. This Method will throw an exception if you pass a non fitting byte[].
    83. /// </summary>
    84. /// <param name="mess">the serialized objects.</param>
    85. /// <returns></returns>
    86. public static object[] DisassembleByteArray(byte[] mess)
    87. {
    88. int objectCnt = GetInt(mess.Take(4).ToArray());
    89. List<int> lstLengths = new List<int>();
    90. for(int i = 0; i < objectCnt; i++)
    91. { lstLengths.Add(GetInt(mess.Skip(4 * (i + 1)).Take(4).ToArray())); }
    92. byte[] Data = mess.Skip(4 * (objectCnt + 1)).ToArray();
    93. List<byte[]> lstObjects = new List<byte[]>();
    94. foreach(int item in lstLengths)
    95. {
    96. byte[] tmep = new byte[item];
    97. Buffer.BlockCopy(Data, 0, tmep, 0, item);
    98. lstObjects.Add(tmep);
    99. Data = Data.Skip(item).ToArray();
    100. }
    101. List<object> lstResultObjects = new List<object>();
    102. foreach(byte[] item in lstObjects)
    103. { lstResultObjects.Add(ByteArrayToObject(item)); }
    104. return lstResultObjects.ToArray();
    105. }
    106. /// <summary>
    107. /// creates a single byte[] ready to be sent over the network. The DisassembleByteArray() function can retrieve the single objects.
    108. /// </summary>
    109. /// <param name="add">All the objects you want to be serialized</param>
    110. /// <returns></returns>
    111. public static byte[] AssembleByteArray(params object[] add)
    112. {
    113. return assembleByteArray(add);
    114. }
    115. public static byte[] AssemblePreparedByteArray(object[] add)
    116. {
    117. return assembleByteArray(add);
    118. }
    119. private static byte[] assembleByteArray(object[] add)
    120. {
    121. if(add != null && add.Length > 0)
    122. {
    123. if(add.All(x => x != null))
    124. { return GlueObjects(add); }
    125. else
    126. { return null; }
    127. }
    128. else
    129. { return null; }
    130. }
    131. private static int GetInt(byte[] b)
    132. {
    133. return BitConverter.ToInt32(b, 0);
    134. }
    135. }
    136. }

    RSAEH

    C#-Quellcode

    1. public static class RSAEH
    2. {
    3. private static Encoding Enc = Encoding.UTF8;
    4. /// <summary>
    5. /// Macht aus einem byteArray einen String der zuvor angegebenen Kodierung(z.B UTF-8)
    6. /// </summary>
    7. /// <param name="Message">Die ByteArray Nachricht</param>
    8. /// <returns>Gibt einen String zurück</returns>
    9. public static string EncodeToString(byte[] Message)
    10. { return Enc.GetString(Message); }
    11. /// <summary>
    12. /// Macht aus einem String ein ByteArray
    13. /// </summary>
    14. /// <param name="Message">Die String Nachricht</param>
    15. /// <returns>Gibt ein ByteArray zurück</returns>
    16. public static byte[] EncodeToByte(string Message)
    17. { return Enc.GetBytes(Message); }
    18. public static void SetEnc(Encoding enc)
    19. { Enc = enc; }
    20. public static void ResetEnc()
    21. { Enc = Encoding.UTF8; }
    22. }

    Anmerkung:
    Das Encoding ist immer UTF8. Die Methode SetEnc wird nie aufgerufen.
    Erstmal: Du verwendest SocketAsyncEventArgs falsch. SocketAsyncEventArgs nutzt man um die Resourcen die dafür erstellt werden neu zu verwenden statt sie bei jeder Operation neu zu erstellen. z.B. erstellt man einen Pool mit x SocketAsyncEventArgs Objekten und die Read/Write Operationen holen sich dann ein Objekt aus dem Pool, sobald es nicht mehr gebraucht wird wirft man das wieder in den Pool.
    Wenn man hin geht und jedes mal das SocketAsyncEventArgs Objekt neu erstellt kann man auch gleich die Begin/End Funktionen vom Socket nutzen.

    Dann zu dem Fehler den ich schon angesprochen habe: Beim Receive empfängst du nicht die genaue länge von deinem Buffer. Du gibst hier nur an wie groß der receive Buffer ist, also wie viel maximal empfangen werden kann. Wie viel du tatsächlich empfängst wird dir zurückgegeben. Bei SocketAsyncEventArgs: BytesTransferred.
    Okay, das mit den Socket AsyncEventArgs ist gut zu Wissen, werde ich, wenn ich Zeit hab überarbeiten.

    Pinki schrieb:

    Wie viel du tatsächlich empfängst wird dir zurückgegeben. Bei SocketAsyncEventArgs: BytesTransferred
    Anders gesagt, ich muss also auch hier Prüfen, ob alle Daten angekommen sind, oder ob es noch Reste gibt? Und danach ein weiteres Receive aufrufen?
    Ja. Du schaust ob die Anzahl der empfangenen Daten deiner Size Angabe übereinstimmt, falls nicht speicherst du die Daten irgendwo und rufst noch mal receive auf. Das machst du solange bis dein Paket komplett ist.

    So mach ich das in meiner Library:

    Spoiler anzeigen

    C#-Quellcode

    1. var bytesRead = Socket.EndReceive(ar);
    2. if (bytesRead > 0)
    3. {
    4. // Da nicht der komplette Buffer gefüllt wird wollen wir nur die empfangenen Daten haben
    5. var data = new byte[bytesRead];
    6. Array.Copy(_buffer, data, bytesRead);
    7. foreach (var message in _messageReader.ReadAll(Session, data))
    8. {
    9. _recvQueue.Add(message);
    10. }
    11. Socket.BeginReceive(_buffer, 0, _buffer.Length, SocketFlags.None, EndReceive, null);
    12. }
    13. else
    14. {
    15. Session?.Dispose();
    16. }


    C#-Quellcode

    1. public IEnumerable<IMessage> ReadAll(ISession session, byte[] buffer)
    2. {
    3. // buffer sind die Daten die empfangen wurden
    4. var workBuffer = buffer;
    5. if (_lastBuffer != null)
    6. {
    7. // Wir haben Daten von einem alten Receive
    8. // die alten Daten kommen an den Anfang vom workBuffer
    9. // und da hinter kommen die neuen Daten
    10. workBuffer = new byte[buffer.Length + _lastBuffer.Length];
    11. Array.Copy(_lastBuffer, workBuffer, _lastBuffer.Length);
    12. Array.Copy(buffer, 0, workBuffer, _lastBuffer.Length, buffer.Length);
    13. _lastBuffer = null;
    14. }
    15. while (true)
    16. {
    17. // Kann die Nachricht gelesen werden?
    18. // Hier wird nur geschaut ob der workBuffer lang genug ist um die komplette Nachricht zu lesen
    19. if (_codec.IsDecodable(session, workBuffer))
    20. {
    21. int bytesRead;
    22. // Nachricht lesen
    23. // bytesRead gibt an wie viel Daten die Nachricht ausgelesen hat
    24. var message = _codec.Decode(session, workBuffer, out bytesRead);
    25. yield return message;
    26. // Sind noch Daten im workBuffer vorhanden?
    27. if (workBuffer.Length > bytesRead)
    28. {
    29. // Falls ja entfernen wir die Daten die gelesen wurden und fangen die While Schleife von vorne an
    30. var bytesLeft = workBuffer.Length - bytesRead;
    31. var tmp = new byte[bytesLeft];
    32. Array.Copy(workBuffer, bytesRead, tmp, 0, tmp.Length);
    33. workBuffer = tmp;
    34. }
    35. else
    36. {
    37. // Die Nachricht hat alle vorhandenen Daten ausgelesen
    38. break;
    39. }
    40. }
    41. else
    42. {
    43. // Es sind nicht genug Daten im workBuffer vorhanden um die Nachricht zu lesen
    44. // Die Daten, falls vorhanden, zwischenspeichern fürs nächste Receive Event
    45. if(workBuffer.Length > 0)
    46. _lastBuffer = workBuffer;
    47. break;
    48. }
    49. }
    50. }
    Und ich hatte so sehr darauf gehofft endlich mal ein Receive zu haben, dass man nciht derart überwachen muss :D

    Nun gut, dann werde ich mich mal an die Änderungen machen und schauen obs danach besser wird.

    Btw. kann es auch passieren, dass per Receive nur ein einziges byte von sagen wir mal 1000 übertragen wird?
    Woher kommt das? sollte nicht mit einem Receive der Buffer bei genügend Daten auch entsprechend beschrieben sein?
    Wo ist die Logik dahinter, dass man einmal 1000Bytes sendet, man aber mehrmals nach diesen 1000Bytes fragen muss?
    Versucht man zu früh die Daten zu Empfangen? Ist TCP noch mit der Validierung und Fehlerbehebung der fehlenden Bytes beschäftigt?
    Keine Ahnung ob es auch nur 1 byte lang sein kann, sollte man aber trotzdem bedenken. Tcp splittet deine Daten in mehrere Pakete, falls nötig, und da kann es halt vor kommen, dass du die bereits empfangenen Daten abrufst bevor alles da ist.
    Was genau da passiert weiß ich selber nicht, so sehr hab ich mich nun auch wieder nicht mit dem Thema beschäftigt :>.
    Das Problem hatte ich noch nie. - Immer wenn die Daten angekommen sind und ich EndReceive aufgerufen habe, waren ALLE Daten des Pakets da. Vorrausgesetzt du man ruft EndReceive auch in der Delegate auf die man bei BeginReceive angegeben hat.
    Das war meine nächste Überlegung. Eventuell auf Begin- und End- Umzusteigen, und zwar durchweg... Im moment ist es ein gemisch aus synchronem Accept und Verbindungsaufbau(auch Schlüsselübertragung) sowie asynchronem Send und Receive. Das war für mich von Anfang an suboptimal, doch leider damals nicht besser und schneller machbar, da mir damals IAsyncResult und Callbacks absolut suspekt waren. Heute ist das anders ;)

    ThuCommix schrieb:

    Das Problem hatte ich noch nie


    Wenn man nur wenig Daten sendet kommt das auch nur selten vor(Auch Lokal und im LAN wird man eher selten drauf stoßen). Wenn man größere Daten sendet und das über das Internet wird man auf jeden Fall auf das Problem stoßen. Die Verhaltensweiße ist überall so, egal ob Begin/End, SocketAsyncEventArgs, synchronen Funktionen oder NetworkStream.
    Tcp ist ein Stream von Daten. Das was du sendest wird nicht, wie bei Udp, als komplettes Paket angesehen sondern als Daten die auf einen Stream geschrieben wurden. Es wird ein Teil vom Stream genommen und übers Netzwerk gesendet, Tcp weiß nicht was davon jetzt dein ganzes Paket ist.
    Tcp sorgt nur dafür, dass deine Daten sicher und fehlerfrei ankommen.