VB.NET-来自TCP客户端的数据是';井然有序

VB.NET-来自TCP客户端的数据是';井然有序,vb.net,tcp,client,buffer,Vb.net,Tcp,Client,Buffer,我已经在这个项目上工作了大约一年了。这是一个基本的客户端\服务器聊天程序。经过长时间的改进,我决定测试一下服务器的性能 在客户端,我尽可能快地向服务器发送了200条聊天信息(“FLOOD#1”…“FLOOD#200”)。结果是:服务器立即崩溃。经过一些轻微的篡改,我能够让服务器在放弃之前处理200条消息中的135条。它不再崩溃,但发生了一些不同的事情。来自客户端的数据是按顺序接收的,但是当我将该消息传递给函数(myForm.OnLineReceived)时,数据完全是无序的。如果我在调用OnLi

我已经在这个项目上工作了大约一年了。这是一个基本的客户端\服务器聊天程序。经过长时间的改进,我决定测试一下服务器的性能

在客户端,我尽可能快地向服务器发送了200条聊天信息(“FLOOD#1”…“FLOOD#200”)。结果是:服务器立即崩溃。经过一些轻微的篡改,我能够让服务器在放弃之前处理200条消息中的135条。它不再崩溃,但发生了一些不同的事情。来自客户端的数据是按顺序接收的,但是当我将该消息传递给函数(myForm.OnLineReceived)时,数据完全是无序的。如果我在调用OnLineRecieved函数之间加上一点延迟,消息的顺序就完美了

来自客户机的每条消息首先进行加密,然后在base64中进行编码。结尾处添加“-”,以便服务器可以轻松找到每个数据“包”的结尾

我相信这是一些愚蠢的错误,你们会很容易发现并向我指出。感谢您的关注;)

服务器代码: 当您创建新线程来处理数据时,无法保证这些线程获得CPU时间的顺序,因此添加它们的原因是无序的



我现在没有什么需要调试的,但是我想知道GetStream是否会在每次调用它时返回不同的对象引用,从而使Synclock对您尝试执行的操作无效。我会尝试在客户端上进行同步锁定。

典型的TCP/IP网络错误。您假设发送的数据是消息或数据包,但它实际上是一个流。假设您的客户机发送message1-message2-message3-message4。在读取回调的服务器端,您可能会得到:

message1-m

或者只是

m
考虑一下当您得到这样的分段消息时,解析代码(命令的拆分)会发生什么情况。好的TCP/IP代码应该能够在每次读取一个字节的数据后存活下来。如果做不到,那么你肯定会遇到问题


典型的方法是不断添加到缓冲区,每次检查缓冲区是否有已完成的消息,然后只弹出该消息,在缓冲区中留下任何部分消息以供以后填写。检查DOS攻击/问题,如缓冲区过大时丢弃缓冲区(根据您的协议),也应该在某个时候添加。

好的,多亏了大家的投入(主要是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
导入System.Net.Sockets
导入系统文本
'UserConnection类封装了TcpClient连接的功能
'为单个用户提供流媒体服务。
公共类用户连接
作为TcpClient的私有客户端
Private income_message_buffer As New System.Text.StringBuilder'所有新邮件都添加到此文件末尾。消息从一开始就被及时地提取出来
公共子运行(ByVal客户端作为TcpClient)
Me.client=client
调用MessageParser()
端接头
公共分支机构kill()
出错时继续下一步
client.GetStream.Close()
client.Close()
客户机=无
端接头
私有子消息解析器()
做
如果client.Connected=True,则
如果client.GetStream.DataAvailable=True,则
Dim tmp_字节(client.ReceiveBufferSize)作为字节
Dim字节读取为整数
将内容设置为字符串
同步锁客户端
BytesRead=Me.client.GetStream.Read(tmp_字节,0,client.ReceiveBufferSize)
端同步
尝试
content=Encoding.ASCII.GetString(tmp_字节,0,字节读取)
收入\消息\缓冲区。追加(线条修剪(内容))
特例
结束尝试
如果结束
如果结束
Dim EndOfFirstMessage As Integer=income\u message\u buffer.ToString.IndexOf(“-”)获取缓冲区中第一个出现的“-”
如果EndOfFirstMessage>=0,则
Dim message As String=income\u message\u buffer.ToString.Substring(0,EndOfFirstMessage)'获取“-”之前的所有内容
income_message_buffer.Remove(0,EndOfFirstMessage+1)'删除第一条消息和“-”
呼叫解析消息(消息)
如果结束
环
端接头
已接收公共事件行(ByVal发送方作为用户连接,ByVal数据作为字符串)
'此子例程使用StreamWriter向用户发送消息。
公共子SendData(ByVal数据作为字符串)
'同步锁定确保没有其他线程试图同时使用该流。
同步锁客户端
Dim编写器作为新IO.StreamWriter(client.GetStream)
Write.Write(ToBase64(AES_加密(数据、会话密钥))和“-”)
'确保现在发送所有数据。
writer.Flush()
端同步
端接头
Public Sub ParseMessage(ByVal消息作为字符串)'这是传入数据运行的第一个函数
Dim解密的内容为String=AES\u Decrypt(来自Base64(消息),会话密钥)
如果已解密内容“”,则
调用myForm.OnLineReceived(我,解密的内容)
如果结束
端接头
末级

我无意中发布了一些旧代码。我已经用我的最新版本更新了它。“Join()”没有告诉调用线程暂停执行,直到创建的线程退出吗?谢谢你的回复!是的,确实如此,我还以为你有一个线程在那里工作,但是旧代码已经不存在了…所以,使用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
message1-m
message1-message2-
message1-message2-message3-message4
m
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