Constantly losing TCPClient connection from application - vb.net

I have an application which connects to a Zebra QLn420 label printer through a TCPClient connection to the printer's static IP address and port number.
We have been having some issues with the software "stalling" and skipping over some of the labels, and after investigating (by adding some logging capabilities to the software to write out to a text file each time it checks the connection and actually reconnects) I have noticed that the connection does not seem to stay open for more than one second (not a solid number, it can vary from less than a second to 5 seconds or more from my tests).
While testing at my desk, this is almost a non-issue because the signal strengths are so strong that it reconnects instantly, but out in the warehouse where this is actually used, in some areas the signal isn't so strong. The function which handles the connection will try to reconnect for up to 5 seconds twice, then display an error message and ask if they want to retry connecting.
I'm curious if anyone has experienced anything like this, and what can be done to resolve it. I understand that the software will lose connection with the printer occasionally, especially in the weaker signal areas. But I would like to find a way to keep the connection open until either I close it or the signal is too weak to keep it going, as I feel this has a lot to do with the "missing" labels and definitely impacts the speed of the software.
Below is my connection code (a combination of my own code and some snippets found online):
Public Class LabelPrinter
Private strIP as String = "xxx.xxx.xxx.xxx"
Private intPort As Integer = "6101"
Private client As System.Net.Sockets.TcpClient
Private blnConnected As Boolean = False
Private blnValidIP As Boolean = False
Private intTimeoutLength As Integer = 5000
Private TimeoutTime As New Timers.Timer
Private clntSockParams As ClientSocketParameters
Private Structure ClientSocketParameters
Public addrs As String
Public prt As Integer
End Structure
Public Sub New(Picker As Picker, newIP As String, newPort As Integer, Optional PrinterTimeout As Integer = 5000)
If newIP <> "" Then strIP = newIP
If newPort <> 0 Then intPort = newPort
intTimeoutLength = PrinterTimeout
If newIP = "" Or newPort = 0 Then Exit Sub
Dim ipAddress As System.Net.IPAddress = Nothing
If Not System.Net.IPAddress.TryParse(strIP, ipAddress) Then Exit Sub
End Sub
Public Function Connect() As Boolean
Try
'connect to printer via TcpClient, need ip address and port number
'connects without thread, hangs program for 10-20 seconds if printer is not turned on, replaced with code below to thread the connection and set timeout
'If client Is Nothing OrElse Not client.Connected Then
' client = New System.Net.Sockets.TcpClient
' client.Connect(strIP, intPort)
'End If
'RRB 02/10/15 - added for loop to try to connect twice each attempt instead of only once
For i As Integer = 1 To 2
If client Is Nothing OrElse Not client.Connected Then
'uses ClientSocketParameters structure to pass to recursive function ConnectionReturned()
clntSockParams = New ClientSocketParameters
clntSockParams.addrs = strIP
clntSockParams.prt = intPort
'create client and call BeginConnect (attempts to connect on separate thread until TimeoutTime has elapsed)
client = New System.Net.Sockets.TcpClient
client.SendTimeout = intTimeoutLength
client.ReceiveTimeout = intTimeoutLength
'setup timer with timeout length and start, if timer goes past intTimeoutLength, the Timeout() function is called which closes everything and leaves client = Nothing
AddHandler TimeoutTime.Elapsed, AddressOf Timeout
TimeoutTime.Interval = intTimeoutLength
TimeoutTime.Start()
client.BeginConnect(strIP, intPort, New AsyncCallback(AddressOf ConnectionReturned), clntSockParams)
'keeps the program from doing anything else until BeginConnect either succeeds or fails (due to connect on separate thread)
Do While TimeoutTime.Enabled
System.Threading.Thread.Sleep(500)
Loop
End If
'if TimeoutTime is elapsed and client is Nothing, connection didn't happen, throw an error
If client Is Nothing Then
blnConnected = False
Else
blnConnected = True
Exit For
End If
Next
Catch ex As Exception
blnConnected = False
End Try
Return blnConnected
End Function
Private Sub ConnectionReturned(ByVal ar As System.IAsyncResult)
'this method is called from the client.BeginConnect line in Connect(), make sure timer is running
If TimeoutTime.Enabled Then
'ensure client is initialized
If client Is Nothing Then client = New System.Net.Sockets.TcpClient
'keep calling ConnectionReturned until client.Connected is true
If client.Connected Then
TimeoutTime.Stop()
Else
Dim actualParameters As ClientSocketParameters = DirectCast(ar.AsyncState, ClientSocketParameters)
client.BeginConnect(actualParameters.addrs, actualParameters.prt, New AsyncCallback(AddressOf ConnectionReturned), clntSockParams)
End If
End If
End Sub
Private Sub Timeout(ByVal sender As Object, ByVal e As EventArgs)
'this method is only called if TimeoutTime elapsed, which means no connection was made. close the client object if needed, set to Nothing, and stop TimeoutTime
If TimeoutTime.Enabled Then
Try
client.Close()
Catch ex As Exception
End Try
client = Nothing
TimeoutTime.Stop()
End If
End Sub
Public Sub Disconnect()
'need to make sure StreamWriter connection and TcpClient connection to printer are closed
Try
client.Close()
Catch ex As Exception
End Try
client = Nothing
blnConnected = False
End Sub
End Class
Please let me know if more information is needed. I'm also open to alternative connection types, so long as the static IP and port can be used (over wifi connection).
EDIT: I've modified the code in my program to use the System.Net.Socket class, and the connection seems to hold steady in my initial tests. What would cause this connection method to work where TCPClient doesn't seem to, in the same location?

