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