数小时后TCPClient断开连接

时间:2013-01-07 11:14:13

标签: vb.net windows-services tcpclient

我创建了一个等待TCPClient连接的Windows服务,并将所有消息中继到所有连接的客户端(发件人除外)。我的代码基于this示例。

一个客户端在触发事件时连接,发送一些进度更新,然后断开连接。其他客户端是接收和显示更新的前端应用程序。

如果这些客户端闲置几个小时,他们似乎松开连接而没有任何错误\警告。我找不到任何闲置期间的相关时间,是否有我遗漏的东西?

服务代码:

Protected Overrides Sub OnStart(ByVal args() As String)
    _Listener = New TcpListener(IPAddress.Any, 1314)
    _Listener.Start()
    ListenForClient()
    _ConnectionMontior = Task.Factory.StartNew(AddressOf DoMonitorConnections, New MonitorInfo(_Listener, _Connections), TaskCreationOptions.LongRunning)
End Sub

Private Sub ListenForClient()
    Dim info As New ConnectionInfo(_Listener)
    _Listener.BeginAcceptTcpClient(AddressOf DoAcceptClient, info)
End Sub

Private Sub DoAcceptClient(result As IAsyncResult)
    Try
        Dim monitorInfo As MonitorInfo = CType(_ConnectionMontior.AsyncState, MonitorInfo)
    If monitorInfo.Listener IsNot Nothing AndAlso Not monitorInfo.Cancel Then
        Dim info As ConnectionInfo = CType(result.AsyncState, ConnectionInfo)
        monitorInfo.Connections.Add(info)
        info.AcceptClient(result)
        ListenForClient()
        info.AwaitData()
    End If
    Catch ex As Exception
        WriteToEventLog("DoAcceptClient: " & ex.Message)
    End Try
End Sub

Private Sub DoMonitorConnections()

    Try

        'Create delegate for updating output display
        ' Dim doAppendOutput As New Action(Of String)(AddressOf AppendOutput)

        'Get MonitorInfo instance from thread-save Task instance
        Dim monitorInfo As MonitorInfo = CType(_ConnectionMontior.AsyncState, MonitorInfo)

        'Implement client connection processing loop
        Do
            'Create temporary list for recording closed connections
            Dim lostConnections As New List(Of ConnectionInfo)

            'Examine each connection for processing
            For Each info As ConnectionInfo In monitorInfo.Connections
                If info.Client.Connected Then
                    'Process connected client
                    If info.DataQueue.Count > 0 Then
                        'The code in this If-Block should be modified to build 'message' objects
                        'according to the protocol you defined for your data transmissions.
                        'This example simply sends all pending message bytes to the output textbox.
                        'Without a protocol we cannot know what constitutes a complete message, so
                        'with multiple active clients we could see part of client1's first message,
                        'then part of a message from client2, followed by the rest of client1's
                        'first message (assuming client1 sent more than 64 bytes).
                        Dim messageBytes As New List(Of Byte)
                        While info.DataQueue.Count > 0
                            messageBytes.Add(info.DataQueue.Dequeue)
                        End While

                        'Relay the message to all clients except the sender
                        For Each inf As ConnectionInfo In monitorInfo.Connections
                            If inf.Client.Connected Then
                                Dim msg As String = info.Client.Client.RemoteEndPoint.ToString & "|" & System.Text.Encoding.ASCII.GetString(messageBytes.ToArray)
                                If Not inf.Client.Client.RemoteEndPoint.ToString = msg.Split("|")(0) Then
                                    inf.Client.Client.Send(messageBytes.ToArray)
                                End If
                            End If
                        Next

                    End If
                Else
                    'Record clients no longer connected
                    lostConnections.Add(info)
                End If
            Next

            'Clean-up any closed client connections
            If lostConnections.Count > 0 Then
                While lostConnections.Count > 0
                    monitorInfo.Connections.Remove(lostConnections(0))
                    lostConnections.RemoveAt(0)
                End While
            End If

            'Throttle loop to avoid wasting CPU time
            _ConnectionMontior.Wait(1)
        Loop While Not monitorInfo.Cancel

        'Close all connections before exiting monitor
        For Each info As ConnectionInfo In monitorInfo.Connections
            info.Client.Close()
        Next
        monitorInfo.Connections.Clear()

    Catch ex As Exception
        WriteToEventLog("DoMonitorConnections" & ex.Message)
    End Try

