2013-01-07 73 views
1

我創建了一個Windows服務,該服務等待TCPClient連接並將任何消息轉發給所有連接的客戶端(發件人除外)。我的代碼基於this示例。TCPClient在幾個小時後斷開連接

一個客戶端在事件觸發時連接,發送一些進度更新然後斷開連接。其他客戶端是接收和顯示更新的前端應用程序。

如果這些客戶端閒置了幾個小時,他們似乎沒有任何錯誤\警告就失去了連接。我在空閒時間找不到任何相關的時序,有沒有我失蹤的東西?

服務代碼:

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

TCP並不能保證一個側面沒有試圖發送的數據可以檢測損失的連接。您在設計應用程序協議時應該考慮到這一點。

你所看到的最常見的是由NAT或有狀態防火牆造成的。實際上,如果您至少每十分鐘不發送一次數據,至少可以期望某些客戶端斷開連接。他們的NAT設備或狀態防火牆只是忘記了連接。直到它嘗試發送數據時,雙方都不會通知。

我會建議創建一種虛擬消息,服務器每五分鐘發送一次給所有的客戶端。基本上,這只是一小部分可以唯一標識爲僅用於保持連接活動的數據。

每個客戶端都會通過向服務器發回一個虛擬消息來響應虛擬消息。如果客戶在10分鐘內未收到虛擬消息,則應考慮丟失連接,關閉連接並嘗試再次連接。

嘗試發送虛假消息的行爲將會導致服務器檢測到任何丟失的連接,但是您應該也可以考慮將任何與虛擬消息對應的客戶端的連接視爲無效準備發送下一個。當客戶端沒有收到虛擬消息時,它會知道連接丟失。消息交換將使NAT /防火牆條目保持活動狀態。