Click here to Skip to main content
15,894,362 members
Articles / Programming Languages / Visual Basic
Article

Winsock Revamped

Rate me:
Please Sign up or sign in to vote.
4.79/5 (66 votes)
24 Jun 2008CPOL14 min read 1.3M   13.5K   114   624
A fairly simple to use Winsock component, allowing simple networked applications. Built modeling the VB6 component of the same name, with enhancements.

Screenshot - Multi-EnvDemo.png

Table of Contents

Introduction

Hello, and welcome to the 4th edition of the Winsock.NET component. Previous versions included Winsock2007, Winsock2005, and the original Winsock.NET.

I have taken some time to try and make this the best version of the component you could want, and in doing so, I decided to create a version for each version of the .NET framework (1.1, 2.0, and 3.5)!

What started out as something to satisfy my own needs of the lack of Winsock support in the .NET framework has taken on a new direction. It started out shaky, with questionable data separation techniques (using the EOT character) and not very thread-safe event routines, and has morphed into using a decent packet header and thread-safe events. The best part is, the packet header can be turned off using LegacySupport to allow communication with other servers/clients such as the Apache Web Server and the Telnet client.

I have even tested running a server built with VS2003 and running a client built using VB 2005 without LegacySupport, and it worked just fine! This means, you could even serialize an object between the frameworks, as long as the Type (full Type - this includes the namespace) exists on each side.

Let's dive into the features, construction, and the use of this set of tools.

Features

There are some new features, compared to the last version of this component. Here is a list of features that this control contains. The new features are marked with an asterisk (*).

  • Thread-safe event calling
  • Object serialization
  • UDP support
  • IPv6 support
  • Generics support* (not in the 2003 version)
  • Design-time UI support* (revamped action list - not in the 2003 version)
  • Legacy support
  • Enhanced legacy support conversion*
  • Easy to use file sending
  • WinsockCollection for easy multiple connection handling

Construction

If I detailed the entire construction of this component, this article would be extremely long, and bore most people to tears, so I will try to focus my concentration on some of the major details.

The key to the construction of this component is the Socket object. This allows communication across the network using TCP/UDP and IPv4/IPv6. The only hiccup is that in order to listen on both IPv4 and IPv6 at the same timen you need two Socket objects - so the AsyncSocket class contains two.

The first thing you'll notice about this version is I'm using an interface to define parts of the Winsock object. This is really due to the fact I was working on the AsyncSocket object first, and needed a placeholder until I started work on the Winsock object itself. I could have just as easily used an exact reference to the object instead of the interface.

In order for your application to run smoothly, the processing that the component needs to do should be done in a separate thread - hence, everything uses asynchronous calls. If not handled properly, asynchronous calls could lead to problems when you try to update controls on a form from one of your event handlers. To prevent these problems, I've implemented a thread-safe event calling function.

VB
Public Event Connected( _
         ByVal sender As Object, _
         ByVal e As WinsockConnectedEventArgs) _
         Implements IWinsock.Connected

Public Sub OnConnected( _
        ByVal e As WinsockConnectedEventArgs) _
        Implements IWinsock.OnConnected
  RaiseEventSafe(ConnectedEvent, New Object() {Me, e})
End Sub

Private Sub RaiseEventSafe( _
        ByVal ev As System.Delegate, _
        ByRef args() As Object)
  Dim bFired As Boolean
  If ev IsNot Nothing Then
    For Each singleCast As System.Delegate In _
            ev.GetInvocationList()
      bFired = False
      Try
        Dim syncInvoke As ISynchronizeInvoke = _
            CType(singleCast.Target, ISynchronizeInvoke)
        If syncInvoke IsNot Nothing _
              AndAlso syncInvoke.InvokeRequired Then
          bFired = True
          syncInvoke.Invoke(singleCast, args)
        Else
          bFired = True
          singleCast.DynamicInvoke(args)
        End If
      Catch ex As Exception
        If Not bFired Then singleCast.DynamicInvoke(args)
      End Try
    Next
  End If
End Sub

Let's take a look at this.

First, you'll notice I've declared an event named Connected (implements an event defined in the interface).

Next, you see the method that raises the event. Technically, it doesn't raise the event, but it allows the AsyncSocket to raise it by calling the method OnConnected. This method just calls another method that is designed to generically take an event and the parameters, and raise it in a thread safe manner. The other thing you'll notice about this method is the reference to ConnectedEvent. In Visual Basic, each event creates a delegate behind the scenes with the same name as the event you specified - just appending Event to it. So, say, you create an event called MyEvent. VB would create a delegate called MyEventEvent. These delegates don't even show up in the IntelliSense menus, but they exist - and help us to create this thread-safe calling method.

Now, for the RaiseEventSafe method. The first thing we do is declare a Boolean to make sure the event doesn't fire twice within the same call. This can happen if there is an error in the event handler code you wrote! The Try...Catch here would catch that error and then try to call the event again - resulting in two errors. As events can have multiple handlers, we need to cycle through each handler and call it - hence the For...Each loop. With each handler, the routine attempts to cast the target of the handler to the ISynchronizeInvoke interface. This is necessary to get the event called on the proper thread. If the cast is successful, the event is invoked on the original thread - if not, the event is still raised, but just on the current thread.

Object serialization is handled using an in-memory BinaryFormatter (see the ObjectPacker class). This allows any object that can be serialized to be sent to a remote computer. This is much simpler than trying to send all the properties across and reconstructing the object yourself.

VB
Public Function [Get](Of dataType)() As dataType
  Dim byt() As Byte = _asSock.GetData()
  Dim obj As Object
  If LegacySupport AndAlso _
        GetType(dataType) Is GetType(String) Then
    obj = System.Text.Encoding.Default.GetString(byt)
  Else
    obj = ObjectPacker.GetObject(byt)
  End If
  Return DirectCast(obj, dataType)
End Function

While not overly complicated to implement, Generics support has got to be one of my favorite features. Generics allows you to call a method specifying a Type, and the method will use the type you specified for whatever purpose. In this case, it allows you to call the Get and Peek methods and have them return the data as the Type you wanted - no more messy CType or DirectCast conversion routines in your event handlers! Watch out though - you could cause an error if you specify a type other than the type of the object in the buffer.

Screenshot - DesignMode.png

Another feature I'm excited about is the design-time UI support. I was finally able to achieve what I envisioned for the 2005 version of the component - and that was event links. The event links in the Action list will create and take you to the event handler for the specified event. It will only create the event handler if an event handler isn't already specified. It all hinges on the IEventBindingService interface and the ShowCode method of it. Here is the code to do this (specified in the WinsockActionList class):

VB
Public Sub TriggerStateChangedEvent()
  CreateAndShowEvent("StateChanged")
End Sub

Private Sub CreateAndShowEvent(ByVal eventName As String)
  Dim evService As IEventBindingService = _
     CType( _
     Me.Component.Site.GetService( _
     GetType( _
      System.ComponentModel.Design.IEventBindingService)), _
      IEventBindingService)
  Dim ev As EventDescriptor = GetEvent(evService, eventName)
  If ev IsNot Nothing Then
    CreateEvent(evService, ev)
    Me.designerActionUISvc.HideUI(Me.Component)
    evService.ShowCode(Me.Component, ev)
  End If
End Sub

Private Sub CreateEvent( _
      ByRef evService As IEventBindingService, _
      ByVal ev As EventDescriptor)
  Dim epd As PropertyDescriptor = _
      evService.GetEventProperty(ev)
  Dim strEventName As String = Me.Component.Site.Name & _
      "_" & ev.Name
  Dim existing As Object = epd.GetValue(Me.Component)
  'Only create if there isn't already a handler


  If existing Is Nothing Then
    epd.SetValue(Me.Component, strEventName)
  End If
End Sub

Private Function GetEvent( _
      ByRef evService As IEventBindingService, _
      ByVal eventName As String) As EventDescriptor
  If evService Is Nothing Then Return Nothing
  ' Attempt to obtain a PropertyDescriptor for the 


  ' specified component event.


  Dim edc As EventDescriptorCollection = _
      TypeDescriptor.GetEvents(Me.Component)
  If edc Is Nothing Or edc.Count = 0 Then
    Return Nothing
  End If
  Dim ed As EventDescriptor = Nothing
  ' Search for the event.


  Dim edi As EventDescriptor
  For Each edi In edc
    If edi.Name = eventName Then
      ed = edi
      Exit For
    End If
  Next edi
  If ed Is Nothing Then
    Return Nothing
  End If
  Return ed
