ServerForm.vb 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256
  1. Imports System.Net
  2. Imports System.Net.Sockets
  3. Imports System.Threading
  4. Imports System.Threading.Tasks
  5. Public Class ServerForm
  6. Private _Listener As TcpListener
  7. Private _Connections As New List(Of ConnectionInfo)
  8. Private _ConnectionMontior As Task
  9. Private Sub StartStopButton_CheckedChanged(sender As System.Object, e As System.EventArgs) Handles StartStopButton.CheckedChanged
  10. If StartStopButton.Checked Then
  11. StartStopButton.Text = "Stop"
  12. StartStopButton.Image = My.Resources.Resources.StopServer
  13. _Listener = New TcpListener(IPAddress.Any, CInt(PortTextBox.Text))
  14. _Listener.Start()
  15. Dim monitor As New MonitorInfo(_Listener, _Connections)
  16. ListenForClient(monitor)
  17. _ConnectionMontior = Task.Factory.StartNew(AddressOf DoMonitorConnections, monitor, TaskCreationOptions.LongRunning)
  18. Else
  19. StartStopButton.Text = "Start"
  20. StartStopButton.Image = My.Resources.Resources.StartServer
  21. CType(_ConnectionMontior.AsyncState, MonitorInfo).Cancel = True
  22. _Listener.Stop()
  23. _Listener = Nothing
  24. End If
  25. End Sub
  26. Private Sub PortTextBox_Validating(sender As Object, e As System.ComponentModel.CancelEventArgs) Handles PortTextBox.Validating
  27. Dim deltaPort As Integer
  28. If Not Integer.TryParse(PortTextBox.Text, deltaPort) OrElse deltaPort < 1 OrElse deltaPort > 65535 Then
  29. MessageBox.Show("Port number must be an integer between 1 and 65535.", "Invalid Port Number", MessageBoxButtons.OK, MessageBoxIcon.Exclamation)
  30. PortTextBox.SelectAll()
  31. e.Cancel = True
  32. End If
  33. End Sub
  34. Private Sub ListenForClient(monitor As MonitorInfo)
  35. Dim info As New ConnectionInfo(monitor)
  36. _Listener.BeginAcceptTcpClient(AddressOf DoAcceptClient, info)
  37. End Sub
  38. Private Sub DoAcceptClient(result As IAsyncResult)
  39. Dim monitorInfo As MonitorInfo = CType(_ConnectionMontior.AsyncState, MonitorInfo)
  40. If monitorInfo.Listener IsNot Nothing AndAlso Not monitorInfo.Cancel Then
  41. Dim info As ConnectionInfo = CType(result.AsyncState, ConnectionInfo)
  42. monitorInfo.Connections.Add(info)
  43. info.AcceptClient(result)
  44. ListenForClient(monitorInfo)
  45. info.AwaitData()
  46. Dim doUpdateConnectionCountLabel As New Action(AddressOf UpdateConnectionCountLabel)
  47. Invoke(doUpdateConnectionCountLabel)
  48. End If
  49. End Sub
  50. Private Sub DoMonitorConnections()
  51. 'Create delegate for updating output display
  52. Dim doAppendOutput As New Action(Of String)(AddressOf AppendOutput)
  53. 'Create delegate for updating connection count label
  54. Dim doUpdateConnectionCountLabel As New Action(AddressOf UpdateConnectionCountLabel)
  55. 'Get MonitorInfo instance from thread-save Task instance
  56. Dim monitorInfo As MonitorInfo = CType(_ConnectionMontior.AsyncState, MonitorInfo)
  57. 'Report progress
  58. Me.Invoke(doAppendOutput, "Monitor Started.")
  59. 'Implement client connection processing loop
  60. Do
  61. 'Create temporary list for recording closed connections
  62. Dim lostCount As Integer = 0
  63. 'Examine each connection for processing
  64. For index As Integer = monitorInfo.Connections.Count - 1 To 0 Step -1
  65. Dim info As ConnectionInfo = monitorInfo.Connections(index)
  66. If info.Client.Connected Then
  67. 'Process connected client
  68. If info.DataQueue.Count > 0 Then
  69. 'The code in this If-Block should be modified to build 'message' objects
  70. 'according to the protocol you defined for your data transmissions.
  71. 'This example simply sends all pending message bytes to the output textbox.
  72. 'Without a protocol we cannot know what constitutes a complete message, so
  73. 'with multiple active clients we could see part of client1's first message,
  74. 'then part of a message from client2, followed by the rest of client1's
  75. 'first message (assuming client1 sent more than 64 bytes).
  76. Dim messageBytes As New List(Of Byte)
  77. While info.DataQueue.Count > 0
  78. Dim value As Byte
  79. If info.DataQueue.TryDequeue(value) Then
  80. messageBytes.Add(value)
  81. End If
  82. End While
  83. Me.Invoke(doAppendOutput, System.Text.Encoding.ASCII.GetString(messageBytes.ToArray))
  84. End If
  85. Else
  86. 'Clean-up any closed client connections
  87. monitorInfo.Connections.Remove(info)
  88. lostCount += 1
  89. End If
  90. Next
  91. If lostCount > 0 Then
  92. Invoke(doUpdateConnectionCountLabel)
  93. End If
  94. 'Throttle loop to avoid wasting CPU time
  95. _ConnectionMontior.Wait(1)
  96. Loop While Not monitorInfo.Cancel
  97. 'Close all connections before exiting monitor
  98. For Each info As ConnectionInfo In monitorInfo.Connections
  99. info.Client.Close()
  100. Next
  101. monitorInfo.Connections.Clear()
  102. 'Update the connection count label and report status
  103. Invoke(doUpdateConnectionCountLabel)
  104. Me.Invoke(doAppendOutput, "Monitor Stopped.")
  105. End Sub
  106. Private Sub AppendOutput(message As String)
  107. If RichTextBox1.TextLength > 0 Then
  108. RichTextBox1.AppendText(ControlChars.NewLine)
  109. End If
  110. RichTextBox1.AppendText(message)
  111. RichTextBox1.ScrollToCaret()
  112. End Sub
  113. Private Sub UpdateConnectionCountLabel()
  114. ConnectionCountLabel.Text = String.Format("{0} Connections", _Connections.Count)
  115. End Sub
  116. End Class
  117. 'Provides a simple container object to be used for the state object passed to the connection monitoring thread
  118. Public Class MonitorInfo
  119. Public Property Cancel As Boolean
  120. Private _Connections As List(Of ConnectionInfo)
  121. Public ReadOnly Property Connections As List(Of ConnectionInfo)
  122. Get
  123. Return _Connections
  124. End Get
  125. End Property
  126. Private _Listener As TcpListener
  127. Public ReadOnly Property Listener As TcpListener
  128. Get
  129. Return _Listener
  130. End Get
  131. End Property
  132. Public Sub New(tcpListener As TcpListener, connectionInfoList As List(Of ConnectionInfo))
  133. _Listener = tcpListener
  134. _Connections = connectionInfoList
  135. End Sub
  136. End Class
  137. 'Provides a container object to serve as the state object for async client and stream operations
  138. Public Class ConnectionInfo
  139. 'hold a reference to entire monitor instead of just the listener
  140. Private _Monitor As MonitorInfo
  141. Public ReadOnly Property Monitor As MonitorInfo
  142. Get
  143. Return _Monitor
  144. End Get
  145. End Property
  146. Private _Client As TcpClient
  147. Public ReadOnly Property Client As TcpClient
  148. Get
  149. Return _Client
  150. End Get
  151. End Property
  152. Private _Stream As NetworkStream
  153. Public ReadOnly Property Stream As NetworkStream
  154. Get
  155. Return _Stream
  156. End Get
  157. End Property
  158. Private _DataQueue As System.Collections.Concurrent.ConcurrentQueue(Of Byte)
  159. Public ReadOnly Property DataQueue As System.Collections.Concurrent.ConcurrentQueue(Of Byte)
  160. Get
  161. Return _DataQueue
  162. End Get
  163. End Property
  164. Private _LastReadLength As Integer
  165. Public ReadOnly Property LastReadLength As Integer
  166. Get
  167. Return _LastReadLength
  168. End Get
  169. End Property
  170. 'The buffer size is an arbitrary value which should be selected based on the
  171. 'amount of data you need to transmit, the rate of transmissions, and the
  172. 'anticipalted number of clients. These are the considerations for designing
  173. 'the communicaition protocol for data transmissions, and the size of the read
  174. 'buffer must be based upon the needs of the protocol.
  175. Private _Buffer(63) As Byte
  176. Public Sub New(monitor As MonitorInfo)
  177. _Monitor = monitor
  178. _DataQueue = New System.Collections.Concurrent.ConcurrentQueue(Of Byte)
  179. End Sub
  180. Public Sub AcceptClient(result As IAsyncResult)
  181. _Client = _Monitor.Listener.EndAcceptTcpClient(result)
  182. If _Client IsNot Nothing AndAlso _Client.Connected Then
  183. _Stream = _Client.GetStream
  184. End If
  185. End Sub
  186. Public Sub AwaitData()
  187. _Stream.BeginRead(_Buffer, 0, _Buffer.Length, AddressOf DoReadData, Me)
  188. End Sub
  189. Private Sub DoReadData(result As IAsyncResult)
  190. Dim info As ConnectionInfo = CType(result.AsyncState, ConnectionInfo)
  191. Try
  192. 'If the stream is valid for reading, get the current data and then
  193. 'begin another async read
  194. If info.Stream IsNot Nothing AndAlso info.Stream.CanRead Then
  195. info._LastReadLength = info.Stream.EndRead(result)
  196. For index As Integer = 0 To _LastReadLength - 1
  197. info._DataQueue.Enqueue(info._Buffer(index))
  198. Next
  199. 'The example responds to all data reception with the number of bytes received;
  200. 'you would likely change this behavior when implementing your own protocol.
  201. info.SendMessage("Received " & info._LastReadLength & " Bytes")
  202. For Each otherInfo As ConnectionInfo In info.Monitor.Connections
  203. If Not otherInfo Is info Then
  204. otherInfo.SendMessage(System.Text.Encoding.ASCII.GetString(info._Buffer))
  205. End If
  206. Next
  207. info.AwaitData()
  208. Else
  209. 'If we cannot read from the stream, the example assumes the connection is
  210. 'invalid and closes the client connection. You might modify this behavior
  211. 'when implementing your own protocol.
  212. info.Client.Close()
  213. End If
  214. Catch ex As Exception
  215. info._LastReadLength = -1
  216. End Try
  217. End Sub
  218. Private Sub SendMessage(message As String)
  219. If _Stream IsNot Nothing Then
  220. Dim messageData() As Byte = System.Text.Encoding.ASCII.GetBytes(message)
  221. Stream.Write(messageData, 0, messageData.Length)
  222. End If
  223. End Sub
  224. End Class