Introduction Manual Class Reference Header Reference

Optimizing Network Data

Pack the data manually

The classical approach to networking is defining a struct for each message, and the first one or two bytes in the struct denote the packet type, like so:

struct login_packet_s
{
  unsigned char packettype;
  char login[20];
  char password[20];
  unsigned short program_version;
};

A system like this can be used with Zoidcom, but is highly inefficient, bandwidth-wise:

ZCom_BitStream *data = new ZCom_BitStream();
login_packet_s login;
data->addBuffer(&login, sizeof(login));
client->ZCom_sendData(connection_id_of_server, data);

Whenever the loginname or the password is shorter than 19 characters (+1 byte for the trailing zero), a few bytes get wasted because they are sent anyway. A more efficient way is to pack this data manually into a bitstream:

ZCom_BitStream *data = new ZCom_BitStream();
login_packet_s login;
data->addInt(PacketType_Login, 8);
data->addString(login.login);
data->addString(login.password);
data->addInt(login.program_version, 16);
client->ZCom_sendData(connection_id_of_server, data);

ZCom_BitStream::addString() will only write the actual length of the string to the stream. Granted, you won't be sending login packets 200 times per second, so the savings in this case here are marginal, but the example can be expanded to any message you can think of.

Pack The Numerics

Numeric values in network messages are often sent as 8, 16 or 32 bit values. Bitstreams allow finer control over the amount of needed bits.

Example: A variable depicting player health. You, as application programmer, know that the maximum value of this variable is 100. So how many bits are needed to cover the whole possible range of it? It's 7, as 2^7 = 128. 6 bits wouldn't suffice as 2^6 = 64 and the health value can go higher than that.

So everytime a health value is packed into a stream, tell the bitstream that only 7 bits are needed. Doing this for one variable won't do much to save traffic, but doing this throughout the whole program for everything you send will save you a lot.

Example

Each network object that has a node usually has to send different events, so for the header of the events we create an enum with all the possible events.

Something like:

class MyShip
{
public:
  enum NetEvents {
    Shoot,
    Die,
    Etc,
    NetEventsCount   // Counts the amount of events
  };
  // rest of class
}

Defining this function called bitsOf( n ) that returns the minimum amount of bits needed to encode numerics up to n:

inline unsigned int bitsOf(unsigned long n)
{
  unsigned int bits = 0;
  for(; n; n >>= 1)
    bits++;
  return bits;
}

Each time an event is sent, bitsOf(MyShip::NetEventsCount) is used to determine the optimal amount of bits needed to send any of the enum values.

ZCom_BitStream *shootevent = new ZCom_BitStream();
// add event type
shootevent->addInt(MyShip::Shoot, bitsOf(MyShip::NetEventsCount));
// add more data if needed
node->sendEvent(eZCom_ReliableOrdered, ZCOM_REPRULE_AUTH_2_ALL, shootevent);

This allows you to modify the enum at will and have the program automatically adjust to the minimal amount of bits needed to transmit them.

Conditional Data

Another benefit of sending streams is, that they can be packed more dynamically by embedding condition variables. For example, an enhanced login packet, that can optionally contain extra data about the user logging in:

ZCom_BitStream *data = new ZCom_BitStream();
data->addInt(PacketType_Login, 8);
data->addString(username);
data->addString(password);
if (extra_data_available)
{
  data->addBool(true);
  data->addString(comment);
  data->addString(phonenumber);
} else
  data->addBool(false);
data->addInt(program_version, 16);

The peer that has to unpack this stream has to execute:

int packettype = data->getInt(8);
if (packettype == PacketType_Login) {
  char uname[20]; data->getString(uname, 20);
  char passwd[20]; data->getString(passwd, 20);
  // if the next bool is true, extra data is coming
  if (data->getBool() == true) {
      char comment[80]; data->getString(comment, 80);
      char phonenumber[20]; data->getString(phonenumber, 20);
  }
  int client_version = data->getInt(16);
}

As you can see, for the cost of one bit (the boolean), the extra data is only sent when it is available. This technique can be applied anywhere where Zoidcom is used to send data, so make use of it, as it is another good way to save a lot of bandwidth.


This file is part of the documentation for Zoidcom. Documentation copyright © 2004-2008 by Jörg Rüppel. Generated on Sat Aug 16 15:26:51 2008 for Zoidcom by doxygen 1.4.6-NO