🎉 Celebrating 25 Years of GameDev.net! 🎉

Not many can claim 25 years on the Internet! Join us in celebrating this milestone. Learn more about our history, and thank you for being a part of our community!

A Story of Some Servers and Their Game.

posted in Septopus for project Unsettled World
Published October 04, 2018
C#
Advertisement

Unsettled World, makes a great title I think, but without a server, it's just a terribly lonely place indeed.

I've written many servers over the years and clients to go with them, and in lots of different languages(never before for games).  However,I've never been one to hang onto the proper semantics of communicating how a program works so, stick in there and I'll do my best. ;)  Please feel free to chime in if you see something technical that could be fixed or if it just looks at you funny, you can tell me that too.  I'm good with it. 

Architecture:

Multiple, functionally segmented, server applications / To many, identically functional clients.  To sum it up..  haha  It's important to say that all servers maintain an authoritative role in all matters of game play.  If the Avatar Server hasn't authorized that item in your inventory, it won't be there.  If the Economy Server has a 0.00 in your bank account, the game client will reflect that.

Avatar Server(Functional in all listed categories): C# UDP server application, game time keeper, controls authentication, resource access, player inventories, land ownership and mining claim ownership and is the "echo point" for network player data such as positioning and appearance data.

Economy Server(Currently in ACTIVE Dev): C# UDP server application, Communicates with the Avatar Server to coordinate time synchronization, and to update its list of active player id#s and their encryption keys. Its purpose is to house all the functionality that one would attribute to a game economy(aside from physical resource access).  Banking, Auctions and a Resource Stock Market.  My Player1 checked the balance on his bank account for the first time just yesterday in fact.  He was pleased to finally see the 2000.00 Settler Credits he was promised months ago.  lol.

NPC Server(s)(Currently Completely Imaginary): C# UDP server application that communicates the positions and coordinates the movements of NPCs that are instantiated into the world by their NPC Client applications.

NPC Client(s)(Currently Completely Imaginary): C#(or c#/unity) UDP Client applications that will control the movement and interactivity of one or many NPCs, all NPC code will interact with the server just like player code does, via a UDP connection.  However, since most of the NPC data will already be built into the client, these connections will be significantly less chatty than a real players connection.

Other Servers/Clients(Also Completely Imaginary): C# probably, UDP most likely.. the rest, who knows. ;)

So, how do I keep all these data bits ready for player interaction?  Data Structures!!  Everything in my servers runs from in-memory data structures, I have no database back end, nor a desire to ever need one.  I'm not saying databases aren't cool, or massively useful in similar scenarios.  I have built MANY of those too, but to get the response times I want, well that would require something in the range of an in-memory instance of mysql or another big hitter similarly configured.  I don't want to have to add that to the arena, and certainly not at this stage of the game.. heh..  stage of the game... Anyway!

Some of the Data Structures Currently Utilized in my Avatar Server:


 //This keeps track of the player ids and encryption strings
 //When a player logs on an encryption string is negotiated...
 Dictionary<int, string> playerKeys = new Dictionary<int, string>();
 
 //Reverse lookup for player ID#s to stored GUID ids.
 //ID#s are shorter and travel better in a compact UDP packet(1470ishbytes) than a big Ol'GUID!
 Dictionary<int, string> playerGUIDLookup = new Dictionary<int, string>();

 //"Table" of currently authenticated Player Save data for many uses here and there, but mostly for persistence purposes.
 //This gets updated by the client when the player makes changes and is saved on the server side.
 //Including the character's avatar description. (Avatar Server)
 Dictionary<int, AvatarSave> PlayerAuth = new Dictionary<int, AvatarSave>();
 
 //Seems redundant somehow, but it isn't. Just, a bad name. 
 //This keeps count of how long a player is idle from the server's perspective, so it can give them the BOOT when it goes too long.
 Dictionary<int, int> livePlayers = new Dictionary<int, int>();
 
 //Seperate "Table" of data specific to the player avatar movements and orientation, position/rotation/etc...
 Dictionary<int, AvatarAttitudeUpd> livePlayerData = new Dictionary<int, AvatarAttitudeUpd>();
 
 //This holds data recieved asynchronously from the game clients(or other servers) as well as the EndPoint it was recieved from, for replies.
 //Each recieved command in this Queue is given a Task to process and reply to the game client with.
 //One message/activity/request/command per packet and only ONE, so no complicated re-assembly is needed on either end of the wire.
 Queue<KeyValuePair<string, EndPoint>> UDPClientMessages = new Queue<KeyValuePair<string, EndPoint>>();
       
 //A less organized version of the above data structure.  These get filtered for players who aren't logged in anymore, commands out of order get skipped, and etc..  Essentially it's the RAW data from the asyncronous receiver.
 Dictionary<EndPoint, Queue<string>> UDPMessages = new Dictionary<EndPoint, Queue<string>>();

 //The collection of all known player IP/PORT end points, that haven't been booted for inactivity.
 Dictionary<int, EndPoint> playerEPs = new Dictionary<int, EndPoint>();

 

Here's a quick run..

Avatar Server Application Starts:
 


udpServerThread = new Thread(new ThreadStart(UDPServer));
UDPServer()
{
 //Starts up a standard UDP Socket Server here with an asynchronous receive mechanism which pumps into the UDPClientMessages Queue.
 	Loops here waiting for clients to send data.
 	while(true)
	{
		Increments Game Time, a double precision counter that keeps track of the total run time of the avatar server. 

		Parallel.ForEach() through the playerEPs sending them them the current server time.
		
		foreach() through the livePlayerData sending all relevant network player data to the clients who should see them.
		
		Some threadpool scaling to increase/decrease the pool size depending on # of users logged in.
		
		Requeueing of messages from UDPMessages into UDPClientMessages happens via some Task.Factor Tasks.
		
		And finally a new Task.Factory Task is created for every item in the UDPClientMessages Queue to process the incoming command from the player's game client.  (player's new coordinates, logins, inventory requests, changes,etc,etc,etc)...
	}
}

 

I can't even call that pseudo-code but I think it conveys what I'm trying to share without going into too much gory detail.  If anybody wants some of that, feel free to comment below and I'll expand on anything I can.  But this describes the basic conceptual design of my server applications.

There are whole other layers of data structures in that server that I've omitted as it gets extremely gory in there...  But that's where the inventories / resources / and crafted items live and it's a bit messy right now.  I'm going to try to get those a little bit more finalized before I try to convey their madness to you.  Suffice to say, there's a lot more to it than this.  But that is less related to the "server" and more related to the game.

Until next, Thanks for reading!

0 likes 0 comments

Comments

Nobody has left a comment. You can be the first!
You must log in to join the conversation.
Don't have a GameDev.net account? Sign up!
Advertisement