Visual Basic .NET Nightmare or Guide to upgrading a Guru.

Page 9

The Dryer ate my Winsock

 

Imports System.Net.Sockets

Public Class Socket

Private MySocket As New UdpClient()

End Class

Imports System.Net.Sockets

Imports System.Text.Encoding

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)

End Function

End Class

Private MySock As New Socket()
Private Sub btnSend_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnSend.Click

MySock.SendText(tbSend.Text, "LocalHost", 5600)

End Sub

  • Now in of itself, our code is functional. But there's no socket looking for this incoming DataStream. So we need to create the other half of our socket to receive data. This is of' course not as easy as sending. I had sending down in a short while, but receiving didn't see the light of day for awhile after I Shelved dot net and did some serious work in VB 6. So here we go.
  • If you have used UDP in VB 6 with the WinSock control you may be very familiar with it's Bind method. So the first thing we want to do is develop one of our own. We are going to Pass our local Port to it. And we are also going to make that optional by adding it as a property .LocalPort.
  • Let's start by creating our field to hold the property value. We are also going to set our default to 0.
Private iLocalPort As Integer = 0

Set(ByVal Value)

sLocalHost = Value

End Set

End Property

Public Property LocalPort()

Get

LocalPort = iLocalPort

End Get

Set(ByVal Value)

iLocalPort = Value

End Set

End 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.

Public Function Bind(Optional ByVal nLocalPort1 As Integer = 0)

If nLocalPort1 <> 0 Then iLocalPort = nLocalPort1

End Function

  • 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.

Public Enum SocketStates

NotBound = 0

Bound = 1

End Enum

Private MyState As SocketStates

Public ReadOnly Property State() As SocketStates

Get

State = MyState

End Get

End 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)
  • Then our sub should look like thus.
Private Sub GetData()

Dim GetSocket As New UdpClient( iLocalPort)

Dim RemoteComputer As New System.Net.IPEndPoint(System.Net.IPAddress.Any, 0)

Dim MyByte() As Byte

Dim 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)

Loop

End Sub

  • 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.

Imports System.Threading
  • Now we want to add a thread to our class for use with our GetData() sub. So in our declarations area add this line.
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.

Public Function Bind(Optional ByVal nLocalPort1 As Integer = 0)

If nLocalPort1 <> 0 Then iLocalPort = nLocalPort1

If MyState = SocketStates.NotBound Then

MyState = SocketStates.Bound

MyThread.Start()

End If

End 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.

Public Function StopSocket()

If MyState = SocketStates.Bound Then

MyState = SocketStates.NotBound

SendText("", "LocalHost", iLocalPort)

End If

End 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.

Private Sub MySock_DataArrival(ByVal sData As String, ByVal bData() As Byte, ByVal sRemoteHost As String, ByVal nRemotePort As Integer) Handles MySock.DataArrival

tbReceive.Text = tbReceive.Text & sData & vbCrLf

End Sub

  • 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.

Page 10