套接字聊天无法发送完整消息的问题

时间:2014-07-30 15:44:41

标签: vb.net sockets

我对这个套接字聊天有一点问题,如果我发送像“你好世界”这样的小消息它完全正常但是如果我尝试发送大约10,000个字符的巨大消息它只是崩溃,我一直在努力在过去3天没有运气的情况下解决这个问题,我该怎么办?

服务器来源:

Imports System.Text
Imports System.Net
Imports System.Net.Sockets

Enum Command
    Login
    'Log into the server
    Logout
    'Logout of the server
    Message
    'Send a text message to all the chat clients
    List
    'Get a list of users in the chat room from the server
    Null
    'No command
End Enum


Public Class Form1


    'The ClientInfo structure holds the required information about every
    'client connected to the server
    Private Structure ClientInfo
        Public socket As Socket
        'Socket of the client
        Public strName As String
        'Name by which the user logged into the chat room
    End Structure

    'The collection of all clients logged into the room (an array of type ClientInfo)
    Private clientList As ArrayList

    'The main socket on which the server listens to the clients
    Private serverSocket As Socket

    Private byteData As Byte() = New Byte(1023) {}



    Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
        Try
            clientList = New ArrayList()

            'We are using TCP sockets
            serverSocket = New Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp)

            'Assign the any IP of the machine and listen on port number 1000
            Dim ipEndPoint As New IPEndPoint(IPAddress.Any, 2277)

            'Bind and listen on the given address
            serverSocket.Bind(ipEndPoint)
            serverSocket.Listen(4)

            'Accept the incoming clients
            serverSocket.BeginAccept(New AsyncCallback(AddressOf OnAccept), Nothing)
        Catch ex As Exception
            MessageBox.Show(ex.Message, "SGSserverTCP", MessageBoxButtons.OK, MessageBoxIcon.[Error])
        End Try
    End Sub

    Private Sub OnAccept(ar As IAsyncResult)
        Try
            Dim clientSocket As Socket = serverSocket.EndAccept(ar)

            'Start listening for more clients
            serverSocket.BeginAccept(New AsyncCallback(AddressOf OnAccept), Nothing)

            'Once the client connects then start receiving the commands from her
            clientSocket.BeginReceive(byteData, 0, byteData.Length, SocketFlags.None, New AsyncCallback(AddressOf OnReceive), clientSocket)
        Catch ex As Exception
            MessageBox.Show(ex.Message, "SGSserverTCP", MessageBoxButtons.OK, MessageBoxIcon.[Error])
        End Try
    End Sub

    Private Sub OnReceive(ar As IAsyncResult)
        Try
            Dim clientSocket As Socket = DirectCast(ar.AsyncState, Socket)
            clientSocket.EndReceive(ar)

            'Transform the array of bytes received from the user into an
            'intelligent form of object Data
            Dim msgReceived As New Data(byteData)

            'We will send this object in response the users request
            Dim msgToSend As New Data()

            Dim message As Byte()

            'If the message is to login, logout, or simple text message
            'then when send to others the type of the message remains the same
            msgToSend.cmdCommand = msgReceived.cmdCommand
            msgToSend.strName = msgReceived.strName

            Select Case msgReceived.cmdCommand
                Case Command.Login

                    'When a user logs in to the server then we add her to our
                    'list of clients

                    Dim clientInfo As New ClientInfo()
                    clientInfo.socket = clientSocket
                    clientInfo.strName = msgReceived.strName

                    clientList.Add(clientInfo)

                    'Set the text of the message that we will broadcast to all users
                    msgToSend.strMessage = "<<<" & msgReceived.strName & " has joined the room>>>"
                    Exit Select

                Case Command.Logout

                    'When a user wants to log out of the server then we search for her 
                    'in the list of clients and close the corresponding connection

                    Dim nIndex As Integer = 0
                    For Each client As ClientInfo In clientList
                        If client.socket Is clientSocket Then
                            clientList.RemoveAt(nIndex)
                            Exit For
                        End If
                        nIndex += 1
                    Next

                    clientSocket.Close()

                    msgToSend.strMessage = "<<<" & msgReceived.strName & " has left the room>>>"
                    Exit Select

                Case Command.Message

                    'Set the text of the message that we will broadcast to all users
                    msgToSend.strMessage = msgReceived.strName & ": " & msgReceived.strMessage
                    Exit Select

                Case Command.List

                    'Send the names of all users in the chat room to the new user
                    msgToSend.cmdCommand = Command.List
                    msgToSend.strName = Nothing
                    msgToSend.strMessage = Nothing

                    'Collect the names of the user in the chat room
                    For Each client As ClientInfo In clientList
                        'To keep things simple we use asterisk as the marker to separate the user names
                        msgToSend.strMessage += client.strName & "*"
                    Next

                    message = msgToSend.ToByte()

                    'Send the name of the users in the chat room
                    clientSocket.BeginSend(message, 0, message.Length, SocketFlags.None, New AsyncCallback(AddressOf OnSend), clientSocket)
                    Exit Select
            End Select

            If msgToSend.cmdCommand <> Command.List Then
                'List messages are not broadcasted
                message = msgToSend.ToByte()

                For Each clientInfo As ClientInfo In clientList
                    If clientInfo.socket IsNot clientSocket OrElse msgToSend.cmdCommand <> Command.Login Then
                        'Send the message to all users
                        clientInfo.socket.BeginSend(message, 0, message.Length, SocketFlags.None, New AsyncCallback(AddressOf OnSend), clientInfo.socket)
                    End If
                Next

                Debug.Print(msgToSend.strMessage)
            End If

            'If the user is logging out then we need not listen from her
            If msgReceived.cmdCommand <> Command.Logout Then
                'Start listening to the message send by the user
                clientSocket.BeginReceive(byteData, 0, byteData.Length, SocketFlags.None, New AsyncCallback(AddressOf OnReceive), clientSocket)
            End If
        Catch ex As Exception
            MessageBox.Show(ex.Message, "SGSserverTCP", MessageBoxButtons.OK, MessageBoxIcon.[Error])
        End Try
    End Sub

    Public Sub OnSend(ar As IAsyncResult)
        Try
            Dim client As Socket = DirectCast(ar.AsyncState, Socket)
            client.EndSend(ar)
        Catch ex As Exception
            MessageBox.Show(ex.Message, "SGSserverTCP", MessageBoxButtons.OK, MessageBoxIcon.[Error])
        End Try
    End Sub
