I am trying to port some NET Framework 4.8 asynchronous UDP listener code to NET 5. The code lives in a Windows Forms app that receives incoming UDP messages and displays them in a simple ListBox control. The code has been running fine on NET Framework for a year or two without failures.
The problem is that it will not run reliably on NET 5. The display window shows 1 or 2 incoming UDP messages and then stops displaying anything after that. Sometimes it won't even display the first incoming UDP message.
My theory (not backed by any real evidence) is that something goes wrong with the async listener events, the app does not start a new listening operation in the callback after receiving a packet, and thus stops listening on the port.
I have scanned the net for UDP async examples that are significantly different than my code, searched through the hot examples site, read the Microsoft TCP examples (there are lots of TCP examples around), and searched here on SO, but all without success.
The biggest change I tried was to change the simple ReceiveFromAsync call to one that watched for the willRaiseEvent flag that might be returned, but that did not help either. Here is an example of the modified calling code that I tried without success.
var willRaiseEvent = _sockReceiver.ReceiveFromAsync(newRxArgs);
if (!willRaiseEvent) ProcessMyData(newRxArgs);
Another change I made was to allocate new SocketAsyncEventArgs in the callback method instead of reusing the old ones. That sometimes seemed to help and multiple log messages would be displayed before the app stopped displaying any more incoming UDP messages. (The code below is the original code and so does not show my debugging NET 5 modifications.)
Can anyone see what might be wrong/inadequate with the code below that has been working for so long, or what I might be missing on NET 5? Thank you.
UPDATE:
Continued debugging work has shown that the ReceiveFromAsync call in the callback method always returns false (indicating that it will not raise a receive completed event). And if it never raises an event, that would explain why no log messages are displayed.
Another disturbing thing is that the event arguments returned immediately by ReceiveFromAsync contain the trailing portion of the incoming message that was just processed, even if I allocate new SocketEventArgs and a new buffer for SetBuffer before calling ReceiveFromAsync. As far as I can tell, in the callback, the ReceiveFromAsync call thinks that a second receive IO event has actually occurred, when it has not. (I am in the debugger and have sent 1 packet only to the code.) Very strange.
I am hoping that someone who knows more about UDP ReceiveFromAsync can shed some light on what is happening, especially about the conditions under which ReceiveFromAsync should and should not return true or false.
public void UdpReceivePacket48() {
try {
// listen on the port from any IP address
_endPointReceiveFrom = new IPEndPoint(IPAddress.Any, Port);
_sockReceiver.Bind(_endPointReceiveFrom);
// make a localhost endpoint for the receive operation
var myReturnAddress = IPAddress.Parse(Localhost);
_endPointSendTo = new IPEndPoint(myReturnAddress, Port);
// fill out the event arguments and callback method
var rxEventArgs = new SocketAsyncEventArgs();
rxEventArgs.RemoteEndPoint = _endPointSendTo;
rxEventArgs.SetBuffer(new byte[_cbuffersize], 0, _cbuffersize);
rxEventArgs.Completed = UdpReceiveCallback48;
// initiate a receive operation
_sockReceiver.ReceiveFromAsync(rxEventArgs);
}
catch (Exception e) {
LogTheFailure();
throw;
}
}
void UdpReceiveCallback48(object sock, SocketAsyncEventArgs rxArgs) {
var bytesRx = rxArgs.BytesTransferred;
var textReceived = Encoding.ASCII.GetString(rxArgs.Buffer, 0, bytesRx);
Console.WriteLine($@"Text received: {textReceived}");
// clear the buffer before you start another receive operation
Array.Clear(rxArgs.Buffer, 0, rxArgs.Count);
// The event args are the original ones, so you can reuse them all.
((Socket) sock).ReceiveFromAsync(rxArgs);
// process the received data (not on the UI thread)
Program.ListBoxLog.AddToLog(textReceived);
}
CodePudding user response:
I am still not positively sure why the original code would not work on NET 5. In the end, I rewrote the callback method to 1) raise an event to process the incoming data by some non-UI thread other than the callback thread and 2) to loop and process packets until ReceiveFromAsync finally returned true. My new code is shown below.
Reusing the event args object or allocating a new one made no observable difference at all. I reused the same one for performance reasons rather than adding code for a pool of reusable objects.
Clearing the buffer in the event args object (in the original code) made no difference either (so I removed it as well).
The code below seems to work without failures for me on NET 5. I added a loop to handle bursts of high traffic. The loop spins until ReceiveFromAsync finally returns true to say that it will raise an event when the next packet is received.
private void
UdpReceiveCallback(object sock, SocketAsyncEventArgs rxArgs) {
// the SockReceiver is the original socket that initiated the receive
// the rxArgs are the same ones passed in by the original caller
if (rxArgs.SocketError == SocketError.Success) {
// fire an event for asynch processing elsewhere
var textData = ExtractData(rxArgs);
var eventData = new TextReceivedEventData(textData);
RaiseTextReceived(eventData);
// ReceiveFromAsync returns true if it will notify you in the future
var willRaiseEvent = ((Socket) sock).ReceiveFromAsync(rxArgs);
// if it returns false it means it will not raise a future event because
// it has received a packet already. Since the listener can get a burst
// of packets from apps on the localhost, the loop below hands the
// processing off to another thread until ReceiveFromAsync finally
// returns true and says that it will raise an event in the future
// when the next packet arrives.
while (! willRaiseEvent) {
// fire an event for asynch processing elsewhere
textData = ExtractData(rxArgs);
eventData = new TextReceivedEventData(textData);
RaiseTextReceived(eventData);
willRaiseEvent = ((Socket) sock).ReceiveFromAsync(rxArgs);
}
}
