VB.NET - данные из TCP-клиента не в порядке

Я работаю над этим проектом уже около года. Это базовая клиентская/серверная чат-программа. После длительного улучшения я решил проверить силу своего сервера.

На клиенте я выпустил 200 сообщений чата («FLOOD # 1» ... «FLOOD # 200») на сервер как можно быстрее. Результат: Сервер немедленно сбой. После небольшого вмешательства я смог заставить сервер обрабатывать 135 из 200 сообщений, прежде чем сдаваться. Он больше не падает, но происходит что-то другое. Данные от клиента принимаются по порядку, но когда я передаю это сообщение функции ( myForm.OnLineReceived ), данные полностью выходят из строя. Если я добавлю небольшую задержку между вызовом функции OnLineRecieved, сообщения находятся в полном порядке.

Каждое сообщение от клиента сначала зашифровывается, а затем закодировано в base64. К концу добавляется «-», так что сервер может легко найти конец каждого «пакета» данных.

Я уверен, что это какая-то глупая ошибка, которую вы, ребята, легко найдете и укажете мне. Спасибо, что посмотрели;)

Код сервера:

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

Код клиента:

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
вы создаете соединение с каждым сообщением или используете только одно соединение для отправки всех сообщений?
добавлено автор BlackICE, источник
Одно соединение для всех сообщений.
добавлено автор Andrew Paglusch, источник

3 ответы

Классическая ошибка TCP/IP. Вы считаете, что данные отправлены в сообщениях или пакетах, но это действительно поток. Предположим, что ваш клиент отправляет сообщение1-message2-message3-message4. На стороне сервера на вашем обратном вызове вы можете получить:

message1-m

или

message1-message2-

или

message1-message2-message3-message4

или just

m

Подумайте о том, что происходит с вашим кодом разбора (расщепление команд), когда вы получаете фрагментированные сообщения. Хороший код TCP/IP должен иметь возможность выжить при получении данных на один байт на чтение. Если это невозможно, вы должны столкнуться с проблемами.

The typical approach is to keep adding to a buffer and inspecting the buffer each time fили a completed message and then poping off just that message, leaving any partial message trailing in the buffer to get filled out later. Checks fили 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
добавлено
Для отслеживания трудно предсказать, как много способов может сломаться код, потому что сообщения могут frgamnet различными способами. Иногда ваш дешифр не мог представить себе. Если вы получили два сообщения на одном из них, похоже, что вы бы Call Worker() дважды, а не один раз, как предполагалось.
добавлено автор tcarvin, источник
Рад помочь. Если вы считаете ответ правильным, отметьте его как принятый. Вы будете знать, что больше пользователей захочет потратить время, чтобы ответить на ваши будущие вопросы, если они смогут увидеть, что вы нашли время, чтобы принять ответы, когда это необходимо :)
добавлено автор tcarvin, источник
Я боялся, что это то, что происходит. Но как вы могли объяснить, что сообщения будут перегруппированы? Сообщение об ошибке №4 после 80-го сообщения.
добавлено автор Andrew Paglusch, источник
Спасибо, что указали на дубликат кода Call Worker (). Это остатки от отладочного безумия :)
добавлено автор Andrew Paglusch, источник
Получил некоторый рабочий код. Будет опубликован как можно скорее (может быть, завтра)
добавлено автор Andrew Paglusch, источник
            Dim thrd As New System.Threading.Thread(AddressOf RecieveDataAndSplit)
            thrd.Start(bytesFrom)
            thrd.Join()

Вы создаете новые потоки для обработки данных, нет гарантии того, как эти потоки получат процессорное время, поэтому причина их добавления не соответствует порядку.


    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

Мне сейчас нечего отлаживать, но мне интересно, сможет ли GetStream возвращать разные ссылки на объекты каждый раз, когда вы его вызываете, тем самым делая Synclock неэффективным для того, что вы пытаетесь сделать. Я бы попытался сделать Synclock только на клиенте.

