Sign in to follow this  
  • entries
    4
  • comments
    4
  • views
    3668

UDP Server development begins

Sign in to follow this  

929 views

While I wait for some additional guidance on animations, and get some more experience with the artistic side of things, I decided to start working on my server-side code. The game is a social avatar styled game, similar to Second Life, but not quiet as large and done all in 2d with anime styled chibi characters.

The server will be written in .Net for no other reason other than that's what I'm comfortable with. I'm hoping the learn enough about UDP communications in a language i'm experienced with, before moving over to writing UDP client side code in a language I'm not experienced with (C++).

The server has a simple interface defined for now that holds the data that must be sent to the client over the socket.public interface IMessage{ byte[] GetMessage();}
This lets me implement the interface on multiple classes, with each containing the data that represents their message payload, and allowing the server to not care about what the payload is. It just needs the collection of bytes to send. An example of a message would be sending the updated X/Y coordinates of a character in the world to the client.public class LocationUpdateMessage : IMessage{ public LocationUpdateMessage(int x, int y, int userId) { this.UserId = userId; this.X = x; this.Y = y; } public int UserId { get; private set; } public int X { get; private set; } public int Y { get; private set; } public byte[] GetMessage() { using (var stream = new MemoryStream ()) { var writer = new BinaryWriter (stream); // TODO: Build a header object instead of sending along the message type here. writer.Write (this.GetType().Name); // Package up our location writer.Write(X); writer.Write (Y); writer.Write (UserId); // Get the bytes out of the writer. stream.Seek (0, SeekOrigin.Begin); byte[] data = new byte[stream.Length]; stream.Read (data, 0, stream.Length); return data; } } }
The server can listen to requests, update the characters X/Y location and then push the location update message down to any client that needs to be told of this change.


My very basic server only supports a single client at the moment. I spent the evening mostly just researching UDP, talking with a friend of mine about his experiences with UDP and binary serialization, and then threw this together really quick.public class Server{ private readonly UdpClient client; private readonly IPEndPoint destinationClient; public Server () { this.destinationClient = new IPEndPoint (IPAddress.Parse ("127.0.0.1"), 3000); this.client = new UdpClient (this.destinationClient, 3000); client.Receive (ref destinationClient); } public TransmissionInfo SendMessage (IMessage message) { byte[] data = message.GetMessage (); this.client.Send (data, this.destinationClient); return new TransmissionInfo (data, destinationClient); }}
This lets me very easily spin up a new instance of the location update message and send it to a client.server.SendMessage(new LocationUpdateMessage(253, 251, 6));
Obviously it'll have to get re-worked some to store a collection of destinations that represent all of the clients, handle a client time-out, and being given a character object that can be mapped to an IPEndPoint for publishing.


My next bit of work that I want to do is wire this up in a UI, that lets easily view a list of connected EndPoints, send them messages if I want and view stats such as their last transmission, packet sizes, time spent transmitting etc to help troubleshoot issues and performance. I wrote the server-side code tonight in OS X using Xamarin. I've not written a UI based app in OS X yet, so I might just go ahead and spin up my Windows 10 VM and build it out in WPF initially just to get me going. I like to follow the MVVM pattern, so if I want to move the tool over to a OS X UI, I can move most of my code without issue.

Until next time.

Sign in to follow this  


4 Comments


Recommended Comments

Hey, looks good.

Using UDP isn't hard, but there are some nasty traps. Some thoughts you could consider developing your UDP framework:

1. UDP is not reliable, that is , UDP messages can never arrive and can be arriving in a different order. Use a sequence number to identify wrong order and missing messages.

2. Think about defining channels. A channel is a communication between one or multiple PCs. E.g. a chat channel, a lobby channel, a in-game channel, a setup channel.

3. Develop a reliable protocol which will be associated with a channel. There are messages like setting up a new player which must arrive and arrive in the correct order, whereas other data like position can be skipped/ignored if a newer position message arrives. You can base this on the sequence number. The server holds the last X messages and discard them only, when the client acknoledged, that it received all messages up to sequence number Y. If the client detects a sequence number gap, it sends a request to resend message Z. This way you can build up a reliable communication based on UDP.

4. View UDP like a "stream" (which can be out-of-order), so you need to send a header with each message. Sometimes only parts of a message or multiple messages at once arrive. The header should contain at least a message Id, sequence number and the length of the message. When reading a UDP message, expect to get only some parts of it, so sometimes you need to wait multiple frames until a message is completely present.

5. Messages can be split up when send over the internet, you should try to avoid splitting them up, it is not funny to get only one half of a message out of order. Try to limit your messages to the minimum MTU size which could be really low. I would start with something like 512 bytes. If you need larger messages, split them up in multiple game messages instead.

6. Instead of sending each message separately, try to batch them up until you reach either a timeout or the target message size (e.g. 512 bytes).

Share this comment


Link to comment

1. UDP is not reliable, that is , UDP messages can never arrive and can be arriving in a different order. Use a sequence number to identify wrong order and missing messages.

 

Had not thought of a sequence number, that's a good point thanks.

 

2. Think about defining channels. A channel is a communication between one or multiple PCs. E.g. a chat channel, a lobby channel, a in-game channel, a setup channel.

 

 

I've got that concept under development as "routes". I like the Channel semantic better, thanks.

 

4. View UDP like a "stream" (which can be out-of-order), so you need to send a header with each message. Sometimes only parts of a message or multiple messages at once arrive. The header should contain at least a message Id, sequence number and the length of the message. When reading a UDP message, expect to get only some parts of it, so sometimes you need to wait multiple frames until a message is completely present.

 

 

This is interesting, I was under the impression it was all or nothing with UDP. I'd either receive the entire packet, not malformed, or no packet at all. I did realize there would be packet loss, but wasn't aware i'd loose bytes out of the individual packets themselves. How would one go about ensuring the data isn't malformed without attempting to deserialize the bytes first?

 

What is the intent of a message id? Is that just so I can know how to handle the incoming message (what it's intent is)? I'm currently doing that by sending the name of the message from the game, that maps back to a strongly typed object on the server. I'm sending it as part of the header so when the server parses the header, it identifies the message type, creates an instance of the Type that represents it and then reads the contents of the packet into that Type for me to interact with. Seems to run quickly, I'm able to receive the datagram, parse the header and create the message instance in 1.9 Ticks.
 

6. Instead of sending each message separately, try to batch them up until you reach either a timeout or the target message size (e.g. 512 bytes).

 

 

Thanks, another good point. I was contemplating that today but wasn't sure about the batching. I'm currently using 256 bytes as my message size, i'm pretty sure that number will have to be increased as I develop it.

Share this comment


Link to comment

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