UDP Pong - Server
Before jumping in, I want to note that none of the game logic lives in
PongServer.cs. That code is in
Arena.cs. Likewise, if you see a class called
Arena, just know that the code for it will be on the next page.
This is another data structure but this one only lives on the Server. It contains information about a connected Client and their current state.
Those three fields that have to deal with time might look a little confusing, let me explain them a bit better:
LastPacketReceivedTime- This is to denote the time that we received a
Packetfrom a Client. It is taken using a call to
DateTime.Now, in the function
PongServer._networkRun(). It's used to check for timeouts.
LastPacketSentTime- This marks the last time that we've sent a
Packetto a Client. It's recorded in the
Arena._sendTo()method. It's used to make sure that we don't spam a Client with data too often.
LastPacketReceivedTimestamp- Unlike the other two, this one is measured from Client time and not Server time. It's taken from the
Packet.Timestampfield. When setting it, it should only be set if the value is higher than it was before. It's used to make sure we only use the latest information from a Client. Because in UDP some later sent datagrams might arrive before earlier sent ones, this will discard those old ones.
The duties of
PongServer are only to read in data from the network, write it out to Clients, and manage the
Alright, lemme explain what's going on here:
At the top of the class, we have quite a few ConcurrentQueues and ConcurrentDictionaries. The first three are used for message processing. Just to note,
_activeArenas is being used as HashSet seeing as that collection doesn't have a concurrent counterpart.
The constructor's only job is to set the
Port number and initialize the underlying
UdpClient. It's set to listen in for all incoming IPv4 connections.
Start() is used to mark that our Server can start handling Clients.
Shutdown() is the opposite. If the Server was already running, it will tell any active
Arenas to stop their games.
Close() will clean up our resources.
_addNewAreana() is a small helper function that will startup a new
Arena instance (also starts another
Thread) and place it in the
NotifyDone() is only called by an
Arena. It tells the
PongServer to remove any of its connected Clients from the
_playerToArenaMap and itself from the
Run() is the main method of the
PongServer. It will only execute it's code if
Start() has been previously called. At first, it will spin up a new
Thread that will handle all of our incoming and outgoing messages (over the network) and instantiate the first
Arena. In it's
while(running) loop, it will check if we have received a new
- If the
RequestJoin, then it will add it to the next active
Arena. In the case that the
_nextArenais full (
false), it will then call
_addArena()and try to add it again.
- But if the
Packetis not a
RequestJoin, it will try to dispatch it to which
- If you're wondering what happens if the
Senderis not in any active
Arena, the message will be discarded.
- If you're wondering what happens if the
At the end of that loop
Thread.Sleep() is called to save on CPU resources and a check is made to see if the server is still running or not.
_networkRun() is another important looping function. It is run in it's own thread (the
_networkThread). While we haven't told the
PongServer to stop, it will check to see if there are any datagrams waiting for us and write out any
Packets that we've queued (both from
_outgoingMessages and _
sendByPacketTo). In the event there wasn't anything to send or receive, we have the function take a tiny nap. When were done listening for messages (i.e. the
PongServer was told to shutdown), it will get all active
Arenas to stop their
Threads. After that if there still are any connected Clients it will send a
ByePacket to them.
SendBye() are nearly identical in functionality; queuing up messages to the Clients. The only exception is that the later function is used to disconnect Clients.
At the bottom of this file we have some code that is for program execution. In the
Main() function we create the
PongServer instance and add an interrupt handler for when the user presses Ctrl-C in their consoles.