GGZ Gaming Zone for KDE 4 - Tutorial 2 -------------------------------------- This is the second tutorial for GGZ in KDE, now dealing with kggznet, after kggzmod was handled already. Actually kggzmod uses kggznet internally but this is not relevant for us to know. We will only look at how kggznet can be used in games. The task of kggznet is an easy one: networking. It provides some C++ classes for handling the communication between a game server and a game client. It is important to know that the kggznet libraries do not have to be used with every game. However, they should be used whenever compatibility with existing game servers (or clients) is needed which already use the same protocols. Those protocols are 'easysock' and 'dio' from the GGZ Gaming Zone project and are in use with several games in GGZ, GNOME games and various other game projects. Both are binary protocols. This makes them efficient but terribly hard to debug. Some debugging facilities are included in kggznet to help out. In the future, we want to have abstract game protocol descriptions and generate the actual networking code automatically from it. This is possible to some extent already in an experimental fashion, but not ready for production yet, using the GGZComm protocol generator. Therefore, we recommend to use kggznet right now and switch to generated code later. KGGZRaw ------- This is one of the two classes in kggznet. It can be used similary to a QDataStream. However, QDataStream works best with buffered sockets, whereas there are certain conditions when unbuffered sockets are wanted - for example, if after reading a certain value the socket handling should be switched to another handler, or if partial value reads should be forbidden. KGGZRaw doesn't offer all Qt type serialisations mostly because the Qt types serialise themselves to QDataStream instead of letting QDataStream serialise them, and therefore have QDataStream hardcoded. If you need Qt types in KGGZRaw, they could be added, but keep in mind that other implementations of the same class will likely not be able to read them! Therefore, adding is only possible when the type can easily be deconstructed into two parts, for example writing a QSize in a KDE game and reading two integers (x, y) in a game server written in plain C. Usage example: Add a KGGZRaw* pointer to your class and initialise it to NULL. When the KGGZMod::Module object reports the file descriptor to the game server via signalNetwork(), check if the pointer is still NULL, and if so, create the KGGZRaw object and assign the file descriptor. Just in case, the error signal should also be slotted in somewhere. // matches KGGZMod::Module::signalNetwork(fd) void slotNetwork(int fd) { if(!m_raw) { m_raw = new KGGZRaw(); m_raw->setNetwork(fd); connect(m_raw, SIGNAL(signalError()), SLOT(slotError())); } // now read from m_raw int opcode; m_raw >> opcode; // and write back the answer int answer = 42; m_raw << answer; } // called by kggzraw in case of read/write errors void slotError() { delete m_raw; m_raw = NULL; // don't forget to inform the user! // in GGZ game clients, we likely want to terminate everything now delete KGGZMod::Module::instance(); } KGGZPacket ---------- KGGZRaw has serious drawbacks: due to it being unbuffered, it might block the GUI if an integer (4 bytes) is read but only 3 bytes are in the buffer at this time, which might actually happen in practice on TCP/IP connections! Blocking is not bad if it happens in a dedicated thread only, but usually game clients are single-threaded and we want non-blocking network operations. Enter KGGZPacket: Each 'packet' is a collection of integers, characters and strings which somehow fit together as a logical unit. The format of the packets is left up to the game authors. Packets are used in Freeciv, for example, but also in other games. The interface is similar to KGGZRaw, but is not the same one. The reason behind the difference is that KGGZPacket might need to read several times before a packet is finished. Since buffering is not an issue, the developer also gets access to a standard QDataStream, one for reading and one for writing. The example from above would read as follows, in case we have a KGGZPacket *m_packet member in our class and want to read whenever KGGZMod::Module *m_mod reports data from the game server: void initNetwork() { m_packet = new KGGZPacket(); connect(m_mod, SIGNAL(signalNetwork(int)), m_packet, SLOT(slotNetwork(int))); connect(m_packet, SIGNAL(signalPacket()), SLOT(slotPacket())); } void slotPacket() { // now read from m_packet - which is guaranteed to be successful int opcode; *m_packet->inputstream() >> opcode; // and write back our answer packet int answer = 42; *m_packet->outputstream() << answer; *m_packet->flush(); // don't forget this! } So basically, KGGZPacket is used as a redirector for the network traffic which takes the raw traffic and cuts it into nicely usable packets with a known size but unknown content. A higher-level protocol code could also deliver known content, that is, one special struct or object on the stack for each incoming message. However, this is specific for each game, and can be implemented by game developers as needed. GGZComm-generated code will support such higher-level packets.