A Network Protocol for Movement (Part 2)

by Chris at 11:01 am

The Server Application

In Part 1 I discussed that the creation of the server a application that would accept connection requests and broadcast mob locations to other users. This article will explain in detail exactly how to set up the server, and you will be able to download a copy of my (somewhat messy) source code that performs the task.

Having not yet written a client application to perform tests against the server with, I came up with a nifty, simple method of testing that the server is doing what we expect. Because we are using standard TCP/IP, we can simply use the telnet program that comes with windows to simulate our client.

Problems with the initial design

While I was working though the server creation process, I noticed that there were a few problems with the protocol messages and structure. Here’s an updated table:

Byte - Message Type
   01 - LOC_Connect
      Int32     4 bytes   SessionID
   02 - LOC_AcceptConnection
      Int32     4 bytes   MobID
   03 - LOC_MobLocation
      Int32     4 bytes   MobID
      Vec3     24 bytes   Location
      Vec3     24 bytes   UpVector
      Vec3     24 bytes   RightVector
      Vec3     24 bytes	  Velocity
   04 - LOC_UpdateMobLocation
      Int32     4 bytes   MobID
      Vec3     24 bytes   Location
      Vec3     24 bytes   UpVector
      Vec3     24 bytes   RightVector
      Vec3     24 bytes	  Velocity

The main changes are the removal of the LOC_GetMobLocation. I changed the Velocity items to vectors, so that we could specify a velocity in all planes (x,y,z). Finally, I gave myself a slap across the forehead for thinking that Doubles were 64-byte values, they are in fact 64-bit, which is 8 bytes.

Drawing the User Interface

The user interface is fairly simple and looks like the screen below.

The Start / Stop button will call the server’s Start or Stop routine, depending on it’s current status.

The LstMessages window will show all of the log messages that the MovementServer class throws.

The MovementServer Class

Here’s a basic outline of the MovementServer class

public class MovementServer
{
	public delegate void LogEntryEventHandler(String Message);
	public event LogEntryEventHandler LogEntry;

	public int ServerStatus {get;}
	public void Start();
	public void Stop();
	public void Iterate();
}

Basically what happens when you start the application is as follows:

  1. A new MovementServer class is created
  2. Form1 allocates a handler for the LogEntry event
  3. Form1 creates a new Timer, and allocates it’s Tick event to an internal function
  4. Every time the Timer ticks, the Movement Server’s Iterate() function is called.

When the user clicks on the Start / Stop button for the first time, the MovementServer’s Start() function will be called. Here’s the code for this below.

IPEndPoint endPoint;

LogEntry("Server Starting");

// set up the internal variables
m_BufferedData = new Dictionary();
m_MobData = new Dictionary();
m_SocketMobs = new Dictionary();

// set up the connection to listen for new TCP connections
m_Listener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
m_Listener.NoDelay = true; // disable nagle algorithm
endPoint = new IPEndPoint(IPAddress.Any, ListeningPort);
m_Listener.Bind(endPoint);
m_Listener.Listen(ConnectionBacklog);

// add a log entry, and set the server status to connected
LogEntry("Listening on port " + ListeningPort.ToString());
m_Status = 1;

// create the client pool
m_ClientPool = new List();

This simply tells the socket to listen for new connections on the requested port, which in this case is 12654. Note the LogEntry routine being called. This will tell the UI to put another message on the LstMessages list.

The remaining work occurs in the Iterate() function, which is called at set intervals by the user interface.

First the Iterate() function will check if there are any new clients attempting to connect.

// check for new connections
// if there are new connections available, accept the
// connection and add it to the available list
IList list = new List();
list.Add(m_Listener);

// Socket.Select will check for incoming data on the list,
// and remove any items that do not have data available
Socket.Select(list, null, null, 100);
if (list.Contains(m_Listener))
{
	// accept the first available connection
	// Socket.Accept - Accepts the connection and
        //   creates a new socket that you can communicate
        //   with the client on
	newSocket = m_Listener.Accept();
	m_ClientPool.Add(newSocket);

	// get the client address and post a log entry
	clientAddress = ((IPEndPoint)newSocket.RemoteEndPoint).Address.ToString();
	LogEntry(clientAddress + " Connected");

	// select again, to check if there are any further connections
	Socket.Select(list, null, null, 100);
}

Next, Iterate() checks for any new data on the sockets in the client pool

// check for new data on existing connections
if (m_ClientPool.Count > 0)
{
	// copy the client pool list
	list = new List();
	foreach (Socket client in m_ClientPool) list.Add(client);

	// select the items with data available
	Socket.Select(list, null, null, 100);
	foreach (Socket client in list)
	{
		buffer = new byte[client.Available];
		if (client.Connected)
		{
			try
			{
				client.Receive(buffer);

				// process the new buffer
				ProcessBuffer(client, buffer);
			}
			catch (Exception)
			{
			}
		}
	}
}

You can see that the ProcessBuffer() function has been called at this point, this function will determine which message has been called, and process it as required. Download the source code for more details.

Running some tests

Testing the LOC_Connect message:

  1. Open telnet using “telnet localhost 12654″
  2. Type the following into the screen that is shown: (Ctrl + A)TEST.
  3. This will result in the following bytes being sent to the server 0×01 0×54 0×45 0×53 0×54
  4. The server should now respond with some cryptic binary gobbledegook, and show a message in it’s log.

Next, connect another client using the same steps above. Remember to keep both telnet clients open so that we can test the broadcast functionality as well. You should recieve some additional gobbledegook on this one. The server is just sending you the current location of all mobs, so you should be recieving around double the amount.

Now, we want to test sending a message to update the mob location.

  1. Using one of the open telnet clients, send the following message: (Ctrl + D)(100 other bytes)
  2. This will send a LOC_UpdateMobLocation message, which should then be broadcast to all other users.

And to that I would say TEST_SUCCESSFUL.

ToDo List

Let me say again that I am very green at the whole network programming thing, which is part of the reason for this blog entry. If I were to revisit this server application, I would have a look at doing the following things:

  • Handle Client disconnects better, rather than just ignoring them
  • Send a message to the clients when someone disconnects, so that they know to remove a mob
  • Perform more checks on the api calls to ensure that there are no errors, such as socket already in use
  • Allow users to specify the port that they want to run the server on

I’m sure that there are other things that could be improved on, but this seems like enough for now.

Source Code: Download