End Function

The last bit of construction that I will cover is the enhanced legacy support conversion feature.

In the last few iterations of the component (the ones that use the ObjectPacker), sending data using legacy support required you to convert the String to a Byte array yourself and having the component send the Byte array. This was necessary as the ObjectPacker ignored the Byte array during serialization.

Now, the component checks if legacy support is active and the data to send is a String; the component does the conversion for you, making sending much simpler. This also goes for the Generic enhanced versions of the Get and Peek methods as well - so, it works on returning data as well.

Using the Component

First, make sure the component is added to your Toolbox (not 100% necessary, but the easiest way; advanced users should know how to use whatever instructions you need from below with referenced components and creating the objects purely from code).

Servers

Servers need to both listen and be able to send and receive data. As such, some of what they need to do is very similar to what the clients do. Under this section, I'll cover just the server specific routines you need to know.

I'm going to assume you'll want to handle multiple connections. If you don't wish to handle multiple connections, then you won't need to use the WinsockCollection - but for the purposes of instruction, we will use just that.

First, you'll need a listener. Add a Winsock component to your form, and call it wskListener. Go ahead and set the LocalPort property equal to the port you want your application to use - this is the port we will listen on. Adding the component to the form automatically adds a reference to the DLL, so we can use anything in the Winsock toolset (like the WinsockCollection).

Since we want to handle multiple connections, we will need a place to store all the connections. We do this using the WinsockCollection. Let's add one now - add the following code to the code designer for your form: Private WithEvents _wsks As New Winsock_Orcas.WinsockCollection(True). This code will create a new WinsockCollection, with the option for auto-removal of disconnected connections enabled. We also specified WithEvents to make creating event handlers for it easier. The form code should look like this:

VB
Public Class Form1

  Private WithEvents _wsks As _
      New Winsock_Orcas.WinsockCollection(True)

End Class

You can either have the listener start listening at form startup, or on a button press. Go to the event for whichever method you wish, and enter the following code: wskListen.Listen(). This should result in some code that looks similar to this:

VB
Private Sub cmdListen_Click( _
      ByVal sender As System.Object, _
      ByVal e As System.EventArgs) Handles cmdListen.Click
  wskListener.Listen()
End Sub

You could also specify an Integer as a parameter, telling the component what port to listen on.

The other thing you will need to do is handle the incoming connection requests. Do this by creating a handler for the ConnectionRequest event (you can use the Action list and choose ConnectionRequest from the events list). We need to Accept the incoming request using the WinsockCollection to make it active. Do this by adding the following code to the ConnectionRequest handler: _wsks.Accept(e.Client). This should look something like the following:

VB
Private Sub wskListener_ConnectionRequest( _
      ByVal sender As System.Object, _
      ByVal e As _
      Winsock_Orcas.WinsockConnectionRequestEventArgs) _
      Handles wskListener.ConnectionRequest
  _wsks.Accept(e.Client)
End Sub

With the client accepted, all of its events are raised through the WinsockCollection. As the last step of the server specific side, I'll show you how to create handlers for the WinsockCollection events.

Here is the definition of any of the events you could possible want:

VB
Public Event Connected( _
    ByVal sender As Object, _
    ByVal e As WinsockConnectedEventArgs)
Public Event ConnectionRequest( _
    ByVal sender As Object, _
    ByVal e As WinsockConnectionRequestEventArgs)
Public Event CountChanged( _
    ByVal sender As Object, _
    ByVal e As WinsockCollectionCountChangedEventArgs)
Public Event DataArrival( _
    ByVal sender As Object, _
    ByVal e As WinsockDataArrivalEventArgs)
Public Event Disconnected( _
    ByVal sender As Object, _
    ByVal e As System.EventArgs)
Public Event ErrorReceived( _
    ByVal sender As Object, _
    ByVal e As WinsockErrorReceivedEventArgs)
Public Event SendComplete( _
    ByVal sender As Object, _
    ByVal e As WinsockSendEventArgs)
Public Event SendProgress( _
    ByVal sender As Object, _
    ByVal e As WinsockSendEventArgs)
Public Event StateChanged( _
    ByVal sender As Object, _
    ByVal e As WinsockStateChangedEventArgs)

You need to construct a method with the same structure as the event you want to handle, and then tell it to handle it using the Handles clause of the method declaration. Here's an example:

VB
Private Sub _wsks_ErrorReceived( _
   ByVal sender As System.Object, _
   ByVal e As Winsock_Orcas.WinsockErrorReceivedEventArgs) _
   Handles _wsks.ErrorReceived

End Sub

Typical events that are handled are DataArrival, ErrorReceived, Connected, Disconnected, and StateChanged - this applies to both servers and clients.

Clients

A client needs to be able to Connect to a server as well as send and receive data, but they only need to handle one connection. Add a single Winsock to your form for the client connection, and call it wskClient.

We now need a way to connect to the server; you can do this in the form startup or a button press - take your pick. I'll be demonstrating with a button press. You can either set the RemoteHost and RemotePort manually (via code or the property designer) and call the Connect method with no parameters, or you can specify the server and port as parameters to the Connect method - which is the method I'll be using.

VB
Private Sub cmdClientConnect_Click( _
      ByVal sender As System.Object, _
      ByVal e As System.EventArgs) _
      Handles cmdClientConnect.Click
  wskClient.Connect(txtServer.Text, CInt(nudPort.Value))
End Sub

Here, you can see I'm getting my server and port from other controls on the form, namely a TextBox and a NumericUpDown control. Doing it this way allows greater flexibility for the user, though you can hard code the server and port as well, with the following code: wskClient.Connect("localhost", 8080). Notice, you don't have to use the IP address of the remote computer - you can also specify the name. The component automatically tries to resolve the name to the IP address for you.

Client and Server

On both the client and server, you will need to handle the DataArrival event. This event is raised every time something is received by the component. On the server side, you'll want the handler for this event to be attached to the WinsockCollection; on the client side, you'll want it attached to the Winsock object.

Let's take a look at a very simple DataArrival implementation from both the client and the server side. Let's assume an extremely simple chat application, where the server must send out to all clients but the client that sent it the message.

Server-side handler:

VB
Private Sub SendToAllBut( _
      ByVal sender As Object, _
      ByVal msg As String)
  For Each wsk As Winsock_Orcas.Winsock In _wsks.Values
    If wsk IsNot sender Then wsk.Send(msg)
  Next
End Sub

Private Sub _wsks_DataArrival( _
     ByVal sender As Object, _
     ByVal e As Winsock_Orcas.WinsockDataArrivalEventArgs) _
     Handles _wsks.DataArrival
  Dim strIn As String = CType(sender, Winsock_Orcas.Winsock).Get(Of String)()
  SendToAllBut(sender, strIn)
End Sub

Notice here, there is an extra method. The WinsockCollection doesn't currently have sending capabilities, so we need another method to send the data to all the connected clients. Even if the WinsockCollection did have sending abilities, you may want your own method - especially, if you will require users to login first, because, then you can check to make sure they are logged in, before sending them data.

The second thing you will notice is the Of String in the Get method. The Visual Studio 2005 and 2008 versions support Generics, which allows the ability to cast the method as a String, thus doing the conversion of the Object to a String for you, making your handling code cleaner. Unfortunately, the 2003 version of VS doesn't support Generics, so you will just get an Object back from the Get method, which you can then convert to a String in any manner you see fit.

There is one other thing you might put in the server, and that is a logging ability (to a TextBox, or a file, etc...) for feedback. We'll keep this simple, with the server only relaying messages - not sending anything itself.

Now, let's look at the client-side handler.

VB
Private Sub wskClient_DataArrival( _
     ByVal sender As Object, _
     ByVal e As Winsock_Orcas.WinsockDataArrivalEventArgs) _
     Handles wskClient.DataArrival
  Dim msg As String = _
      CType(sender, Winsock_Orcas.Winsock).Get(Of String)()
  txtLog.AppendText(msg & vbCrLf)
End Sub

Again, this is rather simple. The handler retrieves the data from the Winsock into a String, and then adds the String to a TextBox on the form.

There is one other thing you need to know - though you did see it in the code above: sending data. Sending data should be very simple as well. Simply call the Send method on the Winsock. You must pass it the data you wish to send. This can be any object, a String, an Integer, even custom classes you designed - as long as you marked it as Serializable. If you are doing a custom class, you must make sure both the client and the server have the same class in their project.

Here is an example of sending text entered in the UI:

VB
Private Sub cmdSend_Click( _
      ByVal sender As System.Object, _
      ByVal e As System.EventArgs) Handles cmdSend.Click
  Dim dt As String = txtSend.Text.Trim()
  If dt <> "" Then wskClient.Send(dt)
  txtSend.Text = ""
  txtSend.Focus()
End Sub

First, the data is checked to make sure there is data to send, and then sent - finally clearing the TextBox and setting the focus to it again.

Finishing Up

This is the most advanced version of the component yet, and I've really enjoyed making it. I'm sure there are plenty of bugs/gotchas in it just waiting to be discovered. If you find any, let me know, and I'll try to make improvements to it as rapidly as I can. Also, feature requests, I'd love to hear them - though they will be evaluated on difficulty/usefulness and may not make it into future versions, I'd still love to hear them.

Watch out for where you may need to use LegacySupport. Anytime you are interacting with clients/servers that do not use this control, you must enable LegacySupport as they won't understand the packet header this component pre-pends to the outgoing data.

One of the best things you can do if you are trying to debug something that is going wrong is to handle the ErrorReceived event. This can help you identify any errors you seem to be having in your event handler code, because the component actually traps them during the raising of the event.

Enjoy the component!

History

  • 12.13.2007 - Fixed problems with .GetUpperBound(0) in PacketHeader.AddHeader, and in AsyncSocket.ProcessIncoming.
  • 12.26.2007 - Added a new event, ReceiveProgress.
  • 12.28.2007 - Winsock.Get and Winsock.Peek updated to check for Nothing. SyncLock added to all qBuffer instances in AsyncSocket and _buff (ProcessIncoming).
  • 02.14.2008 - Fixed a bug in UDP receiving that caused it to always receive at the full byte buffer instead of the size of the incoming data.
  • 03.24.2008 - Fixed Listen methods to properly raise state changed events for UDP as well as TCP. Modified IWinsock, Winsock, and AsyncSocket to allow AsyncSocket to modify the LocalPort property of the component.
  • 03.25.2008 - Added a NetworkStream property to expose a NetworkStream object that uses the connection made by this component.
  • 04.21.2008 - Fixed RaiseEventSafe in Winsock.vb and WinsockCollection.vb to use BeginInvoke instead of Invoked. Changed the order of operations in ReceiveCallbackUDP to allow a remote IP address to be detected properly.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
GeneralRe: IndexOf Pin
Member 395054725-Sep-08 5:41
Member 395054725-Sep-08 5:41 
GeneralRe: IndexOf Pin
Chris Kolkman25-Sep-08 5:46
Chris Kolkman25-Sep-08 5:46 
GeneralRe: IndexOf Pin
Member 395054725-Sep-08 5:47
Member 395054725-Sep-08 5:47 
GeneralRe: IndexOf Pin
Member 395054725-Sep-08 5:58
Member 395054725-Sep-08 5:58 
GeneralRe: IndexOf Pin
Chris Kolkman25-Sep-08 5:59
Chris Kolkman25-Sep-08 5:59 
GeneralSuggested solution for lost packet (failure while sending) problem (BeginSend or Send?) Pin
xman91423-Sep-08 14:09
xman91423-Sep-08 14:09 
GeneralRe: Suggested solution for lost packet (failure while sending) problem (BeginSend or Send?) Pin
Chris Kolkman24-Sep-08 2:53
Chris Kolkman24-Sep-08 2:53 
AnswerRe: Suggested solution for lost packet (failure while sending) problem (BeginSend or Send?) Pin
xman91425-Sep-08 14:14
xman91425-Sep-08 14:14 
Hi, I have modified the AsyncSocket Class to achieve what I suggested in the earlier post. Below is the full code for the modified Class:

'-----------------------------------------------------
Imports System.Net
Imports System.Net.Sockets
Imports System.Threading

''' <summary>
''' A class that encapsulates all the raw functions of the System.Net.Sockets.Socket
''' </summary>
Public Class AsyncSocket

Private myParent As IWinsock ' The parent Winsock - allows access to properties and event raising
Private mSock1 As Socket ' for IPv4 (listening) or both IPv4 and IPv6 (connections)
Private mSock2 As Socket ' for IPv6 (listening) only
Private byteBuffer() As Byte ' Stores the incoming bytes waiting to be processed
Private incBufferSize As Integer = 8192 ' The buffer size of the socket
Private _buff As ByteBufferCol ' Temporary byte buffer - used while an object is being assembled
Private _closing As Boolean = False ' Prevents the Close() method from being run while it already running
Private qBuffer As Queue ' The Buffer Queue - where objects wait to be picked up by the Get() method
Private phProcessor As PacketHeader ' The PacketHeader processor - looks for and reads the packet header added to the byte array
Private sendBuffer As Deque ' The Sending Buffer Queue - where objects wait to be sent.
Private thdSendLoop As Thread ' Used to send everything in the sendBuffer
Private mAutoRestEvent As AutoResetEvent

Public Sub New(ByRef parent As IWinsock)
Try
myParent = parent
phProcessor = New PacketHeader
qBuffer = New Queue
_buff = New ByteBufferCol
sendBuffer = New Deque
ReDim byteBuffer(incBufferSize)
mAutoRestEvent = New AutoResetEvent(False)
thdSendLoop = New Thread(AddressOf DoSend)
thdSendLoop.IsBackground = True
'Start a thread which loops continuously to handle data sending
thdSendLoop.Start()
Catch ex As Exception
SharedMethods.RaiseError(myParent, "Unable to initialize the AsyncSocket.")
End Try
End Sub

''' <summary>
''' Gets a value containing the remote IP address.
''' </summary>
Protected Friend ReadOnly Property RemoteIP() As String
Get
Dim rEP As System.Net.IPEndPoint = CType(mSock1.RemoteEndPoint, System.Net.IPEndPoint)
Return rEP.Address.ToString()
End Get
End Property

''' <summary>
''' Gets a value containing the remote port number.
''' </summary>
Protected Friend ReadOnly Property RemotePort() As Integer
Get
Dim rEP As System.Net.IPEndPoint = CType(mSock1.RemoteEndPoint, System.Net.IPEndPoint)
Return rEP.Port
End Get
End Property

''' <summary>
''' Gets a value containing the local port number.
''' </summary>
Protected Friend ReadOnly Property LocalPort() As Integer
Get
Dim lEP As System.Net.IPEndPoint = CType(mSock1.LocalEndPoint, IPEndPoint)
Return lEP.Port
End Get
End Property

Protected Friend ReadOnly Property BufferCount() As Integer
Get
Return qBuffer.Count
End Get
End Property

Protected Friend Property BufferSize() As Integer
Get
Return incBufferSize
End Get
Set(ByVal value As Integer)
incBufferSize = value
'update buffer size used by Socket object
If mSock1 IsNot Nothing Then
mSock1.SendBufferSize = incBufferSize
mSock1.ReceiveBufferSize = incBufferSize
End If
If mSock2 IsNot Nothing Then
mSock2.ReceiveBufferSize = incBufferSize
End If
End Set
End Property

Protected Friend ReadOnly Property UnderlyingStream() As Net.Sockets.NetworkStream
Get
If mSock1 IsNot Nothing Then Return New Net.Sockets.NetworkStream(mSock1, IO.FileAccess.ReadWrite, False)
Return Nothing
End Get
End Property

#Region " Public Methods "

''' <summary>
''' Accepts an incoming connection and starts the data listener.
''' </summary>
''' <param name="client">The client to accept.</param>
Public Function Accept(ByVal client As Socket) As Boolean
Try
If myParent.State <> WinsockStates.Closed Then
Throw New Exception("Cannot accept a connection while the State is not closed.")
End If
mSock1 = client
Receive()
myParent.ChangeState(WinsockStates.Connected)
Dim e As New WinsockConnectedEventArgs(CType(mSock1.RemoteEndPoint, System.Net.IPEndPoint).Address.ToString, CType(mSock1.RemoteEndPoint, System.Net.IPEndPoint).Port)
myParent.OnConnected(e)
Return True
Catch ex As Exception
SharedMethods.RaiseError(myParent, ex.Message)
Return False
End Try
End Function

''' <summary>
''' Closes the socket if its already open or listening.
''' </summary>
Public Sub Close()
Try
' If we are already closing then exit the subroutine
If _closing Then Exit Sub
' Set the closing flag so that this doesn't get run more than once
' at a time.
_closing = True
' If we are already closed - exit the subroutine
If myParent.State = WinsockStates.Closed Then _closing = False : Exit Sub
'clear data in sendBuffer
sendBuffer.Clear()
Dim bAllowDisconnect As Boolean = False
' Close the Socket(s) as necessary
Select Case myParent.State
Case WinsockStates.Connected
' Change the state to Closing
myParent.ChangeState(WinsockStates.Closing)
If mSock1 IsNot Nothing Then mSock1.Close()
' Allow disconnect event to raise
bAllowDisconnect = True
Case WinsockStates.Listening
' Change the state to Closing
myParent.ChangeState(WinsockStates.Closing)
If mSock1 IsNot Nothing Then mSock1.Close()
If mSock2 IsNot Nothing Then mSock2.Close()
' Do not allow Disconnect event - we weren't connected to anything
' only listening.
bAllowDisconnect = False
End Select
' Change state to Closed
myParent.ChangeState(WinsockStates.Closed)
' Raise the Disconnected event - if allowed to
If bAllowDisconnect Then myParent.OnDisconnected()
_closing = False
Catch ex As Exception
_closing = False
If ex.InnerException IsNot Nothing Then
SharedMethods.RaiseError(myParent, ex.Message, ex.InnerException.Message)
Else
SharedMethods.RaiseError(myParent, ex.Message)
End If
End Try
End Sub

''' <summary>
''' Starts Listening for incoming connections. For UDP sockets it starts listening for incoming data.
''' </summary>
''' <param name="port">The port to start listening on.</param>
''' <param name="max_pending">The maximum length of the pending connections queue.</param>
Public Sub Listen(ByVal port As Integer, ByVal max_pending As Integer)
Try
If myParent.Protocol = WinsockProtocol.Tcp Then
Dim blnChangePort As Boolean = False
' Start listening on IPv4 - if available
If Socket.SupportsIPv4 Then
mSock1 = New Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp)
Dim ipLocal As New IPEndPoint(IPAddress.Any, port)
mSock1.Bind(ipLocal)
mSock1.Listen(max_pending)
mSock1.BeginAccept(New AsyncCallback(AddressOf ListenCallback), mSock1)
End If
' if port was 0 find port used for IPv4 and use it for IPv6
If port = 0 Then
Dim lEP As System.Net.IPEndPoint = CType(mSock1.LocalEndPoint, IPEndPoint)
port = lEP.Port
blnChangePort = True
End If
' Start listening on IPv6 - if available
If Socket.OSSupportsIPv6 Then
mSock2 = New Socket(AddressFamily.InterNetworkV6, SocketType.Stream, ProtocolType.Tcp)
Dim ipLocal As New IPEndPoint(IPAddress.IPv6Any, port)
mSock2.Bind(ipLocal)
mSock2.Listen(max_pending)
mSock2.BeginAccept(New AsyncCallback(AddressOf ListenCallback), mSock2)
End If
If blnChangePort Then
myParent.ChangeLocalPort(port)
End If
' Change state to Listening
myParent.ChangeState(WinsockStates.Listening)
ElseIf myParent.Protocol = WinsockProtocol.Udp Then
If port <= 0 Then
Throw New ArgumentException("While port 0 works for getting random port for UPD, there is no way for the server operator to know the port used until a completed send/receive method call is used which means the port is known already.", "port")
End If
' Start data listening on IPv4 - if available
If Socket.SupportsIPv4 Then
mSock1 = New Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp)

Dim ipLocal As New IPEndPoint(IPAddress.Any, port)
Dim ipeSender As New IPEndPoint(IPAddress.Any, 0)

Dim xe As New UdpReceiveState()
xe.SendingSocket = mSock1
xe.ReceivingEndpoint = ipeSender

mSock1.Bind(ipLocal)
mSock1.BeginReceiveFrom(byteBuffer, 0, incBufferSize, SocketFlags.None, xe.ReceivingEndpoint, New AsyncCallback(AddressOf ReceiveCallbackUDP), xe)
End If
' Start data listening on IPv6 - if available
If Socket.OSSupportsIPv6 Then
mSock2 = New Socket(AddressFamily.InterNetworkV6, SocketType.Dgram, ProtocolType.Udp)

Dim ipLocal As New IPEndPoint(IPAddress.IPv6Any, port)
Dim ipeSender As New IPEndPoint(IPAddress.IPv6Any, 0)

Dim xe As New UdpReceiveState()
xe.SendingSocket = mSock2
xe.ReceivingEndpoint = ipeSender

mSock2.Bind(ipLocal)
mSock2.BeginReceiveFrom(byteBuffer, 0, incBufferSize, SocketFlags.None, xe.ReceivingEndpoint, New AsyncCallback(AddressOf ReceiveCallbackUDP), xe)
End If
myParent.ChangeState(WinsockStates.Listening)
End If
Catch ex As Exception
SharedMethods.RaiseError(myParent, ex.Message)
End Try
End Sub

''' <summary>
''' Starts Listening for incoming connections on the specified IP address. For UDP sockets it starts listening for incoming data.
''' </summary>
''' <param name="port">The port to start listening on.</param>
''' <param name="max_pending">The maximum length of the pending connections queue.</param>
''' <param name="ip">The IP address on which to listen.</param>
Public Sub Listen(ByVal port As Integer, ByVal max_pending As Integer, ByVal ip As IPAddress)
Try
' IP contains information on type (IPv4 vs. IPv6) so we can just work with that
If myParent.Protocol = WinsockProtocol.Tcp Then
mSock1 = New Socket(ip.AddressFamily, SocketType.Stream, ProtocolType.Tcp)
Dim ipLocal As New IPEndPoint(ip, port)
mSock1.Bind(ipLocal)
mSock1.Listen(max_pending)
mSock1.BeginAccept(New AsyncCallback(AddressOf ListenCallback), mSock1)
If port = 0 Then
Dim lEP As System.Net.IPEndPoint = CType(mSock1.LocalEndPoint, IPEndPoint)
myParent.ChangeLocalPort(lEP.Port)
End If
ElseIf myParent.Protocol = WinsockProtocol.Udp Then
If port <= 0 Then
Throw New ArgumentException("While port 0 works for getting random port for UPD, there is no way for the server operator to know the port used until a completed send/receive method call is used which means the port is known already.", "port")
End If
mSock1 = New Socket(ip.AddressFamily, SocketType.Dgram, ProtocolType.Udp)

Dim ipLocal As New IPEndPoint(ip, port)
Dim ipeSender As New IPEndPoint(ip, 0)

Dim xe As New UdpReceiveState()
xe.SendingSocket = mSock1
xe.ReceivingEndpoint = ipeSender

mSock1.Bind(ipLocal)
mSock1.BeginReceiveFrom(byteBuffer, 0, incBufferSize, SocketFlags.None, xe.ReceivingEndpoint, New AsyncCallback(AddressOf ReceiveCallbackUDP), xe)
End If
myParent.ChangeState(WinsockStates.Listening)
Catch ex As Exception
SharedMethods.RaiseError(myParent, ex.Message)
End Try
End Sub

''' <summary>
''' Gets the first object in the buffer without removing it.
''' </summary>
Public Function PeekData() As Byte()
If qBuffer.Count = 0 Then Return Nothing
Return DirectCast(qBuffer.Peek(), Byte())
End Function

''' <summary>
''' Gets and removes the first object in the buffer.
''' </summary>
Public Function GetData() As Byte()
If qBuffer.Count = 0 Then Return Nothing
Return DirectCast(qBuffer.Dequeue, Byte())
End Function

