use callback to allow multiple connections to socket? - vb.net

I am trying to create a VB.Net server program that has an external link to process data. The server will wait on port 7878 for a request to be made from a client. When the server receives a request it will send a string containing the state of all variables and send a string every second containing values for only changed variables. The server does not need to receive any information from the client for processing. I have successfully implemented everything with the exception of allowing for multiple requests at once. The basic program looks like this:
Class Server
Public Sub Main()
'thread start for ListenForRequests
End Sub
Public Sub ListenForRequests()
'code to set up socket
'bind socket
'listen
Using newConnection as socket.accept()
'do stuff
newConnection.send'stuff
End Using
End Sub
End Class
Can anyone tell me a SIMPLE way to implement a callback for async code? I think I need to move my 'do stuff and send stuff code into a new sub and fire a new thread upon socket.accept, but I can't figure out how to pass the newConnection through to a new thread.

Related

Objects are Nothing when referenced by remote client

I have made several VB apps that communicate using Windows Remoting, but this is the first time I ran into this problem.
There are two programs in my project, a client and a server. The server program has a queue. The client program adds items to the queue by calling a server method. But when the server program checks the queue, it is empty.
Furthermore, the server program instantiates several classes, but when the client tries to use them, it finds that they are Nothing. So this is a general problem not just an issue with the queue per se.
I have had experience with threading problems in the past, so I assumed that this was some kind of threading problem. I tried using a delegate function, but that did not help.
Here is a snippet of code to illustrate where the problem appeared. My apologies for not knowing how to make it properly formatted, this is my first attempt.
' ====================================================================
' this class is instantiated on the server at startup time
Public Class CPAutoDispatcher
' EXAMPLE #1
Public mWLQueue As New Collection
' This function is called from the remote client using Windows Remoting
Public Function SendWorkList(ByVal theList As String) As Boolean
Dim objWL As New AutoWorkList
If Not parseWorkList(theList, objWL) Then Exit Function
Call mWLQueue.Add(objWL)
SendWorkList = True
End Function
' This function is called from the server
Public Sub Tick()
If mWLQueue.Count = 0 Then Exit Sub ' <-- THIS ALWAYS EXITS!
Dim objWL As AutoWorkList = mWLQueue.Item(1)
Call mWLQueue.Remove(1)
' ... do something with objWL ...
End Sub
' EXAMPLE #2
Private mServerReports() As CPAutoServerReport
Private mDelGNR As DEL_GetNewReport = AddressOf getNewReportDelegate
' This function is called from the server
Public Function ProcessMessage(objSR As CPAutoServerReport) As Boolean
If mServerReports Is Nothing Then
ReDim mServerReports(0)
mServerReports(0) = objSR
Else
' ... do something else ...
End If
End Function
' This function is called from the remote client using Windows Remoting
Public Function GetNewReport() As CPAutoServerReport
GetNewReport = mDelGNR.Invoke
End Function
Private Function getNewReportDelegate() As CPAutoServerReport
If mServerReports Is Nothing Then Exit Function ' <-- THIS ALWAYS EXITS!
' ... do something with mServerReports ...
End Function
End Class
' ================================================================
Example #1: Similar code in other projects works, so I expected mWLQueue and mServerReports to be reachable by both the server and the client. But they are not. The client finds mWLQueue, but it is empty.
Example #2: If this was simply a threading issue, I would expect the delegate to make thing right. But it does not. The client finds that mServerReports Is Nothing, even after it has been set.
My code is behaving as if there are TWO instances of my CPAutoDispatcher class, one for the server thread, and another for the client thread (the remoting calls). But there is only one global variable, which is referenced by both threads.
I am baffled by this situation. Am I missing something that should be obvious?
The ultimate cause of my problems was the presence of duplicate declarations, which did not cause a compile error, and so went unnoticed. When I removed the extra declarations, all of the weird behavior went away.
I suspect that somehow, an instance of the alternate class was instantiated automatically, and being uninitialized, had an empty queue and references that were Nothing. But I don't understand how that came about.

Accessing TcpClient in VB.net DLL

