VB.NET - Daten vom TCP-Client ist nicht in Ordnung

Ich arbeite seit ungefähr einem Jahr an diesem Projekt. Es ist ein einfaches Client/Server-Chat-Programm. Nach einer langen Zeit der Verbesserung entschied ich mich, die Stärke meines Servers zu testen.

Auf dem Client habe ich 200 Chat-Nachrichten ("FLOOD # 1" ... "FLOOD # 200") so schnell wie möglich an den Server abgefeuert. Das Ergebnis: Der Server stürzt sofort ab. Nach ein paar leichten Manipulationen konnte ich den Server dazu bringen, 135 der 200 Nachrichten zu verarbeiten, bevor ich aufgab. Es stürzt nicht mehr ab, aber etwas anderes passiert. Die Daten vom Client werden der Reihe nach empfangen, aber wenn ich diese Nachricht an eine Funktion übergebe ( myForm.OnLineReceived ), sind die Daten vollständig außer Betrieb. Wenn ich eine leichte Verzögerung zwischen dem Aufruf der OnLineRecieved-Funktion hinzufüge, sind die Nachrichten in perfekter Reihenfolge.

Jede Nachricht vom Client wird zuerst verschlüsselt und dann in base64 codiert. Ein "-" wird an das Ende angehängt, so dass der Server das Ende jedes Daten- "Pakets" leicht finden kann.

Ich bin mir sicher, dass es ein dummer Fehler ist, den ihr leicht findet und auf mich aufmerksam macht. Danke für einen Blick;)

Servercode:

Imports System.Net.Sockets
Imports System.Text

' The UserConnection class encapsulates the functionality of a TcpClient connection
' with streaming for a single user.
Public Class UserConnection

Private client As TcpClient
Private readBuffer(READ_BUFFER_SIZE) As Byte

Public UID As String = ""
Public isAdmin As Boolean
Public IpAddress As String
Public username As String = ""
Public Country As String = ""
Public ServerID As String = ""
Public Status As String = ""
Public UserComp As String = ""
Public OS As String = ""

Public SessionKey As String = ""
Public UsePublicKeyEncryption As Boolean = True

Public Version As Decimal = 0.0

Const READ_BUFFER_SIZE As Integer = 500

Private _commands As New System.Text.StringBuilder
Private command_count As Integer = 1

' Overload the New operator to set up a read thread.
Public Sub New(ByVal client As TcpClient) 'this runs every time a new client is added
    Me.client = client
    IpAddress = Me.client.Client.RemoteEndPoint.ToString.Substring(0, Me.client.Client.RemoteEndPoint.ToString.LastIndexOf(":")) 'ip address of client
    ' This starts the asynchronous read thread.  The data will be saved into
    ' readBuffer.
    Call Worker()
End Sub

Public Sub ForceKill()
    On Error Resume Next
    client.GetStream.Close()
    client.Close()
    client = Nothing
End Sub

Private Sub Worker()
    Try
        SyncLock client
            Dim tmp_byte(client.ReceiveBufferSize) As Byte
            Me.client.GetStream.BeginRead(tmp_byte, 0, client.ReceiveBufferSize, AddressOf RecieveDataAndSplit, Nothing)
            readBuffer = tmp_byte
        End SyncLock

    Catch
        Call myForm.OnLineReceived(Me, "D") 'this also calls ForceKill()
    End Try
End Sub


Public Event LineReceived(ByVal sender As UserConnection, ByVal Data As String)

' This subroutine uses a StreamWriter to send a message to the user.
Public Sub SendData(ByVal Data As String)
    ' Synclock ensure that no other threads try to use the stream at the same time.
    SyncLock client
        Dim writer As New IO.StreamWriter(client.GetStream)
        writer.Write(ToBase64(AES_Encrypt(Data, SessionKey)) & "-")
        ' Make sure all data is sent now.
        writer.Flush()
    End SyncLock
End Sub



Public Sub RecieveDataAndSplit(ByVal ar As IAsyncResult) 'this is the FIRST function that incoming data is ran through
    Dim BytesRead As Integer
    Dim Content As String

    Try
        ' Ensure that no other threads try to use the stream at the same time.
        SyncLock client
            ' Finish asynchronous read into readBuffer and get number of bytes read.
            BytesRead = client.GetStream.EndRead(ar)
        End SyncLock
    Catch e As Exception
        Call myForm.OnLineReceived(Me, "D") 'couldn't read the stream from the client. Kill our connection with them :P
        Exit Sub
    End Try



    Try
        Content = Encoding.ASCII.GetString(readBuffer, 0, BytesRead)
    Catch ex As Exception
        Call Worker()
        Exit Sub
    End Try


    Dim commands() As String
    Try
        commands = LineTrim(Content).Split("-")
    Catch
    End Try


    Dim i As Integer = 0

    For i = 0 To commands.Length - 1

        If commands(i) <> "" Then

            Dim decrypted_content As String = AES_Decrypt(FromBase64(commands(i)), SessionKey)
            If decrypted_content <> "" Then

                'If decrypted_content = "D" Or Nothing Then
                '    client.GetStream.Close()
                '    client.Close()
                '    Call myForm.OnLineReceived(Me, decrypted_content)
                'Else

                Call myForm.OnLineReceived(Me, decrypted_content)
                Call Worker() 'reads the stream again
                'End If
            End If
        End If
    Next