''' <summary>
''' Attemps to connect to a remote computer.
''' </summary>
''' <param name="remoteHostOrIp">The remote host or IP address of the remote computer.</param>
''' <param name="remote_port">The port number on which to connect to the remote computer.</param>
Public Sub Connect(ByVal remoteHostOrIp As String, ByVal remote_port As Integer)
Try
If myParent.State <> WinsockStates.Closed Then
Throw New Exception("Cannot connect to a remote host when the Winsock State is not closed.")
End If

myParent.ChangeState(WinsockStates.ResolvingHost)

Dim resolvedIP As IPAddress = Nothing
If IPAddress.TryParse(remoteHostOrIp, resolvedIP) Then
myParent.ChangeState(WinsockStates.HostResolved)
Connect(resolvedIP, remote_port)
Else
Dns.BeginGetHostEntry(remoteHostOrIp, New AsyncCallback(AddressOf DoConnectCallback), remote_port)
End If
Catch ex As Exception
SharedMethods.RaiseError(myParent, ex.Message)
End Try
End Sub
''' <summary>
''' Attempts to connect to a remote computer.
''' </summary>
''' <param name="remIP">The IP address of the remote computer.</param>
''' <param name="port">The port number on which to connect to the remote computer.</param>
Public Sub Connect(ByVal remIP As IPAddress, ByVal port As Integer)
Try
Dim remEP As New IPEndPoint(remIP, port)
If myParent.State <> WinsockStates.HostResolved Then Exit Sub
mSock1 = New Socket(remIP.AddressFamily, SocketType.Stream, ProtocolType.Tcp)
myParent.ChangeState(WinsockStates.Connecting)
mSock1.BeginConnect(remEP, New AsyncCallback(AddressOf ConnectCallback), mSock1)
Catch ex As Exception
SharedMethods.RaiseError(myParent, ex.Message, ex.ToString)
End Try
End Sub

''' <summary>
''' Sends data to the remote computer.
''' </summary>
''' <param name="byt">The byte array of data to send.</param>
Public Sub Send(ByVal byt() As Byte)
Try
' If it's going out UDP, get the location it's going to
Dim remEP As IPEndPoint = Nothing
If myParent.Protocol = WinsockProtocol.Udp Then
Dim ihe As IPHostEntry = Dns.GetHostEntry(myParent.RemoteHost)
remEP = New IPEndPoint(ihe.AddressList(0), myParent.RemotePort)
End If

' LegacySupport doesn't need a header, so if it's NOT active we can add one
If Not myParent.LegacySupport Then
' LegacySupport INACTIVE - add a packet header, the other end knows how to decode it
phProcessor.AddHeader(byt)
End If

' Create the data object and add it to the queue
Dim sqData As New SendQueueData(remEP, byt)
' We must lock access to the send buffer to prevent simultaneous access
' from multiple threads
SyncLock sendBuffer.SyncRoot
sendBuffer.Enqueue(sqData)
End SyncLock
'Notify the data sending thread that new data has been added to sendBuffer
mAutoRestEvent.Set()

Catch ex As Exception
SharedMethods.RaiseError(myParent, ex.Message)
End Try
End Sub

''' <summary>
''' Terminate the data sending thread to release resource
''' </summary>
''' <remarks></remarks>
Public Sub TerminateSenderThread()
If thdSendLoop.IsAlive Then
thdSendLoop.Abort()
End If
End Sub

#End Region

#Region " Callback Methods "

''' <summary>
''' The callback for the listener - only used for a TCP listener.
''' </summary>
''' <remarks>This routine starts again when finished making it loop to continuously receive connections.</remarks>
Private Sub ListenCallback(ByVal ar As IAsyncResult)
Try
' Get the socket doing the listening, if it's not there
' we can't continue.
Dim listener As Socket = TryCast(ar.AsyncState, Socket)
If listener Is Nothing Then
Throw New Exception("Listener object no longer exists.")
End If

' End the Accept that was started
Dim client As Socket = listener.EndAccept(ar)
' Raise ConnectionRequest event
Dim e As New WinsockConnectionRequestEventArgs(client)
myParent.OnConnectionRequest(e)

' If the Winsock is no longer in the listening state
' close and exit gracefully.
If myParent.State <> WinsockStates.Listening Then
listener.Close()
Exit Sub
End If
' start listening again
listener.BeginAccept(New AsyncCallback(AddressOf ListenCallback), listener)
Catch exO As ObjectDisposedException
' Close was called, destroying the object - exit without
' displaying an error.
Exit Try
Catch exS As SocketException
' There was a problem with the connection
' If the SocketError is not Success, then close and
' show an error message
Select Case exS.SocketErrorCode
Case Is <> SocketError.Success
'Close()
SharedMethods.RaiseError(myParent, exS.Message, "", exS.SocketErrorCode)
Exit Try
End Select
Catch ex As Exception
' General execption - show an error message.
SharedMethods.RaiseError(myParent, ex.Message)
End Try
End Sub

''' <summary>
''' The callback method for the Receive method (UDP only) - used when there is incoming data.
''' </summary>
Private Sub ReceiveCallbackUDP(ByVal ar As IAsyncResult)
Try
' Get actively receiving socket and IPEndPoint
Dim xe As UdpReceiveState = CType(ar.AsyncState, UdpReceiveState)
Dim cb_UDP As Socket = CType(xe.SendingSocket, Socket)
' Get the size of the received data
Dim iSize As Integer = cb_UDP.EndReceiveFrom(ar, xe.ReceivingEndpoint)
Dim remEP As IPEndPoint = TryCast(xe.ReceivingEndpoint, IPEndPoint)
If iSize < 1 Then
If _buff.Count > 0 Then _buff.Clear()
Close()
Exit Sub
End If
' Process the receieved data
ProcessIncoming(byteBuffer, iSize, remEP.Address.ToString, remEP.Port)
' Clear and resize the buffer
ReDim byteBuffer(incBufferSize)
' Restart data listener
Dim ipeSender As IPEndPoint
If remEP.AddressFamily = AddressFamily.InterNetwork Then
ipeSender = New IPEndPoint(IPAddress.Any, 0)
Else
ipeSender = New IPEndPoint(IPAddress.IPv6Any, 0)
End If
xe.ReceivingEndpoint = ipeSender
cb_UDP.BeginReceiveFrom(byteBuffer, 0, incBufferSize, SocketFlags.None, xe.ReceivingEndpoint, New AsyncCallback(AddressOf ReceiveCallbackUDP), xe)
Catch exO As ObjectDisposedException
' Close was called, destroying the object - exit without
' displaying an error.
Exit Try
Catch exS As SocketException
' There was a problem with the connection
' If the SocketError is not Success, then close and
' show an error message
Select Case exS.SocketErrorCode
Case Is <> SocketError.Success
'Close()
SharedMethods.RaiseError(myParent, exS.Message, "", exS.SocketErrorCode)
Exit Try
End Select
Catch ex As Exception
' General execption - show an error message.
SharedMethods.RaiseError(myParent, ex.Message)
End Try
End Sub

''' <summary>
''' The callback for the Connect method - used on the client to start looking for data.
''' </summary>
Private Sub ConnectCallback(ByVal ar As IAsyncResult)
Try
If myParent.State <> WinsockStates.Connecting Then Exit Sub
Dim sock As Socket = CType(ar.AsyncState, Socket)
sock.EndConnect(ar)
If myParent.State <> WinsockStates.Connecting Then sock.Close() : Exit Sub
' start listening for data
Receive()
' Finished - raise events...
myParent.ChangeState(WinsockStates.Connected)
' Raise the Connected event
Dim ipE As IPEndPoint = DirectCast(sock.RemoteEndPoint, IPEndPoint)
Dim e As New WinsockConnectedEventArgs(ipE.Address.ToString, ipE.Port)
myParent.OnConnected(e)
Catch exS As SocketException
Select Case exS.SocketErrorCode
Case Is <> SocketError.Success
Close()
SharedMethods.RaiseError(myParent, exS.Message, "", exS.SocketErrorCode)
Exit Try
End Select
Catch ex As Exception
SharedMethods.RaiseError(myParent, ex.Message)
End Try
End Sub

