Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Winsock.NET

0.00/5 (No votes)
28 Oct 2005 2  
A VB 6 Winsock clone for VB.NET.

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
        'AddHandler Winsock3.DataArrival, AddressOf DataArrival
        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
        'found an EOT (end of transmission) 
        'marker split it if necessary and
        'put it in the buffer to get - call DataArrival
        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
    &lsquo;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
    • Released.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here