Related

Threading issue with TCP?

I've written a Windows-based TCP server program based on some VB code I found on the internet several years ago (I can't find the link now, but it was called "multi-client server program" or something like that).
The server communicates with a couple of Enterprise iPhone client apps that I have also developed. I've successfully incorporated this code into two separate server programs that work just fine on both the development server and the production server. I'm working on a third iOS client application and a corresponding server program, which works fine on the development machine, but will not work on the production server -- the same machine that hosts the other two server programs with no problem.
It was working for a while, but as I have added more functionality to the server class, it seems to have stopped processing the incoming data, but only on the production machine.
The code that performs the connection functions is contained in a separate public class which is supposed to communicate with the Windows form that processes the incoming data. Using msgbox's for debugging tools, I have been able to confirm that the Client Connection class is connecting and is receiving the data, but it for some reason will not communicate that data with the Windows form.
My only thought is that it must be a threading issue, but I am not sure, and I have no idea how to find out.
To recap, just to be clear:
The code works fine on the development server, but not on the production server.
The client connection class is identical in every way with the other two working server programs.
That the client is communicating with the client connection class has been confirmed. The incoming data can be captured inside the connection class and displayed in msgbox's, so the data is getting through.
The Windows class that processes the incoming data is not receiving the data from the public client connection class.
EDIT: I should have included this in the original post: The development server is Win 7; the production sever is Windows Server 2012.
Because of the complexity of this code, I am not sure what snippets would be applicable for this, but I'll do my best to include what makes most sense to me here.
This is the entirety of the client connection class with the exception of the method that sends outgoing data. Again, this was copied from a website several years ago, and the comments are those of the original developer:
Imports System.IO
Imports System.Net.Sockets
Imports System.Data.SqlClient
Public Class ConnectedClient
Private cli As TcpClient 'declare a tcp client which will be the client that we assign to an instance of this class
Private uniqueid As String 'this will be used for the name property
Dim strErrorLogPath As String
Public Property name ''This will be the name of the ID containing its Unique ID
Get
Return uniqueid 'when we want to get it, it will return the Unique ID
End Get
Set(ByVal value)
uniqueid = value 'Used for setting the name
End Set
End Property
Sub New(ByVal client As TcpClient)
Dim r As New Random 'create a new random to serve as way to create our unique ID
Dim x As String = String.Empty 'declare a new variable to hold the ID
For i = 0 To 7 'we are going to have an ID of 7 randomly generated characters
x &= Chr(r.Next(65, 89)) 'create a generate dnumber between 65 and 89 and get the letter that has the same ascii value (A-Z)
' and add it onto the ID string
Next
Me.name = client.Client.RemoteEndPoint.ToString().Remove(client.Client.RemoteEndPoint.ToString().LastIndexOf(":")) & " - " & x 'set the name to the Unique ID
cli = client 'assign the client specified to the TCP client variable to we can operate with it
cli.GetStream.BeginRead(New Byte() {0}, 0, 0, AddressOf read, Nothing) 'start reading using the read subroutine
End Sub
Public Event gotmessage(ByVal message As String, ByVal client As ConnectedClient) 'this is raised when we get a message from the client
Public Event disconnected(ByVal client As ConnectedClient) 'this is raised when the client disconnects
Sub read(ByVal ar As IAsyncResult) 'this will process all messages being received
'bn-note: This is the entry point of the data being received from the client.
Try
Dim sr As New StreamReader(cli.GetStream) 'initialize a new streamreader which will read from the client's stream
Dim msg As String = sr.ReadLine() 'create a new variable which will be used to hold the message being read
If msg = "" Then
RaiseEvent disconnected(Me) 'WE CAN ASSUME THE CLIENT HAS DISCONNECTED
Exit Try
'msg = "Null Message"
End If
WriteToRawIncomingDataLog(msg)
RaiseEvent gotmessage(msg, Me) 'tell the server a message has been received. Me is passed as an argument which represents
' the current client which it has received the message from to perform any client specific
' tasks if needed
cli.GetStream.BeginRead(New Byte() {0}, 0, 0, AddressOf read, Nothing) 'continue reading from the stream
'End Ifz
'Catch ex As System.NullReferenceException
Catch ex As Exception
Try 'if an error occurs in the reading purpose, we will try to read again to see if we still can read
Dim sr As New StreamReader(cli.GetStream) 'initialize a new streamreader which will read from the client's stream
Dim msg As String = sr.ReadLine() 'create a new variable which will be used to hold the message being read
WriteToRawIncomingDataLog(msg)
RaiseEvent gotmessage(msg, Me) 'tell the server a message has been received. Me is passed as an argument which represents
' the current client which it has received the message from to perform any client specific
' tasks if needed
cli.GetStream.BeginRead(New Byte() {0}, 0, 0, AddressOf read, Nothing) 'continue reading from the stream
'End If
'Catch ex2 As System.NullReferenceException
' Stop
Catch ' IF WE STILL CANNOT READ
RaiseEvent disconnected(Me) 'WE CAN ASSUME THE CLIENT HAS DISCONNECTED
End Try
End Try
End Sub
Here is the Windows form code that processes the incoming data:
Private Sub formMain_Load(sender As Object, e As System.EventArgs) Handles Me.Load
Try
Dim listener As New System.Threading.Thread(AddressOf listen) 'initialize a new thread for the listener so our GUI doesn't lag
listener.IsBackground = True
listener.Start(CInt(IncomingPortString)) 'start the listener, with the port specified as a parameter (textbox1 is our port textbox)
Catch ex As Exception
txtSystemMessages_AddText(String.Format("Error in formMain_Load sub: {0}{1}", ex.Message.ToString, vbNewLine))
End Try
End Sub
Sub listen(ByVal port As Integer)
Try
Dim t As New TcpListener(IPAddress.Any, port) 'declare a new tcplistener
t.Start() 'start the listener
Do
Dim client As New ConnectedClient(t.AcceptTcpClient) 'initialize a new connected client
AddHandler client.gotmessage, AddressOf received 'add the handler which will raise an event when a message is received
AddHandler client.disconnected, AddressOf disconnected 'add the handler which will raise an event when the client disconnects
Loop Until False
Catch ex As Exception
txtSystemMessages_AddText(String.Format("Error in Listen sub: {1}{2}", ex.Message.ToString, vbNewLine))
End Try
End Sub
Sub received(ByVal msg As String, ByVal client As ConnectedClient)
Try
If Not clients.ContainsKey(client) Then
clients.Add(client, client.name.ToString) 'add the client to our hashtable
End If
Catch ex As ArgumentException
End Try
(The sub that processes the incoming data string "msg".)
End Sub
I'm at a complete loss to know what could be causing this issue, and a threading issue is all that I can think of. Could a different machine have different threading schemes? Any help would be greatly appreciated.

