NightCrawler
04-10-2003, 10:09 PM
I am designing a network based multiplayer game for the sole purpose of learning how to work with network sockets. Assume that the server/client will communicate via strings, and that the server will need to be able to accomodate multiple concurrent connections (As many as possible). I would like to develop it so that the code is 100% operational on both Windows (98/2000/XP.etc..) and the FreeBSD unix platform. I would like to use a TCP stream socket
Now on to the question =)
I'm wondering which network architecture would best suit my purpose?
I'm familiar with Blocking and Non-blocking theory, and a little bit of threading, but I'm not sure what would be the best method to achieve my goal.
Thank you for your advice,
Mark
taken from my posts on the gamedev.net :
http://www.gamedev.net/community/forums/topic.asp? topic_id=148480
So... if I were you, I would set up things in the following way:
I would set up three types of threads: parent (main) thread, processing threads and a thread responsible for communication.
1. The parent thread should be responsible for setting up all the resources, running a socket, creating and managing child threads, processing admin commands, setting up logging and do other preprocessing things.
2. The communication thread should run select() in a loop and manage incoming and outcoming data + listen for new connections and handle other communication thing. It also has to be best 'secured and stable'. You need to log each connection, request, etc... So... For example, if some data is received, the connection thread should check whether it is a connection request or a user command. If it is a connection request then it should be accepted and the client descriptor should be added to the main set of descriptors and passed to the select() during the next turn of the main loop. If we get some connected user data we need to check for integrity, authorize and secure it and then place it in the incoming data queue... If the select() only returns with 'ready to write' descriptors in the descriptor set, then we should check the outcoming queue for data addressed to the proper client, check its actuality (compare its time stamp with the current server time, etc...), compile a packet and transmit it to the client...
You will also have to build your headers on top of the protocol your app utilizes and a messaging system depending on the kind of your app.
The queues should be protected with mutexes and condition variables so that there's no blocking, dead-locking or race conditions... Consider stable server work (not speed) your priority. As a gamer I would rather like to play on a slow but stable server than reconnecting every 5 seconds... =) But it would be great to play on a both stable and fast server ;).
It is not actually a synchronization hell, everything is clear and stable (works fine under my linux and freeBSD boxes =)). Depends on your design and structure. Mutexes really help to avoid problems with synchronization, and they are quite clear and easy-to-use.
3. The processing threads run in endless loops. They check for data in the incoming queue, process it according to some rules and then place the replies, time stamps, IDs, status or error statements and other info into the outcoming queues, so that clients on the other side are 'up-to-date'. You can assign each thread a region of your game world to be responsible for, or set up a quad tree to manage huge server load, or create something of your own... it is up to you to decide.
Try implementing a simple app of this structure, testing to see if everything is ok, and then develop and upgrade it to a full-powered server.
Once again... the basic structure of such an application is: the client connects to a server, then begins communication. For example, a gamer wants to move his unit from point A to point B on the map. So, he just selects the proper unit and then points it to the destination. But the other clients need to see whether that unit has moved or not. So his client part sends data which contains fields similar to this: [UID][UNUMBER][COMMAND][ARGS]. UID - is a user (client) ID.
UNUMBER - the number of the object client is operating on.
COMMAND - is the action for the object to perform (0 == move, 1 == attack, 2 == take, etc...)ARGS - is the field containing additional data for the user command to be properly processed by the server (if COMMAND == 0(move), then ARGS should contain coordinates of the destination point). Server is to resend that data to all other clients.
Depending on the type of data the client can wait for confirmation or rejection back from the server... For example, if the server couldn't move a unit to the destination point, it sends a 'reject' packet... and on the client side the unit says something like 'I can't go there, Master!'... If it is a confirmation - the unit says 'As you wish, Master!' and begins to move...
And on the server side the logic is more complex as it has to process and calculate everything except graphics (graphics rendering is the task of the client part): AI, game world and map changes, coordination and user interaction. etc... there's a huge amount of work for the server.
Depending on the result of its calculations server needs to decide whether to send a 'confirm' or a 'reject' packet. Now imagine you have 800 clients connected to your server... As there's only one client request being processed in the moment the other clients would have to wait until their commands are processed. For example imagine it takes 0.3 seconds for the server to process a certain command. And one of the clients has sent data. As long as the server is only capable to process one command at a time, every client has to wait (0.3 * 800 = 24 seconds) until his next command is processed... Well, I think a gamer wouldn't be happy to play in such an environment... =) So the server needs to make its calculations as fast as possible and to process as many requests as possible at a time. That's why we need several processing threads. So... the server needs to make an illusion of simultaneous processing. That's it! We have to run several processing threads. They are not really simultaneous, but the gamer won't see that... He will think the the server is only processing his input, and nobody else...
As for the connection threads, i would recommend using only one thread to manage connections and send/receive data, because the only thing it should do - is verify the data and copy the buffers to the proper queues.
Search for Beej's Guide to Network Programming - it will tell you how to build a loop with a select() (see the 'multiplexing with select()' section of the guide) and how to manage different types of incoming data (data from an already connected user, or a connection request), how to manage client disconnections, etc... You'll just have to add queues and protect them with mutexes.
As for the messaging system, I'm implementing it with the pluggable factories. Those are really cool! =).
So the result is: one main thread, one connection thread and several processing threads (be aware of starting 800 threads... it takes Linux approx. 0.1 second to switch between the threads, so all the processor time could be eaten with the OS and the server won't have access to the CPU to perform its tasks). Do some calculations and decide which number of processing threads suits your server most.
As for the platform independence... i think this would be quite hard to implement. General functions (socket(), connect(), send(), recv(), etc...) are supported under both *nix and windoze OSs, but there's also a huge amount of platform-dependent code (headers, libs, set-up code, etc...)...
I would rather recommend you coding for FreeBSD and Windoze separately.
vBulletin® v3.7.4, Copyright ©2000-2009, Jelsoft Enterprises Ltd.