End Class

'The data structure by which the server and the client interact with 
'each other
Class Data

    Public strName As String
    'Name by which the client logs into the room
    Public strMessage As String
    'Message text
    Public cmdCommand As Command
    'Command type (login, logout, send message, etcetera)


    'Default constructor
    Public Sub New()
        cmdCommand = Command.Null
        strMessage = Nothing
        strName = Nothing
    End Sub

    'Converts the bytes into an object of type Data
    Public Sub New(data__1 As Byte())
        'The first four bytes are for the Command
        cmdCommand = CType(BitConverter.ToInt32(data__1, 0), Command)

        'The next four store the length of the name
        Dim nameLen As Integer = BitConverter.ToInt32(data__1, 4)

        'The next four store the length of the message
        Dim msgLen As Integer = BitConverter.ToInt32(data__1, 8)

        'This check makes sure that strName has been passed in the array of bytes
        If nameLen > 0 Then
            Me.strName = Encoding.UTF8.GetString(data__1, 12, nameLen)
        Else
            Me.strName = Nothing
        End If

        'This checks for a null message field
        If msgLen > 0 Then
            Me.strMessage = Encoding.UTF8.GetString(data__1, 12 + nameLen, msgLen)
        Else
            Me.strMessage = Nothing
        End If
    End Sub

    'Converts the Data structure into an array of bytes
    Public Function ToByte() As Byte()
        Dim result As New List(Of Byte)()

        'First four are for the Command
        result.AddRange(BitConverter.GetBytes(CInt(cmdCommand)))

        'Add the length of the name
        If strName IsNot Nothing Then
            result.AddRange(BitConverter.GetBytes(strName.Length))
        Else
            result.AddRange(BitConverter.GetBytes(0))
        End If

        'Length of the message
        If strMessage IsNot Nothing Then
            result.AddRange(BitConverter.GetBytes(strMessage.Length))
        Else
            result.AddRange(BitConverter.GetBytes(0))
        End If

        'Add the name
        If strName IsNot Nothing Then
            result.AddRange(Encoding.UTF8.GetBytes(strName))
        End If

        'And, lastly we add the message text to our array of bytes
        If strMessage IsNot Nothing Then
            result.AddRange(Encoding.UTF8.GetBytes(strMessage))
        End If

        Return result.ToArray()
    End Function