I'm writing a DLL for SimTools that accesses the telemetry data of NoLimitsRollercoasterSimulator. The TcpClient is created during loading the DLL with
Dim tcpClientNLS As New TcpClient()
The tcpClientNLS can then be accessed by all the different subroutines in the DLL. When I create the TcpClient in one of the subroutines (e.g. Public Sub GameStart() ) the client is only accessible in this subroutine (GameStart). The issues is that when the NoLimits simulation ends, the DLL-subsoutine GameStop must close the TcpClient, otherwise the NoLimits simulation hangs.
Because the tcpClientNLS.Close() call not only closes the connetcion but also disposes the tcpClient, it is no longer accessible.
So the next time the NoLimits simulation starts and the DLL-GameStart routine tries to connect the TcpClient with tcpClientNLS.Connect("127.0.0.1", 15151) it throws an exception.
I have tried several different options - so far with no luck.
Is it possible to create a new TcpClient within a subroutine (e.g.
DLL-GameStart) and access it in another subroutine (e.g.
DLL-GameStop)?
I can also create a new TcpClient in the
DLL-Process_Telemetry subroutine everytime I read the telemetry data
from the NoLimits Simulation and close the TcpClient right after
(100× per sec) in the same DLL-Process_Telemetry subroutine. But I
guess this just consumes a lot of processing time?
Is there
another way to close the connection and reuse the TcpClient?
Thank you for your help in advance!
Is it possible to create a new TcpClient within a subroutine (e.g. DLL-GameStart) and access it in another subroutine (e.g. DLL-GameStop)?
It's not possible to create a new variable within a method/subroutine and then access it from the outside,
BUT you can always reinstantiate your global tcpClientNLS variable whenever you like, for example in your GameStart() method:
Dim tcpClientNLS As TcpClient
Public Sub GameStart()
tcpClientNLS = New TcpClient()
tcpClientNLS.Connect("127.0.0.1", 15151)
...
End Sub
I can also create a new TcpClient in the DLL-Process_Telemetry subroutine everytime I read the telemetry data from the NoLimits Simulation and close the TcpClient right after (100× per sec) in the same DLL-Process_Telemetry subroutine. But I guess this just consumes a lot of processing time?
Doing so can potentially slow things down, yes.
Is there another way to close the connection and reuse the TcpClient?
You can always reinstantiate it (like shown above) right after the tcpClientNLS.Close() call too.

TCP Client to Server communication

All I'm looking for is a simple TCPClient/Listner example on Windows Form VB.Net. I'm a newbie and Microsoft TCPClient/Listner class examples are not what I am looking for. All I am looking is for the TCPClient to send a message and for a TCPListener to get the message and to send a message back "I got your message" ?
A little help would be great. I have some codes, but is only to send message to server and not back from server to client..
Any help will be very appreciated..
TCP communication is stream-based, which means it doesn't handle any packets. Due to this, messages that you receive might be either partial or lumped together.
You could for example send:
Hello!
How are you?
But you might receive:
Hello!How are you?
or:
Hello!How ar
e you?
(or something similar)
To fix this you must apply something called "length-prefixing". Length-prefixing (or length prefixing) means that before you send a message, you put its length (amount of characters/bytes) in the beginning of it. By doing so, the endpoint will know exactly how many bytes to read for each message. Thus there will be no problems with messages being partial or lumped together.
This is not the most straightforward thing to do as a beginner, as to get it to work properly on both sides you have to structure your code just right. So I've created two classes that will take care of this for you. See the examples below on how to use them for simple text message-based communication.
Link to source: http://www.mydoomsite.com/sourcecodes/ExtendedTcpClient.zip
Link to C# source : http://www.mydoomsite.com/sourcecodes/ExtendedTcpClient%20CSharp.zip
EDIT (2019-11-08)
Some time ago I made an upgraded version of this with a bit better code structure and error handling. For those of you interested, the new code can be downloaded here (VB.NET only):
https://www.mydoomsite.com/sourcecodes/ExtendedTcpClient%202.0.zip
Example usage
Note that in those examples Client does not refer to the client side, but to the TcpClient.
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 will need a TcpListener and a Timer to check for incoming connections.
Dim Listener As New TcpListener("0.0.0.0", 5555) 'Listen for any connection on port 5555.
Dim WithEvents Tmr As New System.Windows.Forms.Timer
Then you need to subscribe to the timer's Tick event.
Private Sub Tmr_Tick(sender As System.Object, e As System.EventArgs) Handles Tmr.Tick
End Sub
In there you check for incoming connections via the Listener.Pending() method. When you are to accept a connection you first declare a new
instance of the ExtendedTcpClient. The class requires to have a
form as its owner, in this application Me is the current form.
Then you use the ExtendedTcpClient.SetNewClient() method with
Listener.AcceptTcpClient() as its argument to apply the
TcpClient from the listener. Put this code in the Tmr_Tick sub:
If Listener.Pending() = True Then
Client = New ExtendedTcpClient(Me)
Client.SetNewClient(Listener.AcceptTcpClient())
End If
Now the client and server are connected to each other.
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
All received data are presented in an array of bytes.
In the PacketReceived sub you can 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.PlainText Then
TextBox1.AppendText("Message recieved: " & System.Text.Encoding.Default.GetString(e.Packet.Contents) & Environment.NewLine)
End If
System.Text.Encoding.Default.GetString() will convert a byte array to normal text.
In the PacketReceived sub you can also make it send "Message received" to the client.
Dim ResponsePacket As New TcpMessagePacket(System.Text.Encoding.Default.GetBytes("Message received."), TcpMessagePacket.PacketHeader.PlainText)
ResponsePacket.Send(Client.Client) 'Get the ExtendedTcpClient's underlying TcpClient.
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 do pretty much the same as the server side, though you won't be needing a TcpListener nor a Timer.
Dim WithEvents Client As New ExtendedTcpClient(Me) 'The current form as its owner.
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 text to the server you'd do something like this (in for example a button):
Dim MessagePacket As New TcpMessagePacket(System.Text.Encoding.Default.GetBytes(TextBox2.Text), TcpMessagePacket.PacketHeader.PlainText)
MessagePacket.Send(Client.Client)
TextBox2 includes the text you want to send.
Lastly, you will need to subscribe to the PacketReceived event here too to check for responses from the server. In there you receive text just like the server does.
Private Sub Client_PacketReceived(sender As Object, e As ExtendedTcpClient.PacketReceivedEventArgs) Handles Client.PacketReceived
If e.Packet.Header = TcpMessagePacket.PacketHeader.PlainText Then
TextBox1.AppendText(System.Text.Encoding.Default.GetString(e.Packet.Contents) & Environment.NewLine) 'Prints for example "Message received." from the server.
End If
End Sub
And now everything should be working!
Link to a complete example project (only client-to-server): http://www.mydoomsite.com/sourcecodes/TCP%20Messaging%20System.zip
Link to C# example: http://www.mydoomsite.com/sourcecodes/CSharp%20TCP%20Messaging%20System.zip
If you want to add more headers to the class (the headers indicate to you what kind of data each packet contains), 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)

How to handle unexpected shut down of vb.net application

I'm developing VB.Net application and i wanted to block users from open another session of the program while they already has one
so i made a value in my DB that changes from Offline to Online and reverse according to the status of the log in
Program Started = Online (In Form Load Event)
Program Closed = Offline (In Form Closing Event)
The problem is : some times the program hang or windows ..... etc so the program don't change the DB value for that user from Online to Offline and this prevent him from opening the application
so how i can handle the unexpected shut down of my program to deal with this case?
you could simply use the built in functionality Single instance application by clicking on your project and on application check Make Single instance application as the screen shot shows
I think you're talking about user session management here. If I understand you correctly, you want to stop any one user from logging in to two instances at the same time rather than there being two copies of your application open at one time (obviously with different users logged into each). If this is the case then it isn't actually that difficult to implement something very basic.
When your user logs on, perform a check to see if they already have a record in your session table. If not, then create one and issue that user with the session id. This id must then be supplied during all operations so that you can check it is valid. So far, so good.
If there is already a record in that session table then invalidate it (delete it, change the status - whatever) so that the old session id can't be used and issue a new one. That way, if your user already has a logged in copy of the app open then start another, the first one will no longer actually do anything useful (don't forget to inform users that an invalid session id is the reason why they can't do anything).
Obviously you will need to persist that session id for the life of the application, but for the love of all that is good, don't put it in a global variable: Pass it as part of the constructor to any object you instantiate which will perform any DB actions. Maybe even wrap it in a class of it's own if you need functionality and that variable or object can be private to your main class.
For example, lets say your main class is a form called frmMain which spawns a login dialog when it starts to capture username and password:
Public Class SessionManagerClass
_dal As New DAL
Private _sessionId As Int
Private _userName As String
Public Sub New(ByVal UserName As String, ByVal Password As string)
_sessionId = _dal.Login(UserName, Password)
End Sub
End Class
Partial Class frmMain
Private _session As SessionManagerClass
Public Sub frmMain_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles MyBase.Load
Dim loginForm As New dlgLogin
loginForm.ShowDialog()
_session = New SessionManagerClass(loginForm.UserName, loginForm.Password)
loginForm.Dispose()
End Sub
Public Sub btnLaunchChildForm_Click(ByVal sednder As Object, ByVal e As EventArgs) Handles btnLaunchChildForm.Click
Dim frm As New frmChildForm(_session)
frm.ShowDialog()
End Sub
End Class
Partial Class frmChildForm
_session As SessionManagerClass
_dal As New DAL
Public Sub New(ByVal Session As SessionManagerClass)
_session = Session
End Sub
Private Sub DoSomething()
If _dal.SaveValues(_session.SessionId, Value1, Value2, Value3) Then
'All Good
Else
'Something went wrong. Maybe invalid session ID or perhaps you didn't validate your data. Whatever. Handle the problem if you can.
End If
End Sub
End Class
This is in no way working code, just an example of how you might go about it to get you started.
You should attempt to remove the lock from the database when your application exits, but you can't stop someone going into task manager and ending the process. In this case your application does not get any notification of a shutdown and so can't write to the database.
We handle this occurrence by getting users to request an unlock code a secret code which allows them to clear the logged in flag. The unlock code changes each day so if they want one tomorrow they have to ask for another one.
Review your entire approach: you can use the Process class instead.
Dim list() As Process = Process.GetProcessesByName(Process.GetCurrentProcess.ProcessName)
If list.Length > 1 Then
Application.Exit()
End If
Well,
I've created a new solution
another field should be created with name currentsession
when the user open the program it takes a number
and when UN-expected shutdown occur i made a patch to change the current status to offline and the session id to the next number
then I've created a check sub in each save button to check for the current session and if it matches the one in the database it will continue
and if not it will shutdown the program

Cannot get port to close

I am trying to fix an app that uses TCP to receive files. Unfortunately, I do not have the source code for the sending application. The problem that I am having is that after the first file is received, the second one is sent by the sending application, but it is not being picked up by the receiving application.
I believe the issue is that the socket is not being closed after receiving the file. I have a method that should close it, but _socket.Connected = false, so nothing is done. However, if I check the port, it is still bound, even after the socket is closed.
Private Sub CloseSocket(ByVal disconnect As Boolean)
If Not (_socket Is Nothing) Then
If (_socket.Connected = True) Then
_socket.Shutdown(SocketShutdown.Both)
If(disconnect) Then
_socket.Disconnect(True)
End If
End If
_socket.Close()
_socket = Nothing
End If
End Sub
I realize that I have not included much code, but the Listen method is quite large and convoluted. I can get additional code if it will provide insight. Any assistance would be greatly appreciated.
EDITED
The code that is used to check the port status is below:
Private Shared Function TcpIpGetPortStatus(ByVal port As Int32) As String
Dim properties As NetworkInformation.IPGlobalProperties
Dim listeners As Net.IPEndPoint()
Dim local As Net.IPEndPoint
Dim connections As NetworkInformation.TcpConnectionInformation()
Dim t As NetworkInformation.TcpConnectionInformation
Dim portStatus As String
portStatus = "Disconnected"
properties = NetworkInformation.IPGlobalProperties.GetIPGlobalProperties()
listeners = properties.GetActiveTcpListeners()
Try
' Cycle through all listening TCP connections and find the one that is associated with port.
For Each local In listeners
If (local.Port = port) Then
portStatus = "Connected"
Exit For
End If
Next local
' Port wasn't in the listening state so check if it is established.
If (portStatus = "Disconnected") Then
properties = NetworkInformation.IPGlobalProperties.GetIPGlobalProperties()
connections = properties.GetActiveTcpConnections()
' Cycle through all active TCP connections and find the one that is associated with port.
For Each t In connections
If (t.LocalEndPoint.Port = port) Then
Select Case t.State
Case NetworkInformation.TcpState.Established
portStatus = "Connected"
Exit For
End Select
End If
Next t
End If
Catch ex As Exception
' Handle Exception...
End Try
Return portStatus
End Function
This function will return "Connected" when called. I've traced it through, and found that the port I was using was still bound. Again, thank you for all help.
ADDITION
I just ran this between two separate systems, using WireShark to capture the data. There is a Sender and Receiver that I do not have sourcecode for, and am trying to integrate with, and the Sender and Receiver that I am updating the code to get it to communicate properly with the existing ones. The stream sent is identical in both cases (except for datetime in the ACK). If I send a second message from the existing sender to the Receiver being upgraded, the message is sent to the port, but the Receiver in development is never notified, which hangs both programs.
After pouring over the code that was provided to me, I finally realized that it was closing and disconnecting the wrong socket. Rather than calling Shutdown and Disconnect on the socket that was used to receive the message, it was called on the listening socket.
Once i realized this, I was able to close the correct socket, and the application began to work. Now I just need to go through and correct all of the issues that this caused with the underlying code, and things should be good.
Thank you to all of the people who responded. You got me thinking in the right direction.