Hello. I am trying to implement this for a simple server that would have multiple clients and will serve 5 weather parameters in one tab-delimited string. Connection and sending data is no problem, however, I am having problems with a memory leak and with disposing of disconnected clients. The removeClient subroutine does not really get rid of the client because I've noticed that doRead continues to execute even after the client disconnects. My CPU usage jumps to 100% when a client disconnects. In addition, the memory usage rises by about 8kB/s when it is running. I've tried setting up a Dispose call, but even after this call doRead continues to run. In my normal application I don't ever need to get rid of objects under execution is over. Any ideas?
Server Form
Code:
Imports System.Net.Sockets
Imports System.Threading
Public Class frmServerMain
Private VaisalaDevice As Vaisala
Private listener As System.Net.Sockets.TcpListener
Private listenThread As System.Threading.Thread
Private tmrWrite As Timer
Private TimerWriteDelegate As TimerCallback
Private Open As Boolean
Private clients As New List(Of ConnectedClient) 'This list will store all connected clients.
Private Sub frmServerMain_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
listener = New System.Net.Sockets.TcpListener(System.Net.IPAddress.Any, 43001) 'The TcpListener will listen for incoming connections at port 43001
listener.Start() 'Start listening.
listenThread = New System.Threading.Thread(AddressOf doListen) 'This thread will run the doListen method
listenThread.IsBackground = True 'Since we dont want this thread to keep on running after the application closes, we set isBackground to true.
listenThread.Start() 'Start executing doListen on the worker thread.
' Write to the port 4 times per second.
TimerWriteDelegate = AddressOf SendMessage
tmrWrite = New System.Threading.Timer(TimerWriteDelegate, Open, 0, 1000)
End Sub
Private Sub doListen()
Dim incomingClient As System.Net.Sockets.TcpClient
Do
incomingClient = listener.AcceptTcpClient 'Accept the incoming connection. This is a blocking method so execution will halt here until someone tries to connect.
Dim connClient As New ConnectedClient(incomingClient, Me) 'Create a new instance of ConnectedClient (check its constructor to see whats happening now).
AddHandler connClient.dataReceived, AddressOf Me.messageReceived
clients.Add(connClient) 'Adds the connected client to the list of connected clients.
Control.CheckForIllegalCrossThreadCalls = False
connClient.Username = incomingClient.Client.RemoteEndPoint.ToString
ListBox1.Items.Add(connClient.Username & " connected.")
Loop
End Sub
Public Sub SendMessage()
For Each cc As ConnectedClient In clients
cc.SendMessage("this is a test")
Next
End Sub
Public Sub removeClient(ByVal client As ConnectedClient)
If clients.Contains(client) Then
clients.Remove(client)
client.Dispose()
ListBox1.Items.Remove(client.Username & " connected.")
End If
End Sub
Private Sub messageReceived(ByVal sender As ConnectedClient, ByVal message As String)
'A message has been received from one of the clients.
'To determine who its from, use the sender object.
'sender.SendMessage can be used to reply to the sender.
Dim data() As String = message.Split("|"c) 'Split the message on each | and place in the string array.
Select Case data(0)
Case "CONNECT"
'We use GetClientByName to make sure no one else is using this username.
'It will return Nothing if the username is free.
'Since the client sent the message in this format: CONNECT|UserName, the username will be in the array on index 1.
If GetClientByName(data(1)) Is Nothing Then
'The username is not taken, we can safely assign it to the sender.
sender.Username = data(1)
End If
Case "DISCONNECT"
removeClient(sender)
End Select
End Sub
Private Function GetClientByName(ByVal name As String) As ConnectedClient
For Each cc As ConnectedClient In clients
If cc.Username = name Then
Return cc 'client found, return it.
End If
Next
'If we've reached this part of the method, there is no client by that name
Return Nothing
End Function
End Class
ConnectedClient Class
Code:
Public Class ConnectedClient
Implements IDisposable
Private mClient As System.Net.Sockets.TcpClient
Private mUsername As String
Private mParentForm As frmServerMain
Private readThread As System.Threading.Thread
Private Const MESSAGE_DELIMITER As Char = ControlChars.Cr
Protected disposed As Boolean = False
Public Event dataReceived(ByVal sender As ConnectedClient, ByVal message As String)
Sub New(ByVal client As System.Net.Sockets.TcpClient, ByVal parentForm As frmServerMain)
mParentForm = parentForm
mClient = client
readThread = New System.Threading.Thread(AddressOf doRead)
readThread.IsBackground = True
readThread.Start()
End Sub
Public Property Username() As String
Get
Return mUsername
End Get
Set(ByVal value As String)
mUsername = value
End Set
End Property
Private Sub doRead()
Const BYTES_TO_READ As Integer = 255
Dim readBuffer(BYTES_TO_READ) As Byte
Dim bytesRead As Integer
Dim sBuilder As New System.Text.StringBuilder
Do
Try
If mClient.Connected Then
bytesRead = mClient.GetStream.Read(readBuffer, 0, BYTES_TO_READ)
If (bytesRead > 0) Then
Dim message As String = System.Text.Encoding.UTF8.GetString(readBuffer, 0, bytesRead)
If (message.IndexOf(MESSAGE_DELIMITER) > -1) Then
Dim subMessages() As String = message.Split(MESSAGE_DELIMITER)
'The first element in the subMessages string array must be the last part of the current message.
'So we append it to the StringBuilder and raise the dataReceived event
sBuilder.Append(subMessages(0))
RaiseEvent dataReceived(Me, sBuilder.ToString)
sBuilder = New System.Text.StringBuilder
'If there are only 2 elements in the array, we know that the second one is an incomplete message,
'though if there are more then two then every element inbetween the first and the last are complete messages:
If subMessages.Length = 2 Then
sBuilder.Append(subMessages(1))
Else
For i As Integer = 1 To subMessages.GetUpperBound(0) - 1
RaiseEvent dataReceived(Me, subMessages(i))
Next
sBuilder.Append(subMessages(subMessages.GetUpperBound(0)))
End If
Else
'MESSAGE_DELIMITER was not found in the message, so we just append everything to the stringbuilder.
sBuilder.Append(message)
End If
End If
Else
mParentForm.removeClient(Me)
'Exit Do
End If
Catch ex As Exception
End Try
Loop
End Sub
Public Sub SendMessage(ByVal msg As String)
Dim sw As IO.StreamWriter
Try
SyncLock mClient.GetStream
sw = New IO.StreamWriter(mClient.GetStream) 'Create a new streamwriter that will be writing directly to the networkstream.
sw.Write(msg)
sw.Flush()
End SyncLock
Catch ex As Exception
MessageBox.Show(ex.ToString)
End Try
'As opposed to writing to a file, we DONT call close on the streamwriter, since we dont want to close the stream.
End Sub
Protected Overridable Overloads Sub Dispose( _
ByVal disposing As Boolean)
If Not Me.disposed Then
If disposing Then
'managedResource.Dispose()
End If
' Add code here to release the unmanaged resource.
'unmanagedResource = IntPtr.Zero
' Note that this is not thread safe.
End If
Me.disposed = True
End Sub
#Region " IDisposable Support "
' Do not change or add Overridable to these methods.
' Put cleanup code in Dispose(ByVal disposing As Boolean).
Public Overloads Sub Dispose() Implements IDisposable.Dispose
Dispose(True)
GC.SuppressFinalize(Me)
End Sub
Protected Overrides Sub Finalize()
Dispose(False)
MyBase.Finalize()
End Sub
#End Region
End Class