TCP Games - Async/Multithreaded Server
Okay, this source code is the largest chunk of the entire two projects. It has async calls all over the place. There actually isn't too much complex multithreading here.
Don't forget to add the Newtonsoft.Json package to your TcpGamesServer
project via NuGet.
The Games Interface
This is the "Games," server, so we want to support multiple text based games. (Though I only implemented one for this part of the tutorial series. : P) Here is the game interface that all games on the server should implement:
Name
is some meta-data of what the game should be called. RequiredPlayers
tells the server how many players this game needs before starting. The AddPlayer()
function is used by the server to "give," a player to a Game. It should return true
if the player was successfully added to the game. The server should only add players before a Game has been started. DisconnectClient()
is used to notify a Game if a client has been detected by the server to have disconnected. With this information the Game might choose to end early. The final function Run()
is where the main game loop should go, and this method will be run in its own thread. It's the job of Run()
to handle any new packets coming in from clients, and to check for possible disconnects too.
The Server
There is quite a bit going on here. The explanation is at the end. You'll notice the class GuessMyNumberGame
is mentioned, we'll be adding that in the next section, so just pretends it's already there.
Up at the top we have all of our member data. The things to note are the _waitingLobby
and _nextGame
. _waitingLobby
is a List that we are treating as queue, from the name of it you can safely assume its where we store clients that have connected but aren't in any games right now. _nextGame
is where we place the next game that we will run once we have enough clients.
The constructor doesn't doo much except initialize some data and create a TcpListener
. Shutdown()
is a method that we will call in an interrupt handler to well, shutdown the server.
Run()
is the heart of the server. In the beginning we turn on the listener and create a GuessMyNumberGame
. If there is a new connection pending, we hand that off to _handleNewConnection()
. That method is run as a Task
, so its asynchronous. If we have enough players waiting in the lobby then we pull out that many and try to hand them over to the _nextGame
. In case a Game doesn't want to accept a client (I dunno, maybe they don't like their IP), we put it back onto the end of the lobby. Once all the players have been added, the game is started in a new thread and we setup another GuessMyNumberGame
to be queued up. After that, we have a foreach
loop check all of the clients in the lobby if they might have disconnected.
Once execution has exited while(Running)
, we make sure that any new connection Tasks have completed. We also kill any Games that may be running with the Thread.Abort()
method. After that we disconnect all of the clients saying that we are shutting down the server. Lastly we stop listening for new connections.
_handleNewConnection()
will asynchronously accept any clients and then put them into into the waiting area. We also tell them "hi.".
DisconnectClient()
is a method that is used to gracefully end a connection with a client. It will notify any Games that the client is being disconnected by the server (the game will chose how to handle the disconnected client). We have a call to Thread.Sleep()
because it's possible that the bye
Packet
might have been sent, then the FIN/ACK from the server, but before the client could process it. This would then cause the client to think that there was an ungraceful disconnect. Sleeping for 100ms gives the client enough time to process the message and close before its resources are cleaned up on the server side. (Yes, this might be classified as a race condition, I'll address it in the Recap section)
HandleDisconnectedClient()
is a little helper to just remove the client from any collections (e.g. the _waitingLobby
) and free their resources on the server.
SendPacket()
is another async
method that will transmit a Packet
over the network. It handles all of the byte array formatting for us. ReceivePacket()
is like that in reverse; it gets a packet. If there is no packet readily available from the requested client, it will return null
.
After the transmission methods we have our TcpClient
helpers. IsDisconnected()
was described in the intro to this section. _cleanupClient()
closes some resources of a TcpClient
.
And at the end of the class, we just have a little bit of code for program execution. There is an interrupt handler to catch the Ctrl-C presses to quit the server.
Next, we're going to implement the GuessMyNumberGame
.