|
-
Feb 18th, 2008, 01:53 PM
#1
[RESOLVED] Strange UDP Issue
Here's a UDP class:
vb Code:
Public Class UDPCommon
Implements IDisposable
'The UDP listening components.
Private WithEvents locUDP As System.Net.Sockets.Socket
Private WithEvents locUDPOut As System.Net.Sockets.UdpClient
Private ThreadListen As New Threading.Thread(AddressOf WaitForPending)
Private timeCount As Integer
Private broad As System.Net.IPEndPoint
'The dataQueue
Private dgList As Queue(Of DataGramReceiver)
'The result
Private disposedValue As Boolean = False ' To detect redundant calls
'Synchronization.
Private Shared myContext As System.Threading.SynchronizationContext
#Region "Constructors and Destructors"
Public Sub New()
'Set up the queue.
dgList = New Queue(Of DataGramReceiver)
locUDPOut = New Net.Sockets.UdpClient(11013)
locUDP = New Net.Sockets.Socket(Net.Sockets.AddressFamily.InterNetwork, Net.Sockets.SocketType.Dgram, Net.Sockets.ProtocolType.Udp)
locUDP.SetSocketOption(Net.Sockets.SocketOptionLevel.Socket, Net.Sockets.SocketOptionName.ReuseAddress, True)
locUDP.Bind((New Net.IPEndPoint(Net.IPAddress.Any, 11013)))
locUDPOut.EnableBroadcast = True
broad = New System.Net.IPEndPoint(System.Net.IPAddress.Broadcast, 11013)
myContext = System.Threading.SynchronizationContext.Current
'Start listening.
ThreadListen.Name = "Listening Thread"
ThreadListen.Start()
End Sub
' IDisposable
Protected Overridable Sub Dispose(ByVal disposing As Boolean)
If Not Me.disposedValue Then
If disposing Then
ThreadListen.Abort()
locUDP.Close()
locUDPOut.Close()
End If
' TODO: free shared unmanaged resources
End If
Me.disposedValue = True
End Sub
#Region " IDisposable Support "
' This code added by Visual Basic to correctly implement the disposable pattern.
Public Sub Dispose() Implements IDisposable.Dispose
' Do not change this code. Put cleanup code in Dispose(ByVal disposing As Boolean) above.
Dispose(True)
GC.SuppressFinalize(Me)
End Sub
#End Region
#End Region
#Region "Properties and Events"
Public Event MessageIn()
Public ReadOnly Property MessageCount() As Integer
Get
Return dgList.Count
End Get
End Property
Public ReadOnly Property NextMessage() As DataGramReceiver
Get
Return dgList.Dequeue
End Get
End Property
#End Region
#Region "Public Functions"
'This will get the next DGR from the queue.
Public Function GetNext() As DataGramReceiver
Dim dgRet As New DataGramReceiver
Threading.Monitor.Enter(Me)
Try
If dgList.Count > 0 Then
dgRet = New DataGramReceiver(dgList.Dequeue)
End If
Catch ex As Exception
Windows.Forms.MessageBox.Show(ex.Message, "Error Getting DataGramReceiver", MessageBoxButtons.OK, MessageBoxIcon.Error)
Finally
Threading.Monitor.Exit(Me)
End Try
'It may be the default, or it may not, either way it is a good local copy in the calling thread, return it.
Return dgRet
End Function
'Empties the queue
Public Sub Flush()
Threading.Monitor.Enter(Me)
Try
dgList.Clear()
Finally
Threading.Monitor.Exit(Me)
End Try
End Sub
Public Sub SendMessage(ByVal btArr() As Byte, ByVal toWho As Byte, ByVal what As Byte)
Dim lBtArr(btArr.Length + 2) As Byte
lBtArr(0) = 100 'This is the OL ID.
lBtArr(1) = toWho
lBtArr(2) = what
System.Array.Copy(btArr, 0, lBtArr, 3, btArr.Length)
'NOTE: NEED TO CHECK THAT THIS IS DOING AS IT SHOULD.
locUDP.SendTo(lBtArr, lBtArr.Length, Net.Sockets.SocketFlags.Broadcast, broad)
End Sub
#End Region
#Region "Private Functions"
Private Sub WaitForPending()
Dim bHolder(511) As Byte
Dim numIn As Integer
Dim dgRec As DataGramReceiver
Dim bTrans() As Byte
While True
If CBool(locUDP.Available) Then
numIn = locUDP.Receive(bHolder, 512, Net.Sockets.SocketFlags.None)
If numIn > 4 Then
If bHolder(1) = 0 OrElse bHolder(1) = 100 Then
'Set the holder up to the right size.
ReDim bTrans(numIn)
Array.Copy(bHolder, 3, bTrans, 0, numIn - 3)
dgRec = New DataGramReceiver(128, bHolder(2), bTrans)
Add(dgRec)
End If
End If
End If
End While
End Sub
'Adds one DGR to the queue
Private Sub Add(ByVal dgR As DataGramReceiver)
'Set the monitor.
Threading.Monitor.Enter(Me)
Try
dgList.Enqueue(dgR) 'That's all that happens.
Finally
Threading.Monitor.Exit(Me)
End Try
myContext.Post(AddressOf GotData, Nothing)
End Sub
'This is invoked by the other thread.
Private Sub GotData(ByVal state As Object)
RaiseEvent MessageIn()
End Sub
#End Region
End Class
The intention for this class is that it will be included in multiple modules on multiple computers. I have tried it on two different computers, but not in talking to each other, I haven't gotten that far. On both computers I have projects that have an instance of this class along with an instance of a class that is similar, but uses two ports rather than one, because that other class will have only one module writing to one port and listening to the other port, while all the other modules will listen to the first port and write to the second. That was working fine with two modules...now we'll see, but not yet.
Therefore, on both systems there is one instance of this class, and one instance of the class with two ports. On the system running XP Home (I have no idea whether or not that matters), if I step through the two lines that create the instances of the two classes, the first class is created pretty much instantly, while the class posted above takes a tiny fraction of a second. There is a hesitation, but it is very brief.
On the system running XP Pro, the two port class is also created instantly. Both classes are in Try blocks for various reasons, but the first class is created without any issues. When I step over the line that creates the class posted above, there is a pause of a second or two (which is an ENORMOUS length of time), then the next line highlighted is a function in the catch block, but the function isn't executed, and no exception was actually raised. I've NEVER seen this happen before, where VS will incorrectly highlight the next line.
If I run this without adding a breakpoint, the form takes up to five seconds to be displayed. Since there is nothing else that takes any time, I assume the entire issue is the class posted above. Frankly, I've tinkered around with this class so many times by now that I forget whether it is even actually valid now, but it throws no exceptions.
If I step through the creation of the class, there are a few minor pauses, such as when the constructor starts the listener thread, but the pauses are minor. The delay occurs after the New sub returns, and the execution highlight is, again, taken to the first line in the catch block, though no exception was raised, and the line in the catch block is not actually executed.
Any suggestions? Any alternatives? The key is to have a class that can read and write to a single port using UDP, and created such that there could be multiple instances of the class in different modules, all running simultaneously on the same system.
My usual boring signature: Nothing
 
