• Announcements

    • khawk

      Download the Game Design and Indie Game Marketing Freebook   07/19/17

      GameDev.net and CRC Press have teamed up to bring a free ebook of content curated from top titles published by CRC Press. The freebook, Practices of Game Design & Indie Game Marketing, includes chapters from The Art of Game Design: A Book of Lenses, A Practical Guide to Indie Game Marketing, and An Architectural Approach to Level Design. The GameDev.net FreeBook is relevant to game designers, developers, and those interested in learning more about the challenges in game development. We know game development can be a tough discipline and business, so we picked several chapters from CRC Press titles that we thought would be of interest to you, the GameDev.net audience, in your journey to design, develop, and market your next game. The free ebook is available through CRC Press by clicking here. The Curated Books The Art of Game Design: A Book of Lenses, Second Edition, by Jesse Schell Presents 100+ sets of questions, or different lenses, for viewing a game’s design, encompassing diverse fields such as psychology, architecture, music, film, software engineering, theme park design, mathematics, anthropology, and more. Written by one of the world's top game designers, this book describes the deepest and most fundamental principles of game design, demonstrating how tactics used in board, card, and athletic games also work in video games. It provides practical instruction on creating world-class games that will be played again and again. View it here. A Practical Guide to Indie Game Marketing, by Joel Dreskin Marketing is an essential but too frequently overlooked or minimized component of the release plan for indie games. A Practical Guide to Indie Game Marketing provides you with the tools needed to build visibility and sell your indie games. With special focus on those developers with small budgets and limited staff and resources, this book is packed with tangible recommendations and techniques that you can put to use immediately. As a seasoned professional of the indie game arena, author Joel Dreskin gives you insight into practical, real-world experiences of marketing numerous successful games and also provides stories of the failures. View it here. An Architectural Approach to Level Design This is one of the first books to integrate architectural and spatial design theory with the field of level design. The book presents architectural techniques and theories for level designers to use in their own work. It connects architecture and level design in different ways that address the practical elements of how designers construct space and the experiential elements of how and why humans interact with this space. Throughout the text, readers learn skills for spatial layout, evoking emotion through gamespaces, and creating better levels through architectural theory. View it here. Learn more and download the ebook by clicking here. Did you know? GameDev.net and CRC Press also recently teamed up to bring GDNet+ Members up to a 20% discount on all CRC Press books. Learn more about this and other benefits here.
Sign in to follow this  
Followers 0
Morxeton

C# .NET 4.0/4.5 UDP send issue

5 posts in this topic

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[i];
 
		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;                    
			}            
	}
} 
Edited by Morxeton
0

Share this post


Link to post
Share on other sites

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

0

Share this post


Link to post
Share on other sites

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).

0

Share this post


Link to post
Share on other sites

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.

Edited by Morxeton
1

Share this post


Link to post
Share on other sites

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.
0

Share this post


Link to post
Share on other sites

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.

Edited by Morxeton
0

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!


Register a new account

Sign in

Already have an account? Sign in here.


Sign In Now
Sign in to follow this  
Followers 0