Question Fairly confused about async operations, callbacks and custom hooks

erupter

Member
Joined
Jun 29, 2010
Messages
12
Programming Experience
1-3
I'm writing a class to encapsulate an udpclient and do a little processing on the messages sent and received.
I think that what i need is something similar to a hook.
Meaning that when the parent class instantiates an object of my custom class, it should provide a callback function to call when a packet is received and processed.
Now I have mainly two doubts:
-should I force the New constructor to ask for a callback and fail without? if so, how do I do it?
-right now I've written a blank sub to trigger the reception of messages, which should accept the callback function as parameter, how should this declaration be constructed?

I hope my doubts are clear, I'm reading about delegates and the likes, but I'm still not very familiar with all these concepts.

VB.NET:
Public Class RemoteSciamiInterface

    Private _receive As Boolean
    Private _message As String
    Private _udpclient As New System.Net.Sockets.UdpClient(12500)
    Private _hostname As String = System.Net.Dns.GetHostName
    Private _hostaddr As System.Net.IPAddress = System.Net.Dns.GetHostByName(_hostname).AddressList.GetValue(0)
    Private _localendpoint As New System.Net.IPEndPoint(0, 0)
    Private _destendpoint As New System.Net.IPEndPoint(0, 0)
    Private _subnet As New System.Net.IPAddress(0)
    Private _receivingaddress As New System.Net.IPEndPoint(System.Net.IPAddress.Any, 0)
    Private _enc As New System.Text.ASCIIEncoding
    Private regPath As String = "System\CurrentControlSet\Services\Tcpip\Parameters\Interfaces\"
    Private registrystart As RegistryKey
    Private _mycallback As AsyncCallback
    Private _myobj As New Object
    Private _local32 As UInteger = GetBinaryAddress(_hostaddr)

    Public Sub New()
        _mycallback = AddressOf processreceive
        registrystart = Registry.LocalMachine.OpenSubKey(regPath, False)
        Dim counter As Integer = 0
        'Dim subkeys As String() = registrystart.GetSubKeyNames
        For Each temp As String In registrystart.GetSubKeyNames
            counter = counter + 1
            Dim temp2 As RegistryKey = Registry.LocalMachine.OpenSubKey(regPath & temp, False)

            Try
                If temp2.GetValue("DhcpIPAddress").Equals(_hostaddr.ToString) Then
                    Net.IPAddress.TryParse(temp2.GetValue("DhcpSubnetMask"), _subnet)
                ElseIf temp2.GetValue("IPAddress").Equals(_hostaddr.ToString) Then
                    Net.IPAddress.TryParse(temp2.GetValue("SubnetMask"), _subnet)
                End If
            Catch ex As Exception

            End Try

        Next
        Dim _broadcast As Net.IPAddress = GetLocalBroadcast(_hostaddr, _subnet)
        _destendpoint.Address = _broadcast
        _destendpoint.Port = 12500
        _localendpoint.Address = _hostaddr
        _localendpoint.Port = 12500
    End Sub

    Public Sub StartListening()
        _udpclient.BeginReceive(_mycallback, _myobj)
        _receive = True
    End Sub

    Public Sub StopListening()
        _receive = False
    End Sub

    Private Sub processreceive(ByVal result As IAsyncResult)
        Try
            Dim _mystring As String = _enc.GetChars(_udpclient.EndReceive(result, _receivingaddress))

            If MessaggeIsForUS(_mystring) Then

            End If

        Catch ex As Exception
            MsgBox(ex)
        End Try

        If _receive Then
            _udpclient.BeginReceive(_mycallback, _myobj)
        End If

    End Sub

    Public Function SendMessage(ByVal mess As String) As Boolean
        Dim localmess As String = "SRC=" + _hostaddr.ToString + "," + "DST=" + _destendpoint.Address.ToString + "," + "MSG=" + mess + ",END"
        Try
            _udpclient.Send(_enc.GetBytes(localmess), _enc.GetByteCount(localmess), _destendpoint)
            Return 0
        Catch ex As Exception
            Return -1
        End Try

    End Function

    Public Function SendMessage(ByVal mess As String, ByVal address As String)
        Dim destendpoint As New System.Net.IPEndPoint(address, 0)
        Dim localmess As String = "SRC=" + _hostaddr.ToString + "," + "DST=" + address + "," + "MSG=" + mess + ",END"
        Try
            _udpclient.Send(_enc.GetBytes(localmess), _enc.GetByteCount(localmess), _destendpoint)
            Return 0
        Catch ex As Exception
            Return -1
        End Try

    End Function

    Private Function GetLocalBroadcast(ByVal addr As Net.IPAddress, ByVal subn As Net.IPAddress) As Net.IPAddress
        Dim host32 As UInteger = GetBinaryAddress(addr.ToString)
        Dim subn32 As UInteger = GetBinaryAddress(subn.ToString)
        Dim broad32 As UInteger = host32 Or Not (subn32)
        Dim _mybytes As Byte() = {0, 0, 0, 0}
        _mybytes(0) = (broad32 And &HFF000000) >> 24
        _mybytes(1) = (broad32 And &HFF0000) >> 16
        _mybytes(2) = (broad32 And &HFF00) >> 8
        _mybytes(3) = broad32 And &HFF
        Dim broadcast As Net.IPAddress = Net.IPAddress.None
        Net.IPAddress.TryParse(_mybytes(0).ToString + "." + _mybytes(1).ToString + "." + _mybytes(2).ToString + "." + _mybytes(3).ToString, broadcast)
        Return broadcast
    End Function

    Private Function GetBinaryAddress(ByVal addr As String) As UInteger
        Dim _hostaddr_bytes As String() = addr.ToString.Split(".")
        Dim _host_int32 As UInteger = (Convert.ToUInt32(_hostaddr_bytes(0)) << 24) + (Convert.ToUInt32(_hostaddr_bytes(1)) << 16) + (Convert.ToUInt32(_hostaddr_bytes(2)) << 8) + Convert.ToUInt32(_hostaddr_bytes(3))
        Return _host_int32
    End Function

    Private Function GetBinaryAddress(ByVal add As Net.IPAddress) As UInteger
        Dim addr As String = add.ToString
        Dim _hostaddr_bytes As String() = addr.ToString.Split(".")
        Dim _host_int32 As UInteger = (Convert.ToUInt32(_hostaddr_bytes(0)) << 24) + (Convert.ToUInt32(_hostaddr_bytes(1)) << 16) + (Convert.ToUInt32(_hostaddr_bytes(2)) << 8) + Convert.ToUInt32(_hostaddr_bytes(3))
        Return _host_int32
    End Function

    Private Function MessaggeIsForUS(ByVal mess As String) As Boolean
        Dim localaddr() As String = mess.Split(",")
        localaddr(1) = localaddr(1).Remove(0, 4)
        Dim dest32 As UInteger = GetBinaryAddress(localaddr(1))
        If ((dest32 And _local32) = _local32) Then
            Return True
        Else
            Return False
        End If
    End Function