-
Feb 20th, 2008, 01:22 PM
#2
Re: Strange UDP Issue
Hey SH.
I trying your class but its throwing an SocketException on this line
locUDP.Bind((New Net.IPEndPoint(Net.IPAddress.Any, 11013)))
The exception is:
An attempt was made to access a socket in a way forbidden by its access permissions.
I dont know if this is due to any security settings in Vista, but its defenitly caused by the fact that the UdpClient is already using that address. Have you gotten this?
-
Feb 21st, 2008, 11:26 AM
#3
Re: Strange UDP Issue
Nope, that's a totally new error for me. On XP Home or Pro, I get no errors in the constructor with the posted code. However, I do get that bizarre behavior during the construction. That gives me something more to look at. This is beginning to look like an interaction between UDP sockets and OS versions.
My usual boring signature: Nothing
 
-
Feb 23rd, 2008, 04:51 PM
#4
Re: Strange UDP Issue
The working UDP class:
vb Code:
Public Class UDPCommon
Implements IDisposable
'The UDP listening components.
Private WithEvents locUDP As System.Net.Sockets.Socket
Private WithEvents locUDPOut As System.Net.Sockets.UdpClient
Private ThreadListen As New Threading.Thread(AddressOf WaitForPending)
Private timeCount As Integer
Private broad As System.Net.IPEndPoint
'The dataQueue
Private dgList As Queue(Of DataGramReceiver)
'The result
Private disposedValue As Boolean = False ' To detect redundant calls
'Synchronization.
Private Shared myContext As System.Threading.SynchronizationContext
#Region "Constructors and Destructors"
Public Sub New()
'Set up the queue.
dgList = New Queue(Of DataGramReceiver)
locUDPOut = New Net.Sockets.UdpClient()
locUDPOut.Client.SetSocketOption(Net.Sockets.SocketOptionLevel.Socket, Net.Sockets.SocketOptionName.ReuseAddress, True)
locUDP = New Net.Sockets.Socket(Net.Sockets.AddressFamily.InterNetwork, Net.Sockets.SocketType.Dgram, Net.Sockets.ProtocolType.Udp)
locUDP.SetSocketOption(Net.Sockets.SocketOptionLevel.Socket, Net.Sockets.SocketOptionName.ReuseAddress, True)
locUDP.Bind((New Net.IPEndPoint(Net.IPAddress.Any, 11000)))
locUDPOut.EnableBroadcast = True
broad = New System.Net.IPEndPoint(System.Net.IPAddress.Broadcast, 11000)
myContext = System.Threading.SynchronizationContext.Current
'Start listening.
ThreadListen.Name = "Listening Thread"
ThreadListen.Start()
End Sub
' IDisposable
Protected Overridable Sub Dispose(ByVal disposing As Boolean)
If Not Me.disposedValue Then
If disposing Then
ThreadListen.Abort()
locUDP.Close()
locUDPOut.Close()
End If
' TODO: free shared unmanaged resources
End If
Me.disposedValue = True
End Sub
#Region " IDisposable Support "
' This code added by Visual Basic to correctly implement the disposable pattern.
Public Sub Dispose() Implements IDisposable.Dispose
' Do not change this code. Put cleanup code in Dispose(ByVal disposing As Boolean) above.
Dispose(True)
GC.SuppressFinalize(Me)
End Sub
#End Region
#End Region
#Region "Properties and Events"
Public Event MessageIn()
Public ReadOnly Property MessageCount() As Integer
Get
Return dgList.Count
End Get
End Property
Public ReadOnly Property NextMessage() As DataGramReceiver
Get
Return dgList.Dequeue
End Get
End Property
#End Region
#Region "Public Functions"
'This will get the next DGR from the queue.
Public Function GetNext() As DataGramReceiver
Dim dgRet As New DataGramReceiver
Threading.Monitor.Enter(Me)
Try
If dgList.Count > 0 Then
dgRet = New DataGramReceiver(dgList.Dequeue)
End If
Catch ex As Exception
Windows.Forms.MessageBox.Show(ex.Message, "Error Getting DataGramReceiver", MessageBoxButtons.OK, MessageBoxIcon.Error)
Finally
Threading.Monitor.Exit(Me)
End Try
'It may be the default, or it may not, either way it is a good local copy in the calling thread, return it.
Return dgRet
End Function
'Empties the queue
Public Sub Flush()
Threading.Monitor.Enter(Me)
Try
dgList.Clear()
Finally
Threading.Monitor.Exit(Me)
End Try
End Sub
Public Sub SendMessage(ByVal btArr() As Byte, ByVal toWho As Byte, ByVal what As Byte)
Dim lBtArr(btArr.Length + 2) As Byte
lBtArr(0) = 99 'This is the OL ID.
lBtArr(1) = toWho
lBtArr(2) = what
System.Array.Copy(btArr, 0, lBtArr, 3, btArr.Length)
'NOTE: NEED TO CHECK THAT THIS IS DOING AS IT SHOULD.
locUDPOut.Send(lBtArr, lBtArr.Length, broad)
End Sub
#End Region
#Region "Private Functions"
Private Sub WaitForPending()
Dim bHolder(511) As Byte
Dim numIn As Integer
Dim dgRec As DataGramReceiver
Dim bTrans() As Byte
While True
If CBool(locUDP.Available) Then
numIn = locUDP.Receive(bHolder, 512, Net.Sockets.SocketFlags.None)
If numIn > 4 Then
If bHolder(0) <> 99 AndAlso bHolder(1) = 0 OrElse bHolder(1) = 100 Then
'Set the holder up to the right size.
ReDim bTrans(numIn)
Array.Copy(bHolder, 3, bTrans, 0, numIn - 3)
dgRec = New DataGramReceiver(128, bHolder(2), bTrans)
Add(dgRec)
End If
End If
End If
End While
End Sub
'Adds one DGR to the queue
Private Sub Add(ByVal dgR As DataGramReceiver)
'Set the monitor.
Threading.Monitor.Enter(Me)
Try
dgList.Enqueue(dgR) 'That's all that happens.
Finally
Threading.Monitor.Exit(Me)
End Try
myContext.Post(AddressOf GotData, Nothing)
End Sub
'This is invoked by the other thread.
Private Sub GotData(ByVal state As Object)
RaiseEvent MessageIn()
End Sub
#End Region
End Class
This class is not useful to anybody else as is, because much of the code is HIGHLY specific. However, the general style may well be of use to somebody. The items that should be noted for reuse of this class are these:
1) The port number is hardcoded in this example to 11000. It would make the class more generic to accept the port number as an argument to the constructor.
2) The whole bit about the DataGramReceiver may actually be of use to people, but if not, then pretty much everything other than the names of the functions and the constructor, may as well be deleted. The DataGramReceiver is a class with these pieces:
Byte(0): Who sent the data (every module needs its own ID number).
Byte(1): Who should receive the data (I have 0 being EVERYBODY).
Byte(2): The type of the message (see below)
The other part of the DataGramReceiver is an array of bytes. The consumer checks the type of the message to decide whether or not the byte array needs to be deserialized, and if so, what type of structure it should be deserialized into. By doing this, I allow for the transfer of any arbitrarily sized amount of information between modules throughout the LAN that this runs on.
My usual boring signature: Nothing
 
