Game development is one of the most common areas for home developers to have a go at, which is exactly what I am doing. However, though it is fairly easy to set up a simple, standalone game in XNA, it is very difficult to set up an online game. The reason is that XNA only supports the Windows Live system for network gaming. This is a fine system to use, but only if you are developing for an XBox and have one yourself. So far as I can tell, it is very difficult to get a Windows Live account working without an XBox and to use the network gaming classes is not as easy as it should be. So for these reasons, I decided to make my own Network Gaming Library that would allow me to produce reliable, fast multiplayer games.
Using the Code
The code contains 4 main classes. The most simple class is
GlobalMethods which in short, has two
static methods for converting an object to and from a byte array. Note, if the object is a class you have defined, it must have the
[Serializable] attribute. The serializer methods use
MemoryStream to convert the object/byte array. (
BinaryFormatter is from
The 3 remaining classes are the interesting ones. They are:
GameServer is a server that allows a maximum number of connections to it and allows clients to send data through it to other players or just to the server itself if specified.
ManagerServer does the same thing, but also has the built-in ability to keep track of
GameServer IP addresses and then pass those out to clients who request them.
Client has two modes,
GameClient should be used when the client is connecting to a
ManagerClient should be used when the client is connecting to a
ManagerServer. This is because the base message object types used to transfer data quickly and efficiently between the
Client and two types of server are different.
I will explain the classes by going through the test application line by line and explaining what the different method calls do and the code they run.
To begin, there are a number of variables defined at the start of the console test application. They are:
static int GSMaxConnections = 2;
static int SMMaxConnections = 100;
static int GSPort = 56789;
static int MSPort = 56788;
static int LoopDelay = 500;
public static int BufferSize = 9999999;
static bool Terminate = false;
static List<IPAddress> ServerAddresses = null;
static ManualResetEvent AddressesReceivedEvent = new ManualResetEvent(false);
static Client MSClient2 = new Client(ClientModes.ManagerClient,
static ManagerServer TheMS = new ManagerServer(
GSMaxConnections defines the maximum number of connections that the
GameServer (created later) will be allowed to accept.
SMMaxConnections defines the maximum number of connections that the
ManagerServer (created later) will be allowed to accept.
GSPort is the port number the
GameServer will listen on.
MSPort is the port number the
ManagerServer will listen on.
LoopDelay is used later to define how quickly a
while loop should send test messages. It is set in milliseconds. The minimum number it should be set to is
10 if sending small messages or
50 if sending larger amounts of data to ensure maximum reliability. If you find that messages appear to be lost, then send messages less frequently and you will probably find they get through.
BufferSize is the maximum size of the send and receive buffers, used by the underlying sockets can be. The maximum this number can is 9999999 as any bigger and the underlying socket throws an error.
Terminate is used to stop the test message while loop (see later).
ServerAddresses is used to store the list of addresses returned by the
AddressesRecievedEvent is used to wait for the addresses to be received (see later).
MSClient2 is a
ManagerServerClient that is used to get the server addresses and remove the
GameServer from the
ManagerServer address list.
TheMS is the
ManagerServer. It is used to keep track of existing
GameServers and to display how many
GameServers it knows about.
All the main code runs in
Main which is also the entry point for the application (as required by C# standards).
Thread TerminateThread = new Thread(new ThreadStart(TerminateThreadRun));
These first two lines of code start a background thread that waits for the user to press the escape key and then sets
true. This allows the user to exit at any suitable time.
Continue = TheMS.Start(MSPort, SMMaxConnections);
This calls the
Start methods on the
ManagerServer. This method starts the
ManagerServer listening for connections. The code it calls is:
OK = Initialised = Initialise(Port);
MaxConnections = MaximumConnections;
This initialises the
ManagerServer which creates a new
TcpListener that listens for TCP connections on the specified port. It sets up the maximum connections the
ManagerServer is allowed to accept, starts the
TcpListener and finally calls
BeginAcceptSocket which allows the
ManagerServer to asynchronously accept connections.
The next lines of code in Main are:
Client MSClient1 = new Client(ClientModes.ManagerClient,
MSClient1.OnErrorMessage += new ErrorMessageDelegate(MSClient_OnErrorMessage);
MSClient1.OnDisconnected += new Client.DisconnectDelegate(MSClient_OnDisconnected);
Continue = MSClient1.Connect(MSPort, Dns.GetHostAddresses(Dns.GetHostName()).First());
This creates a new
Client that is set to
ManagerClient mode, i.e., it will be connected to a
ManagerServer. If the mode is incorrect for the server type you try to connect to, the connection will still be accepted but most if not all messages sent across the connection will be unhandled at the other end. The key line of code here is
Connect that attempts to connect the client to the local machine (which the
ManagerServer is running on) on the
MSPort (defined earlier). The code it calls looks like:
TheSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
byte Buffer = new byte[BufferSize];
TheSocket.BeginReceive(Buffer, 0, Buffer.Length,
SocketFlags.None, ReceiveCallback, Buffer);
This creates a new socket which is set to use the TCP protocol and is set to type stream, meaning data will be sent both ways across the connection the same way you would with a stream. It then tries to connect the socket to the specified IP address on the specified socket. The code does not need to check if the socket is connected afterwards because if the socket fails to connect, an error is thrown which is handled by a
catch block. Finally,
BeginReceive is called which allows the client to receive data asynchronously.
MSClient connects, it then sends an
AddServer request which is a default
ManagerServerMessageType which causes the
ManagerServer to add the IP address of the client it receives this type of message from, to its list of servers.
Next, the code sets up a
GameServer. In theory, the above line of code should be called after the game server is set up but for testing purposes, it is easier to do it this way round. The game server setup code looks like:
GameServer.BufferSize = BufferSize;
GameServer TheGS = new GameServer(new MaxConnectionsDelegate(TheGS_OnMaxConnections),
TheGS.MessageHiding = false;
TheGS.OnError += new ErrorMessageDelegate(TheGS_OnError);
TheGS.OnClientDisconnect += new GameServer.ClientDisconnectDelegate(
Note that the
BufferSize is a
static variable that can be set anywhere during the code. However, the
GameServer BufferSize and
Client BufferSize should be the same (providing no data greater than the smaller buffer size is sent across the connection, no error occurs as the buffer size is a maximum size) and also the
ManagerServer BufferSize must be the same as the
if (TheGS.Start(GSPort, GSMaxConnections))
The above line of code does the same thing as
Start except that it starts the
GameServer listening on the
GSPort not the
MSPort (defined earlier).
Client.BufferSize = BufferSize;
MSClient2.OnErrorMessage += new ErrorMessageDelegate(MSClient_OnErrorMessage);
MSClient2.OnDisconnected += new Client.DisconnectDelegate(MSClient_OnDisconnected);
if (MSClient2.Connect(MSPort, Dns.GetHostAddresses(Dns.GetHostName()).First()))
The above code again does the same as the previous code for
MSClient1 except note the first line now defines the
Client BufferSize. This should, ideally, be defined earlier but in this simple test application it makes no difference to the running or outcome of the program.
MSClient2.MSSend(null, ManagerServerMessageType.GetServers, false);
The above two lines of code send a request to the
ManagerServer for its list of server addresses and then waits for them to be sent back. It will wait a maximum of 1000 milliseconds (1 second) for the response. This timeout ensures that the program doesn't wait indefinitely if anything goes wrong. Here we must jump to
ManagerServerMessageObject TheClass = (ManagerServerMessageObject)
if (TheClass != null)
if (TheClass.TheMessageType == ManagerServerMessageType.ServersResponse)
ServerAddresses = (List<IPAddress>)(GlobalMethods.FromBytes(TheClass.TheBytes));
if (ServerAddresses != null)
Console.WriteLine("Server addresses received.
Addresses count : " + ServerAddresses.Count);
Console.WriteLine("Server addresses received. Addresses were null!");
The above code handles any message received by either of the
MSClients. It gets the
ManagerServerMessageObject (which is the underlying message object) using the
FromBytes and then tests to see if it is
null. (N.B. It would only be
null if the
FromBytes encountered an error.) The code then tests to see if the message was a
ServersResponse, i.e. the data is a list of server IP addresses. If it is, it gets the list, sets
ServerAddresses and then releases the thread that is waiting on the
AddressesReceivedEvent, i.e. the main thread.
Finally, the code sets up two game
Clients and repeatedly sends test messages over one of the connections.
Client GSClient1 = new Client(ClientModes.GameClient,
GSClient1.OnErrorMessage += new ErrorMessageDelegate(GSClient_OnErrorMessage);
GSClient1.OnDisconnected += new Client.DisconnectDelegate(GSClient_OnDisconnected);
bool Connected = GSClient1.Connect(GSPort, ServerAddresses);
Console.WriteLine("Attempted to connect game server client 1.
Connected : " + Connected.ToString());
Client GSClient2 = new Client(ClientModes.GameClient,
GSClient2.OnErrorMessage += new ErrorMessageDelegate(GSClient_OnErrorMessage);
GSClient2.OnDisconnected += new Client.DisconnectDelegate(GSClient_OnDisconnected);
Connected = GSClient2.Connect(GSPort, ServerAddresses);
Console.WriteLine("Attempted to connect game server client 2.
Connected : " + Connected.ToString());
DataClass NewDataClass = new DataClass
("Your random number is : ", new Random().Next());
GSClient1.GSSend(GlobalMethods.ToBytes(NewDataClass), false, false);
The very last thing the code does is call disconnect on the two
GameServer Clients, disconnects
MSClient1 has already been disconnected) and stops the
ManagerServer. These are done using
The output of the program looks like this (number of test messages depends on when you decide to exit!) :
Points of Interest
This being the second time I have made a networking library, I have learnt from past errors, i.e., this library is more reliable, faster and more flexible!
I have added two demo applications that run together to demonstrate the network structure I intended the classes to be used for. The first application is the Manager Server console application that simply displays what the
ManagerServer class does. The second is a Client console application that demonstrates what you would do with a game. In brief, the program loads, connects to the manager server, gets the list of existing servers, if the list has no servers in it, it becomes a server, if it does have some IP addresses in it, it tries to connect to each IP address in turn until it gets a connection. If no connection can be made, the application ends. If the game becomes a game server, it tells the manager server and then connects to the game server it has just created, i.e., itself. If the program is now connected to a game server, it will allow the user to send test messages to all other clients of the game server. Both programs should be exited by pressing the Escape key as this closes all connections and cleans-up without the risk of leaving open connections behind. The image below shows the output of an instance of a manager server, and two instances of the console game application. One instance is running as the game server and game client, the other just as a game client.
New update: I have updated the code so it is more reliable. I found that if I sent messages immediately after each other, they were lost. At first, I thought the packets were being lost but I was assured by several sources that this wasn't possible. I therefore came to the conclusion that as the packets were being sent immediately after each other, TCP was probably grouping the packets into one. I have therefore updated my code so that it can separate the messages when they are received. I did this by placing a six digit number at the front of every message that contained how many bytes long the message was. This allows my program to separate out the messages at the other end. The code changes are compatible with the previous versions of the library, so please re-download the files if you have already started using it.
New update: I have added
OnConnected events to
Client classes. For
GameServer, the event is fired when a client connects to them. For client, the event is fired immediately after it connects.