我创建了一个 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()
连接信息类:
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