TCPClient отключается через несколько часов

Я создал службу Windows, которая ожидает соединений 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

person madlan    schedule 07.01.2013    source источник


Ответы (1)


TCP не гарантирует, что сторона, не пытающаяся отправить данные, сможет обнаружить потерю соединения. Вы должны были принять это во внимание при разработке протокола приложения.

То, что вы видите, чаще всего вызвано NAT или брандмауэрами с отслеживанием состояния. На практике, если вы не отправляете данные по крайней мере каждые десять минут, вы можете ожидать, что по крайней мере некоторые клиенты будут отключены. Их устройства NAT или межсетевые экраны с отслеживанием состояния просто забывают о соединении. Ни одна из сторон не замечает, пока не попытается отправить данные.

Я бы предложил создать какое-то фиктивное сообщение, которое сервер отправляет всем своим клиентам каждые пять минут. По сути, это всего лишь небольшой фрагмент данных, который можно однозначно идентифицировать как служащий только для поддержания соединения.

Каждый клиент отвечает на фиктивное сообщение, отправляя фиктивное сообщение обратно на сервер. Если клиент не получил фиктивное сообщение в течение десяти минут, он должен считать соединение потерянным, закрыть его и попытаться снова подключиться.

Простое действие попытки отправить фиктивное сообщение заставит сервер обнаруживать любые потерянные соединения, но вам, вероятно, также следует считать мертвым любое соединение с клиентом, который не ответил на фиктивное сообщение к тому времени, когда вы будете готовы. отправить следующий. Клиент будет знать, что соединение потеряно, если он не получит фиктивное сообщение. Обмен сообщениями будет поддерживать запись NAT/брандмауэра.

person David Schwartz    schedule 07.01.2013