user3281410
user3281410

Reputation: 512

C++ Networked Program Design: Boost Asio, Serialization, and OStream

Background Info:

I am beginning to learn about networking for a small demo project that I'm working on. I have a server with a bunch of Ball objects that have a variety of parameters (size, color, velocity, acceleration, etc.). I would like the server to be able to do 2 things

  1. Send all of the parameters to the client so that the client can create a new Ball object that's exactly like how it is on the server.
  2. Be able to periodically send smaller updates about the ball that only change some of its parameters (usually position and velocity). The idea is to not redundantly send information.

I'm a little overwhelmed at how to approach this, since there is so much to deal with. My idea was to create a class called ClientUpdate that would be an abstract base class for specific update types that I might want to send.

class ClientUpdate
{
protected:
    UpdateTypes type;
public:
    ClientUpdate(){};
    void setType(UpdateTypes t){ type = t; }
    virtual void print(ostream& where)const;
    friend std::ostream& operator<<(std::ostream& os, const ClientUpdate & obj)
    {
        obj.print(os);
        return os;
    }
};

Then for every event that might occur on the server, like when the a ball changes color or changes its state from frozen to not-frozen, I would create a subclass of ClientUpdate to describe the event. The subclasses would have simple variables (strings, integers, booleans) that I would write to the ostream with the print function.

Finally, I would store all of the updates that happen in a certain area of my game (such as a room) in each update cycle, and then for any clients who are subscribed to that area, I would send 1 large byte array of client updates that would have the form UPDATETYPE_DATA_UPDATETYPE_DATA_....etc. The client would parse the input stream and re-create the update class from it (I haven't written this code yet, but I assume it won't be difficult).

I'm using Boost::Asio for the networking code, and I'm following the tutorials here : http://www.gamedev.net/blog/950/entry-2249317-a-guide-to-getting-started-with-boostasio/?pg=10. I just mention this because I'm pretty sure I want to stick with boost asio, since I'm trying to very comfortable with boost and modern c++ in general.


Question:

(1) The basic question is "is this a reasonable way of approaching my problem?" I feel very confident that I could at least make it work, but as a novice at anything network-related, I'm not sure if I am re-inventing wheels or wasting time when there are simpler ways of doing things. In particular, is it inefficient to gather all of the "update" objects together and send them with 1 large write or should I send the individual updates with separate writes to the socket?

(2) For example, I've read about Boost::Serialize, and it seems to be very similar to what I'm doing. However, I am more interested in updating certain member variables of objects that should be almost the same on both the client and server. Is Boost::serialize good for this, or is it more for sending whole objects? Are there any other libraries that do things similar to what I'm describing?

Upvotes: 1

Views: 175

Answers (1)

sehe
sehe

Reputation: 393583

The trade offs are hard to judge from here.

I can see a few approaches (disclaimer, I didn't try to be exhaustive, just thinking aloud):

  1. every mutation to game state is an "event"; you "journal" events and every once in a while you send a batch of these to the other side. The other side applies them and sends back a checksum verifying that the resulting state matches that on the sending side (at the time of the sending).

  2. alternatively, you treat the whole game state as a "document". Every once in xxx milliseconds, you snapshot the gamestate, and send it to the other party. The other party replaces its gamestate with that from the document. The server could optimize bandwidth by differencing the gamestate to the former (by saving the previously sent snapshot) and sending only the delta.

    In that last respect there might be a similarity to the first approach, but there is a fundamental difference: in the first approach, the mutations sent to the other side are exactly the same as they happened on the source system; In the second approach, the 'delta' mutations are synthesized from the effective difference to the last snapshot: they have no relation to the sequence of events that actually lead to the current game state.

Now, the trade-offs are plentiful and depend on such factors as:

  • how big is the ful gamestate (a chess board is trivially encoded in few bytes, a 3D shooter cannot afford to send whole snapshots, and may not even be able to afford keeping a snapshot for differencing)
  • how many balls are there, and how are they stored; if they're in a node-based data structure, replacing the replacing the whole game state may become expensive (since there might be many allocations).
  • how many distinct state mutations are there (how complex would the command language get; would it make sense to devise a "command language" for the journal, or would it become too complicated?)
  • how many events will occur per second (is the number of triggers solely input based? E.g. in chess, there will be a move once every n seconds, but in a balancing game there maybe hundreds of inputs each second, if not more).

Etc. All these questions will make certain approaches more attractive and others less.


One crucial question that you didn't address is: will there be "inputs" on both sides? If so, could there be conflicting inputs? Could there be consequences of changes on one side that lead to a different outcome if the inputs from the other side have been received slightly later?

I won't go into this for now. If you need bi-directional synchronization, you will become very dependent on low latency and frequent updates, so that you can correct divergent gamestates before the difference becomes humanly noticeable and annoying.


I also won't go into how you should send the data, as it depends very much on the chosen approach. If you send full documents, as you've noticed, Boost Serialization would look like a good candidate.

Upvotes: 2

Related Questions