C# .NET 4.0/4.5 UDP send issue

Started by
4 comments, last by Morxeton 11 years ago

Hello,

I am working on writing a network library in C# and originally had used the .NET 3.5 Framework. I recently decided to switch to .NET 4.5 but started running into an issue with sending UDP packets. What I'm running into is if UDP packets are sent too fast, the Socket.SendToAsync method completes with a SocketError of AddressFamilyNotSupported and the packets are never sent. If I switch the project to .NET 3.5, I never run into the issue no matter how hard I try to repeat it. This also can be reproduced in .NET 4.0.

I attached a project I put together to reproduce the issue. If you spam the "ClientSnd" or "ServerSnd" buttons you'll see the error occur. Switch the project to .NET 3.5 and spam all you want... no issues at all.

I haven't been able to find much useful information googling this issue. Any ideas?

EDIT (added code from the sample project demoing the issue):

Here's where the binds are happening for both the client and server:


                
byte[] clientBuffer = new byte[32768];                
byte[] serverBuffer = new byte[32768];
 
                
IPEndPoint clientLocalEndPoint = GetLocalIPEndPoint(0, AddressFamily.InterNetwork);                
IPEndPoint serverLocalEndPoint = GetLocalIPEndPoint(6337, AddressFamily.InterNetwork);
 
                
m_ClientSocket.ExclusiveAddressUse = true;                
m_ServerSocket.ExclusiveAddressUse = true;
                
m_ClientSocket.Bind(clientLocalEndPoint);                
m_ServerSocket.Bind(serverLocalEndPoint);
 
                
m_ClientSendArgs.RemoteEndPoint = GetRemoteIPEndPoint("127.0.0.1", 6337, AddressFamily.InterNetwork);                
m_ClientRecvArgs.RemoteEndPoint = m_ClientSocket.LocalEndPoint;
                
m_ServerSendArgs.RemoteEndPoint = GetRemoteIPEndPoint("127.0.0.1", ((IPEndPoint)m_ClientSocket.LocalEndPoint).Port, AddressFamily.InterNetwork);                
m_ServerRecvArgs.RemoteEndPoint = m_ServerSocket.LocalEndPoint;
 
                
m_ClientSendArgs.Completed += new EventHandler<SocketAsyncEventArgs>(OnClientCompletion);                
m_ClientRecvArgs.Completed += new EventHandler<SocketAsyncEventArgs>(OnClientCompletion);                
m_ServerSendArgs.Completed += new EventHandler<SocketAsyncEventArgs>(OnServerCompletion);                
m_ServerRecvArgs.Completed += new EventHandler<SocketAsyncEventArgs>(OnServerCompletion);
 
                
m_ClientRecvArgs.SetBuffer(clientBuffer, 0, clientBuffer.Length);                
m_ServerRecvArgs.SetBuffer(serverBuffer, 0, serverBuffer.Length);
 
                
ClientReceive();                
ServerReceive();
 

The GetRemoteIPEndPoint and GetLocalIPEndPoint methods:


private static IPEndPoint GetRemoteIPEndPoint(string address, int port, AddressFamily addressFamily)        
{            
	IPAddress[] ipAddresses = null;
            
	ipAddresses = Dns.GetHostAddresses(address);
	
	List<IPEndPoint> ipEndPointList = new List<IPEndPoint>();
 
	for (int i = 0; i < ipAddresses.Length; i++)            
	{                
		IPAddress ipAddress = ipAddresses;
 
		if (ipAddress.AddressFamily == addressFamily)                
		{                    
			IPEndPoint ipEndPoint = new IPEndPoint(ipAddress, port);

			ipEndPointList.Add(ipEndPoint);                
		}
	}

	return ipEndPointList.ToArray()[0];
}

private static IPEndPoint GetLocalIPEndPoint(int port, AddressFamily addressFamily)        
{            
	IPEndPoint localEndPoint = null;

	switch (addressFamily)            
	{                
		case AddressFamily.InterNetwork:                    
			{                        
				localEndPoint = new IPEndPoint(IPAddress.Any, port);

				break;                    
			}                
		case AddressFamily.InterNetworkV6:                    
			{                        
				localEndPoint = new IPEndPoint(IPAddress.IPv6Any, port);

				break;                    
			}
	}

	return localEndPoint;

} 

Since this happens regardless of who sends the data (client or server), I'll focus on the client being the sender:

Clicking the ClientSnd button:


private void Button_ClientSnd_Click(object sender, RoutedEventArgs e)        
{
	lock (SyncRoot)            
	{                
		byte[] buffer = Encoding.ASCII.GetBytes("Hello there.  Just testing.  Nothing to see here.  Move along.");

		m_ClientSendQueue.Enqueue(buffer);

		if (!m_ClientTransmitting)
		{                    
			m_ClientTransmitting = true;

			ClientSendBuffer();                
		}            
	}
} 

Sending methods for the client:


private void ClientSendBuffer()        
{
	lock (SyncRoot)            
	{                
		if (m_ClientSendQueue.Count > 0)                
		{                    
			byte[] buffer = m_ClientSendQueue.Dequeue();

			m_ClientSendArgs.SetBuffer(buffer, 0, buffer.Length);

			ClientSend();                
		}                
		else                
		{                    
			m_ClientTransmitting = false;                
		}            
	}
}
 