VB.NET: TCP Client Socket timeout?

I have a TCP Client socket that connects to a server when Form1 loads. I need a way for the client socket to "give up" trying to connect to the remote server if it takes too long to connect. i.e Time out after 30 seconds.
Currently when the socket cannot connect to the server for what ever reason the form will appear to freeze up until the server can be reached.
Its probably worth noting im using System.Net.Sockets.TcpClient()
Here is My Sub for creating the connection:
Public serverStream As NetworkStream
Public clientSocket As New System.Net.Sockets.TcpClient()
Public Sub CreateTCPConnection()
Try
clientSocket.Connect(System.Net.IPAddress.Parse(My.Settings.ServerIP), My.Settings.ServerPort)
ConnectionStatusLbl.Text = "Connection: Connected"
ConnectionStatusPB.Image = My.Resources.LED_Green
'This should work according to MSDN but does not
clientSocket.SendTimeout = 3000
clientSocket.ReceiveTimeout = 3000
UpdateTimer.Start()
Catch ex As Exception
ConnectionStatusLbl.Text = "Connection: Not Connected"
ConnectionStatusPB.Image = My.Resources.LED_Red
clientSocket.Close()
MsgBox(ex.ToString)
End Try
End Sub
You could thread the whole CreateTCPConnectionfunction, then you use a Timer in the Mainthread to abort the thread after X Seconds, if a public boolean is not setted to true.
Pseudo Code for the thread initialization:
Dim isConnected as Boolean
Private Sub Form1_Load( _
ByVal sender As System.Object, ByVal e As System.EventArgs) _
Handles MyBase.Load
isConnected = false;
trd = New Thread(AddressOf CreateTCPConnection)
trd.IsBackground = True
//Afer the start function the thread tries to connect asyncron to your server
trd.Start()
Timer timer = new Timer();
//Start the timer to check the boolean
myTimer.Tick += new EventHandler(TimerEventProcessor);
myTimer.Start()
End Sub
Private Sub CreateTCPConnection()
//Your Connection method
End Sub
//Checks the isConnected status each second. If isConnected is false and 3 seconds are reached, the thread gets aborted.
Private Sub TimerEventProcessor(Object myObject,
EventArgs myEventArgs)
if(myEventArgs.elapsedTime >= 3 AND isConnected == false) Then
trd.Abort();
End if
End Sub
This is a small workaround.

