Imports System.ComponentModel ''' ''' A collection of Winsock objects. ''' Public Class WinsockCollection Inherits CollectionBase #Region " Private Members " ' These two hashtables store the key's and the values. ' The base class's List store the GUID that ties the ' keys to the values Private _keys As Hashtable Private _values As Hashtable ' These are for auto removal of the Winsock object ' when the Winsock's Disconnected event is raised. Private _autoRemoval As Queue Private _autoRemove As Boolean = False ' These are for timing and removal of every Winsock ' object in the collection - only used when the ' Clear() method is run. Private _clearRemoval As Queue Private _clearRemove As Boolean = False Private _clearOK As Boolean = False ' Allows LegacySupport to work in a multi-client ' environment Private _legacySupport As Boolean = False #End Region #Region " Constructor " ''' ''' Initializes a new instance of the WinsockCollection class. ''' ''' ''' Determines if the collection should automatically remove the ''' connection when the Disconnected event is fired. ''' ''' ''' Enables LegacySupport for connections accepted using the ''' collections Accept method. ''' Public Sub New(Optional ByVal auto_remove As Boolean = False, Optional ByVal legacy_support As Boolean = False) _keys = New Hashtable _values = New Hashtable _autoRemoval = New Queue _autoRemove = auto_remove _clearRemoval = New Queue _legacySupport = legacy_support End Sub #End Region #Region " Overriden Methods " ''' ''' Run when the base class's list is finished clearing. ''' Triggers clearing of the keys and values - closing ''' all connections. ''' Protected Overrides Sub OnClearComplete() MyBase.OnClearComplete() _keys.Clear() Dim b As Boolean = _autoRemove : _autoRemove = False _clearOK = False _clearRemove = True For Each wsk As Winsock In _values.Values wsk.Close() Next _clearOK = True _autoRemove = b End Sub ''' ''' Causes the CountChanged event to be triggered when an item is added to the collection. ''' Protected Overrides Sub OnInsertComplete(ByVal index As Integer, ByVal value As Object) MyBase.OnInsertComplete(index, value) OnCountChanged(List.Count - 1, List.Count) End Sub ''' ''' Causes the CountChanged event to be triggered when an item is removed from the collection. ''' Protected Overrides Sub OnRemoveComplete(ByVal index As Integer, ByVal value As Object) MyBase.OnRemoveComplete(index, value) OnCountChanged(List.Count + 1, List.Count) End Sub #End Region #Region " Private Methods " ''' ''' Run during the Clearing of the collection. ''' The method actually clears the values safely ''' so as not to cause exceptions. Triggered via ''' the last Disconnected event. ''' Private Sub ClearAllValues() While _values.Count > 0 If _clearOK AndAlso _clearRemoval.Count > 0 Then Dim i As Integer = _values.Count Dim gid2Rem As Guid = CType(_clearRemoval.Dequeue(), Guid) _values.Remove(gid2Rem) OnCountChanged(i, _values.Count) End If End While End Sub ''' ''' Attemps to retrieve the GUID of the item at the index specified. ''' ''' The zero-based index of the GUID you are attempting to find. Private Function getGID(ByVal index As Integer) As Guid Return CType(List.Item(index), Guid) End Function ''' ''' Attempts to retrieve the GUID of the item using the Key given to the item. ''' ''' The key whose GUID you are looking for. Private Function getGID(ByVal key As Object) As Guid For Each gid As Guid In _keys.Keys If Object.ReferenceEquals(_keys(gid), key) Then Return gid Next Return Guid.Empty End Function ''' ''' Removes the given GUID and it's ties from the collections. ''' ''' The GUID to remove. Private Sub RemoveGID(ByVal gid As Guid) If gid <> Guid.Empty Then CType(_values(gid), Winsock).Dispose() _values.Remove(gid) _keys.Remove(gid) List.Remove(gid) End If End Sub ''' ''' Adds a winsock value to the collection. ''' ''' The GUID of the object. ''' The Key of the object (may be nothing). ''' The Winsock that is to be added to the collection. ''' Attaches handlers to each Winsock event so the collection can act as a proxy. Private Sub Add(ByVal gid As Guid, ByVal key As Object, ByVal value As Winsock) AddHandler value.Connected, AddressOf OnConnected AddHandler value.ConnectionRequest, AddressOf OnConnectionRequest AddHandler value.DataArrival, AddressOf OnDataArrival AddHandler value.Disconnected, AddressOf OnDisconnected AddHandler value.ErrorReceived, AddressOf OnErrorReceived AddHandler value.SendComplete, AddressOf OnSendComplete AddHandler value.SendProgress, AddressOf OnSendProgress AddHandler value.StateChanged, AddressOf OnStateChanged _keys.Add(gid, key) _values.Add(gid, value) List.Add(gid) End Sub ''' ''' Method to remove an object automatically - threaded to avoid problems. ''' Private Sub RemovalThread() Threading.Thread.Sleep(50) Dim gid As Guid = CType(_autoRemoval.Dequeue(), Guid) RemoveGID(gid) End Sub #End Region #Region " Public Methods " ''' ''' Retrieves the GUID assigned to the specified Winsock object in the collection. ''' ''' The Winsock object to find the GUID of. Public Function findGID(ByVal value As Winsock) As Guid If Not ContainsValue(value) Then Return Guid.Empty For Each gid As Guid In _values.Keys If Object.ReferenceEquals(_values(gid), value) Then Return gid Next Return Guid.Empty End Function ''' ''' Retrieves the Key assigned to the specified Winsock object in the collection. ''' ''' The Winsock object to find the Key of. ''' The key object that was assigned to the Winsock - may be Nothing. Public Function findKey(ByVal value As Winsock) As Object Dim gid As Guid = findGID(value) If gid = Guid.Empty Then Return Nothing Return _keys(gid) End Function ''' ''' Determines if the collection contains the key specified. ''' ''' The key to search the collection for. Public Function ContainsKey(ByVal key As Object) As Boolean If key Is Nothing Then Throw New ArgumentNullException("key") End If Return _keys.ContainsValue(key) End Function ''' ''' Determines if the collection contains the specified value. ''' ''' The value to search the collection for. Public Function ContainsValue(ByVal value As Winsock) As Boolean Return _values.ContainsValue(value) End Function ''' ''' Removes the value at the specified index. Use this instead of RemoveAt. ''' ''' The zero-based index of the item you wish to remove. Public Sub Remove(ByVal index As Integer) Dim gid As Guid = getGID(index) RemoveGID(gid) End Sub ''' ''' Removes the value with the specified key. ''' ''' The key of the value you wish to remove. Public Sub Remove(ByVal key As Object) If TypeOf (key) Is Integer Then Dim gidIndex As Guid = getGID(CInt(key)) RemoveGID(gidIndex) Exit Sub End If If Not ContainsKey(key) Then Exit Sub Dim gid As Guid = getGID(key) RemoveGID(gid) End Sub ''' ''' Removes the value with the specified Guid. ''' ''' The Guid of the value you wish to remove. Public Sub Remove(ByVal gid As Guid) RemoveGID(gid) End Sub ''' ''' Adds a value to the collection. ''' ''' The Winsock object to add to the collection. ''' Returns the GUID assigned to the element. Public Function Add(ByVal value As Winsock) As Guid Dim gid As Guid = Guid.NewGuid() Add(gid, Nothing, value) Return gid End Function ''' ''' Adds a value to the collection. ''' ''' The Winsock object to add to the collection. ''' The Key of the element to add. ''' Returns the GUID assigned to the element. Public Function Add(ByVal value As Winsock, ByVal key As Object) As Guid Dim gid As Guid = Guid.NewGuid() Add(gid, key, value) Return gid End Function ''' ''' Accepts an incoming connection and adds it to the collection. ''' ''' The client to accept. ''' Returns the GUID assigned to the element. Public Function Accept(ByVal client As System.Net.Sockets.Socket) As Guid Dim wsk As New Winsock() wsk.LegacySupport = _legacySupport Dim gid As Guid = Add(wsk, Nothing) wsk.Accept(client) Return gid End Function ''' ''' Accepts an incoming connection and adds it to the collection. ''' ''' The client to accept. ''' The Key of the element to add. ''' Returns the GUID assigned to the element. Public Function Accept(ByVal client As System.Net.Sockets.Socket, ByVal key As Object) As Guid Dim wsk As New Winsock() wsk.LegacySupport = _legacySupport Dim gid As Guid = Me.Add(wsk, key) wsk.Accept(client) Return gid End Function ''' ''' Connects to a remote host and adds it to the collection. ''' ''' A containing the Hostname or IP address of the remote host. ''' A value indicating the port on the remote host to connect to. ''' Return the GUID assigned to the element. Public Function Connect(ByVal remoteHostOrIP As String, ByVal remotePort As Integer) As Guid Dim wsk As New Winsock() wsk.Protocol = WinsockProtocol.Tcp wsk.LegacySupport = _legacySupport Dim gid As Guid = Add(wsk, Nothing) CType(_values(gid), Winsock).Connect(remoteHostOrIP, remotePort) End Function ''' ''' Connects to a remote host and adds it to the collection. ''' ''' A containing the Hostname or IP address of the remote host. ''' A value indicating the port on the remote host to connect to. ''' The Key of the element to add. ''' Return the GUID assigned to the element. Public Function Connect(ByVal remoteHostOrIP As String, ByVal remotePort As Integer, ByVal key As Object) As Guid Dim wsk As New Winsock() wsk.Protocol = WinsockProtocol.Tcp wsk.LegacySupport = _legacySupport Dim gid As Guid = Add(wsk, key) CType(_values(gid), Winsock).Connect(remoteHostOrIP, remotePort) End Function ''' ''' Gets an Array of all the remote IP addresses of each connection in this collection. ''' Public Function GetRemoteIPs() As ArrayList Dim ar As New ArrayList For Each key As Object In _values.Keys ar.Add(CType(_values(key), Winsock).RemoteHost) Next Return ar End Function #End Region #Region " Public Properties " ''' ''' Gets a Collection containing all the keys in this collection. ''' Public ReadOnly Property Keys() As System.Collections.ICollection Get Return _keys.Values() End Get End Property ''' ''' Gets a Collection containing all the values in this collection. ''' Public ReadOnly Property Values() As System.Collections.ICollection Get Return _values.Values End Get End Property ''' ''' Gets or sets the Winsock at the specified index. ''' ''' A zero-based index of the Winsock to get or set. Default Public Property Item(ByVal index As Integer) As Winsock Get Dim gid As Guid = getGID(index) Return CType(_values(gid), Winsock) End Get Set(ByVal value As Winsock) Dim gid As Guid = getGID(index) If Not _values.ContainsKey(gid) Then Add(gid, Nothing, value) Else _values(gid) = value End If End Set End Property ''' ''' Gets or sets the Winsock associated with the specified key. ''' ''' The key whose value to get or set. Default Public Property Item(ByVal key As Object) As Winsock Get Dim gid As Guid = getGID(key) Return CType(_values(gid), Winsock) End Get Set(ByVal value As Winsock) Dim gid As Guid = getGID(key) If Not _values.ContainsKey(gid) Then Add(gid, Nothing, value) Else _values(gid) = value End If End Set End Property ''' ''' Gets or sets the Winsock associated with the specified GUID. ''' ''' The GUID whose value to get or set. Default Public Property Item(ByVal gid As Guid) As Winsock Get Return CType(_values(gid), Winsock) End Get Set(ByVal value As Winsock) If Not _values.ContainsKey(gid) Then Add(gid, Nothing, value) Else _values(gid) = value End If End Set End Property #End Region #Region " Events " ''' ''' Occurs when connection is achieved (client and server). ''' Public Event Connected(ByVal sender As Object, ByVal e As WinsockConnectedEventArgs) ''' ''' Occurs on the server when a client is attempting to connect. ''' ''' Client registers connected at this point. Server must Accept in order for it to be connected. Public Event ConnectionRequest(ByVal sender As Object, ByVal e As WinsockConnectionRequestEventArgs) ''' ''' Occurs when the number of items in the collection has changed. ''' Public Event CountChanged(ByVal sender As Object, ByVal e As WinsockCollectionCountChangedEventArgs) ''' ''' Occurs when data arrives on the socket. ''' ''' Raised only after all parts of the data have been collected. Public Event DataArrival(ByVal sender As Object, ByVal e As WinsockDataArrivalEventArgs) ''' ''' Occurs when disconnected from the remote computer (client and server). ''' Public Event Disconnected(ByVal sender As Object, ByVal e As System.EventArgs) ''' ''' Occurs when an error is detected in the socket. ''' ''' May also be raised on disconnected (depending on disconnect circumstance). Public Event ErrorReceived(ByVal sender As Object, ByVal e As WinsockErrorReceivedEventArgs) ''' ''' Occurs when sending of data is completed. ''' Public Event SendComplete(ByVal sender As Object, ByVal e As WinsockSendEventArgs) ''' ''' Occurs when the send buffer has been sent but not all the data has been sent yet. ''' Public Event SendProgress(ByVal sender As Object, ByVal e As WinsockSendEventArgs) ''' ''' Occurs when the state of the socket changes. ''' Public Event StateChanged(ByVal sender As Object, ByVal e As WinsockStateChangedEventArgs) ''' ''' Triggers an event declared at module level within a class, form, or document in a thread-safe manner. ''' ''' The event to be raised. ''' The arguements for the event. 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.BeginInvoke(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 ''' ''' Raises the Connected event. ''' Protected Friend Sub OnConnected(ByVal sender As Object, ByVal e As WinsockConnectedEventArgs) RaiseEventSafe(ConnectedEvent, New Object() {sender, e}) End Sub ''' ''' Raises the ConnectionRequest event, and closes the socket if the ConnectionRequest was rejected. ''' Protected Friend Sub OnConnectionRequest(ByVal sender As Object, ByVal e As WinsockConnectionRequestEventArgs) RaiseEventSafe(ConnectionRequestEvent, New Object() {sender, e}) End Sub ''' ''' Raises the DataArrival event. ''' Protected Friend Sub OnDataArrival(ByVal sender As Object, ByVal e As WinsockDataArrivalEventArgs) RaiseEventSafe(DataArrivalEvent, New Object() {sender, e}) End Sub ''' ''' Raises the Disconnected Event. ''' Protected Friend Sub OnDisconnected(ByVal sender As Object, ByVal e As System.EventArgs) RaiseEventSafe(DisconnectedEvent, New Object() {sender, e}) If _clearRemove Then Dim gid As Guid = findGID(CType(sender, Winsock)) _clearRemoval.Enqueue(gid) If _clearRemoval.Count = _values.Count Then Dim thd As New Threading.Thread(AddressOf ClearAllValues) thd.Start() _clearRemove = False End If ElseIf _autoRemove Then Dim gid As Guid = findGID(CType(sender, Winsock)) _autoRemoval.Enqueue(gid) Dim thd As New Threading.Thread(AddressOf RemovalThread) thd.Start() End If End Sub ''' ''' Raises the ErrorReceived event. ''' Protected Friend Sub OnErrorReceived(ByVal sender As Object, ByVal e As WinsockErrorReceivedEventArgs) RaiseEventSafe(ErrorReceivedEvent, New Object() {sender, e}) End Sub ''' ''' Raises the SendComplete event. ''' Protected Friend Sub OnSendComplete(ByVal sender As Object, ByVal e As WinsockSendEventArgs) RaiseEventSafe(SendCompleteEvent, New Object() {sender, e}) End Sub ''' ''' Raises the SendProgress event. ''' Protected Friend Sub OnSendProgress(ByVal sender As Object, ByVal e As WinsockSendEventArgs) RaiseEventSafe(SendProgressEvent, New Object() {sender, e}) End Sub ''' ''' Raises the StateChanged event. ''' Protected Friend Sub OnStateChanged(ByVal sender As Object, ByVal e As WinsockStateChangedEventArgs) RaiseEventSafe(StateChangedEvent, New Object() {sender, e}) End Sub ''' ''' Raises the count changed event. ''' Private Sub OnCountChanged(ByVal old_count As Integer, ByVal new_count As Integer) Dim e As New WinsockCollectionCountChangedEventArgs(old_count, new_count) RaiseEventSafe(CountChangedEvent, New Object() {Me, e}) End Sub #End Region End Class