End Class

The sub i refer to is "StartListening".
Some comments on the code:
the New sub essentially finds the local main ip address and related subnet mask, from those it calculates the sub-network broadcast ip.

StartListening and StopListening should be self explanatory.

ProcessReceive is the internal sub that encapsulates system.net.udpclient doing the hardwork.

SendMessage is the exposed interface to receive the string to send from the parent class.

The rest are service subs and functions.
 
Yeah, right. Events are good in my case.
But then why classes like the udpclient don't use events while relying on callbacks?
In my opinion in the udpclient case, firing a "messagereceived" event would lead to the very same result. Yet it probably isn't so, but I can't see the difference.
 
Events are raised, not "fired". Events uses delegates (method pointers) 'behind the scenes', but hides this complexity for the VB delevoper. Events is just a very simple interface to delegates, and is widely supported by the IDEs designer and code editor. Delegates are used when the method implementation is to be defined by caller, which is exactly what event handlers are for. There is however an element of reoccurence for events, this is typically methods that "call back" several times, and usually with no direct call for it, in many cases there could be various other triggers that cause the event to be raised again. Delegate instances may though be passed around more freely than events, for example through several class layers without the need to redeclare and handle each level. Events on the other hand has the protection that only the declaring class can raise them (ie invoke the handlers), when using delegates anyone with access to the delegate instance may modify it and invoke it. UdpClient is not set up as a listener, with start/stop methods and events, because that may not be appropriate for any communication protocol.
 
One note about broadcast packets, UdpClient must have EnableBroadcast property enabled to send/receive such packets. For IPv4 the broadcast address may be the local (calculated by subnet) or the global (limited to local network), and this is the address you send to, eg when using port 12500:
VB.NET:
Private _broadcast As New System.Net.IPEndPoint(System.Net.IPAddress.Broadcast, 12500)
The remote endpoint is populated by the Receive/EndReceive call and you don't need to define this.
 
One note about broadcast packets, UdpClient must have EnableBroadcast property enabled to send/receive such packets.
But, if I set the flag, what kind of broadcast would then be enacted? Local or Global?
I resorted to manually calculate the bcast address and using this one as destination address.

The remote endpoint is populated by the Receive/EndReceive call and you don't need to define this.
But the EndReceive call explicitly asks for a receiving address to be specified. I can't omit the value.

Another theoretical question:
I've written the class to encapsulate the updclient and another class to process the messages, and am in the process or writing others to do other stuff.
Now all these classes are separate. From the main program i declare the new instances. How can I hook a subroutine of one class, to an event raised by another class?
Is there a more elegant way than rerouting the call manually from an eventhandler in the main class?
 
But, if I set the flag, what kind of broadcast would then be enacted? Local or Global?
A broadcast packet is a broadcast packet. The only difference would be the global address would send the packet to local network of all connected adapters, while the specific address would only send the packet to the local network of the corresponding adapter for that subnet. If you only have one adapter there would be no difference. Using the global Broadcast ip is simpler and more convenient though.
But the EndReceive call explicitly asks for a receiving address to be specified. I can't omit the value.
It is a ByRef parameter used for output purpose only (ie 'return value'), either you supply Nothing if you don't need the returned information, or a IPEndPoint variable initalized to Nothing if you need the information after call has returned.
How can I hook a subroutine of one class, to an event raised by another class?
To handle an event you need a reference to the class instance that raise it, plain and simple. If you expose the class instance the user may handle its events. Routing events through several class layers means handle the event in each class and define and raise a new event in each class.
 
Thought I'd also share a function that gets the broadcast address in a more logical way, which follows the definition "bitwise logical OR operation between the bit complement of the subnet mask and the host's IP address":
VB.NET:
Public Function GetBroadcastAddress(ByVal ip As Net.IPAddress, ByVal subnet As Net.IPAddress) As Net.IPAddress
    Dim ipBits As New BitArray(ip.GetAddressBytes)
    Dim subnetBits As New BitArray(subnet.GetAddressBytes)
    subnetBits.Not() 'inverse subnet and
    ipBits.Or(subnetBits) ' include it with network
    Return Me.BitsToIP(ipBits)
End Function

Private Function BitsToIP(ByVal bits As BitArray) As Net.IPAddress
    Dim b((bits.Length \ 8) - 1) As Byte
    bits.CopyTo(b, 0)
    Return New Net.IPAddress(b)
End Function
 
Back
Top