Tuesday, November 22, 2011

So I want make an MMORPG, Where do I start from? PART 2

Welcome back  to the MMORPG series, part 2!


<Incoming connections....>

After expending one year to learn c++ and SDL I felt ready for the next step: Networking.

I did a chat server just to learn how SDL_net works, It is very interesting and I recommend to try to code one to everybody who dares to create an RPG/MMORPG server because it is basically the same with lower processing needs..

In a chat server (basically ) what happens is:
  1. Clients connect to the server
  2. They Login with user name / password
  3. Every time they type something and press <ENTER> message is sent to all the other connected clients.
(I'm not including here channels and other things... it is just for demonstration purposes only)

If you want you can Download the sources for the server and client and test it, (Unfortunately it only works in Linux because if you run it under windows, it will redirect all the output to a file, so basically is useless..).

But for the MMORPG server I had to analyze it before starting....

What happens (basically )in a MMORPG server is:

(Client side:)
  1. Clients connect to the server
  2. They Login with user name / password (Or they can create a new user...)
  3. Wait for the server to send UID (See below), and world data
  4. Transmit actions to the server (update position, use skills, pic objects, attack, etc)
  5. Update world state from data received from the server.
  6. Goto point 4...
 (Server side:)
  1. Allocate resources to accept and process data from incoming clients  (That includes queues and threads to process data)
  2. For every client that logs into the server, assign an Unique identifier that is not going to change until client disconnects, the UID is going to be the identifier for all the process in the server (and the client too), the client will receive too the basic world data to start playing (like map data, players and enemies...)
  3. Server processes incoming data from connected clients (that includes moving around / and all the skills the player have), for example a player moves forward, server receives a message from client(x) that wants to move forward, so it's going to increase client(x)->Speed
  4. Update world state , here it comes were timing is important, the amount of times a world update is applied will change the speed of the game for ALL PLAYERS, for example, players wont send position changes to the server, but  move forward, backward, turn right/left commands, what happens when a world update is executed is that clients will move according to their speed and direction and update X/Y position, so no matter if a client tries to hack the client to run at 100 FPS and not the 25FPS set as default, it wont run faster...
  5. Make a list of the players that have changed state (position, skills used)..
  6. Send updated data from the clients with changed state to all the clients connected and in a X range...
  7. Go to point 3....
I know it is a very raw way to describe what the server does, and you may be missing many features such as maps, skills, enemies / NPCS, AI, etc... but my aim is to make it a simple yet stable network server, then I will add the persistence to do world with  a database (Mysql).

To describe how my server works I'm going to explain how it evolved so it's easier to understand why I changed things...

So let's start to speak with the 3 versions / milestones my server has reached:

<Version Number 0.1:>

Classes:
  • cSockServer: Stores global data , a vector to store all the clients connected, server socket, etc...
  • cSockClient:Stores data for every client such as socket to communicate
Threads:
  • 1x Master thread: Master thread listen for the clients and assigns a new socket to everyone.
  • 1xProcessor thread:It processes the data into the input queues and updates wold data
  • 1xUpdater thread: Insert updated data to the output queues
  • 1xListener Thread/Client:listen for incoming data and inserts it into the Input queue.
  • 1xSender thread/Client:Sends data from the output queue to the client.
Queues / vectors:
  • Clients vector:Here I store clients data (sockets, UIDs, etc)
  • Input queue:Every client has one, used to store incoming data
  • Output Queue:Every client has one, used to store outgoing data
Good things of the design:

  • Very simple.
  • Functional.

Bad Things of the design:

  • Very bad performance, when there was more than 3 clients connected you started to feel it lagging even working with the loop-back interface...
  • There is no time control, so the server will have to be confident in the clients <Horrid mistake>
  • Wasted data: Two threads per client, two queues per client.. too much
What I learned from this version:
Threads are a must when speaking of a socket server, so in this very first version what I saw is I needed to use them in a more efficient way, I was using many threads to just send and receive data to the client but only three to process data.. other big mistake was to use so many queues in the clients, it was a pain in the a** to check them and probably not efficient at all.., with all this things in my head I decided to make an updated version:


<Version Number 0.1.7:>


Classes:

  • cSockServer: Stores global data , a vector to store all the clients connected, server socket, etc...
  • cSockClient:Stores data for every client such as socket to communicate
Threads:
  • 1x Master thread: Master thread listen for the clients and assigns a new socket to everyone.
  • 2xProcessor thread:It processes the data into the input queue and updates wold data
  • 1xUpdater Thread, inserts into output queue the data to send
  • XxSender threads: Send data from the output queue
  • 1xListener Thread/Client:listen for incoming data and inserts it into the Input queue.
Queues / vectors:
  • Clients vector:Here I store clients data (sockets, UIDs, etc)
  • Input queue:One in the server, used to store incoming data
  • Output Queue:One in the server, used to store outgoing data
Good things of the design:

  • Centralizing queues make it to waste less data.
  • Having more threads to process data makes it to be able to handle more clients (watch the screen-shot in the top of the post), 101 clients connected...
  • Timing centralized in the server, more secure now

Bad Things of the design:

  • Still bad performance,after adding 20 clients it started to degrade performance no matter how many threads you added to the server
What I learned from this version:
Threads collide, if you use semaphores / mutexes you will prevent it from happening but, after adding 5 threads to process data from a queue, there is no performance improvement because threads have to wait each-other. It was a design fault, to continue increasing performance I had to add more queues with different semaphores so the threads wont collide so much, so that brings me to the last version:



<Version Number 0.1.12:>


<867 Clients connected at the same time, everyone constantly moving sending data to the server, 25 incoming packets /s per client, total, +2.000 incoming messages processed/s,  peak:18.000 messages SENT in a second, that is like 18 packets /millisecond, latency/lag for the clients from 49ms to 113ms average 70-80ms>



Classes:

  • cSockServer: Stores global data , a vector to store all the clients connected, server socket, etc...
  • cSockClient:Stores data for every client such as socket to communicate
Threads:
  • 1x Master thread: Master thread listen for the clients and assigns a new socket to everyone.
  • 2xProcessor threads:It processes the data into the input queue and updates wold data
  • 1xUpdater Thread: inserts clients with updated data to a queue to update
  • 4xUpdaterQueues:it gets the client UID from the UpdaterQueue and check which clients are near and need to get an update state, so it inserts the outgoing data to the Pool of Output queues
  • Pool of Sender threads:The thread gets data from the Pool of Queues and Send data to the clients
  • 1xListener Thread/Client:listen for incoming data and inserts it into the Input queue.
Queues / vectors:
  • Clients vector:Here I store clients data (sockets, UIDs, etc)
  • Input queue:One in the server, used to store incoming data
  • <vector>Pool Output Queue:It can handle many queues so if the client number rise, you can add more queues to prevent thread collisions.
  • <vector>Pool Sender thread Queue: Used to store / add Sender threads dynamically.
Good things of the design:
  • Finally threads start to unleash their power, as many threads / output queues you add, as many users can handle, 867 clients (BOTs) are starting to be something good for a server....

Bad Things of the design:
  • No optimized at all, there are many parts in the server that could change to make it work smoother, but for didactic purposes, I wont change them at the moment.
What I learned from this version:
I have learned to be very careful with the threads, many threads can access to a variable and read from it and processing power will go up, but when writing data you are forced to use semaphores, so speed stops increasing after a certain number of threads.

I added a command for the server that was "AddPower" it adds one output queue and 5 sender threads, so you can adjust performance, but speaking with a friend he told me that I could automate it so automatically it added more queues /threads, so I did! checking if the queues were filled and needed to be processed faster was easy, and the result is the server you have in the picture absorving 867 clients data....

Thanks Jhonny D!

See you soon in the MMORPG series....

No comments: