Visual Basic .NET Nightmare or Guide to upgrading a Guru.
The Dryer ate my Winsock
Well incase you hadn't noticed our friend the Winsock control is not readily available to us form our dot net controls set. My theory is they hadn't gotten around to making one yet in time for the release of .NET for the shelves. But it could be any reason unfathomed by we lowly programmers.
What we don't want to do in our dot net application is go running for the ActiveX control, which is still available along with all the other old com objects. Interop is not your friend. It's a marketing strategy to keep what's been working all along to keep working. If it's not people's faces turn red and nasty words start flying around. This is why DOS was still floating around until ME. Now the old goat runs on an emulator, this my friends adds layers to already existing layers of code. We don't want that. C programmers will scoff at VB for this reason alone. VB of the past has required it's own code interpreter. This support file was necessary to run your program. Before windows ME the file did not already come installed with the operating system and was installed with your app. This of' course only supported VB's intrinsic controls. ActiveX and COM also have to ship with your program for any of the extra controls you added.
Now as long as you stay in the framework, your on the same level as your friends who program in C. Not only that but your program will be constrained to the framework and have more portability. If your sill unclear think of it this way when you compile your release you can put it on a box that Only has the dot net framework installed and it will run without being installed.
So our goal right now is to create a class that acts similar to the Winsock control. Because as is the sockets objects available to us in dot net are not only confusing we don't want to recode them for each project, we want reusable code that works and encompasses our needs. If you have never used the Winsock control you still want to follow along, because you may one day see the need and it will help you in becoming the dot net Guru of tomorrow. 2003 is coming!!! We want to have as much of a heads up as we can before that.
Your first thought is without it being a control it is going to be some big huge program to get it to work. This is true if you coded for this in VB 6 with API. These boys & girls were the days. Today we are offered up much easier access to our system through dot net and we can master this in very little code.
Let's begin with a project. Sockets. For me this is going to have to start off as a windows application because of only having standard VB .NET and not being able to make a nice class library. But this doesn't throw me at all because even so I can still make a class file and use it in all my projects that need a socket. So if you don't already have one, add a class file to your project. Socket might be a good name for our class, or you could get all groovy and name it Xsock :)
Today's keyword is Imports. Now we have to define this in terms of our project, it truly is a shortcut in our project and not a piece of code in of itself. So therefore we want to type it Above our class code. What this does is give us a shortcut to the objects belonging in it's name space.
We are going to focus our socket on the UDP protocol, later you can adapt it to TCP and add it as a property setting and filter it through your code. So what we have so far should look like so.
Public Class Socket
Private MySocket As New UdpClient()
UdpClient is an object belonging to System.Net.Sockets. Since we are importing the namespace we can go straight to the object we need and go from there. When it comes time to compile the IDE handles piecing the namespace back together.
The basic functionality of the UdpClient is to send and receive data. In sending data this was uncomplicated in of itself and did not cause me to waste time in figuring it out. But when it came time to receive I had to keep shelving the code until I had advanced myself enough to figure out how to use it without losing my program.
UDP is also a connectionless protocol, which means it does not have to connect to the remote computer to send data, it will send the data and does not care if there is a program waiting to receive it or not.
So our first method is going to be .SendText. Now when a socket sends data it sends it in bytes and you do that by creating a Byte array from a string and sending the byte array. We want our class to handle the data translation for simple text. Here's a look at our function with translation code. Also I added another imports.
Public Class Socket
Private MySocket As New UdpClient()
Public Function SendText(ByVal sValue As String, ByVal sRemoteHost As String, ByVal sRemotePort As Integer)
Dim MyByte = ASCII.GetBytes(sValue)
MySocket.Send(MyByte, MyByte.Length, sRemoteHost, sRemotePort)
If you typed this out instead of C&P and didn't explicitly write ByVal VB automatically populates this needed keyword for you. Our method is taking it's parameters, the string to send, the remote host name and the port. At this point it's a single function method, before to long though we want to make it multi-functional. We are getting our Byte array from our string in one line of code. So in creating our Byte Array object we are setting it's value. Keep it in mind that it is an object and an array. Our UdpClient's .Send method is using a simple form. You'll notice it has other sets of params you can pass.
At this point we want to see how our object looks in our windows app. So we want to go to our form1 and add some code to access our object. Let's add a single line textbox, tbSend and a button btnSend. Now above the windows designer area let's add an object reference and create our socket with one line of code.
|Private MySock As New Socket()|
You may have noticed that under System.Net.Sockets is the same name socket class. Since this belongs to the to the .Net name space it is not going to be confused with our own Socket class. This is legal. Right now our socket class belongs to our application's name space. To see it start out by right clicking your project in the solutions explorer and go to properties. Your going to see that Our root namespace is the name we gave to our project Sockets. If this is confusing you can rename it to a more applicable scheme.
Your going to find out if you referenced it right pretty quickly. Let's generate our tbSend button's click event.
To start off we will simply send our text to our own computer with either your computer's name or the default LocalHost name. When we put our dot code in your going to see the base class methods with only one more added .SendText. If this is not the case then you may be referencing System.Net.Sockets somehow.
sender As System.Object, ByVal
e As System.EventArgs) Handles
MySock.SendText(tbSend.Text, "LocalHost", 5600)
iLocalPort As Integer
sLocalHost = Value
End SetEnd Property Public Property LocalPort()
LocalPort = iLocalPort
End GetSet(ByVal Value)
iLocalPort = Value
End SetEnd Property
Happiness is when you type in your public property <name> as Type it automatically creates the property structure for get and set and all you have to do is add two more lines of code :)
Net we want to start out our .Bind method with an optional parameter. This way if our LocalPort is already set we will not need to pass it. Our method is fleshed out looking like this.
ByVal nLocalPort1 As
Integer = 0)
If nLocalPort1 <> 0 Then iLocalPort = nLocalPort1
Optional values must have defaults under dot net. It will continue to complain until you set them, so we are using it to check against a new value being passed so that we can reset our internal property field accordingly if something is passed.
Next we want a state property so that if the bind method is called when our socket is already waiting for incoming messages it won't start the process again, this is important as you will see how this function is going to be handled. Our .Bind method's function is not going to do this work, it's just going to set it up. We also want to pass our state info back as a read only property. So we are going to set it up with an enum of different states.
NotBound = 0
Bound = 1
End EnumPrivate MyState As SocketStates
Public ReadOnly Property State() As SocketStates
State = MyState
End GetEnd Property
Next we want to actually write our Receive code. When you call the .Bind method it's going to set our receive code into action.
We are going to start off with an infinite do while loop. The reason for this is the UdpClient's .Receive method is synchronous. That means after you call it's .Receive method the code execution halts until something is actually received. We will call our sub GetData() so as not to confuse it with the .Receive method.
The .Receive method requires an IPEndPoint object to store the DataStream info. In creating this object we set it to retrieve the host name and port that the data is coming from.
Then we want to create a new instance of our UdpClient to get our data from the data stream.
We also need a byte array to receive the data in the same way we created one to send the data. Then we will convert our data to a string and pass it to a DataArrival event along with the base byte data.
Add this to your class declarations:
|Public Event DataArrival(ByVal sData As String, ByVal bData As Byte(), ByVal sRemoteHost As String, ByVal nRemotePort As Integer)|
Dim GetSocket As New UdpClient( iLocalPort)
Dim RemoteComputer As New System.Net.IPEndPoint(System.Net.IPAddress.Any, 0)
Dim MyByte() As ByteDim MyText As String Do While MyState = SocketStates.Bound
MyByte = GetSocket.Receive(RemoteComputer)
MyText = ASCII.GetString(MyByte)
RaiseEvent DataArrival(MyText, MyByte, RemoteComputer.Address.ToString, RemoteComputer.Port)
Now how do we use this code without stopping our whole application while the .Receive method waits endlessly for something to arrive? Well we do that by becoming a dot net master and this will be your next step towards that goal.
Introduction to the class thread. Let's add another Import to our list.
|Private MyThread As New Thread(AddressOf GetData)|
Welcome to the AddressOf operator. This sweet tool points to the location of our sub. You can get all the techno info here. Basically it allows us to assign procedures to other things like an event or in this case a thread.
So we are not even going to call our sub directly. We are going to start the thread and have it run the sub outside of our running code. This means our application has it's own internal threads plus the one we just added. Now our separate thread can still communicate with our application, without holding it up from doing whatever else we need it to do.
So now that we have it ready we are going to launch it from our .Bind method and set the state to bound to keep the loop running. Note: The do loop is not really running infinitely, it is stopped at the .Receive method waiting for data, when data is received it will then loop. If we don't loop it the sub will end and the thread will terminate (Stop running) and we won't be able to restart it. I tried :P didn't work.
So after setting our state we will launch the thread. Our .Bind method should now look like this.
nLocalPort1 As Integer
If nLocalPort1 <> 0 Then iLocalPort = nLocalPort1
If MyState = SocketStates.NotBound Then
MyState = SocketStates.Bound
End IfEnd Function
With a simple .Start method our thread is now off and running and our GetData() is waiting on data while we can still interact with our windows app. But before we go and start running the code we need to take some care in having it shut down. Otherwise closing the window will not end the program. One reason for this is our infinite loop on our thread and the other is the loop will be stopped waiting endlessly for data and will never finish the loop even if we set the state to unbound. So we need to account for both. We want a function to close our socket out. And then we want it called internally from within the class if the object is destroyed.
So let's create a .StopSocket method for our class. We don't want to use close as a name because it's going to get confused with the close method of existing objects which we learned disposes of an object that was created within an application.
In order to get out of the loop we need to change the state to unbound then send data to the port to finish the loop. This in turn will terminate the thread. Then we want to call this function from the calling application.
So we should have this for our stop routine.
If MyState = SocketStates.Bound Then
MyState = SocketStates.NotBound
SendText("", "LocalHost", iLocalPort)
End IfEnd Function
Then we want to call our method from our form's base class event Closing. This will insure that our thread ends when the application is closed.
Now let's add a multi-line text box to our app and call it tbReceive. Then we want to get our DataArrival event and add this line into it. In order to get our event to show up in our form's code we need to add the WithEvents keyword to it's declaration.
sData As String,
ByVal bData() As
sRemoteHost As String,
ByVal nRemotePort As
tbReceive.Text = tbReceive.Text & sData & vbCrLf
And now we have the bases of our socket class ready to serve up communication between two clients. If you have two networked computers set the host value to point to each other on each computer and send some messages. If you did everything right the messages should send back and fourth.
From here you can add TCP functionality as well as sending byte data. With this as the basis for your code you can add in the appropriate error handlers and checks and balances. Further learning maybe required to make this production grade.