-
Feb 23rd, 2008, 05:15 PM
#5
Re: [RESOLVED] Strange UDP Issue
So its fixed? Good to hear. I wish I could've been of more help though..
-
Feb 23rd, 2008, 07:16 PM
#6
Re: [RESOLVED] Strange UDP Issue
By the way, don't use the class I posted unless you really want to, I am currently VERY close to completing one that doesn't use polling on the listening thread. The problem with polling, even in a background thread, is that it will use tons of CPU time, which sucks. The class posted above uses 100% of CPU time, and running multiple instances will lag even a good system, though perhaps not noticeably. I have currently written, and tested, a version that takes 0% of the CPU when no messages are being sent. It still requires a little optimization, and has only been tested in some situations, but I am running off to a party, and won't finish it up today. The above class is all I am posting, for now.
My usual boring signature: Nothing
 
-
Feb 24th, 2008, 12:36 PM
#7
Re: [RESOLVED] Strange UDP Issue
Here is a vastly improved, and pretty much self-contained UDP class that doesn't use polling. The major difference is that the listening thread blocks until a message is received, queues up the message, then starts a new listening thread on the same socket to await the next incoming message. Some internet posts suggest that a drawback of Receive is that it blocks, and will block forever without timing out. However, for a server type of behavior, that's entirely acceptable. The key is that the class be properly disposed so that the thread gets aborted.
I also improved the class to accept a port number in the constructor. The class only deals with broadcasts rather than specific peer-to-peer communication, but if you want to spread the message to multiple listeners, then broadcast is the way to go. A person might alter this to use multicast, but I saw no advantage in doing so for my purposes.
vb Code:
Public Class UDPCommon
Implements IDisposable
'The UDP listening components.
Private WithEvents locUDP As System.Net.Sockets.Socket
Private WithEvents locUDPOut As System.Net.Sockets.UdpClient
Private ThreadListen As Threading.Thread
Private timeCount As Integer
Private broad As System.Net.IPEndPoint
Private whoAmI As Byte
'The dataQueue
Private dgList As Queue(Of DataGramReceiver)
'The result
Private disposedValue As Boolean = False ' To detect redundant calls
'Synchronization.
Private Shared myContext As System.Threading.SynchronizationContext
#Region "Constructors and Destructors"
Public Sub New(ByVal myAdd As Byte, ByVal myPort As Integer)
'Set up the queue.
whoAmI = myAdd
dgList = New Queue(Of DataGramReceiver)
locUDPOut = New Net.Sockets.UdpClient()
locUDPOut.Client.SetSocketOption(Net.Sockets.SocketOptionLevel.Socket, Net.Sockets.SocketOptionName.ReuseAddress, True)
locUDP = New Net.Sockets.Socket(Net.Sockets.AddressFamily.InterNetwork, Net.Sockets.SocketType.Dgram, Net.Sockets.ProtocolType.Udp)
locUDP.SetSocketOption(Net.Sockets.SocketOptionLevel.Socket, Net.Sockets.SocketOptionName.ReuseAddress, True)
locUDP.Bind((New Net.IPEndPoint(Net.IPAddress.Any, myPort)))
locUDPOut.EnableBroadcast = True
broad = New System.Net.IPEndPoint(System.Net.IPAddress.Broadcast, myPort)
myContext = System.Threading.SynchronizationContext.Current
'Start listening.
ThreadListen = New Threading.Thread(AddressOf WaitForPending)
ThreadListen.Name = "Listening Thread"
ThreadListen.Start()
End Sub
' IDisposable
Protected Overridable Sub Dispose(ByVal disposing As Boolean)
If Not Me.disposedValue Then
If disposing Then
ThreadListen.Abort()
locUDP.Close()
locUDPOut.Close()
End If
' TODO: free shared unmanaged resources
End If
Me.disposedValue = True
End Sub
#Region " IDisposable Support "
' This code added by Visual Basic to correctly implement the disposable pattern.
Public Sub Dispose() Implements IDisposable.Dispose
' Do not change this code. Put cleanup code in Dispose(ByVal disposing As Boolean) above.
Dispose(True)
GC.SuppressFinalize(Me)
End Sub
#End Region
#End Region
#Region "Properties and Events"
Public Event MessageIn()
Public ReadOnly Property MessageCount() As Integer
Get
Return dgList.Count
End Get
End Property
Public ReadOnly Property NextMessage() As DataGramReceiver
Get
Return dgList.Dequeue
End Get
End Property
#End Region
#Region "Public Functions"
'This will get the next DGR from the queue.
Public Function GetNext() As DataGramReceiver
Dim dgRet As New DataGramReceiver
Threading.Monitor.Enter(Me)
Try
If dgList.Count > 0 Then
dgRet = New DataGramReceiver(dgList.Dequeue)
End If
Catch ex As Exception
Windows.Forms.MessageBox.Show(ex.Message, "Error Getting DataGramReceiver", MessageBoxButtons.OK, MessageBoxIcon.Error)
Finally
Threading.Monitor.Exit(Me)
End Try
'It may be the default, or it may not, either way it is a good local copy in the calling thread, return it.
Return dgRet
End Function
'Empties the queue
Public Sub Flush()
Threading.Monitor.Enter(Me)
Try
dgList.Clear()
Finally
Threading.Monitor.Exit(Me)
End Try
End Sub
Public Sub SendMessage(ByVal btArr() As Byte, ByVal toWho As Byte, ByVal what As Byte)
Dim lBtArr(btArr.Length + 2) As Byte
lBtArr(0) = whoAmI
lBtArr(1) = toWho
lBtArr(2) = what
System.Array.Copy(btArr, 0, lBtArr, 3, btArr.Length)
'NOTE: NEED TO CHECK THAT THIS IS DOING AS IT SHOULD.
locUDPOut.Send(lBtArr, lBtArr.Length, broad)
End Sub
#End Region
#Region "Private Functions"
Private Sub WaitForPending()
Try
Dim bHolder(511) As Byte
Dim numIn As Integer
Dim dgRec As DataGramReceiver
Dim bTrans() As Byte
numIn = locUDP.Receive(bHolder, 512, Net.Sockets.SocketFlags.None)
If numIn > 0 Then
If bHolder(0) <> whoAmI AndAlso bHolder(1) = 0 OrElse bHolder(1) = 100 Then
'Set the holder up to the right size.
ReDim bTrans(numIn)
Array.Copy(bHolder, 3, bTrans, 0, numIn - 3)
dgRec = New DataGramReceiver(bHolder(0), bHolder(2), bTrans)
Add(dgRec)
Else
myContext.Post(AddressOf TimedOut, Nothing)
End If
End If
Catch ex As System.Net.Sockets.SocketException
'Not actually doing anything here now.
Catch ex As Exception
'Not doing anything here.
Finally
myContext.Post(AddressOf TimedOut, Nothing)
End Try
End Sub
'Adds one DGR to the queue
Private Sub Add(ByVal dgR As DataGramReceiver)
'Set the monitor.
Threading.Monitor.Enter(Me)
Try
dgList.Enqueue(dgR) 'That's all that happens.
Finally
Threading.Monitor.Exit(Me)
End Try
myContext.Post(AddressOf GotData, Nothing)
End Sub
'This is invoked by the other thread.
Private Sub GotData(ByVal state As Object)
RaiseEvent MessageIn()
End Sub
Private Sub TimedOut(ByVal state As Object)
ThreadListen = New Threading.Thread(AddressOf WaitForPending)
ThreadListen.Start()
End Sub
#End Region
End Class
My usual boring signature: Nothing
 
