1

TCPClient 接続を待機し、接続されているすべてのクライアント (送信者を除く) にメッセージを中継する Windows サービスを作成しました。私のコードはこの例に基づいています。

イベントがトリガーされると、1 つのクライアントが接続し、進行状況の更新を送信してから切断します。他のクライアントは、更新を受信して​​表示するフロント エンド アプリケーションです。

これらのクライアントが数時間アイドル状態のままになっていると、エラー\警告なしで接続が失われているように見えます。アイドル期間に関連するタイムアウトが見つかりません。不足しているものはありますか?

サービスコード:

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
4

1 に答える 1

1

TCP は、データを送信しようとしていない側が接続の損失を検出できることを保証しません。アプリケーション プロトコルを設計するときは、この点を考慮しておく必要があります。

最も一般的な原因は、NAT またはステートフル ファイアウォールです。実際問題として、少なくとも 10 分ごとにデータを送信しないと、少なくとも一部のクライアントが切断されることが予想されます。彼らの NAT デバイスまたはステートフル ファイアウォールは、単に接続を忘れています。データを送信しようとするまで、どちらの側も気づきません。

サーバーがすべてのクライアントに 5 分ごとに送信する、ある種のダミー メッセージを作成することをお勧めします。基本的に、これは、接続を維持するためだけに機能するものとして一意に識別できるデータの小さなチャンクにすぎません。

各クライアントは、ダミー メッセージをサーバーに送り返すことで、ダミー メッセージに応答します。クライアントが 10 分以内にダミー メッセージを受信しない場合、接続が失われたと見なし、接続を閉じて、再度接続を試行する必要があります。

ダミー メッセージを送信しようとするだけで、サーバーは切断された接続を検出しますが、準備ができた時点でダミー メッセージに応答しなかったクライアントへの接続はすべて無効であると見なす必要があります。次を送信します。クライアントは、ダミー メッセージを受信しないと、接続が失われたことを認識します。メッセージの交換により、NAT/ファイアウォール エントリが維持されます。

于 2013-01-07T11:18:21.160 に答える