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.
PlayerInfo
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 aPacket
from a Client. It is taken using a call toDateTime.Now
, in the functionPongServer._networkRun()
. It's used to check for timeouts.LastPacketSentTime
- This marks the last time that we've sent aPacket
to a Client. It's recorded in theArena._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 thePacket.Timestamp
field. 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.
PongServer
The duties of PongServer
are only to read in data from the network, write it out to Clients, and manage the Arena
s.
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 Arena
s 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 _nextArena
variable.
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 _activeArenas
dictionary.
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 NetworkMessage
.
- If the
Packet
is aRequestJoin
, then it will add it to the next activeArena
. In the case that the_nextArena
is full (TryAdd()
returnsfalse
), it will then call_addArena()
and try to add it again. - But if the
Packet
is not aRequestJoin
, it will try to dispatch it to whichArena
theSender
is in.- If you're wondering what happens if the
Sender
is not in any activeArena
, 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 Packet
s 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 Arena
s to stop their Thread
s. After that if there still are any connected Clients it will send a ByePacket
to them.
SendPacket()
and 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.