-
Feb 24th, 2008, 12:40 PM
#8
Re: [RESOLVED] Strange UDP Issue
In addition, here's the DataGramReceiver class that I use with the UDP Class. If you are passing strings, like a chat program, then neither class will be particularly right for you, but if you are broadcasting data packets, then this will work with any kind of packet. For my purposes, I have a series of structures that I serialize into a byte array. The leading bytes are there such that a broadcast packet can be made specific.
These two classes are not truly generic, since I wrote them for a very specific purpose, but they may be of use to somebody.
vb Code:
'A class that holds a pretty generic datagram. This is nothing but a type and an array of bytes.
Public Class DataGramReceiver
Private mOrigin As Integer
Private mType As Integer
Private mBytes() As Byte
#Region "Constructors"
Public Sub New(ByVal origin As Integer, ByVal typ As Integer, ByVal byt() As Byte)
mOrigin = origin
mType = typ
'Size it.
ReDim mBytes(byt.GetUpperBound(0))
'Copy it. Don't want a shallow copy of byt() in mBytes.
Array.Copy(byt, mBytes, byt.Length)
End Sub
Public Sub New(ByVal dgR As DataGramReceiver)
mOrigin = dgR.mOrigin
mType = dgR.mType
ReDim mBytes(dgR.MaxBytes)
Array.Copy(dgR.GetByteArray, mBytes, dgR.MaxBytes + 1)
End Sub
'A default constructor that sets the datagram into a known good, but empty state.
Public Sub New()
mOrigin = 0
mType = -1
ReDim mBytes(0)
mBytes(0) = 0
End Sub
#End Region
#Region "Properties"
Public ReadOnly Property Type() As Integer
Get
Return mType
End Get
End Property
Public ReadOnly Property MaxBytes() As Integer
Get
Return mBytes.GetUpperBound(0)
End Get
End Property
'This returns the byte at the specified index, or -1 if the index is out of range.
Public ReadOnly Property GetByte(ByVal index As Integer) As Byte
Get
If index > 0 AndAlso index < mBytes.GetUpperBound(0) Then
Return mBytes(index)
Else
Return 0
End If
End Get
End Property
Public ReadOnly Property GetByteArray() As Byte()
Get
Return mBytes
End Get
End Property
Public ReadOnly Property Origin() As Integer
Get
Return mOrigin
End Get
End Property
#End Region
End Class
My usual boring signature: Nothing
 
Posting Permissions
- You may not post new threads
- You may not post replies
- You may not post attachments
- You may not edit your posts
-
Forum Rules
|
Click Here to Expand Forum to Full Width
|