Über einen TCP Server gesendete Bytes kommen manchmal nur unvollständig an

  • VB.NET
  • .NET (FX) 4.5–4.8

Es gibt 26 Antworten in diesem Thema. Der letzte Beitrag () ist von jvbsl.

    Über einen TCP Server gesendete Bytes kommen manchmal nur unvollständig an

    Hallo,
    ich beschäftige mich momentan mit der Programmierung mit TCP-Servern, was bis jetzt auch ganz gut funktioniert hat.
    Nun gibt es aber ein Problem:
    Manchmal kommen zu wenig Bytes beim Server an. Das ist jedoch nur alle 2-5 Male so, also vorwiegend kommt alles komplett an. Als Beispiel: Gesendet werden 550899 Bytes, aber es kommen nur 387377 Bytes an.
    Für den Fall, dass das irgendwie wichtig sein sollte, das Bytearray ist ein Bild.

    Hier ist mein Code für das Senden des Bytearrays (Client): (ich denke kaum, dass das daran liegen wird)

    VB.NET-Quellcode

    1. ​ Debug.Print("Buffer: {0}", buffer.Length)
    2. _stream.Write(buffer, 0, buffer.Length)
    3. _stream.Flush()


    und hier der Code für das Empfangen:

    VB.NET-Quellcode

    1. While True
    2. Try
    3. If TcpClient.IsConnected() Then
    4. Dim token As Byte() = New Byte(2) {}
    5. Stream.Read(token, 0, 2) 'Auf neue Daten warten.
    6. Dim tokenstr = Text.ASCIIEncoding.ASCII.GetString(token).ToUpper()
    7. Dim bytes As New List(Of Byte)
    8. While TcpClient.Available > 0
    9. Dim response As Byte() = New Byte(TcpClient.Available - 1) {}
    10. Stream.Read(response, 0, response.Length)
    11. bytes.AddRange(response)
    12. End While
    13. Debug.Print("Empfangen: {0}", bytes.Count)
    14. Continue While
    15. End If
    16. Catch ex As IOException
    17. Catch ex As ObjectDisposedException
    18. End Try
    19. RaiseEvent Disconnected(Me, EventArgs.Empty)
    20. Exit While
    21. End While


    Ich habe natürlich auch schon ein bisschen im Internet gesucht, aber nichts gefunden, was sich mit dem Problem befasst (wahrscheinlich eher falsch gesucht, denn ich kann mir kaum vorstellen, dass ich der einzige mit so einem Problem bin)
    Mfg
    Vincent

    Was mir beim überfliegen auffällt ist das:

    VB.NET-Quellcode

    1. ​New Byte(TcpClient.Available - 1)


    Der Buffer hat ein Feld zu wenig. Bei der Initialisierung muss man die Anzahl aller aufzunehmender Elemente angeben und das ist TcpClient.Available.
    Der Server empfängt eine Zahl, die angibt, dass 500 Bytes nachfolgen werden, er puffert solange, bis 500 Bytes weitergereicht/empfangen wurden und gibt dieses Datenpacket dann nach außen hin weiter oder konvertiert es. Anschließend folgt die nächste Zahl, die angibt, wie viele Daten nachfolgen werden.

    Gruß
    ~blaze~
    recht elegant kann man das mittm BinaryReader/BinaryWriter lösen. also angenommen, 20014 Bytes seien zu versenden. Dann sendet man

    VB.NET-Quellcode

    1. (dim data(20013) As Byte)
    2. writer.Write(20014)
    3. writer.Write(data)

    und empfängt

    VB.NET-Quellcode

    1. dim dataLength = reader.ReadInt32 'empfängt 20014
    2. dim dataResult= reader.ReadBytes(dataLength) 'empfängt die Daten
    Wobei es häufig noch eleganter ist, Daten selbst zu segmentieren, um bspw. mehrere Pakete parallel verschicken zu können. Das ist dann allerdings relativ komplex zu implementieren, z.B. hier ein vereinfachter Ansatz.
    Auch wenn keine "Segmentierung" (hab' ich einfach mal so genannt) stattfindet, sind Puffer fester Größe zu empfehlen. Man stelle sich vor, es wird ein Video übertragen und plötzlich wird ein Array von 2 GB benötigt oder im Falle von Streaming wäre die Bytezahl höchstens pro Frame bekannt und variabel, müsste man ständig ein neues Array anlegen, das alte wieder freigeben, usw. Stattdessen das gleiche Array zu verwenden und nur ggf. die Daten extrahieren, ist eigentlich fast sinnvoller.

    Gruß
    ~blaze~
    Segmentierung - aber normal kümmert sich da doch Tcp drum, oder? Also irgendwas internes im Networkstream oder TcpClient hätte ich angenommen.
    Von Dateistreams zb ist mir bekannt, dass die intern über einen Puffer verfügen, und nicht jedes popelige Byte einzeln an den Festplatten-Schreibkopf senden müssen.
    Gedacht ist, dass man parallel z.B. eine Datei und Text senden kann, ohne, dass auf den Abschluss des jeweils vorangehenden Vorgangs gewartet werden muss. Es werden halt dann jeweils maximal x Bytes eines Datenstroms genommen und übertragen und dann das gleiche für die nächste ausstehende Nachricht. Und das halt über einen einzigen Tcp-Datenstrom.

    Gruß
    ~blaze~
    Ja, aber TCP sorgt trotzdem für die richtige Reihenfolge und vollständigkeit der Pakete. Somit ist es im Prinzip ein ganzer Datenstrom, wie das intern implementiert ist, ist ja nach außen hin egal. Das ist ja das tolle an Abstraktion ;)
    Solch eine parallelität ist bei Chats, die etwas mehr als nur Nachrichten verschicken können wichtig, aber bei den meisten Anwendungen, die hier gebastelt werden denke ich das nicht :D
    Ich wollte auch mal ne total überflüssige Signatur:
    ---Leer---
    Ich muss das Thema doch noch mal ausgraben, denn ich habe nun wieder dieses Problem:
    Ich möchte gerne über den TCP-Server Dateien übertragen. Eigentlich ja kein Problem: Pakete erstellen und verschicken. Das ganze klappt sogar manchmal, aber vorwiegend ist die Datei "anders", also unvollständig und somit nicht mehr zu gebrauchen.
    Tatsache ist, dass die Übertragene Datei auf den Byte genau so groß wie die Ursprungsdatei ist. Ich habe eingebaut, dass jedes zu versendende und jedes empfangene ByteArray als Liste in eine Text-Datei geschrieben wird.
    Der Code für den Log (nur zum Verständnis):
    Spoiler anzeigen

    VB.NET-Quellcode

    1. Dim counter As Integer = 0
    2. Private Function ByteArrayToString(bytes As Byte()) As String
    3. counter += 1
    4. Dim result As New Text.StringBuilder()
    5. result.AppendLine("----New Packet" & counter.ToString() & "----")
    6. For Each b In bytes
    7. result.AppendLine(b.ToString())
    8. Next
    9. Return result.ToString()
    10. End Function


    Diese Funktion ist auf beiden Seiten exakt gleich. Nun habe ich auf der Website diffchecker.com das erste Viertel dieser beiden Dateien verglichen und folgendes ist dabei raus gekommen:


    Die Dateien sind fast komplett gleich außer diese eine Stelle, wo die ersten 56 Bytes des Paketes nicht übertragen werden. Warum? Ich dachte immer, dass TCP wenn es um das Versenden der Pakete geht ziemlich sicher ist.
    Mein Code:

    Client: (Senden)

    VB.NET-Quellcode

    1. Dim fiSource As New FileInfo(path)
    2. Dim buffersize = 1024
    3. Using fs As New FileStream(fiSource.FullName, FileMode.Open, FileAccess.Read)
    4. Dim NoOfPackets As Integer = Convert.ToInt32(Math.Ceiling(Convert.ToDouble(fs.Length) / Convert.ToDouble(buffersize)))
    5. Dim TotalLength As Integer = CInt(fs.Length), CurrentPacketLength As Integer
    6. Dim SendingBuffer As Byte()
    7. Dim TotalLengthBytes = BitConverter.GetBytes(TotalLength)
    8. SendPacket(TotalLengthBytes.ToArray())
    9. _netstream.Read(New Byte(0) {}, 0, 1)
    10. For i As Integer = 0 To NoOfPackets - 1
    11. If TotalLength > buffersize Then
    12. CurrentPacketLength = buffersize
    13. TotalLength -= CurrentPacketLength
    14. SendingBuffer = New Byte(buffersize - 1) {}
    15. Else
    16. CurrentPacketLength = TotalLength
    17. SendingBuffer = New Byte(CurrentPacketLength - 1) {}
    18. End If
    19. fs.Read(SendingBuffer, 0, CurrentPacketLength)
    20. _netstream.Read(New Byte(0) {}, 0, 1) 'Aus irgendeinem Grund ist die Client immer schneller mit dem Verschicken als der Server mit dem Lesen. Also muss der Server immer einen Byte schicken, um zu sagen, dass er bereit ist, bevor der Client das nächste Paket versendet
    21. SendPacket(SendingBuffer)
    22. IO.File.AppendAllText("log.txt", ByteArrayToString(SendingBuffer))
    23. Next
    24. End Using
    25. Private Sub SendPacket(bytes As Byte())
    26. Dim lst As New List(Of Byte)
    27. lst.AddRange(Text.Encoding.ASCII.GetBytes("dt")) 'Mein System ist so aufgebaut, dass jedes Paket einen Token haben muss. dt heißt für den Client Listner nichts anderes als "einfach ignorieren". Die ersten zwei Bytes sind dann trotzdem schon gelesen
    28. lst.AddRange(bytes)
    29. Dim array = lst.ToArray()
    30. _netstream.Write(array, 0, array.Length)
    31. Debug.Print("Send: {0}", bytes.Length)
    32. End Sub


    Server (empfangen):

    VB.NET-Quellcode

    1. Dim totallengthbytes As Byte() = New Byte(3) {}
    2. netstream.Read(totallengthbytes, 0, 4)
    3. Dim totallength = BitConverter.ToInt32(totallengthbytes, 0)
    4. _connection.ListnerIsEnabled = False
    5. Dim currentlength = totallength
    6. Dim fs As New FileStream(localpath, FileMode.OpenOrCreate, FileAccess.Write)
    7. netstream.Write(New Byte(0) {}, 0, 1)
    8. While True
    9. Dim recdata As Byte()
    10. If currentlength < buffersize Then
    11. recdata = New Byte(currentlength - 1) {}
    12. currentlength = 0
    13. Else
    14. recdata = New Byte(buffersize - 1) {}
    15. currentlength -= buffersize
    16. End If
    17. Debug.Print("Read: {0}", recdata.Length)
    18. netstream.Write(New Byte(0) {}, 0, 1) 'Nachricht an den Client: "Kannst weiter machen, ich bin bereit für das nächste Paket"
    19. netstream.Read(recdata, 0, recdata.Length)
    20. IO.File.AppendAllText("log.txt", ByteArrayToString(recdata))
    21. Debug.Print("Readed: {0}", recdata.Length)
    22. fs.Write(recdata, 0, recdata.Length)
    23. Dim progress As Double = (totallength - currentlength) / totallength
    24. If 1 > progress Then
    25. RaiseEvent ProgressChanged(Me, New FileProgressChangedEventArgs(progress, String.Format("{0} von {1}", RoundSize.RoundSize(totallength - currentlength), RoundSize.RoundSize(totallength))))
    26. End If
    27. If currentlength = 0 Then
    28. RaiseEvent UploadFinished(Me, EventArgs.Empty)
    29. Exit While
    30. End If
    31. End While
    32. _connection.ListnerIsEnabled = True
    33. fs.Close()



    Vielleicht fällt ja Jemandem etwas auf :)
    Mfg
    Vincent

    naja, bei Tcp brauchst du dich eigentlich nicht um die Pakete zu kümmern.

    Der Sender soll die Dateilänge senden, und dann die Datei.
    Der Empfänger empfängt die Dateilänge, und liest dann genau so viele Bytes aus dem Networkstream, und schreibt sie in die Ziel-Datei.

    fertig.

    Der empfänger kann ja auch eine Schleife haben, die mw. immer 1024 Bytes liest und wegschreibt in die Ziel-Datei. Dann braucht man nicht ein riesiges Byte-Array bereitstellen.