''' <summary>
''' The callback method for the Receive method (TCP only) - used when there is incoming data.
''' </summary>
Private Sub ReceiveCallback(ByVal ar As IAsyncResult)
Try
' Get the possible error code
Dim errCode As SocketError = CType(ar.AsyncState, SocketError)
' Get the size of the incoming data while ending the receive
Dim iSize As Integer = mSock1.EndReceive(ar)
If iSize < 1 Then
' no size identified - connection closed
If _buff.Count > 0 Then _buff.Clear()
Close()
Exit Sub
End If
' Get the remote IP address
Dim ipE As IPEndPoint = CType(mSock1.RemoteEndPoint, IPEndPoint)
' Process the incoming data (also raises DataArrival)
ProcessIncoming(byteBuffer, iSize, ipE.Address.ToString, ipE.Port)
ReDim byteBuffer(incBufferSize)
' Start listening for data again
mSock1.BeginReceive(byteBuffer, 0, incBufferSize, SocketFlags.None, errCode, New AsyncCallback(AddressOf ReceiveCallback), errCode)
Catch exS As SocketException
Select Case exS.SocketErrorCode
Case Is <> SocketError.Success
Close()
SharedMethods.RaiseError(myParent, exS.Message, "", exS.SocketErrorCode)
End Select
Catch exO As ObjectDisposedException
Exit Try
Catch ex As Exception
SharedMethods.RaiseError(myParent, ex.Message)
End Try
End Sub

''' <summary>
''' The callback method for resolving the address given - starts the socket on connecting.
''' </summary>
Public Sub DoConnectCallback(ByVal ar As IAsyncResult)
Try
Dim port As Integer = DirectCast(ar.AsyncState, Integer)
Dim resolved As IPHostEntry
Try
resolved = Dns.EndGetHostEntry(ar)
Catch ex As SocketException
resolved = Nothing
End Try
If resolved Is Nothing OrElse resolved.AddressList.Length = 0 Then
Dim name As String = CStr(IIf(resolved IsNot Nothing, """" & resolved.HostName & """ ", ""))
Throw New Exception("Hostname " & name & "could not be resolved.")
End If
myParent.ChangeState(WinsockStates.HostResolved)
Connect(resolved.AddressList(0), port)
Catch ex As Exception
SharedMethods.RaiseError(myParent, ex.Message)
End Try
End Sub

#End Region

#Region " Private Methods "

''' <summary>
''' Processes raw data that was received from the socket and places it into the appropriate buffer.
''' </summary>
''' <param name="byt">The raw byte buffer containing the data received from the socket.</param>
''' <param name="iSize">The size of the data received from the socket (reported from the EndReceive).</param>
''' <param name="source_ip">The IP address the data came from, used for event raising.</param>
''' <param name="source_port">The Port the data arrived on, used for event raising.</param>
Private Sub ProcessIncoming(ByVal byt() As Byte, ByVal iSize As Integer, ByVal source_ip As String, ByVal source_port As Integer)
'check if we are using LegacySupport
If myParent.LegacySupport Then
' legacy support is active just output the data to the buffer
' if we actually received some data
If iSize > 0 Then
' yes we received some data - resize the array
ResizeArray(byt, iSize)
' add the byte array to the buffer queue
qBuffer.Enqueue(byt)
' raise the DataArrival event
Dim e As New WinsockDataArrivalEventArgs(iSize, source_ip, source_port)
myParent.OnDataArrival(e)
End If
Else
' legacy support is inactive
' if total size is <= 0 no data came in - exit
If iSize <= 0 Then Exit Sub
' reduce the size of the array to the reported size (fixes trailling zeros)
ResizeArray(byt, iSize)
' Do we have a packet header?
If Not phProcessor.Completed And _buff.Count > 1 Then
' no packet header and already have more than enough data for a header
' most likely no header is coming - throw an error to use LegacySupport
phProcessor.Reset()
Throw New Exception("Unable to determine size of incoming packet. It's possible you may need to use Legacy Support.")
ElseIf Not phProcessor.Completed Then
phProcessor.ProcessHeader(byt, _buff)
If Not phProcessor.Completed Then
ProcessIncoming(byt, byt.Length, source_ip, source_port)
End If
End If


' Packet Header obtained... Process data, raise data arrival when all is received
If _buff.Count = 0 AndAlso byt.Length >= phProcessor.Size Then
' everything is located in the byte array
If byt.GetUpperBound(0) > phProcessor.Size Then
' everything and MORE in byte array
' remove our data, and run process again on the rest
Dim tmp() As Byte = SharedMethods.ShrinkArray(Of Byte)(byt, phProcessor.Size)
' add the data to the queue
qBuffer.Enqueue(tmp)
' reset the packet header processor
phProcessor.Reset()
' raise the received progress event
Dim eR As New WinsockReceiveProgressEventArgs(source_ip, tmp.Length, phProcessor.Size)
myParent.OnReceiveProgress(eR)
' raise the DataArrival event
Dim e As New WinsockDataArrivalEventArgs(tmp.Length, source_ip, source_port)
myParent.OnDataArrival(e)
' process the extra data
ProcessIncoming(byt, byt.Length, source_ip, source_port)
Else
' add everything to the queue
qBuffer.Enqueue(byt)
' raise the received progress event
Dim eR As New WinsockReceiveProgressEventArgs(source_ip, byt.Length, phProcessor.Size)
myParent.OnReceiveProgress(eR)
' reset the packet header processor
phProcessor.Reset()
' raise the DataArrival event
Dim e As New WinsockDataArrivalEventArgs(byt.Length, source_ip, source_port)
myParent.OnDataArrival(e)
End If
ElseIf _buff.Count > 0 AndAlso _buff.Combine().Length + byt.Length >= phProcessor.Size Then
' if you include the temp buffer, we have all the data
' get everything
_buff.Add(byt)
Dim tmp() As Byte = _buff.Combine()
' clear the temp buffer
_buff.Clear()
If tmp.GetUpperBound(0) > phProcessor.Size Then
' tmp contains more than what we need
' remove what wee need, and then run the process again on the rest
Dim t2() As Byte = SharedMethods.ShrinkArray(Of Byte)(tmp, phProcessor.Size)
' add the data to the queue
qBuffer.Enqueue(t2)
' raise the received progress event
Dim eR As New WinsockReceiveProgressEventArgs(source_ip, tmp.Length, phProcessor.Size)
myParent.OnReceiveProgress(eR)
' reset the packet header processor
phProcessor.Reset()
' raise the DataArrival event
Dim e As New WinsockDataArrivalEventArgs(tmp.Length, source_ip, source_port)
myParent.OnDataArrival(e)
' process the extra data
ProcessIncoming(tmp, tmp.Length, source_ip, source_port)
Else
' tmp contains only what we need - add it to the queue
qBuffer.Enqueue(tmp)
' raise the received progress event
Dim eR As New WinsockReceiveProgressEventArgs(source_ip, tmp.Length, phProcessor.Size)
myParent.OnReceiveProgress(eR)
' reset the packet header processor
phProcessor.Reset()
' raise the DataArrival event
Dim e As New WinsockDataArrivalEventArgs(tmp.Length, source_ip, source_port)
myParent.OnDataArrival(e)
End If
Else
_buff.Add(byt)
' raise the received progress event
Dim eR As New WinsockReceiveProgressEventArgs(source_ip, _buff.Combine.Length, phProcessor.Size)
myParent.OnReceiveProgress(eR)
End If
End If
End Sub

''' <summary>
''' Resizes an array to the desired length - preserving the data at the begining of the array.
''' </summary>
''' <param name="byt">The array to be resized.</param>
''' <param name="iSize">The size to resize the array to.</param>
Private Sub ResizeArray(ByRef byt() As Byte, ByVal iSize As Integer)
If iSize - 1 < byt.GetUpperBound(0) Then
ReDim Preserve byt(iSize - 1)
End If
End Sub

''' <summary>
''' Starts listening for incoming packets on the socket.
''' </summary>
''' <remarks>The is private because, the user should never have to call this.</remarks>
Private Sub Receive()
Try
Dim errorState As SocketError
mSock1.BeginReceive(byteBuffer, 0, incBufferSize, SocketFlags.None, errorState, New AsyncCallback(AddressOf ReceiveCallback), errorState)
Catch ex As Exception
SharedMethods.RaiseError(myParent, ex.Message)
End Try
End Sub

''' <summary>
''' Starts the sending of an object in the send buffer.
''' </summary>
Private Sub DoSend()
'loop until the application is terminated
While True
If sendBuffer.Count > 0 Then
'Start data sending process if sendBuffer is not empty
Try
Dim sqData As SendQueueData = Nothing
SyncLock sendBuffer.SyncRoot
sqData = sendBuffer.Dequeue(Of SendQueueData)()
End SyncLock
If sqData Is Nothing Then
Throw New Exception("Buffer count was greater than zero, yet a nothing was retrieved. Something broke.")
End If
Dim dataToSend As Byte() = sqData.Data()
Dim fullSize As Integer = dataToSend.Length
Dim remEP As IPEndPoint = sqData.IPAddress
Dim startIndex As Integer = 0
If myParent.Protocol = WinsockProtocol.Tcp Then
'handles TCP
While startIndex < fullSize
Dim numLeft As Integer = fullSize - startIndex
If numLeft > incBufferSize Then
mSock1.Send(dataToSend, startIndex, incBufferSize, SocketFlags.None)
startIndex += incBufferSize
Dim e As New WinsockSendEventArgs(RemoteIP, startIndex, fullSize)
myParent.OnSendProgress(e)
Else
mSock1.Send(dataToSend, startIndex, numLeft, SocketFlags.None)
startIndex += numLeft
Dim e As New WinsockSendEventArgs(RemoteIP, startIndex, fullSize)
myParent.OnSendComplete(e)
End If
End While
ElseIf myParent.Protocol = WinsockProtocol.Udp Then
'handles UDP
Dim tmpSock As New Socket(remEP.AddressFamily, SocketType.Dgram, ProtocolType.Udp)
tmpSock.SendBufferSize = incBufferSize
While startIndex < fullSize
Dim numLeft As Integer = fullSize - startIndex
If numLeft > incBufferSize Then
tmpSock.SendTo(dataToSend, startIndex, incBufferSize, SocketFlags.None, remEP)
startIndex += incBufferSize
Dim e As New WinsockSendEventArgs(remEP.Address.ToString, startIndex, fullSize)
myParent.OnSendProgress(e)
Else
tmpSock.SendTo(dataToSend, startIndex, numLeft, SocketFlags.None, remEP)
startIndex += numLeft
Dim e As New WinsockSendEventArgs(remEP.Address.ToString, startIndex, fullSize)
myParent.OnSendComplete(e)
End If
End While
End If
Catch exS As SocketException
If exS.SocketErrorCode <> SocketError.Success Then
Close()
SharedMethods.RaiseError(myParent, exS.Message & vbNewLine & exS.StackTrace, "", exS.SocketErrorCode)
End If
Catch ex As ThreadAbortException
'Terminate the thread when Abort() is called
Exit While
Catch ex As Exception
Close()
SharedMethods.RaiseError(myParent, ex.Message & vbNewLine & ex.StackTrace)
End Try
Else
'Wait if sendBuffer is empty
mAutoRestEvent.WaitOne()
End If
End While
End Sub

#End Region

#Region " Private Classes "

''' <summary>
''' A class that decodes and stores the packet header information.
''' </summary>
Public Class PacketHeader

Private _delimiter As Byte
Private _hasDelim As Boolean
Private _size As Integer

Public Sub New()
_size = -1
_delimiter = Byte.MinValue
_hasDelim = False
End Sub

''' <summary>
''' A Boolean value to determine if the class has found a delimiter yet.
''' </summary>
Public ReadOnly Property HasDelimiter() As Boolean
Get
Return _hasDelim
End Get
End Property

''' <summary>
''' A Boolean value to determine if the class has found the size or not.
''' </summary>
Public ReadOnly Property HasSize() As Boolean
Get
Return _size > -1
End Get
End Property

''' <summary>
''' A Boolean value to determine if the header processing has been completed or not.
''' </summary>
''' <remarks>Based on HasDelimiter and HasSize</remarks>
Public ReadOnly Property Completed() As Boolean
Get
Return HasDelimiter AndAlso HasSize
End Get
End Property

''' <summary>
''' The determined Size that was contained within the header.
''' </summary>
Public ReadOnly Property Size() As Integer
Get
Return _size
End Get
End Property

''' <summary>
''' The delimiter found within the header (typically the first byte).
''' </summary>
Public Property Delimiter() As Byte
Get
Return _delimiter
End Get
Set(ByVal value As Byte)
_delimiter = value
_hasDelim = True
End Set
End Property

''' <summary>
''' Processes a received byte array for possible header information to decode the length of the data received.
''' </summary>
''' <param name="byt">The byte array to process.</param>
''' <param name="_buff">A temporary byte buffer to stored data in.</param>
''' <remarks>The parameters must be passed ByRef to allow the other routines to work with the exact same data (and modified data).</remarks>
Public Sub ProcessHeader(ByRef byt() As Byte, ByRef _buff As ByteBufferCol)
' Do we have an opening delimiter?
If Not HasDelimiter Then
' We do now
Delimiter = SharedMethods.ShrinkArray(Of Byte)(byt, 1)(0)
If byt Is Nothing OrElse byt.Length = 0 Then Exit Sub
End If
' check for the next instance of the delimiter
Dim idx As Integer = Array.IndexOf(byt, _delimiter)
If idx = -1 Then
' delimiter not found - add bytes to temp buffer
_buff.Add(byt)
Exit Sub
End If
' delimiter was found, grab the size (part may be in the temp buffer) so combine them
Dim temp() As Byte = SharedMethods.ShrinkArray(Of Byte)(byt, (idx + 1))
ReDim Preserve temp(temp.GetUpperBound(0) - 1)
_buff.Add(temp)
temp = _buff.Combine()
' Clear the temp buffer
_buff.Clear()
' convert the bytes containing the size back to string
Dim strSize As String = System.Text.Encoding.ASCII.GetString(temp)
' try converting the string back to an integer
If Not Integer.TryParse(strSize, _size) Then
' data not an integer, maybe legacy support should be used
' reset the delimiter and the size
Reset()
' throw the exception
Throw New Exception("Unable to determine size of incoming packet. It's possible you may need to use Legacy Support.")
End If
' is there data to follow?
If _size = 0 Then
' no data followed the size
' reset the size and the delimiter
Reset()
' exit
Exit Sub
End If
End Sub

''' <summary>
''' Resets the packet processor for another run.
''' </summary>
Public Sub Reset()
_delimiter = Byte.MinValue
_hasDelim = False
_size = -1
End Sub

''' <summary>
''' Adds a packet header to the byte array given.
''' </summary>
''' <param name="byt">The byte array to prepend with a packet header.</param>
Public Sub AddHeader(ByRef byt() As Byte)
'Dim arrSize As Integer = byt.GetUpperBound(0) 'Gets the size of the data to be sent
Dim arrSize As Integer = byt.Length ' Gets the size of the data to be sent
Dim strSize() As Byte = System.Text.Encoding.ASCII.GetBytes(arrSize.ToString) ' Converts the size to a string (to handle sizes larger than 255) and converts that to a byte array
Dim fByte As Byte = FreeByte(strSize) ' Determines a byte not used by the size (delimiter)
strSize = EncloseByte(fByte, strSize) ' Put the delimeter around the size header
byt = AppendByte(strSize, byt) ' Combine the header with the data
End Sub

''' <summary>
''' Determines which byte value was not used in the byte array.
''' </summary>
''' <param name="byt">The byte array to check.</param>
Private Function FreeByte(ByVal byt() As Byte) As Byte
'look for a free byte between 1 and 255
Dim lowest As Byte = 0
For i As Integer = 1 To 255
If Array.IndexOf(byt, CByte(i)) = -1 Then
lowest = CByte(i)
Exit For
End If
Next
Return lowest
End Function

''' <summary>
''' Encloses a byte array with another byte.
''' </summary>
''' <param name="byt">A byte to enclose around a byte array.</param>
''' <param name="bytArr">The byte array that needs a byte enclosed around it.</param>
Private Function EncloseByte(ByVal byt As Byte, ByVal bytArr() As Byte) As Byte()
Dim orig As Integer = bytArr.GetUpperBound(0)
Dim newa As Integer = orig + 2
Dim ar(newa) As Byte
ar(0) = byt
Array.Copy(bytArr, 0, ar, 1, bytArr.Length)
ar(newa) = byt
Return ar
End Function

''' <summary>
''' Combines two byte arrays.
''' </summary>
Private Function AppendByte(ByVal first() As Byte, ByVal sec() As Byte) As Byte()
Dim orig As Integer = first.GetUpperBound(0) + sec.Length
Dim ar(orig) As Byte
Array.Copy(first, 0, ar, 0, first.Length)
Array.Copy(sec, 0, ar, first.GetUpperBound(0) + 1, sec.Length)
Return ar
End Function

End Class

''' <summary>
''' A class that allows a state to be transfered from the calling method to the asyncrounous callback method.
''' This class is used for receiving data via UDP.
''' </summary>
Private Class UdpReceiveState

''' <summary>
''' The incoming socket information - allows UDP to determine the sender.
''' </summary>
Public SendingSocket As Object
''' <summary>
''' The EndPoint on which the data was received (server side).
''' </summary>
Public ReceivingEndpoint As EndPoint

End Class

''' <summary>
''' A class that helps store data waiting to be sent in the SendQueue
''' </summary>
''' <remarks>
''' This class was borne out of necessity - not for TCP, but for UDP.
''' I realized that if you are sending large data chunks out via UDP
''' to different remote addresses, you could end up sending data to
''' the wrong remote host. This class allows the component to recognize
''' that it needs to send to a different remote host.
''' </remarks>
Private Class SendQueueData

''' <summary>
''' Initializes a new instance of the SendQueueData class.
''' </summary>
''' <param name="ip">An IPEndPoint containing the IP address that you will be sending to.</param>
''' <param name="byt">The data that needs to be sent.</param>
Public Sub New(ByVal ip As IPEndPoint, ByVal byt() As Byte)
_ip = ip
_data = byt
End Sub

Private _data() As Byte
Private _ip As IPEndPoint

''' <summary>
''' The IPEndPoint that contains the IP address information needed to send the data.
''' </summary>
Public ReadOnly Property IPAddress() As IPEndPoint
Get
Return _ip
End Get
End Property

''' <summary>
''' The data that needs to be sent.
''' </summary>
Public Property Data() As Byte()
Get
Return _data
End Get
Set(ByVal value As Byte())
_data = value
End Set
End Property

End Class

''' <summary>
''' A class that allows a state to be transfered from the calling method to the asyncrounous callback method.
''' This class is used when sending data.
''' </summary>
Private Class SendState

''' <summary>
''' The total length of the original byte array to be sent. (Includes packet header)
''' </summary>
Public Length As Integer
''' <summary>
''' The error code as reported by the socket - used during the callback method.
''' </summary>
Public ErrCode As SocketError
' '' <summary>
' '' The bytes that are to be sent.
' '' </summary>
'Public Bytes() As Byte
''' <summary>
''' The index at which to start sending - usefull when sending packets larger than the buffer size.
''' </summary>
Public StartIndex As Integer
''' <summary>
''' The number of bytes to send during this time - usefull when sending packets larger than the buffer size.
''' </summary>
Public SendLength As Integer
''' <summary>
''' The total number of bytes actually transmitted.
''' </summary>
Public TotalSent As Integer
''' <summary>
''' The socket that is doing the sending - used for UDP statistic information during the callback method.
''' </summary>
Public SendingSocket As Socket
''' <summary>
''' The IP address of the computer you are sending to - used for UDP statistic information during the callback method.
''' </summary>
Public SendToAddress As IPEndPoint

''' <summary>
''' Builds and returns an instance of the SendState class.
''' </summary>
''' <param name="bytUpperBound">The UpperBound of the byte array that will be sent.</param>
''' <param name="sock">The socket to assign to the SendState.</param>
''' <param name="buffer_size">The socket's buffer size.</param>
Public Shared Function Build(ByVal bytUpperBound As Integer, ByRef sock As Socket, ByVal buffer_size As Integer) As SendState
Dim ret As New SendState
ret.Length = bytUpperBound + 1
ret.StartIndex = 0
If bytUpperBound > buffer_size Then
ret.SendLength = buffer_size + 1
Else
ret.SendLength = bytUpperBound
End If
ret.SendingSocket = sock
Return ret
End Function

''' <summary>
''' Returns a boolean indicating whether the object being sent has completed or not.
''' </summary>
Public ReadOnly Property SendCompleted() As Boolean
Get
Return Not (TotalSent < Length)
End Get
End Property
End Class

#End Region
End Class

''' <summary>
''' A special collection class to act as a byte buffer.
''' </summary>
Public Class ByteBufferCol
Inherits CollectionBase

''' <summary>
''' Adds a byte to the byte buffer.
''' </summary>
''' <param name="byt">The byte to add to the buffer.</param>
Public Sub Add(ByVal byt As Byte)
List.Add(byt)
End Sub

''' <summary>
''' Adds a byte array to the byte buffer.
''' </summary>
''' <param name="byt">The byte array to add to the buffer.</param>
''' <remarks>Adds all the bytes in the array individually - not the array itself.</remarks>
Public Sub Add(ByVal byt() As Byte)
For i As Integer = 0 To UBound(byt)
List.Add(byt(i))
Next
End Sub

''' <summary>
''' Combines all the bytes in the buffer into one byte array.
''' </summary>
Public Function Combine() As Byte()
If List.Count = 0 Then Return Nothing
Dim ar(List.Count - 1) As Byte
For i As Integer = 0 To List.Count - 1
ar(i) = CByte(List.Item(i))
Next
Return ar
End Function

End Class
'-----------------------------------------------------
GeneralLoop through Users Pin
Member 395054723-Sep-08 4:16
Member 395054723-Sep-08 4:16 
GeneralRe: Loop through Users Pin
Chris Kolkman23-Sep-08 7:55
Chris Kolkman23-Sep-08 7:55 
QuestionBug? Pin
Marty7820-Sep-08 3:16
Marty7820-Sep-08 3:16 
AnswerRe: Bug? Pin
Chris Kolkman22-Sep-08 2:42
Chris Kolkman22-Sep-08 2:42 
GeneralRe: Bug? [modified] Pin
Marty7822-Sep-08 9:56
Marty7822-Sep-08 9:56 
GeneralRe: Bug? Pin
Chris Kolkman22-Sep-08 10:16
Chris Kolkman22-Sep-08 10:16 
GeneralRe: Bug? Pin
Marty7822-Sep-08 10:34
Marty7822-Sep-08 10:34 
GeneralRe: Bug? Pin
Chris Kolkman22-Sep-08 10:49
Chris Kolkman22-Sep-08 10:49 
GeneralRe: Bug? Pin
Marty7822-Sep-08 10:55
Marty7822-Sep-08 10:55 
GeneralRe: Bug? Pin
Chris Kolkman22-Sep-08 11:01
Chris Kolkman22-Sep-08 11:01 
GeneralRe: Bug? Pin
Marty7822-Sep-08 11:08
Marty7822-Sep-08 11:08 
GeneralRe: Bug? Pin
Marty7822-Sep-08 11:12
Marty7822-Sep-08 11:12 
GeneralRe: Bug? Pin
Chris Kolkman23-Sep-08 4:04
Chris Kolkman23-Sep-08 4:04 
GeneralA few questions Pin
Member 395054719-Sep-08 0:00
Member 395054719-Sep-08 0:00 
GeneralRe: A few questions Pin
Chris Kolkman19-Sep-08 3:40
Chris Kolkman19-Sep-08 3:40 
GeneralLimit the bandwidth [modified] Pin
Uden17-Sep-08 1:03
Uden17-Sep-08 1:03 
GeneralRe: Limit the bandwidth Pin
Chris Kolkman17-Sep-08 2:36
Chris Kolkman17-Sep-08 2:36 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.