Take the 2-minute tour ×
Game Development Stack Exchange is a question and answer site for professional and independent game developers. It's 100% free, no registration required.

I'm working on a generic game server that manages games for an arbitrary number of TCP socket-networked clients playing a game. I have a 'design' hacked together with duct-tape that is working, but seems both fragile and inflexible. Is there a well-established pattern for how to write client/server communication that is robust and flexible? (If not, how would you improve what I have below?)

Roughly I have this:

  • While setting up a game the server has one thread for each player socket handling synchronous requests from a client and responses from the server.
  • Once the game is going, however, all threads except for one sleep, and that thread cycles through all players one at a time communicating about their move (in reversed request-response).

Here's a diagram of what I have currently; click for larger/readable version, or 66kB PDF.

Sequence diagram of flow

Problems:

  • It requires players to respond exactly in turn with exactly the right message. (I suppose I could let each player respond with random crap and only move on once they give me a valid move.)
  • It does not allow players to talk to the server unless it's their turn. (I could have the server send them update about other players, but not process an asynchronous request.)

Final requirements:

  • Performance is not paramount. This will mostly be used for non-realtime games, and mostly for pitting AIs against each other, not twitchy humans.

  • The game play will always be turn-based (even if at a very high resolution). Each player always gets one move processed before all other players get a turn.

The implementation of the server happens to be in Ruby, if that makes a difference.

share|improve this question
    
If you use a TCP socket per client, wouldn't this cap to (65535-1024) clients? –  Lohoris Oct 26 '11 at 8:34
1  
Why is that a problem, Lohoris? Most people will struggle to get 6000 concurrent users, never mind 60000. –  Kylotan Oct 26 '11 at 9:17
    
@Kylotan Indeed. If I have more than 10 concurrent AIs I'll be surprised. :) –  Phrogz Oct 26 '11 at 12:47
    
Out of curiosity, what tool did you use to create that diagram? –  matthias Mar 11 at 19:12
    
@matthias Sadly, that was too much custom work in Adobe Illustrator. –  Phrogz Mar 11 at 22:50

3 Answers 3

up vote 7 down vote accepted

I'm not sure what it is exactly that you want to achieve. But, there's one pattern that is used constantly in game servers, and may help you. Use message queues.

To be more specific: when clients send messages to server, do not process them immediately. Rather, parse them and put into a queue for this specific client. Then, in some main loop (maybe even on another thread) go over all clients in turn, retrieve messages from the queue and process them. When processing indicates that this client's turn is finished, move on to the next.

This way, clients are not required to work strictly turn-by-turn; only fast enough so that you have something in the queue by the time client gets processed (you can, of course, either wait for the client or skip its turn if it lags). And you can add support for asynchronous requests by adding an "async" queue: when a client send a special request, it's added to this special queue; this queue is checked and processed more often then clients' ones.

share|improve this answer

There's certainly more than one way to do it, but personally, I'd skip the separate threads entirely, and just use an event loop. The way you do this will depend somewhat on the I/O library you're using, but basically, your main server loop will look like this:

  1. Set up a connection pool and a listening socket for new connections.
  2. Wait for something to happen.
  3. If the something is a new connection, add it to the pool.
  4. If the something is a request from a client, check if it's something you can handle immediately. If yes, do so; if not, put it in a queue and (optionally) send an acknowledgement to the client.
  5. Also check if there's something in the queue that you can handle now; if so, do it.
  6. Return to step 2.

For example, let's say you have n clients involved in a game. When then first n−1 of them send in their moves, you just check that the move looks valid and send back a message saying that the move has been received but you're still waiting for other players to move. After all n players have moved, you process all the moves you've saved up and send the results to all the players.

You can also refine this scheme to include timeouts — most I/O libraries should have some mechanism for waiting until new data arrives or a given amount of time has elapsed.

Of course, you could also implement something like this with individual threads for each connection, by having those threads pass on any requests they can't handle directly to a central thread (either one per game or one per server) that runs a loop like shown above, except that it talks with the connection handler threads rather than directly with the clients. Whether you find this simpler or more complicated than the single-thread approach is really up to you.

share|improve this answer

Hardware threads don't scale well enough to make "one per player" a reasonable idea for a 3-digit number of players, and the logic of knowing when to wake them up is a complexity that will grow. A better idea is to find an asynchronous I/O package for Ruby that will allow you to send and receive data without having to pause an entire program thread while the read or write operation takes place. This also solves the problem of waiting for players to respond, as you won't have any threads hanging on a read operation. Instead your server can just check whether a time limit has expired and then notify the other player accordingly.

Basically 'asynchronous I/O' is the 'pattern' you're looking for, although it's not really a pattern, more an approach. Instead of explicitly calling 'read' on a socket and pausing the program until the data arrives, you set up the system to call your 'onRead' handler whenever it has data ready, and you carry on processing until that time.

each socket has a turn

Each player has a turn, and each player has a socket which sends data, which is a bit different. One day you might not want one socket per player. You might not use sockets at all. Keep the areas of responsibility separate. Sorry if this sounds like an unimportant detail but when you conflate concepts in your design that should be different then it makes it harder to find and discuss better approaches.

share|improve this answer

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.