End Sub

客户代码:

 _ServerAddress = IPAddress.Parse(ServerIP)
 _Connection = New ConnectionInfo(_ServerAddress, 1314, AddressOf InvokeAppendOutput)
 _Connection.AwaitData()

ConnectionInfo类:

Public Class ConnectionInfo
Private _AppendMethod As Action(Of String)
Public ReadOnly Property AppendMethod As Action(Of String)
    Get
        Return _AppendMethod
    End Get
End Property

Private _Client As TcpClient
Public ReadOnly Property Client As TcpClient
    Get
        Return _Client
    End Get
End Property

Private _Stream As NetworkStream
Public ReadOnly Property Stream As NetworkStream
    Get
        Return _Stream
    End Get
End Property

Private _LastReadLength As Integer
Public ReadOnly Property LastReadLength As Integer
    Get
        Return _LastReadLength
    End Get
End Property

Private _Buffer(255) As Byte

Public Sub New(address As IPAddress, port As Integer, append As Action(Of String))
    _AppendMethod = append
    _Client = New TcpClient
    _Client.Connect(address, port)
    _Stream = _Client.GetStream
End Sub

Public Sub AwaitData()
    _Stream.BeginRead(_Buffer, 0, _Buffer.Length, AddressOf DoReadData, Me)
End Sub

Public Sub Close()
    If _Client IsNot Nothing Then _Client.Close()
    _Client = Nothing
    _Stream = Nothing
End Sub

Private Const MESSAGE_DELIMITER As Char = ControlChars.Cr
Dim sBuilder As New System.Text.StringBuilder

Private Sub DoReadData(result As IAsyncResult)

    Dim info As ConnectionInfo = CType(result.AsyncState, ConnectionInfo)

    Try
        If info._Stream IsNot Nothing AndAlso info._Stream.CanRead Then
            info._LastReadLength = info._Stream.EndRead(result)
            If info._LastReadLength > 0 Then
                Dim message As String = System.Text.Encoding.UTF8.GetString(info._Buffer, 0, info._LastReadLength)


                If (message.IndexOf(MESSAGE_DELIMITER) > -1) Then

                    Dim subMessages() As String = message.Split(MESSAGE_DELIMITER)

                    sBuilder.Append(subMessages(0))
                    If Not info._Client.Client.LocalEndPoint.ToString = sBuilder.ToString.Split("|")(0) Then
                        info._AppendMethod(sBuilder.ToString)
                    End If

                    sBuilder = New System.Text.StringBuilder

                    If subMessages.Length = 2 Then
                        sBuilder.Append(subMessages(1))
                    Else
                        For i As Integer = 1 To subMessages.GetUpperBound(0) - 1
                            'MessageBox.Show(subMessages(i))
                            info._AppendMethod(subMessages(i))
                        Next
                        sBuilder.Append(subMessages(subMessages.GetUpperBound(0)))
                    End If
                Else
                    sBuilder.Append(message)
                End If
            End If
        End If

        info.AwaitData()

    Catch ex As Exception
        info._LastReadLength = -1
    End Try
End Sub
End Class

1 个答案:

答案 0 :(得分:1)

TCP不保证不尝试发送数据的一方可以检测到连接丢失。在设计应用程序协议时,您应该考虑到这一点。

您所看到的最常见的是NAT或状态防火墙。实际上,如果您不至少每十分钟发送一次数据,则至少可能会有一些客户端断开连接。他们的NAT设备或状态防火墙只是忘记了连接。在尝试发送数据之前,双方都没有注意到。

我建议创建一种虚拟消息,服务器每隔五分钟发送给所有客户端。基本上,这只是一小部分数据,可以唯一标识为仅用于保持连接活动。

每个客户端通过向服务器发送虚拟消息来响应虚拟消息。如果客户端在十分钟内没有收到虚假消息,则应考虑连接丢失,关闭连接并再次尝试连接。

尝试发送虚拟消息的单纯行为将导致服务器检测到任何丢失的连接,但您可能还应该认为,当您没有响应虚拟消息时,与客户端的任何连接都已消失。准备发送下一个。当客户端没有收到虚拟消息时,它将知道连接丢失。消息交换将使NAT /防火墙条目保持活动状态。