1247 lines
54 KiB
VB.net
1247 lines
54 KiB
VB.net
Option Strict On
|
|
|
|
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 sBufferMutex As New Mutex()
|
|
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 lckSendLoop As Object ' Used for a syncronized lock on the SendLoop thread
|
|
Private bIsSending As Boolean ' Used internally to tell if a sending loop is in progress.
|
|
|
|
Public Sub New(ByRef parent As IWinsock)
|
|
Try
|
|
myParent = parent
|
|
phProcessor = New PacketHeader
|
|
qBuffer = New Queue
|
|
_buff = New ByteBufferCol
|
|
sendBuffer = New Deque
|
|
lckSendLoop = New Object()
|
|
ReDim byteBuffer(incBufferSize)
|
|
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
|
|
Dim i As Integer = -1
|
|
SyncLock qBuffer.SyncRoot
|
|
i = qBuffer.Count
|
|
End SyncLock
|
|
Return i
|
|
End Get
|
|
End Property
|
|
|
|
Protected Friend Property BufferSize() As Integer
|
|
Get
|
|
Return incBufferSize
|
|
End Get
|
|
Set(ByVal value As Integer)
|
|
incBufferSize = value
|
|
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
|
|
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()
|
|
Dim ret() As Byte
|
|
SyncLock qBuffer.SyncRoot
|
|
If qBuffer.Count = 0 Then
|
|
ret = Nothing
|
|
Else
|
|
ret = DirectCast(qBuffer.Peek(), Byte())
|
|
End If
|
|
End SyncLock
|
|
Return ret
|
|
End Function
|
|
|
|
''' <summary>
|
|
''' Gets and removes the first object in the buffer.
|
|
''' </summary>
|
|
Public Function GetData() As Byte()
|
|
Dim ret() As Byte
|
|
SyncLock qBuffer.SyncRoot
|
|
If qBuffer.Count = 0 Then
|
|
ret = Nothing
|
|
Else
|
|
ret = DirectCast(qBuffer.Dequeue, Byte())
|
|
End If
|
|
End SyncLock
|
|
Return ret
|
|
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
|
|
|
|
' Start the sending process - if the process isn't already started.
|
|
SyncLock lckSendLoop
|
|
If Not bIsSending Then
|
|
thdSendLoop = New Thread(AddressOf DoSend)
|
|
thdSendLoop.Start()
|
|
End If
|
|
End SyncLock
|
|
Catch ex As Exception
|
|
SharedMethods.RaiseError(myParent, ex.Message)
|
|
End Try
|
|
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
|
|
SyncLock _buff.SyncRoot
|
|
If _buff.Count > 0 Then _buff.Clear()
|
|
End SyncLock
|
|
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
|
|
SyncLock _buff.SyncRoot
|
|
If _buff.Count > 0 Then _buff.Clear()
|
|
End SyncLock
|
|
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 the Send method (TCP only) - loops if not all the data was sent.
|
|
''' </summary>
|
|
Private Sub SendCallback(ByVal ar As IAsyncResult)
|
|
Try
|
|
' Retrieve the AsyncState
|
|
Dim ssState As SendState = DirectCast(ar.AsyncState, SendState)
|
|
' Update the total sent - while ending this send call
|
|
ssState.TotalSent += ssState.SendingSocket.EndSend(ar)
|
|
' Build the event args for the event that will be raised
|
|
Dim e As New WinsockSendEventArgs(RemoteIP, ssState.TotalSent, ssState.Length)
|
|
If ssState.SendCompleted Then
|
|
' Object finished sending - raise the SendComplete event
|
|
myParent.OnSendComplete(e)
|
|
' Check for more items in the buffer - if so run the send again
|
|
' we can't run DoSend from within the SyncLock, or we'll run into
|
|
' a deadlock
|
|
Dim blnRunAgain As Boolean = False
|
|
SyncLock sendBuffer.SyncRoot
|
|
If sendBuffer.Count > 0 Then
|
|
blnRunAgain = True
|
|
End If
|
|
End SyncLock
|
|
If blnRunAgain Then DoSend()
|
|
Else
|
|
' Raise SendProgress event
|
|
myParent.OnSendProgress(e)
|
|
' Object still has more data in the buffer, get the next part and send it
|
|
Dim byt() As Byte
|
|
SyncLock sendBuffer.SyncRoot
|
|
Dim sqData As SendQueueData = sendBuffer.Dequeue(Of SendQueueData)()
|
|
byt = sqData.Data
|
|
If byt.GetUpperBound(0) > incBufferSize Then
|
|
Dim tmpByt() As Byte = SharedMethods.ShrinkArray(Of Byte)(byt, incBufferSize + 1)
|
|
sqData.Data = DirectCast(byt.Clone(), Byte())
|
|
sendBuffer.Push(sqData)
|
|
byt = DirectCast(tmpByt.Clone(), Byte())
|
|
End If
|
|
End SyncLock
|
|
ssState.SendingSocket.BeginSend(byt, 0, byt.Length, SocketFlags.None, ssState.ErrCode, New AsyncCallback(AddressOf SendCallback), ssState)
|
|
End If
|
|
Catch exS As SocketException
|
|
Select Case exS.SocketErrorCode
|
|
Case Is <> SocketError.Success
|
|
Close()
|
|
SharedMethods.RaiseError(myParent, exS.Message, "", exS.SocketErrorCode)
|
|
End Select
|
|
Catch ex As Exception
|
|
SharedMethods.RaiseError(myParent, ex.Message)
|
|
End Try
|
|
End Sub
|
|
|
|
''' <summary>
|
|
''' The callback method for the Send method (UDP only) - loops if not all the data was sent.
|
|
''' </summary>
|
|
Private Sub SendToCallback(ByVal ar As IAsyncResult)
|
|
Try
|
|
' Retrieve the AsyncState
|
|
Dim ssState As SendState = DirectCast(ar.AsyncState, SendState)
|
|
' Update the total sent - while ending this send call
|
|
ssState.TotalSent += ssState.SendingSocket.EndSendTo(ar)
|
|
' Build the event args for the event that will be raised
|
|
Dim e As New WinsockSendEventArgs(ssState.SendToAddress.Address.ToString(), ssState.TotalSent, ssState.Length)
|
|
If ssState.SendCompleted Then
|
|
' Object finished sending - raise the SendComplete event
|
|
myParent.OnSendComplete(e)
|
|
' Check for more items in the buffer - if so run the send again
|
|
' we can't run DoSend from within the SyncLock, or we'll run into
|
|
' a deadlock
|
|
Dim blnRunAgain As Boolean = False
|
|
SyncLock sendBuffer.SyncRoot
|
|
If sendBuffer.Count > 0 Then
|
|
blnRunAgain = True
|
|
End If
|
|
End SyncLock
|
|
If blnRunAgain Then DoSend()
|
|
Else
|
|
' Raise the SendProgress event
|
|
myParent.OnSendProgress(e)
|
|
' Object still has more data in the buffer, get it and send it.
|
|
Dim byt() As Byte
|
|
SyncLock sendBuffer.SyncRoot
|
|
Dim sqData As SendQueueData = sendBuffer.Dequeue(Of SendQueueData)()
|
|
byt = sqData.Data
|
|
If byt.GetUpperBound(0) > incBufferSize Then
|
|
Dim tmpByt() As Byte = SharedMethods.ShrinkArray(Of Byte)(byt, incBufferSize + 1)
|
|
sqData.Data = DirectCast(byt.Clone(), Byte())
|
|
sendBuffer.Push(sqData)
|
|
byt = DirectCast(tmpByt.Clone(), Byte())
|
|
End If
|
|
End SyncLock
|
|
ssState.SendingSocket.BeginSendTo(byt, 0, byt.Length, SocketFlags.None, ssState.SendToAddress, New AsyncCallback(AddressOf SendToCallback), ssState)
|
|
End If
|
|
Catch exS As SocketException
|
|
Select Case exS.SocketErrorCode
|
|
Case Is <> SocketError.Success
|
|
Close()
|
|
SharedMethods.RaiseError(myParent, exS.Message, "", exS.SocketErrorCode)
|
|
End Select
|
|
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
|
|
SyncLock qBuffer.SyncRoot
|
|
qBuffer.Enqueue(byt)
|
|
End SyncLock
|
|
' raise the DataArrival event
|
|
Dim e As New WinsockDataArrivalEventArgs(iSize, source_ip, source_port)
|
|
myParent.OnDataArrival(e)
|
|
End If
|
|
Else
|
|
SyncLock _buff.SyncRoot
|
|
' 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
|
|
SyncLock qBuffer.SyncRoot
|
|
qBuffer.Enqueue(tmp)
|
|
End SyncLock
|
|
' 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
|
|
SyncLock qBuffer.SyncRoot
|
|
qBuffer.Enqueue(byt)
|
|
End SyncLock
|
|
' 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
|
|
SyncLock qBuffer.SyncRoot
|
|
qBuffer.Enqueue(t2)
|
|
End SyncLock
|
|
' 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
|
|
SyncLock qBuffer.SyncRoot
|
|
qBuffer.Enqueue(tmp)
|
|
End SyncLock
|
|
' 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 SyncLock
|
|
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()
|
|
Try
|
|
' Retrieve the bytes to send
|
|
Dim byt() As Byte
|
|
Dim fullSize As Integer
|
|
Dim remEP As IPEndPoint
|
|
SyncLock sendBuffer.SyncRoot
|
|
If sendBuffer.Count = 0 Then Exit Sub
|
|
Dim sqData As SendQueueData = sendBuffer.Dequeue(Of SendQueueData)()
|
|
If sqData Is Nothing Then
|
|
Throw New Exception("Buffer count was greater than zero, yet a nothing was retrieved. Something broke.")
|
|
End If
|
|
remEP = sqData.IPAddress
|
|
byt = sqData.Data()
|
|
fullSize = byt.GetUpperBound(0)
|
|
If byt.GetUpperBound(0) > incBufferSize Then
|
|
Dim tmpByt() As Byte = SharedMethods.ShrinkArray(Of Byte)(byt, incBufferSize + 1)
|
|
sqData.Data = DirectCast(byt.Clone(), Byte())
|
|
sendBuffer.Push(sqData)
|
|
byt = DirectCast(tmpByt.Clone(), Byte())
|
|
End If
|
|
End SyncLock
|
|
|
|
' Send according to the appropriate protocol
|
|
If myParent.Protocol = WinsockProtocol.Tcp Then
|
|
Dim ssState As SendState = SendState.Build(fullSize, mSock1, incBufferSize)
|
|
mSock1.BeginSend(byt, 0, byt.Length, SocketFlags.None, ssState.ErrCode, New AsyncCallback(AddressOf SendCallback), ssState)
|
|
ElseIf myParent.Protocol = WinsockProtocol.Udp Then
|
|
Dim tmpSock As New Socket(remEP.AddressFamily, SocketType.Dgram, ProtocolType.Udp)
|
|
|
|
Dim ssState As SendState = SendState.Build(fullSize, tmpSock, incBufferSize)
|
|
ssState.SendToAddress = remEP
|
|
tmpSock.BeginSendTo(byt, 0, byt.Length, SocketFlags.None, remEP, New AsyncCallback(AddressOf SendToCallback), ssState)
|
|
End If
|
|
Catch ex As Exception
|
|
SharedMethods.RaiseError(myParent, ex.Message)
|
|
End Try
|
|
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
|
|
|
|
Public ReadOnly Property SyncRoot() As Object
|
|
Get
|
|
Dim iCL As ICollection = CType(Me, ICollection)
|
|
' Return CType(llList, ICollection).SyncRoot
|
|
Return iCL.SyncRoot
|
|
End Get
|
|
End Property
|
|
|
|
End Class |