End Class

客户来源:

Enum Command
    Login '0 Log into the server
    Logout '1 Logout of the server
    Message '2 Send a text message to all the chat clients
    List '3 Get a list of users in the chat room from the server
    Null  '4 No command
End Enum

Public Class frmMain


    Public clientSocket As System.Net.Sockets.Socket
    'The main client socket
    Public strName As String
    'Name by which the user logs into the room
    Private byteData As Byte() = New Byte(1023) {}



    Private Sub frmMain_Load(sender As Object, e As EventArgs) Handles MyBase.Load

        Call StartChat()

    End Sub

    Private Sub StartChat()
        clientSocket = New System.Net.Sockets.Socket(System.Net.Sockets.AddressFamily.InterNetwork, System.Net.Sockets.SocketType.Stream, System.Net.Sockets.ProtocolType.Tcp)
        Dim ipAddress As System.Net.IPAddress = System.Net.IPAddress.Parse("127.0.0.1")
        Dim ipEndPoint As New System.Net.IPEndPoint(ipAddress, 2277)
        clientSocket.BeginConnect(ipEndPoint, New AsyncCallback(AddressOf OnLoginConnect), Nothing)
    End Sub

    Private Sub OnLoginConnect(ar As IAsyncResult)

        clientSocket.EndConnect(ar)

        'We are connected so we login into the server
        Dim msgToSend As New Data()
        msgToSend.cmdCommand = Command.Login
        msgToSend.strName = txtUser.Text
        msgToSend.strMessage = Nothing

        Dim b As Byte() = msgToSend.ToByte()

        'Send the message to the server
        clientSocket.BeginSend(b, 0, b.Length, System.Net.Sockets.SocketFlags.None, New AsyncCallback(AddressOf OnLoginSend), Nothing)

    End Sub


    Private Sub OnLoginSend(ar As IAsyncResult)
        clientSocket.EndSend(ar)
        strName = txtUser.Text

        Call LoggedIn()
    End Sub

    '***************

    Private Sub LoggedIn()

        'The user has logged into the system so we now request the server to send
        'the names of all users who are in the chat room
        Dim msgToSend As New Data()
        msgToSend.cmdCommand = Command.List
        msgToSend.strName = strName
        msgToSend.strMessage = Nothing

        byteData = msgToSend.ToByte()

        clientSocket.BeginSend(byteData, 0, byteData.Length, System.Net.Sockets.SocketFlags.None, New AsyncCallback(AddressOf OnSend), Nothing)

        byteData = New Byte(1023) {}
        'Start listening to the data asynchronously
        clientSocket.BeginReceive(byteData, 0, byteData.Length, System.Net.Sockets.SocketFlags.None, New AsyncCallback(AddressOf OnReceive), Nothing)
    End Sub


    'Broadcast the message typed by the user to everyone
    Private Sub btnSend_Click(sender As Object, e As EventArgs) Handles btnSend.Click
        'Fill the info for the message to be send
        Dim msgToSend As New Data()

        '.ToString("M/d/yyyy h:mm:ss tt")
        msgToSend.strName = "[" & Date.Now.ToString("h:mm:ss tt") & "] <" & strName & ">"
        msgToSend.strMessage = txtMessage.Text
        msgToSend.cmdCommand = Command.Message

        Dim byteData As Byte() = msgToSend.ToByte()

        'Send it to the server
        clientSocket.BeginSend(byteData, 0, byteData.Length, System.Net.Sockets.SocketFlags.None, New AsyncCallback(AddressOf OnSend), Nothing)

        txtMessage.Text = Nothing
    End Sub

    Private Sub OnSend(ar As IAsyncResult)

        clientSocket.EndSend(ar)

    End Sub

    Private Sub OnReceive(ar As IAsyncResult)

        'clientSocket.EndReceive(ar)

        Dim bytesReceived As Long = clientSocket.EndReceive(ar)

        If (bytesReceived > 0) Then
            Me.Invoke(New iThreadSafe(AddressOf iThreadSafeFinish), New Object() {byteData})
        End If

        byteData = New Byte(1023) {}
        clientSocket.BeginReceive(byteData, 0, byteData.Length, System.Net.Sockets.SocketFlags.None, New AsyncCallback(AddressOf OnReceive), Nothing)
    End Sub

    Private Delegate Sub iThreadSafe(ByVal ibyteData As Byte())
    Private Sub iThreadSafeFinish(ByVal ibyteData As Byte())

        Dim msgReceived As New Data(ibyteData)
        'Accordingly process the message received

        Debug.Print(msgReceived.cmdCommand)

        Select Case msgReceived.cmdCommand
            Case 0 'login
                lstChatters.Items.Add(msgReceived.strName)
                Exit Select

            Case 1 'log out
                lstChatters.Items.Remove(msgReceived.strName)
                Exit Select

            Case 2 'msg
                Exit Select

            Case 3 'List
                lstChatters.Items.AddRange(msgReceived.strMessage.Split("*"c))
                lstChatters.Items.RemoveAt(lstChatters.Items.Count - 1)
                txtChatBox.Text += "<<<" & strName & " has joined the room>>>" & vbCr & vbLf
                Exit Select
        End Select

        If msgReceived.strMessage IsNot Nothing AndAlso msgReceived.cmdCommand <> Command.List Then
            txtChatBox.Text += msgReceived.strMessage & vbCr & vbLf
        End If
    End Sub


    Private Sub txtMessage_TextChanged(sender As Object, e As KeyEventArgs) Handles txtMessage.KeyDown
        If e.KeyCode = Keys.Enter Then
            btnSend_Click(sender, Nothing)
        End If
    End Sub

    Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
        strName = txtUser.Text
    End Sub