1
добавлено
Да, это так, я думал, что у вас есть поток в потоке, который работает там, но старый код ушел ...
добавлено автор BlackICE, источник
зависит от того, где вы его вызываете, если вы создаете кучу потоков, а затем присоединяетесь к каждому из них, это не будет приводить их в порядок, если вы создадите их и присоединитесь, прежде чем создавать больше, чем нужно, но тогда какая точка выполняя резьбу.
добавлено автор BlackICE, источник
почтовый клиентский код, если это возможно, и я попытаюсь взглянуть на него, когда смогу добраться до отладчика.
добавлено автор BlackICE, источник
Я случайно отправил старый код. Я обновил его с помощью моей последней версии. И не «Join ()» сообщает вызывающему потоку приостановить выполнение до тех пор, пока не завершит созданный поток? Спасибо за ответ!
добавлено автор Andrew Paglusch, источник
Итак, если Join() должен поддерживать порядок вызовов функций, правильно?
добавлено автор Andrew Paglusch, источник
Я собираюсь проверить это, просто синхронизируя клиента ...
добавлено автор Andrew Paglusch, источник
SyncLock на просто «клиент» не показывает улучшения. Сообщения все еще запутались.
добавлено автор Andrew Paglusch, источник
Я только что разместил его
добавлено автор Andrew Paglusch, источник

Хорошо, благодаря вкладу всех (в основном идея tcarvin о создании «буфера» частичных команд), мне удалось заставить работать какой-нибудь шикарный код!

Надеюсь, это спасет других в дни агонии, которые я пережил сам.

Вот 100% рабочий код:

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
добавлено
DotNetRuChat
DotNetRuChat
2 992 участник(ов)

Чат русскоязычного .NET сообщества http://dotnet.ru/ Вам могут быть интересны: @dotnetchat, @cilchat, @fsharp_chat, @pro_net, @xamarin_russia, @microsoftstackjobs, @uwp_ru Флуд в @dotnettalks

Microsoft Stack Jobs
Microsoft Stack Jobs
1 788 участник(ов)

Work & freelance only Microsoft Stack. Feed https://t.me/Microsoftstackjobsfeed Чат про F#: @Fsharp_chat Чат про C#: @CSharpChat Чат про Xamarin: @xamarin_russia Чат общения:@dotnettalks

pro.net
pro.net
710 участник(ов)

Обсуждение .NET Framework и всего, что с ним связано. Правила: не флудить не по теме, уважать ваших коллег и никакой рекламы (объявления о вакансиях можно согласовать с @AlexFails). Флудилка: @dotnettalks Участник @proDOT

Microsoft Developer Community Chat
Microsoft Developer Community Chat
584 участник(ов)

Чат для разработчиков и системных администраторов Microsoft Developer Community. __________ Новостной канал: @msdevru __________ Баним за: оскорбления, мат, рекламу, флуд, флейм, спам, NSFW контент, а также большое количество оффтоп тем. @banofbot

.NET Talks: Force Push Masters
.NET Talks: Force Push Masters
490 участник(ов)

Свободный чат .NET разработчиков. Правила: t.me/dotnettalks/56823 Вам могут быть интересны: @dotnetruchat, @dotnetchat, @cilchat, @fsharp_chat, @pro_net, @dotnetgroup, @xamarin_russia, @microsoftstackjobs, @uwp_ru http://combot.org/chat/-1001128250813

.NET Chat Убежище
.NET Chat Убежище
246 участник(ов)

Чат .NET разработчиков под эгидой MSK/SPB .NET Community Group Вам могут быть интересны: @fsharp_chat, @dotnetruchat, @cilchat, @xamarin_russia, @microsoftstackjobs, @dotnetgroup Флуд в @dotnettalks

.NET CIL Chat
.NET CIL Chat
54 участник(ов)

.NET CIL (aka IL aka MSIL)