This version is out of date. Please check out the newer version located here: Winsock Revamped
Contents
Introduction
I started with this control when I found that VB.NET does not support the Winsock control I used to use in VB 6. It's been a while in coming, but I finally got it out and ready. The biggest challenge I had was using the TcpClient
object and getting the RemoteIP
. Good luck doing that - maybe in the next version of Studio but not in 2003. So I ditched the TcpClient
and went with sockets.
Using the control
This control adds to your form like any other invisible control like the timer. Add it to your toolbox and you'll be all set. If you know how to use the VB 6 Winsock control, then you already know how to use this - I've built everything from the VB 6 Winsock control to match, and I think I've gotten the important things. If you already know, then go ahead and skip to the explanation of the control itself. For those who don't know how to use this, keep reading.
Once the control has been added to the form, there are only a few properties in the Properties window:
LocalPort
- This property is used for server controls. It sets the port your program will listen on.
RemoteIP
- This is for client controls. It sets the IP address of the computer you want to connect to.
RemotePort
- This is for client controls. It sets the port of the computer you want to connect to.
Server Specifics
To start your server listening, you must run the Listen()
method of the control (Winsock1.Listen()
). Now, when someone tries to connect to you, all the connection requests will come through the event ConnectionRequest
. You'll need a handler for this event.
In the connection handler, you'll need a Winsock
control you want to accept the connection on. You could use the current listener as your connection - but that requires stopping the listener, and you want to accept other connections at the same time? Right? So, you'll need another Winsock
control. Now, you don't need to add them to the form in order for them to work - so you can just instantiate one before this at some point. Here's an example of a connection request handler:
Private Sub Winsock1_ConnectionRequest(_
ByVal sender As Winsock_Control.Winsock, _
ByVal requestID As System.Net.Sockets.Socket) _
Handles Winsock1.ConnectionRequest
Winsock3.Accept(requestID)
Winsock3.Send("Welcome!")
End Sub
Notice the AddHandler
statement I have commented out. At one point, I was using an instantiated control and needed to add a handler for the incoming data. I created a subroutine called DataArrival
to handle that. Now, in the demo project, it's just another control on the form. As this is the connection request (server, of course), I've decided to send a welcome message as soon as the connection is accepted. This is done using the Send
method. Just place your string there, and it will send it.
Clients
The clients must use the Winsock.Connect()
method to connect to a server. It is overloaded, so you can use no arguments, the IP, or the IP and Port when calling it. When using no arguments, it will use the IP and Port already stored in RemoteIP
and RemotePort
.
Clients and Servers
All the data - for the server and clients - come through the DataArrival
method. You get the data that comes in using the GetData
method. Here's a short example of getting the data:
Private Sub DataArrival(_
ByVal sender As Winsock_Control.Winsock, _
ByVal BytesTotal As Integer) _
Handles Winsock3.DataArrival
Dim s As String
sender.GetData(s)
Notice that the sender is getting, the data and it's passing s
as a ByRef
variable. The event passes the control to the DataArrival
subroutine through the sender control, allowing you to send information right back to it.
Sending and receiving data is done using the Send
and GetData
methods respectively. You've just seen how the control raises the DataArrival
event when data comes in - let's take a look now at how that data is sent out and received by the user.
The Send()
method (as of 10/20/2005) is overloaded, and can accept one of three types for sending, a string, a bitmap, or a byte array. Using these three overloads, you should be able to send any kind of data you want - provided you convert it to a byte array for sending - just make sure you know what you are retrieving when it is received. The string and bitmap overloads will both convert their respective data into a byte array and then send it out using the third overload.
The GetData()
method (again as of 10/20/2005) has also been overloaded the same way for retrieving data, as the Send
method - you get your bitmap, string, and byte array overloads. There is a significant difference between the VB 6 version and this version here, however. In the VB 6 version, if you chose to hold off using the GetData
method in the DataArrival
routine and the DataArrival
routine ran more than once, when you finally use GetData
, it retrieves the entire buffer. With this control, it will grab the first data in a FIFO (first in first out) method. This data is stored as a byte array in a collection until you use one of the GetData
overloads, which it then pops out of the collection and performs the necessary conversion (if any is needed) before returning it to you. Be sure to use the correct overload for the incoming data.
One thing you should remember when you are done communicating with the server - or client - is that you should close the connection. Do so using the Close()
method.
The Control
OK, now for the meat of it - the nitty gritty. You want to know how it's done. OK. The key - sockets, and of course asynchronous function calls (threads too!). I'll just go over a little bit of the code as most of it is pretty self explanatory.
The Listen() method
The Listen
method starts up a new thread, running a continuous async call as in the following code:
Dim tmpSock As Socket
If GetState = WinsockStates.Listening Then
tmpSock = _sockList.EndAccept(asyn)
RaiseEvent ConnectionRequest(Me, tmpSock)
_sockList.BeginAccept(New _
AsyncCallback(AddressOf OnClientConnect), _
Nothing)
End If
Here, I'm declaring a temporary socket used to accept the new connection, and pass it to the ConnectionRequest
event - which of course starts right back again.
The Connect() method - incoming data
Again, I use async calls to begin the connection (see Public Sub Connect()
). This takes us over to the OnConnected
subroutine. OnConnected
actually calls another sub to finalize the connection (why?? I can't remember any more), but the Finalize
connection sub starts our reader. Again - this is another async call. Once the data has been pulled into the byte array, it is sent over to the AddToBuffer
sub. Let's take a look at this routine:
Private Sub AddToBuffer(ByVal bytes() As Byte, ByVal count As Integer)
Dim curUB As Integer
If Not _buffer Is Nothing Then
curUB = UBound(_buffer) Else curUB = -1
Dim newUB As Integer = curUB + count
ReDim Preserve _buffer(newUB)
Array.Copy(bytes, 0, _buffer, curUB + 1, count)
Dim byterm As Byte = 4
Dim idx As Integer = Array.IndexOf(_buffer, byterm)
Dim byObj() As Byte
Dim byTmp() As Byte
While idx <> -1
Dim ln As Integer = _buffer.Length - (idx + 1)
ReDim byObj(idx - 1)
ReDim byTmp(ln - 1)
Array.Copy(_buffer, 0, byObj, 0, idx)
Array.Copy(_buffer, idx + 1, byTmp, 0, ln)
ReDim _buffer(UBound(byTmp))
Array.Copy(byTmp, _buffer, byTmp.Length)
Me._bufferCol.Add(byObj)
RaiseEvent DataArrival(Me, byObj.Length)
idx = Array.IndexOf(_buffer, byterm)
End While
If count < bytes.Length - 1 And _buffer.Length > 0 Then
_bufferCol.Add(_buffer)
RaiseEvent DataArrival(Me, _buffer.Length)
_buffer = Nothing
End If
End Sub
Here, the bytes that were received from the socket and the number of bytes received are passed as arguments. The number received is necessary as the socket will completely fill the byte array - even though it desn't use it all. This will allow us to separate the junk from the data, which is what happens during the first Array.Copy
call.
Next, we check for an EOT (end of transmission) character (Dim byterm as Byte = 4
). The EOT character is appended to all data you send using this control. It allows the control to separate overlapping data - the demo project shows an example of this when sending a picture twice, the receiving sockets receive it as one block of data so we need to be able to separate that as well. This is what goes on during the While
loop, the saving of the first object data to the buffer collection (used for data retrieval during GetData
), and truncating that object out of the byte buffer. Finally, the While
loop raises the DataArrival
event so you can grab the object just sent to the buffer collection.
For backwards compatability, and for other languages that don't send EOTs automatically, we check that the byte count is less than the length of the original byte array - telling us if it's finished receiving or not, but only calling the DataArrival
event if there is data that is in the buffer.
Dynamic Connections
Dynamic connections - ah the joys of dynamics. In VB 6, you would have to create a control array and increment the array as you need more. You could only remove a connection once the upper bound connection was closed (at least easily), and to an unused connection from one that was closed while another connection was open - you would have to iterate through the array to find one that was closed, and use that one.
Not anymore! With .NET, we have collections, which are much better than arrays for storing data this way. Let's walk through the process of using Winsock connection collections!
First, we will need a WinsockCollection
class:
Public Class WinsockCollection
Inherits CollectionBase
Private col As New Collection
Public Sub Add(ByVal value As Winsock_Control.Winsock)
Add(value, "")
End Sub
Public Sub Add(ByVal value As Winsock_Control.Winsock, _
ByVal Key As String)
Try
list.Add(value)
col.Add(Key)
Catch ex As Exception
MsgBox("Add")
End Try
End Sub
Public Shadows Sub Clear()
MyBase.Clear()
End Sub
Public Function IndexOf(ByVal value As _
Winsock_Control.Winsock) As Integer
For i As Integer = 0 To Count - 1
If Item(i) Is value Then Return i
Next
Return -1
End Function
Public Function GetKey(ByVal value As _
Winsock_Control.Winsock) As String
Return col.Item(IndexOf(value) + 1)
End Function
Public Sub Remove(ByVal value As Winsock_Control.Winsock)
Try
If list.Contains(value) Then
col.Remove(IndexOf(value) + 1)
list.Remove(value)
Else
MsgBox("Value doesn't exist in collection.")
End If
Catch ex As Exception
MsgBox("Winsock/Remove")
End Try
End Sub
Public Sub RemoveIndex(ByVal index As Integer)
Try
If index > Count - 1 Or index < 0 Then
Throw New IndexOutOfRangeException
Else
list.RemoveAt(index)
col.Remove(index + 1)
End If
Catch ex As Exception
MsgBox("Winsock/RemoveIndex")
End Try
End Sub
Default Public ReadOnly Property Item(ByVal index As Integer) _
As Winsock_Control.Winsock
Get
Try
If index > Count - 1 Or index < 0 Then
Throw New IndexOutOfRangeException
End If
Return CType(list.Item(index), Winsock_Control.Winsock)
Catch ex As Exception
MsgBox("Collection/Item")
End Try
End Get
End Property
Default Public ReadOnly Property Item(ByVal Key As String) _
As Winsock_Control.Winsock
Get
Try
Dim idx As Integer = -1
For i As Integer = 1 To col.Count
If col.Item(i) = Key Then
idx = i
End If
Next
If idx = -1 Then Return New Winsock_Control.Winsock
Return CType(list.Item(idx - 1), Winsock_Control.Winsock)
Catch ex As Exception
MsgBox("Key/Item")
End Try
End Get
End Property
End Class
The first thing you'll notice here is that I have a collection within the collection. This was necessary to store the key values for my particular application (a chat server) as the Winsock control didn't have a property for a key value.
You'll also notice the message boxes I put in the various Try...Catch
blocks just to know quickly, while it is running, where the errors occur. Other than that - this is a pretty standard inherited collection.
Now that we have our collection - it should be declared with the proper scope for your project, for me this was form global.
Our magic starts with the ConnectionRequest
of your listener:
Private Sub wskListener_ConnectionRequest(ByVal sender As _
Winsock_Control.Winsock, ByVal requestID As _
System.Net.Sockets.Socket) _
Handles wskListener.ConnectionRequest
‘Builds y.UID.ToString for the Winsock Key
Dim x As New Winsock_Control.Winsock
WskCol.Add(x, y.UID.ToString)
AddHandler CType(WskCol.Item(y.UID.ToString), _
Winsock_Control.Winsock).DataArrival, _
AddressOf wsk_DataArrival
AddHandler CType(WskCol.Item(y.UID.ToString), _
Winsock_Control.Winsock).Disconnected, _
AddressOf wsk_Disconnected
CType(WskCol.Item(y.UID.ToString), _
Winsock_Control.Winsock).Accept(requestID)
CType(WskCol.Item(y.UID.ToString), _
Winsock_Control.Winsock).Send("Welcome!")
End Sub
Here, you'll notice y.UID.ToString
. y.UID.ToString
was a unique identifier for my users, this was how I tied my users to their connection - hence the key for the collection provided easy access to the users' connections.
Notice also the order of operations here. I first create the new Winsock
object, then add it to the collection, and finally add the handlers and accept the connection. If I had added the handlers and accepted before adding the Winsock
object to the collection, I would have had problems using the object later on.
The DataArrival
subroutine does not need anything special done to it - as it already receives a copy of the connection via the sender.
The next magic happens in the Disconnected
event:
Private Sub wsk_Disconnected(ByVal sender As Winsock_Control.Winsock)
WskRemoval.Add(sender)
Remove()
End Sub
Private Sub Remove()
If WskRemoval.Count > 0 Then
WskCol.Remove(WskRemoval.Item(1))
WskRemoval.Remove(1)
End If
End Sub
First, take notice of WskRemoval.Add(sender)
- I'm adding the Winsock
control to be removed to another collection. I've found that just removing the Winsock
object from the Winsock collection can tend to cause a bottleneck because it's doing it too quickly. So I left that for the Remove
method.
The Remove()
sub iterates through the WskRemoval
collection one by one, removing each Winsock
object that has been queued for removal until there are none left to be removed.
The nice thing about using the collection is there is nothing in between when you remove a connection, and you never have to search for a closed connection that you can use - you just create on and go. A great improvement over the VB 6 Winsock arrays!
Finishing Up
I hope everyone who uses this control or the code finds this useful. I know I did - I've already created a chat server that runs with a Java client in a browser. Works great.
History
- 10-20-2005
- Changed buffer mechanism to allow storing of multiple objects - required if you use multiple sends on bitmaps, otherwise they stack as one bitmap and don't get retrieved properly. Utilizes the EOT (end of transmission) (ASCII 4) to do the separating, although it still checks for a count less than the byte length for backwards compatibility.
- Sending data now appends an EOT at the end of the data.
- 10-19-2005
- Added support for direct
Byte()
sending and retrieving.
- Added support for bitmap sending and retrieving.
- 08-24-2005