End Class

'The data structure by which the server and the client interact with 
'each other
Class Data

    Public strName As String
    'Name by which the client logs into the room
    Public strMessage As String
    'Message text
    Public cmdCommand As Command
    'Command type (login, logout, send message, etcetera)

    'Default constructor
    Public Sub New()
        cmdCommand = Command.Null
        strMessage = Nothing
        strName = Nothing
    End Sub

    'Converts the bytes into an object of type Data
    Public Sub New(data__1 As Byte())
        'The first four bytes are for the Command
        cmdCommand = CType(BitConverter.ToInt32(data__1, 0), Command)

        'The next four store the length of the name
        Dim nameLen As Integer = BitConverter.ToInt32(data__1, 4)

        'The next four store the length of the message
        Dim msgLen As Integer = BitConverter.ToInt32(data__1, 8)

        'This check makes sure that strName has been passed in the array of bytes
        If nameLen > 0 Then
            strName = System.Text.Encoding.UTF8.GetString(data__1, 12, nameLen)
        Else
            strName = Nothing
        End If

        'This checks for a null message field
        If msgLen > 0 Then
            strMessage = System.Text.Encoding.UTF8.GetString(data__1, 12 + nameLen, msgLen)
        Else
            strMessage = Nothing
        End If
    End Sub

    'Converts the Data structure into an array of bytes
    Public Function ToByte() As Byte()
        Dim result As New List(Of Byte)()

        'First four are for the Command
        result.AddRange(BitConverter.GetBytes(CInt(cmdCommand)))

        'Add the length of the name
        If strName IsNot Nothing Then
            result.AddRange(BitConverter.GetBytes(strName.Length))
        Else
            result.AddRange(BitConverter.GetBytes(0))
        End If

        'Length of the message
        If strMessage IsNot Nothing Then
            result.AddRange(BitConverter.GetBytes(strMessage.Length))
        Else
            result.AddRange(BitConverter.GetBytes(0))
        End If

        'Add the name
        If strName IsNot Nothing Then
            result.AddRange(System.Text.Encoding.UTF8.GetBytes(strName))
        End If

        'And, lastly we add the message text to our array of bytes
        If strMessage IsNot Nothing Then
            result.AddRange(System.Text.Encoding.UTF8.GetBytes(strMessage))
        End If

        Return result.ToArray()
    End Function


End Class

1 个答案:

答案 0 :(得分:2)

您假设您一次会收到整条信息。 TCP不保证保留您编写的块。使用返回值bytesReceived来确定缓冲区中实际有多少字节。

您的代码必须能够处理一次一个字节接收所有未完成数据的可能性。