End Sub
End Class

Kundencode:

Public Sub SendData(ByVal data As String)
    Try
        If data = "D" Then 'telling server that we're closing
            ForceDisconnect(False)
        Else 'any other message
            Dim sendBytes As [Byte]()

            sendBytes = Encoding.ASCII.GetBytes(ToBase64(AES_Encrypt(data, SessionKey)) & "-")

            Dim networkStream As NetworkStream = tcp_client.GetStream()
            networkStream.Write(sendBytes, 0, sendBytes.Length)
            networkStream.Flush()
        End If
    Catch ex As Exception
        connection_state_toggle(False)

        Label1.ForeColor = Color.Black
        Label1.Text = "Idle"
    End Try



End Sub
3
Erstellen Sie eine Verbindung mit jeder Nachricht oder verwenden Sie nur eine Verbindung, um alle Nachrichten zu senden?
hinzugefügt der Autor BlackICE, Quelle
Eine Verbindung für alle Nachrichten.
hinzugefügt der Autor Andrew Paglusch, Quelle

3 Antworten

Klassischer TCP/IP-Netzwerkfehler. Sie gehen davon aus, dass die gesendeten Daten in Nachrichten oder Paketen enthalten sind, aber es ist wirklich ein Stream. Nehmen wir an, Ihr Client sendet message1-message2-message3-message4. Auf der Serverseite auf Ihrem Lese-Callback erhalten Sie:

message1-m

oder

message1-message2-

oder

message1-message2-message3-message4

oder just

m

Denken Sie darüber nach, was mit Ihrem Parsing-Code (das Teilen von Befehlen) passiert, wenn Sie Nachrichten wie diese fragmentieren. Ein guter TCP/IP-Code sollte in der Lage sein, den Empfang von Daten pro Lesevorgang zu überstehen. Wenn das nicht möglich ist, müssen Sie Probleme bekommen.

The typical approach is to keep adding to a buffer and inspecting the buffer each time foder a completed message and then poping off just that message, leaving any partial message trailing in the buffer to get filled out later. Checks foder DOS attacks/problems like discarding the buffer if it gets too large (based on your protocol) should be added at some point as well.

2
hinzugefügt
Im Nachhinein ist es schwer vorauszusagen, auf wie viele Arten der Code brechen kann, da die Nachrichten auf verschiedene Arten fruggnet werden können. Manchmal würde deine Entschlüsselung fehlschlagen, stelle ich mir vor. Wenn Sie bei einem Empfang zwei Nachrichten erhalten haben, sieht es so aus, als würden Sie Call Worker() zweimal statt einmal wie beabsichtigt aufrufen.
hinzugefügt der Autor tcarvin, Quelle
Froh, dass ich Helfen kann. Wenn Sie die Antwort als richtig betrachten, kennzeichnen Sie sie bitte als akzeptiert. Sie werden feststellen, dass mehr Benutzer bereit sein werden, sich die Zeit zu nehmen, um Ihre zukünftigen Fragen zu beantworten, wenn sie sehen, dass Sie sich die Zeit nehmen, die Antworten zu akzeptieren, wenn dies angebracht ist :)
hinzugefügt der Autor tcarvin, Quelle
Ich hatte Angst, dass das passiert. Aber wie könnten Sie erklären, dass die Nachrichten neu geordnet werden? Ich bekomme Nachricht Nr. 4 nach der 80. Nachricht.
hinzugefügt der Autor Andrew Paglusch, Quelle
Danke, dass du auf den Code 'Call Worker ()' hingewiesen hast. Es sind einige Reste von einer Debugging-Raserei :)
hinzugefügt der Autor Andrew Paglusch, Quelle
Habe einen funktionierenden Code. Wird so schnell wie möglich posten
hinzugefügt der Autor Andrew Paglusch, Quelle
            Dim thrd As New System.Threading.Thread(AddressOf RecieveDataAndSplit)
            thrd.Start(bytesFrom)
            thrd.Join()

Sie erstellen neue Threads, um die Daten zu verarbeiten. Es gibt keine Garantie für die Reihenfolge, in der diese Threads CPU-Zeit erhalten, und daher der Grund dafür, dass sie nicht in der richtigen Reihenfolge hinzugefügt werden.


    SyncLock client.GetStream
        Dim tmp_byte(client.ReceiveBufferSize) As Byte
        Me.client.GetStream.BeginRead(tmp_byte, 0, client.ReceiveBufferSize, AddressOf RecieveDataAndSplit, Nothing)
        readBuffer = tmp_byte
    End SyncLock