private void ClientSend()        
{
	if (!m_ClientSocket.SendToAsync(m_ClientSendArgs))            
	{                
		OnClientCompletion(this, m_ClientSendArgs);            
	}
} 

Completion callback for the client:


private void OnClientCompletion(object sender, SocketAsyncEventArgs e)        
{            
	SocketError socketError = e.SocketError;
 
	if (socketError != SocketError.Success)            
	{                
		ClientConsoleWrite("SocketError: {0}\r\n", socketError);            
	}

	switch (e.LastOperation)            
	{                
		case SocketAsyncOperation.SendTo:                    
			{                        
				if (socketError == SocketError.Success)                        
				{                            
					ClientConsoleWrite("Client message sent\r\n");                        
				}

				ClientSendBuffer();

				break;                    
			}                
		case SocketAsyncOperation.ReceiveFrom:                    
			{                        
				int bytesTransferred = e.BytesTransferred;

				byte[] buffer = new byte[bytesTransferred];

				Buffer.BlockCopy(e.Buffer, e.Offset, buffer, 0, bytesTransferred);

				string message = Encoding.ASCII.GetString(buffer);

				ClientConsoleWrite("Message received: {0}\r\n", message);

				ClientReceive();

				break;                    
			}            
	}
} 
Advertisement

I also posted this question to StackOverflow: http://stackoverflow.com/questions/15485999/c-sharp-net-4-0-4-5-udp-send-issue

Something I noticed (copied from my comments on StackOverflow):

I noticed that when it's being called from the UI is where it seems to happen. If I click the "ClientSnd" button then hold enter down to spam it, this issue occurs. If I create a while loop which sends the buffer 10,000 times, and click the "ClientSnd" button once (where the new while loop is located), it works fine. If I click the button again and send the buffer another 10,000 times, it fails.

Here's a screenshot from my sample project demoing the issue:

UDPTest-SocketErrorAddressFamilyNotSuppo

I found out what's causing this error. If I create a new SocketAsyncEventArgs before each send, I can't reproduce the error. If I reuse the same SocketAsyncEventArgs, I eventually run into this error. I verified that the args are used in a thread safe context and the only modification to it is the SetBuffer for each send call. These args are supposed to be reusable, and in fact for receiving I have no problems reusing them there. I also don't have any problems reusing them in .NET 3.5 for sending. Very strange that in 4.0 and 4.5 I can't reuse the args for sending. Sounds like a bug...

I should also note that this is NOT a problem for TCP on .NET 4.0 and 4.5. In fact, the TCP implementation I built is identical to the UDP implementation (as far as the structure of reusing the args and queueing data to be sent using locking for thread safety).

I figured this out. This issue is happening because the underlying buffer on the variable m_ClientSendArgs is constantly being changed using SetBuffer:


byte[] buffer = m_ClientSendQueue.Dequeue();

m_ClientSendArgs.SetBuffer(buffer, 0, buffer.Length); 

When I assigned a static buffer to it and used Buffer.BlockCopy, the issue went away:


byte[] buffer = m_ClientSendQueue.Dequeue();

Buffer.BlockCopy(buffer, 0, m_ClientSendBuffer, 0, buffer.Length);

m_ClientSendArgs.SetBuffer(0, buffer.Length); 

So I've been implementing it wrong all along. It's strange that it wasn't an issue on .NET 3.5, or an issue for TCP on .NET 4.0/4.5.

These args are supposed to be reusable

They are re-usable, but only after the send request that used the arguments has completed (or returned an error!)

If you re-use a SocketAsyncEventArgs before the first operation that it's used on completes, all kinds of bad behavior may occur, including you not sending the data you think you're sending.

If you keep one buffer per SocketAsyncEventArgs and keep track of completed send args in a queue where you can re-use them when next sending (and create a new buffer/args pair if the queue is empty) then your system will work correctly.
enum Bool { True, False, FileNotFound };

These args are supposed to be reusable

They are re-usable, but only after the send request that used the arguments has completed (or returned an error!)

If you re-use a SocketAsyncEventArgs before the first operation that it's used on completes, all kinds of bad behavior may occur, including you not sending the data you think you're sending.

If you keep one buffer per SocketAsyncEventArgs and keep track of completed send args in a queue where you can re-use them when next sending (and create a new buffer/args pair if the queue is empty) then your system will work correctly.

Right, I was implementing it wrong for sending but correctly for receiving. I have a class called SocketState that contains the socket and other important info pertaining to the connection. At the time the SocketState is created, args for both sending and receiving are retrieved from a Stack and assigned to that SocketState for the duration of its existence. They're reset and returned to the Stack on disposal. I used a dedicated buffer for receiving but for sending I was calling SetBuffer and changed the underlying buffer on each send call by setting the buffer to the byte array of data to be sent. It was done in a thread safe context using locking and a queuing mechanism so the next send would NOT execute until after the previous send's callback had completed. Surprisingly enough, this flawed implementation was NOT an issue on .NET 3.5 and was NOT an issue for TCP on .NET 4.0/4.5. Therefore, this is how it managed to elude me for a couple days... smile.png

Once I assigned a dedicated buffer for sending, and used Buffer.BlockCopy to copy the byte array to be sent to the buffer, it worked flawlessly.

This topic is closed to new replies.

Advertisement