How do I call a function on the main thread from another thread in vb

I have a timer function which is called on my windows form which works perfectly when a button on the UI is clicked. However, in my application Im connecting to a server using a TCP socket, when the server disconnects Im catching the exception thrown and Id like at this point for the timer to start (ticker) then run the reconnection.
When i reference the timer from the try catch it wont run, so I imagine its because its on the main thread?
Here is my code for the timer:
Public Delegate Sub DroppedConnectionDelegate(ByVal ReConn As Integer)
Public Sub DroppedConnection(ByVal ReConn As Integer)
Console.Write("Dropped Connection()")
Thread.Sleep(1000)
If ReConn = 1 Then
MakeConnection(My.Settings.savedIP, False)
Else
isRunning = False
Second = 0
'client.Close()
'client.Dispose()
Timer1.Interval = 1000
Timer1.Enabled = True
Timer1.Start() 'Timer starts functioning
End If
' End If
End Sub
Public Sub Timer1_Tick(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Timer1.Tick
Second = Second + 1
Console.WriteLine(Second)
If Second >= 10 Then
Timer1.Stop() 'Timer stops functioning
Timer1.Enabled = False
MakeConnection(ServerAddressString, False)
End If
End Sub
Here is the sub from where Id like to call the timer on catch exception:
Public Shared Sub ReceiveCallback(ByVal ar As IAsyncResult)
Dim state As StateObject = CType(ar.AsyncState, StateObject)
Dim client As Socket = state.workSocket
Dim strReceiveBuffer As String = String.Empty
' Read data from the remote device.
Dim bytesRead As Integer
Console.WriteLine("ar to string = " & ar.ToString)
'While client.Connected
If (AsynchronousClient.connectDone.WaitOne(5000, True)) Then
Try
If client.Connected = True Then
bytesRead = client.EndReceive(ar)
Console.WriteLine("Socket Connected")
If bytesRead > 0 Then
state.sb.Clear()
' There might be more data, so store the data received so far.
state.sb.Append(Encoding.ASCII.GetString(state.buffer, 0, bytesRead))
strReceiveBuffer = state.sb.ToString()
MainForm.main_Response = state.sb.ToString()
If strReceiveBuffer.IndexOf("doses-remaining") > 0 Then
response = state.sb.ToString()
MainForm.GetLabelTextToString(response)
'receiveDone.Set()
'isReceived = True
'Return
End If
' Get the rest of the data.
client.BeginReceive(state.buffer, 0, StateObject.BufferSize, 0, New AsyncCallback(AddressOf ReceiveCallback), state)
Else
' All the data has arrived; put it in response.
If state.sb.Length > 1 Then
response = state.sb.ToString()
End If
' Signal that all bytes have been received.
receiveDone.Set()
isReceived = True
End If
Else
MainForm.WriteLog("RecieveCallback() Error outside catch :" & Date.Today.ToString & " " & TimeOfDay)
MainForm.UpdateList("RecievecallBack error, attempting reconnect..." & client.RemoteEndPoint.ToString())
MainForm.isRunning = False
MainForm.DroppedConnection(0)
End If
Catch ex As Exception
'MessageBox.Show("ReceiveCallback Error, Check Log.")
MainForm.WriteLog("RecieveCallback() Error inside catch :" & Date.Today.ToString & " " & TimeOfDay & ex.Message)
MainForm.UpdateList("RecievecallBack error, attempting reconnect..." & client.RemoteEndPoint.ToString())
client.Shutdown(SocketShutdown.Both)
client.Close()
client.Dispose()
MainForm.isRunning = False
Dim d As DroppedConnectionDelegate = AddressOf MainForm.DroppedConnection
'MainForm.DroppedConnection(0)
d.BeginInvoke(0, Nothing, Nothing)
Exit Try
End Try
End If
' End While
End Sub 'ReceiveCallback
If I run the application and the server disconnects unexpectedly, currently it wont reconnect automatically. But it will run the timer if I click the connect button (which calls the timer anyway)
Can anyone help please?
Thanks in advance
I'm not 100% sure as I've never tried but I think that if you use a Timers.Timer instead of a Windows.Forms.Timer then what you're doing now will work. By default, a Timers.Timer will raise its Elapsed event on a secondary thread but you can assign a form or other control to its SynchronizingObject property to make it raise events on the UI thread. Calling Start from a secondary thread should be OK in that case.
If that doesn't work then you can do what you want by first marshalling a method call to the UI thread before starting the Timer. That might look like this:
Private Sub StartTimer()
If Me.InvokeRequired Then
Me.Invoke(New Action(AddressOf StartTimer))
Else
Me.Timer1.Start()
End If
End Sub
You can call that method on any thread and it will just work.
Note that setting the Enabled property and calling Start or Stop is redundant. Start already sets Enabled to True and Stop sets it to False. Do one or the other, not both. It is most appropriate to call a method when you know for a fact that you want to either start or stop the Timer. If you have a Boolean value that might be either True or False then you should use the property. Setting the property using a literal value is not wrong per se but it is not what was intended. An example of where it's appropriate to use the property is when you use the same method to both start and stop the Timer from a secondary thread:
Private Sub EnableTimer(enable As Boolean)
If Me.InvokeRequired Then
Me.Invoke(New Action(Of Boolean)(AddressOf EnableTimer), enable)
Else
Me.Timer1.Enabled = enable
End If
End Sub
Again, calling that method on any thread will just work.

TCP/ip recive connection but locks up when trying to read data

Please can someone tell me why my server locks up when my rclient connects and try to send data?
If I comment out:
Dim bytes(rclient.ReceiveBufferSize) As Byte
rstream.Read(bytes, 0, CInt(rclient.ReceiveBufferSize))
RString = Encoding.ASCII.GetString(bytes)
TextBox2.Text = RStringcode here
and just have
TextBox2.Text = ("Remote connected")
the server picks up on a connected client so it must be something i have done with the .read ?
Imports System.Net.Sockets
Imports System.Text
Imports System.Threading
Imports System.IO.Ports
Dim tclient As New TcpClient
Dim tstream As NetworkStream
Dim rclient As New TcpClient
Dim rstream As NetworkStream
Dim sArray() As String
Dim Tricopter As New TcpListener(2000)
Dim Remote As New TcpListener(2001)
Dim myVal As String
Dim TricopterThread As New Thread(AddressOf TricopterThreadSub)
Dim RemoteThread As New Thread(AddressOf RemoteThreadSub)
Dim tre(tclient.ReceiveBufferSize) As Byte
Dim TState = False
Dim RState = False
Dim SerialSwitch = False
Dim Toggle = False
Dim TString As String
Dim RString As String
Dim Remo(rclient.ReceiveBufferSize) As Byte
Dim TSendText() As Byte
Private Sub RemoteThreadSub()
rclient = Remote.AcceptTcpClient
rstream = rclient.GetStream
RState = True
End Sub
Private Sub TricopterThreadSub()
tclient = Tricopter.AcceptTcpClient
tstream = tclient.GetStream
TState = True
End Sub
Private Sub Form1_FormClosing(sender As Object, e As FormClosingEventArgs) Handles Me.FormClosing
End Sub
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
CheckForIllegalCrossThreadCalls = False
Tricopter.Start()
Remote.Start()
Timer1.Start()
TricopterThread.Start()
RemoteThread.Start()
End Sub
Private Sub Timer1_Tick(sender As Object, e As EventArgs) Handles Timer1.Tick
myVal = ">" & TrackBar1.Value & "," & TrackBar2.Value & "," & TrackBar3.Value & "," & TrackBar4.Value & "/n"
Try
If TState = False Then
TextBox1.Text = ("No tricopter connected")
Else
TextBox1.Text = myVal
If Toggle = False Then
TSendText = Encoding.ASCII.GetBytes(myVal)
End If
If Toggle = True Then
TSendText = Encoding.ASCII.GetBytes(RString)
End If
tstream.Write(TSendText, 0, TSendText.Length)
End If
If RState = False Then
TextBox2.Text = ("No Remote connected")
Else
' TextBox2.Text = ("Remote connected")
Dim bytes(rclient.ReceiveBufferSize) As Byte
rstream.Read(bytes, 0, CInt(rclient.ReceiveBufferSize))
RString = Encoding.ASCII.GetString(bytes)
TextBox2.Text = RString
End If
Catch ex As Exception
End Try
End Sub
Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click
If Toggle = False Then
CheckBox1.CheckState = 1
Toggle = True
Else
CheckBox1.CheckState = 0
Toggle = False
End If
End Sub
End Class
You are assuming that reads return a specific amount of data such as ReceiveBufferSize. That is not true. A read returns at least one byte, that is all.
Just to clarify, TCP does not support message based transfer.
The correct way to read depends on the protocol. If the exact number of bytes expected is known you need to read in a loop until so many bytes are received (or use BinaryReader which does that for you).
For a line based protocol you can use StreamReader.ReadLine which again automates the looping.
ReceiveBufferSize is completely unrelated to how much data is available or will come.
DataAvailable is how much data can be read right now without blocking. But more data might come. It is almost always a bug to use it. Might return 0 at any time even if data comes in 1ms later.
EDIT: For those of you who have downloaded/tested this already, I made a bugfix to the classes so you'll need to redownload the sources if you're going to use them again.
If you want to perform proper data transfer you'll need to use a more reliable method than just simply reading random data. And as usr pointed out: the TcpClient.ReceiveBufferSize property does not tell you how much data there is to receive, nor how much data there is sent to you. ReceiveBufferSize is just a variable indicating how many bytes you are expected to get each time you read incoming data. Read the MSDN page about the subject for more info.
As for the data transfer, I've created two classes which will do length-prefixed data transfer for you. Just import them to your project and you'll be able to start using them immediatelly. Link: http://www.mydoomsite.com/sourcecodes/ExtendedTcpClient.zip
Example usage
Server side
First declare a new variable for ExtendedTcpClient, and be sure to
include WithEvents in the declaration.
Dim WithEvents Client As ExtendedTcpClient
Then you just need to use a normal TcpListener to check for
incoming connections. The TcpListener.Pending() method can be
checked in for example a timer.
When you are to accept a new TcpClient, first declare a new
instance of the ExtendedTcpClient. The class requires to have a
form as it's owner, in this application Me is the current form.
Then, use the ExtendedTcpClient.SetNewClient() method with
Listener.AcceptTcpClient() as it's argument to apply the
TcpClient from the listener.
If Listener.Pending() = True Then
Client = New ExtendedTcpClient(Me)
Client.SetNewClient(Listener.AcceptTcpClient())
End If
After that you won't be needing the timer anymore, as the
ExtendedTcpClient has it's own thread to check for data.
Now you need to subscribe to the PacketReceived event of the
client. Create a sub like so:
Private Sub Client_PacketReceived(sender As Object, e As ExtendedTcpClient.PacketReceivedEventArgs) Handles Client.PacketReceived
End Sub
In there you can for example output the received packet as text into
a TextBox. Just check if the packet header is PlainText and then
you can convert the received packets contents (which is an array of
bytes, accessed via e.Packet.Contents) to a string and put it in
the TextBox.
If e.Packet.Header = TcpMessagePacket.PacketHeader.PainText Then
TextBox1.AppendText("Message recieved: " & System.Text.Encoding.Default.GetString(e.Packet.Contents) & Environment.NewLine)
End If
Lastly, when closing the form you just need to disconnect the client.
Private Sub ServerWindow_FormClosing(sender As Object, e As System.Windows.Forms.FormClosingEventArgs) Handles Me.FormClosing
If Client IsNot Nothing Then Client.Disconnect()
End Sub
And that's it for the server side.
Client side
For the client side you will only be needing a normal TcpClient (unless you don't want to receive data there too).
Dim Client As New TcpClient
Then connect to the server via the IP and port you've given the listener.
Client.Connect("127.0.0.1", 5555) 'Connects to localhost (your computer) at port 5555.
Now if you want to send plain text to the server you'd do something like this:
Dim MessagePacket As New TcpMessagePacket(System.Text.Encoding.Default.GetBytes(TextBox2.Text), TcpMessagePacket.PacketHeader.PainText)
MessagePacket.Send(Client) 'Client is the regular TcpClient.
And now everything should be working!
Link to a complete example project: http://www.mydoomsite.com/sourcecodes/TCP%20Messaging%20System.zip
If you want to add more headers to the class, just open TcpMessagePacket.vb and add more values in the PacketHeader enum (located in the region called Constants).
Hope this helps!
Screenshot from the example project
(Click the image for larger resolution)

UDP local broadcast

I'm working on a remote control tool. The client runs a program to locally send commands to servers in order to control them.
However, the client doesn't know the server's IP address and vice versa.
I decided to use UDP broadcasting (please tell me if there's a better way to do this, I tried using multicast but I didn't really understand it). When started, the client (which controls the servers) broadcasts a message to tell the servers that a new client connected. Then (or when the server is started), the servers broadcast their own IP addresses. When the client receives an IP address, it tries to connect via TCP.
Unfortunately, I ran into problems with that. I randomly got An existing connection was forcibly closed by the remote host exceptions and I wasn't able to find out why.
The exception occurred in my client program when listening for UDP broadcasts.
Now, I'm looking for a better way to find the clients.
Should I use broadcast or multicast?
How would I implement that?
EDIT: It wouldn't be a problem to use multiple ports. However, I need to be able to run a client AND a server on a single computer.
Here's the code I was using
Client (controls servers)
'Variables
Private UdpBroadcaster As UdpClient
Private UdpBroadcasterEndpoint As New IPEndPoint(IPAddress.Broadcast, 4334)
'Sub New()
Try
UdpBroadcaster = New UdpClient(4333)
UdpBroadcaster.EnableBroadcast = True
Catch
MsgBox("Error creating UDP client! Port already in use?", ...)
End Try
'Called when the application starts
Private Sub StartUdpListener()
Dim ListenerUdp As New Thread(AddressOf UdpListener)
ListenerUdp.IsBackground = True
ListenerUdp.Start()
End Sub
'Started as thread in StartUdpListener()
Private Sub UdpListener()
Try
Do
'The next line fails with the error I described (An existing connection was forcibly closed by the remote host)
Dim ReceivedBytes() As Byte = UdpBroadcaster.Receive(UdpBroadcasterEndpoint)
Dim ReceivedString As String = System.Text.Encoding.UTF32.GetString(ReceivedBytes)
'The following three lines will just connect to the received hostname
Dim ScanThread As New Thread(Sub() ScanSingle(ReceivedString))
ScanThread.IsBackground = True
ScanThread.Start()
Loop
Catch
If Not UdpBroadcaster Is Nothing Then
UdpBroadcaster.Close()
UdpBroadcaster = Nothing
End If
InvokeStatus("UDP connection lost, please try again later.")
End Try
End Sub
'Called when the application starts and when the user manually clicks the "UDP Scan" button
Private Sub StartBroadcastUdpThread()
Dim UdpBroadcastThread As New Thread(Sub() BroadcastUdp())
UdpBroadcastThread.IsBackground = True
UdpBroadcastThread.Start()
End Sub
'Started as thread in StartBroadcastUdpThread()
Private Sub BroadcastUdp()
If UdpBroadcaster Is Nothing Then
Try
UdpBroadcaster = New UdpClient(4333)
UdpBroadcaster.EnableBroadcast = True
Catch
MsgBox("Error creating UDP Client.", MsgBoxStyle.Critical, "Error")
Application.Exit()
Return
End Try
End If
Dim BroadcastBytes() As Byte = System.Text.Encoding.UTF32.GetBytes("Client-Identify")
UdpBroadcaster.Send(BroadcastBytes, BroadcastBytes.Length, UdpBroadcasterEndpoint)
InvokeStatus("UDP request sent successfully")
End Sub
Servers (controlled by client)
'Variables
Private UdpBroadcaster As UdpClient
Private UdpBroadcasterEndpoint As New IPEndPoint(IPAddress.Broadcast, 4333)
'Main method
Public Sub Main()
Try
UdpBroadcaster = New UdpClient(4334)
UdpBroadcaster.EnableBroadcast = True
StartUdpListener()
StartBroadcastUdpThread()
Catch
Console.WriteLine("Failed to create server. Port already in use?")
End Try
End Sub
'Called in Main()
Private Sub StartUdpListener()
Dim ListenerUdp As New Thread(AddressOf UdpListener)
ListenerUdp.IsBackground = True
ListenerUdp.Start()
End Sub
'Started as thread in StartUdpListener()
Private Sub UdpListener()
Try
Do
Dim ReceivedBytes() As Byte = UdpBroadcaster.Receive(UdpBroadcasterEndpoint)
Dim ReceivedString As String = System.Text.Encoding.UTF32.GetString(ReceivedBytes)
If ReceivedString.Equals("Client-Identify") Then
StartBroadcastUdpThread()
End If
Loop
Catch
If Not UdpBroadcaster Is Nothing Then
UdpBroadcaster.Close()
End If
End Try
End Sub
'Called when the application is started or a "Client-Identify" command is received
Private Sub StartBroadcastUdpThread()
Dim UdpBroadcastThread As New Thread(Sub() BroadcastUdp())
UdpBroadcastThread.IsBackground = True
UdpBroadcastThread.Start()
End Sub
'Started as thread in StartBroadcastUdpThread()
Private Sub BroadcastUdp()
Dim BroadcastBytes() As Byte = System.Text.Encoding.UTF32.GetBytes(Dns.GetHostName)
UdpBroadcaster.Send(BroadcastBytes, BroadcastBytes.Length, UdpBroadcasterEndpoint)
End Sub
Thanks in advance!
Thank you for your answers. I fixed it by removing the feature to manually call StartBroadcastUdpThread() in my client.
I still don't understand why this happens though. I use exactly the same code for both client and server, except the ports are swapped. The TCP server doesn't crash even if the StartBroadcastUdpThread() method is called multiple times, the client does. By the way, the problem occurs regardless of whether the client or server is started first.
Even if I don't really understand why broadcasting the second time stops the client from receiving broadcasts - it's fixed for now. Thanks for you help!
I would suggest using Zeroconf to find the server and clients, and then use a TCP socket to communicate between the two. You can see an example implementation on key-value pair zeroconf announcements here: https://github.com/Eyescale/Lunchbox/blob/master/lunchbox/servus.cpp
Minimal UDP server basis:
Imports System.Threading
Shared client As UdpClient
Shared receivePoint As IPEndPoint
client = New UdpClient(2828) 'Port
receivePoint = New IPEndPoint(New IPAddress(0), 0)
Dim readThread As Thread = New Thread(New ThreadStart(AddressOf WaitForPackets))
readThread.Start()
Public Shared Sub WaitForPackets()
While True
Dim data As Byte() = client.Receive(receivePoint)
Console.WriteLine("=" + System.Text.Encoding.ASCII.GetString(data))
End While
End Sub