Ich habe gerade nichts mit mir zu debuggen, aber ich frage mich, ob GetStream bei jedem Anruf eine andere Objektreferenz zurückgibt, was die Synclock für das, was Sie versuchen, unwirksam macht. Ich würde versuchen, Synclock nur auf Client zu tun.

1
hinzugefügt
Ja, das tut es, ich dachte, du hättest einen Thread in einem Thread, der dort arbeitet, aber der alte Code ist weg ...
hinzugefügt der Autor BlackICE, Quelle
hängt davon ab, wo Sie es nennen, wenn Sie eine Reihe von Threads erstellen, dann beitreten jedes wird es nicht in der Reihenfolge zu halten, wenn Sie erstellen und verbinden, bevor Sie mehr erstellen sollte es, aber dann was ist der Sinn von das Threading machen.
hinzugefügt der Autor BlackICE, Quelle
Post Client Code auch wenn Sie können und ich werde versuchen, einen Blick darauf zu werfen, wenn ich zu einem Debugger komme.
hinzugefügt der Autor BlackICE, Quelle
Ich habe versehentlich einen alten Code gepostet. Ich habe es mit meiner neuesten Version aktualisiert. Und teilt "Join ()" dem aufrufenden Thread nicht mit, die Ausführung anzuhalten, bis der erstellte Thread beendet wurde? Danke für die Antwort!
hinzugefügt der Autor Andrew Paglusch, Quelle
Join() sollte also die Funktionsaufrufe in Ordnung halten, oder?
hinzugefügt der Autor Andrew Paglusch, Quelle
Ich werde es testen, indem ich den Client einfach syncludiere ...
hinzugefügt der Autor Andrew Paglusch, Quelle
SyncLock auf nur "Client" zeigt keine Verbesserung. Die Nachrichten werden immer noch vermischt.
hinzugefügt der Autor Andrew Paglusch, Quelle
Ich habe es gerade gepostet
hinzugefügt der Autor Andrew Paglusch, Quelle

Ok, dank der Eingabe von allen (vor allem Tcarvins Idee, einen 'Puffer' von Teilbefehlen zu machen), habe ich es geschafft, ein paar clevere Codes zum Laufen zu bringen!

Hoffentlich wird dies anderen die Tage der Agonie ersparen, die ich selbst durchgemacht habe.

Hier ist der 100% Arbeitscode:

Imports System.Net.Sockets
Imports System.Text

' The UserConnection class encapsulates the functionality of a TcpClient connection
' with streaming for a single user.
Public Class UserConnection

Private client As TcpClient

Private income_message_buffer As New System.Text.StringBuilder 'all new messages are added onto the end of this. messages are pulled from the beginning in a timely manner

Public Sub Run(ByVal client As TcpClient)
    Me.client = client
    Call MessageParser()
End Sub

Public Sub ForceKill()
    On Error Resume Next
    client.GetStream.Close()
    client.Close()
    client = Nothing
End Sub

Private Sub MessageParser()
    Do

        If client.Connected = True Then
            If client.GetStream.DataAvailable = True Then
                Dim tmp_byte(client.ReceiveBufferSize) As Byte
                Dim BytesRead As Integer
                Dim content As String

                SyncLock client
                    BytesRead = Me.client.GetStream.Read(tmp_byte, 0, client.ReceiveBufferSize)
                End SyncLock

                Try
                    content = Encoding.ASCII.GetString(tmp_byte, 0, BytesRead)
                    income_message_buffer.Append(LineTrim(content))
                Catch ex As Exception

                End Try


            End If
        End If


        Dim EndOfFirstMessage As Integer = income_message_buffer.ToString.IndexOf("-") 'gets the first occurace of "-" in the buffer
        If EndOfFirstMessage >= 0 Then
            Dim message As String = income_message_buffer.ToString.Substring(0, EndOfFirstMessage) 'gets everything before the "-"
            income_message_buffer.Remove(0, EndOfFirstMessage + 1) 'removes the first message AND the "-"
            Call ParseMessage(message)
        End If



    Loop
End Sub

Public Event LineReceived(ByVal sender As UserConnection, ByVal Data As String)

' This subroutine uses a StreamWriter to send a message to the user.
Public Sub SendData(ByVal Data As String)
    ' Synclock ensure that no other threads try to use the stream at the same time.
    SyncLock client
        Dim writer As New IO.StreamWriter(client.GetStream)
        writer.Write(ToBase64(AES_Encrypt(Data, SessionKey)) & "-")
        ' Make sure all data is sent now.
        writer.Flush()
    End SyncLock
End Sub



Public Sub ParseMessage(ByVal message As String) 'this is the FIRST function that incoming data is ran through

    Dim decrypted_content As String = AES_Decrypt(FromBase64(message), SessionKey)

    If decrypted_content <> "" Then
        Call myForm.OnLineReceived(Me, decrypted_content)
    End If


End Sub
End Class
1
hinzugefügt