Link to home
Start Free TrialLog in
Avatar of bradbritton
bradbritton

asked on

Issue with UDP Port Program freezing. HELP!!!

I have a program that is listening on a UDP Port for an incoming datapacket, then turns that output to hex. If there is no data comming in (streaming from field device), the program freezes. I have tried using threads and such,  but it is still freezing! Here is the body of the code.

Public Class udpReader
    Public Const udpInPort As Integer = 54321
     Public modemIP As New System.Net.IPEndPoint(System.Net.IPAddress.Any, udpInPort) 'address of sender and port used
    Public bytes As [Byte]()
    Public hexstring As String
    Public udpRecThread As Thread
    Public ba As BitArray ' Bit array to store the data
    Public dataListener As UdpClient ' the UDP Client that is used for the data to be streamed in, port client is sending from

    Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load

    End Sub
    Public Sub recMessage() 'gets message from remote logger
        bytes = dataListener.Receive(modemIP) 'displays modem info
        ba = New BitArray(bytes) 'places incoming data into the bitarray
        hexstring = BytesToString(bytes, True)
        lblSizeInfo.Text = ba.Length.ToString 'gives length of the bit array

    End Sub
    Private Sub btnStart_Click_1(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnStart.Click
        Try
            dataListener = New System.Net.Sockets.UdpClient(udpInPort) 'starts listening for data
            recMessage() 'runs the sub routine to get the data
            udpRecThread = New System.Threading.Thread(AddressOf recMessage) 'thread to handle connection
            udpRecThread.Start() 'starts thread when data is received

        Catch ex As Exception
            MessageBox.Show(ex.Message)
        End Try

        txtIP.Text = modemIP.ToString() ' displays the modems IP
        txtData.Text = hexstring 'displays the hex data from the modem
    End Sub
    Private Sub btnClose_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnClose.Click
        udpRecThread.Abort() ' closes the any threads
        dataListener.Close() ' closes the ports
        Me.Close() ' closes the form
    End Sub
    Function BytesToString(ByVal bytes As Byte(), Optional ByVal AsHex As Boolean = True) As String
        Dim sb As New System.Text.StringBuilder
        Dim sw As New System.IO.StringWriter(sb)
        Dim nl As String = System.Environment.NewLine
        Dim count As Integer = 0
        Dim blockSize As Integer = 16
        Dim blockFormat As String
        Dim itemFormat As String

        If AsHex Then
            blockFormat = "{0:X4}: "
            itemFormat = " {0:X2}"
        Else
            blockFormat = "{0,5}: "
            itemFormat = " {0,3}"
        End If

        For blockStart As Integer = 0 To bytes.Length Step blockSize
            sw.Write(String.Format(blockFormat, blockStart))
            For index As Integer = blockStart To blockStart + blockSize - 1
                If index < bytes.Length Then
                    sw.Write(String.Format(itemFormat, bytes(index)))
                End If
            Next
            sw.WriteLine()
        Next

        Return sw.ToString
    End Function
End Class
Avatar of Mike Tomlinson
Mike Tomlinson
Flag of United States of America image

You might have more luck if you move your question to the VB.Net TA.

You can make a request here:
https://www.experts-exchange.com/Community_Support/
Here is my attempt...it compiles but I can't really test it.   =\

Imports System.Threading
Imports System.net.Sockets

Public Class udpReader
    Inherits System.Windows.Forms.Form

    Public Const udpInPort As Integer = 54321
    Public modemIP As New System.Net.IPEndPoint(System.Net.IPAddress.Any, udpInPort) 'address of sender and port used
    Public bytes As [Byte]()
    Public hexstring As String
    Public udpRecThread As Thread
    Public ba As BitArray ' Bit array to store the data
    Public dataListener As UdpClient ' the UDP Client that is used for the data to be streamed in, port client is sending from

    Private Sub btnStart_Click_1(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnStart.Click
        btnStart.Enabled = False
        dataListener = New System.Net.Sockets.UdpClient(udpInPort) 'starts listening for data
        udpRecThread = New System.Threading.Thread(AddressOf recMessage) 'thread to handle connection
        udpRecThread.Start() 'starts thread when data is received
    End Sub

    Private Sub recMessage() 'gets message from remote logger
        bytes = dataListener.Receive(modemIP) 'displays modem info
        ba = New BitArray(bytes) 'places incoming data into the bitarray
        hexstring = BytesToString(bytes, True)
        updateMessage(hexstring, modemIP.ToString, ba.Length.ToString)
    End Sub

    Private Delegate Sub UpdateMessageDelegate(ByVal hex As String, ByVal IP As String, ByVal length As String)

    Private Sub UpdateMessage(ByVal hex As String, ByVal IP As String, ByVal length As String)
        If Me.InvokeRequired Then
            Dim umd As New UpdateMessageDelegate(AddressOf UpdateMessage)
            Me.Invoke(umd, New Object() {hex, IP, length})
        Else
            lblSizeInfo.Text = length 'gives length of the bit array
            txtIP.Text = IP ' displays the modems IP
            txtData.Text = hex 'displays the hex data from the modem
            btnStart.Enabled = True
            udpRecThread = Nothing
        End If
    End Sub

    Private Sub btnClose_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnClose.Click
        If Not IsNothing(udpRecThread) AndAlso udpRecThread.IsAlive Then
            dataListener.Close() ' closes the ports
            udpRecThread.Abort() ' closes the any threads
        End If
        Me.Close() ' closes the form
    End Sub

    Function BytesToString(ByVal bytes As Byte(), Optional ByVal AsHex As Boolean = True) As String
        Dim sb As New System.Text.StringBuilder
        Dim sw As New System.IO.StringWriter(sb)
        Dim nl As String = System.Environment.NewLine
        Dim count As Integer = 0
        Dim blockSize As Integer = 16
        Dim blockFormat As String
        Dim itemFormat As String

        If AsHex Then
            blockFormat = "{0:X4}: "
            itemFormat = " {0:X2}"
        Else
            blockFormat = "{0,5}: "
            itemFormat = " {0,3}"
        End If

        For blockStart As Integer = 0 To bytes.Length Step blockSize
            sw.Write(String.Format(blockFormat, blockStart))
            For index As Integer = blockStart To blockStart + blockSize - 1
                If index < bytes.Length Then
                    sw.Write(String.Format(itemFormat, bytes(index)))
                End If
            Next
            sw.WriteLine()
        Next

        Return sw.ToString
    End Function

End Class
Avatar of bradbritton
bradbritton

ASKER

this works awesome! However, If I needed to add fucntionality in regards to sending a message (command to send data) to the modem once I get a "DD" command back from it, do I need to add another delegate? Here is the code that I have to send the command:

Public Sub sendCommand()
        Try
            dataSender = New System.Net.Sockets.UdpClient
            Dim datagram As [Byte]()
            Dim cmdString1 As String
            Dim cmdString2 As String
            Dim i As Integer = 0
            Dim readCmd As String = "000000"
            cmdString1 = txtLowBit.Text
            cmdString2 = txtHiBit.Text
            cmdString1 = readCmd + cmdString1 + cmdString2
            cmdString2 = "0" + Convert.ToString(cmdString1.Length - 4) + cmdString1
            cmdString1 = cmdString2
            lblTest.Text = cmdString1
            datagram = HexToBytes(cmdString1)
            dataSender.Send(datagram, datagram.Length - 4, "modem.modemc.com", udpOutPort)
            lblTest.Text = "DONE! Command Sent: " + " " + cmdString1 + " " + i.ToString
            txtData.Text = "Sent the message!" + " " + cmdString1 + " " + Convert.ToString(datagram.Length)
        Catch ex As Exception
            MessageBox.Show(ex.Message)
        End Try
    End Sub

Could you also break this down for me from the code above:

 Me.Invoke(umd, New Object() {hex, IP, length})


Thanks!

Brad
Let's start with the Delegate/Invoke part first:
 
    Private Delegate Sub UpdateMessageDelegate(ByVal hex As String, ByVal IP As String, ByVal length As String)

    Private Sub UpdateMessage(ByVal hex As String, ByVal IP As String, ByVal length As String)
        If Me.InvokeRequired Then
            Dim umd As New UpdateMessageDelegate(AddressOf UpdateMessage)
            Me.Invoke(umd, New Object() {hex, IP, length})
        Else
            lblSizeInfo.Text = length 'gives length of the bit array
            txtIP.Text = IP ' displays the modems IP
            txtData.Text = hex 'displays the hex data from the modem
            btnStart.Enabled = True
            udpRecThread = Nothing
        End If
    End Sub

First we use "Me.InvokeRequired" to determine if the caller is on the same thread as the main UI.  All controls are created on the same thread as the form itself so it is safe to use "Me" instead of a particular control name.  If the caller (the person who ran the UpdateMessage() method) is NOT on the same thread as the main UI then "Me.InvokeRequired" returns TRUE.  This means that we need to Marshal the call onto the main UI thread.  We do this using a Delegate and the Invoke() method.

A Delegate is simply a pointer (technically a reference in VB.Net) to a sub/function.  So we next create a Delegate that points to the UpdateMessage() method using:

    Dim umd As New UpdateMessageDelegate(AddressOf UpdateMessage)

The method being pointed to (UpdateMessage) in the "AddressOf" part must have the same "parameter signature" as the Delegate (UpdateMessageDelegate) being created.

Next we Marshal the call onto the main UI thread using the "Me.Invoke" function.  The Invoke() function takes the Delegate pointed to (along with its parameters) and runs it on the Thread containing the caller ("Me" in this case).  You pass any necessary parameters with the Delegate by creating an Array of type Object containing the parameters.  This is what is happening in this line:

    Me.Invoke(umd, New Object() {hex, IP, length})

When "umd" is run via Invoke(), we will actually make a recursive call to ourself in UpdateMessage (albeit the recursive call is in a different thread now!).

When we enter UpdateMessage() again (because of recursion remember), we first test to see if the caller is on the main UI thread with "Me.InvokeRequired".  This time, however, it should return FALSE because the "Me.Invoke" call should have marshaled the delegate onto the main UI thread.

As a result, we will drop down into the ELSE block where it is safe to update our GUI controls from.

I noticed that in your other question you are using this:

    Control.CheckForIllegalCrossThreadCalls = False

To "properly" handle that situation you would use the same approach I have outlined here using Delegates and Invoke().

Hope that makes sense!....
In answer to the other question:  It depends on where you call sendCommand() from.

If you call sendCommand() in response to anything from the main UI (like a button click) then you should be OK.  

Not sure on the flow here, but you could just check for "DD" in the UpdateMessage() sub, as long as you do it from the ELSE block:

    Private Sub UpdateMessage(ByVal hex As String, ByVal IP As String, ByVal length As String)
        If Me.InvokeRequired Then
            Dim umd As New UpdateMessageDelegate(AddressOf UpdateMessage)
            Me.Invoke(umd, New Object() {hex, IP, length})
        Else
            lblSizeInfo.Text = length 'gives length of the bit array
            txtIP.Text = IP ' displays the modems IP
            txtData.Text = hex 'displays the hex data from the modem
            btnStart.Enabled = True
            udpRecThread = Nothing

            If hex = "DD" Then
                sendCommand()
            End If
        End If
    End Sub

The ELSE block is already on the main UI thread so it would be safe to call sendCommand() from there.

It is safe to READ control values fom another thread.  You just can't SET control values from another thread.  So if you call sendCommand() from a thread other than the main UI, then Yes, you would need a Delegate to process these lines of code:

            ...
            lblTest.Text = cmdString1
            ...
            ...
            lblTest.Text = "DONE! Command Sent: " + " " + cmdString1 + " " + i.ToString
            txtData.Text = "Sent the message!" + " " + cmdString1 + " " + Convert.ToString(datagram.Length)

...because they are setting control values from another thread.
I added the check in for DD, but it does not go into the sub. Maybe I could try using hex.length > 1 for the logic? I have tried to use the DD check in the IF Loop, but it for some strange reason will not invoke the sub. Ahhhhh... I love socket programming ;-( Thanks for all your help so far!
Also, how is the data from from the recMessage sub passed to the update message? Sorry about the questions, but this is some stuff that we never even were warned about in school! Bad DeVry... Bad!
ASKER CERTIFIED SOLUTION
Avatar of Mike Tomlinson
Mike Tomlinson
Flag of United States of America image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Hmmm, good question. It should be just DD. I will have to analyze this a little further. Thanks for the help up until now. I will award points as what you have done has solved my original problem (and then some) If you would like I can repost any additional problems in a new thread (for more points) just to keep it fair. Thanks again and keep up the great work!

Brad