KDEGames

kgamenetwork.cpp
1/*
2 This file is part of the KDE games library
3 SPDX-FileCopyrightText: 2001 Martin Heni <kde at heni-online.de>
4 SPDX-FileCopyrightText: 2001 Andreas Beckermann <b_mann@gmx.de>
5
6 SPDX-License-Identifier: LGPL-2.0-only
7*/
8
9#include "kgamenetwork.h"
10
11// own
12#include "kgameerror.h"
13#include "kgamemessage.h"
14#include "kmessageclient.h"
15#include "kmessageio.h"
16#include "kmessageserver.h"
17#include <kdegamesprivate_kgame_logging.h>
18// KF
19#include <KDNSSD/PublicService>
20// Qt
21#include <QBuffer>
22#include <QList>
23
24class KGameNetworkPrivate
25{
26public:
27 KGameNetworkPrivate() = default;
28
29public:
30 KMessageClient *mMessageClient = nullptr;
31 KMessageServer *mMessageServer = nullptr;
32 quint32 mDisconnectId = 0; // Stores gameId() over a disconnect process
33 KDNSSD::PublicService *mService = nullptr;
34 QString mType;
35 QString mName;
36
37 int mCookie;
38};
39
40// ------------------- NETWORK GAME ------------------------
42 : QObject(parent)
43 , d(new KGameNetworkPrivate)
44{
45 d->mCookie = (qint16)c;
46
47 // Init the game as a local game, i.e.
48 // create your own KMessageServer and a KMessageClient connected to it.
49 setMaster();
50
51 qCDebug(KDEGAMESPRIVATE_KGAME_LOG) << "this=" << this << ", cookie=" << cookie() << "sizeof(this)=" << sizeof(KGameNetwork);
52}
53
54KGameNetwork::~KGameNetwork()
55{
56 qCDebug(KDEGAMESPRIVATE_KGAME_LOG) << "this=" << this;
57 // Debug();
58 delete d->mService;
59}
60
61// ----------------------------- status methods
63{
64 return isOfferingConnections() || d->mMessageClient->isNetwork();
65}
66
67quint32 KGameNetwork::gameId() const
68{
69 // return d->mMessageClient->id() ;
70 // Return stored id in the case of disconnect. In any other
71 // case the disconnect id is 0
72 if (d->mMessageClient->id() != 0) {
73 return d->mMessageClient->id();
74 } else {
75 return d->mDisconnectId;
76 }
77}
78
80{
81 return d->mCookie;
82}
83
85{
86 return (d->mMessageServer != nullptr);
87}
88
90{
91 return (d->mMessageClient->isAdmin());
92}
93
95{
96 return d->mMessageClient;
97}
98
100{
101 return d->mMessageServer;
102}
103
104// ----------------------- network init
106{
107 if (!d->mMessageServer) {
108 d->mMessageServer = new KMessageServer(cookie(), this);
109 } else {
110 qCWarning(KDEGAMESPRIVATE_KGAME_LOG) << "Server already running!!";
111 }
112 if (!d->mMessageClient) {
113 d->mMessageClient = new KMessageClient(this);
118
122
123 // broacast and direct messages are treated equally on receive.
124 connect(d->mMessageClient, &KMessageClient::forwardReceived, d->mMessageClient, &KMessageClient::broadcastReceived);
125
126 } else {
127 // should be no problem but still has to be tested
128 qCDebug(KDEGAMESPRIVATE_KGAME_LOG) << "Client already exists!";
129 }
130 d->mMessageClient->setServer(d->mMessageServer);
131}
132
133void KGameNetwork::setDiscoveryInfo(const QString &type, const QString &name)
134{
135 qCDebug(KDEGAMESPRIVATE_KGAME_LOG) << type << ":" << name;
136 d->mType = type;
137 d->mName = name;
138 tryPublish();
139}
140
141void KGameNetwork::tryPublish()
142{
143 if (d->mType.isEmpty() || !isOfferingConnections())
144 return;
145 if (!d->mService)
146 d->mService = new KDNSSD::PublicService(d->mName, d->mType, port());
147 else {
148 if (d->mType != d->mService->type())
149 d->mService->setType(d->mType);
150 if (d->mName != d->mService->serviceName())
151 d->mService->setServiceName(d->mName);
152 }
153 if (!d->mService->isPublished())
154 d->mService->publishAsync();
155}
156
157void KGameNetwork::tryStopPublishing()
158{
159 if (d->mService)
160 d->mService->stop();
161}
162
164{
165 qCDebug(KDEGAMESPRIVATE_KGAME_LOG) << "on port" << port;
166 if (!isMaster()) {
167 setMaster();
168 }
169
170 // Make sure this is 0
171 d->mDisconnectId = 0;
172
173 // FIXME: This debug message can be removed when the program is working correct.
174 if (d->mMessageServer && d->mMessageServer->isOfferingConnections()) {
175 qCDebug(KDEGAMESPRIVATE_KGAME_LOG) << "Already running as server! Changing the port now!";
176 }
177
178 tryStopPublishing();
179 qCDebug(KDEGAMESPRIVATE_KGAME_LOG) << "before Server->initNetwork";
180 if (!d->mMessageServer->initNetwork(port)) {
181 qCCritical(KDEGAMESPRIVATE_KGAME_LOG) << "Unable to bind to port" << port << "!";
182 // no need to delete - we just cannot listen to the port
183 // delete d->mMessageServer;
184 // d->mMessageServer = 0;
185 // d->mMessageClient->setServer((KMessageServer*)0);
186 return false;
187 }
188 qCDebug(KDEGAMESPRIVATE_KGAME_LOG) << "after Server->initNetwork";
189 tryPublish();
190 return true;
191}
192
193bool KGameNetwork::connectToServer(const QString &host, quint16 port)
194{
195 if (host.isEmpty()) {
196 qCCritical(KDEGAMESPRIVATE_KGAME_LOG) << "No hostname given";
197 return false;
198 }
199 if (connectToServer(new KMessageSocket(host, port))) {
200 qCDebug(KDEGAMESPRIVATE_KGAME_LOG) << "connected to" << host << ":" << port;
201 return true;
202 } else {
203 return false;
204 }
205}
206
208{
209 // Make sure this is 0
210 d->mDisconnectId = 0;
211
212 // if (!d->mMessageServer) {
213 // // FIXME: What shall we do here? Probably must stop a running game.
214 // qCWarning(KDEGAMESPRIVATE_KGAME_LOG) << "We are already connected to another server!";
215 /// }
216
217 if (d->mMessageServer) {
218 // FIXME: What shall we do here? Probably must stop a running game.
219 qCWarning(KDEGAMESPRIVATE_KGAME_LOG) << "we are server but we are trying to connect to another server! "
220 << "make sure that all clients connect to that server! "
221 << "quitting the local server now...";
223 d->mMessageClient->setServer((KMessageIO *)nullptr);
224 delete d->mMessageServer;
225 d->mMessageServer = nullptr;
226 }
227
228 qCDebug(KDEGAMESPRIVATE_KGAME_LOG) << " about to set server";
229 d->mMessageClient->setServer(connection);
230 Q_EMIT signalAdminStatusChanged(false); // as we delete the connection above isAdmin() is always false now!
231
232 // OK: We say that we already have connected, but this isn't so yet!
233 // If the connection cannot be established, it will look as being disconnected
234 // again ("slotConnectionLost" is called).
235 // Shall we differ between these?
236 qCDebug(KDEGAMESPRIVATE_KGAME_LOG) << "connected";
237 return true;
238}
239
240quint16 KGameNetwork::port() const
241{
242 if (isNetwork()) {
243 if (isOfferingConnections()) {
244 return d->mMessageServer->serverPort();
245 } else {
246 return d->mMessageClient->peerPort();
247 }
248 }
249 return 0;
250}
251
253{
254 return d->mMessageClient->peerName();
255}
256
258{
259 // We still are the Master, we just don't accept further connections!
260 tryStopPublishing();
261 if (d->mMessageServer) {
262 d->mMessageServer->stopNetwork();
263 return true;
264 }
265 return false;
266}
267
269{
270 return (d->mMessageServer && d->mMessageServer->isOfferingConnections());
271}
272
274{
275 // TODO MH
278 if (d->mMessageServer) {
279 const QList<quint32> list = d->mMessageServer->clientIDs();
280 for (quint32 id : list) {
281 qCDebug(KDEGAMESPRIVATE_KGAME_LOG) << "Client id=" << id;
282 KMessageIO *client = d->mMessageServer->findClient(id);
283 if (!client) {
284 continue;
285 }
286 qCDebug(KDEGAMESPRIVATE_KGAME_LOG) << " rtti=" << client->rtti();
287 if (client->rtti() == 2) {
288 qCDebug(KDEGAMESPRIVATE_KGAME_LOG) << "DIRECT IO";
289 } else {
290 d->mMessageServer->removeClient(client, false);
291 }
292 }
293 } else {
294 qCDebug(KDEGAMESPRIVATE_KGAME_LOG) << "before client->disconnect() id=" << gameId();
295 // d->mMessageClient->setServer((KMessageIO*)0);
296 qCDebug(KDEGAMESPRIVATE_KGAME_LOG) << "+++++++++++++++++++++++++++++++++++++++++++++++++++++++";
297 d->mMessageClient->disconnect();
298
299 qCDebug(KDEGAMESPRIVATE_KGAME_LOG) << "++++++--------------------------------------------+++++";
300 }
301 // setMaster();
302 /*
303 if (d->mMessageServer) {
304 //delete d->mMessageServer;
305 //d->mMessageServer=0;
306 server=true;
307 qCDebug(KDEGAMESPRIVATE_KGAME_LOG) << " server true";
308 d->mMessageServer->deleteClients();
309 qCDebug(KDEGAMESPRIVATE_KGAME_LOG) << " server deleteClients";
310 }
311 */
312 qCDebug(KDEGAMESPRIVATE_KGAME_LOG) << "DONE";
313}
314
316{
317 qCDebug(KDEGAMESPRIVATE_KGAME_LOG) << "Storing client id of connection " << clientID;
318 d->mDisconnectId = clientID;
319}
320
322{
323 qCDebug(KDEGAMESPRIVATE_KGAME_LOG) << "Resseting client disconnect id";
324 d->mDisconnectId = 0;
325}
326
327void KGameNetwork::electAdmin(quint32 clientID)
328{
329 if (!isAdmin()) {
330 qCWarning(KDEGAMESPRIVATE_KGAME_LOG) << "only ADMIN is allowed to call this!";
331 return;
332 }
333 QByteArray buffer;
334 QDataStream stream(&buffer, QIODevice::WriteOnly);
335 stream << static_cast<quint32>(KMessageServer::REQ_ADMIN_CHANGE);
336 stream << clientID;
337 d->mMessageClient->sendServerMessage(buffer);
338}
339
341{
342 if (!isAdmin()) {
343 qCWarning(KDEGAMESPRIVATE_KGAME_LOG) << "only ADMIN is allowed to call this!";
344 return;
345 }
346 QByteArray buffer;
347 QDataStream stream(&buffer, QIODevice::WriteOnly);
348 stream << static_cast<quint32>(KMessageServer::REQ_MAX_NUM_CLIENTS);
349 stream << (qint32)max;
350 d->mMessageClient->sendServerMessage(buffer);
351}
352
354{
355 if (messageClient()) {
356 messageClient()->lock();
357 }
358}
359
361{
362 if (messageClient()) {
364 }
365}
366
367// --------------------- send messages ---------------------------
368
369bool KGameNetwork::sendSystemMessage(int data, int msgid, quint32 receiver, quint32 sender)
370{
371 QByteArray buffer;
372 QDataStream stream(&buffer, QIODevice::WriteOnly);
373 stream << data;
374 return sendSystemMessage(buffer, msgid, receiver, sender);
375}
376
377bool KGameNetwork::sendSystemMessage(const QString &msg, int msgid, quint32 receiver, quint32 sender)
378{
379 QByteArray buffer;
380 QDataStream stream(&buffer, QIODevice::WriteOnly);
381 stream << msg;
382 return sendSystemMessage(buffer, msgid, receiver, sender);
383}
384
385bool KGameNetwork::sendSystemMessage(const QDataStream &msg, int msgid, quint32 receiver, quint32 sender)
386{
387 return sendSystemMessage(((QBuffer *)msg.device())->buffer(), msgid, receiver, sender);
388}
389
390bool KGameNetwork::sendSystemMessage(const QByteArray &data, int msgid, quint32 receiver, quint32 sender)
391{
392 QByteArray buffer;
393 QDataStream stream(&buffer, QIODevice::WriteOnly);
394 if (!sender) {
395 sender = gameId();
396 }
397
398 quint32 receiverClient = KGameMessage::rawGameId(receiver); // KGame::gameId()
399 int receiverPlayer = KGameMessage::rawPlayerId(receiver); // KPlayer::id()
400
401 KGameMessage::createHeader(stream, sender, receiver, msgid);
402 stream.writeRawData(data.data(), data.size());
403
404 /*
405 qCDebug(KDEGAMESPRIVATE_KGAME_LOG) << "transmitGameClientMessage msgid=" << msgid << "recv="
406 << receiver << "sender=" << sender << "Buffersize="
407 << buffer.size();
408 */
409
410 if (!d->mMessageClient) {
411 // No client created, this should never happen!
412 // Having a local game means we have our own
413 // KMessageServer and we are the only client.
414 qCWarning(KDEGAMESPRIVATE_KGAME_LOG) << "We don't have a client! Should never happen!";
415 return false;
416 }
417
418 if (receiverClient == 0 || receiverPlayer != 0) {
419 // if receiverClient == 0 this is a broadcast message. if it is != 0 but
420 // receiverPlayer is also != 0 we have to send broadcast anyway, because the
421 // KPlayer object on all clients needs to receive the message.
422 d->mMessageClient->sendBroadcast(buffer);
423 } else {
424 d->mMessageClient->sendForward(buffer, receiverClient);
425 }
426 return true;
427}
428
429bool KGameNetwork::sendMessage(int data, int msgid, quint32 receiver, quint32 sender)
430{
431 return sendSystemMessage(data, msgid + KGameMessage::IdUser, receiver, sender);
432}
433
434bool KGameNetwork::sendMessage(const QString &msg, int msgid, quint32 receiver, quint32 sender)
435{
436 return sendSystemMessage(msg, msgid + KGameMessage::IdUser, receiver, sender);
437}
438
439bool KGameNetwork::sendMessage(const QDataStream &msg, int msgid, quint32 receiver, quint32 sender)
440{
441 return sendSystemMessage(msg, msgid + KGameMessage::IdUser, receiver, sender);
442}
443
444bool KGameNetwork::sendMessage(const QByteArray &msg, int msgid, quint32 receiver, quint32 sender)
445{
446 return sendSystemMessage(msg, msgid + KGameMessage::IdUser, receiver, sender);
447}
448
449void KGameNetwork::sendError(int error, const QByteArray &message, quint32 receiver, quint32 sender)
450{
451 QByteArray buffer;
452 QDataStream stream(&buffer, QIODevice::WriteOnly);
453 stream << (qint32)error;
454 stream.writeRawData(message.data(), message.size());
455 sendSystemMessage(stream, KGameMessage::IdError, receiver, sender);
456}
457
458// ----------------- receive messages from the network
459void KGameNetwork::receiveNetworkTransmission(const QByteArray &receiveBuffer, quint32 clientID)
460{
462 int msgid;
463 quint32 sender; // the id of the KGame/KPlayer who sent the message
464 quint32 receiver; // the id of the KGame/KPlayer the message is for
465 KGameMessage::extractHeader(stream, sender, receiver, msgid);
466 // qCDebug(KDEGAMESPRIVATE_KGAME_LOG) << "id=" << msgid << "sender=" << sender << "recv=" << receiver;
467
468 // No broadcast : receiver==0
469 // No player isPlayer(receiver)
470 // Different game gameId()!=receiver
471 if (receiver && receiver != gameId() && !KGameMessage::isPlayer(receiver)) {
472 // receiver=0 is broadcast or player message
473 qCDebug(KDEGAMESPRIVATE_KGAME_LOG) << "Message not meant for us " << gameId() << "!=" << receiver << "rawid=" << KGameMessage::rawGameId(receiver);
474 return;
475 } else if (msgid == KGameMessage::IdError) {
476 QString text;
477 qint32 error;
478 stream >> error;
479 qCDebug(KDEGAMESPRIVATE_KGAME_LOG) << "Got IdError" << error;
480 text = KGameError::errorText(error, stream);
481 qCDebug(KDEGAMESPRIVATE_KGAME_LOG) << "Error text:" << text.toLatin1();
482 Q_EMIT signalNetworkErrorMessage((int)error, text);
483 } else {
484 networkTransmission(stream, msgid, receiver, sender, clientID);
485 }
486}
487
488// -------------- slots for the signals of the client
490{
492
493 // TODO: I'm pretty sure there are a lot of things that should be done here...
494}
495
497{
498 qCDebug(KDEGAMESPRIVATE_KGAME_LOG) << "------------------- KNETWORKGAME -------------------------";
499 qCDebug(KDEGAMESPRIVATE_KGAME_LOG) << "gameId " << gameId();
500 qCDebug(KDEGAMESPRIVATE_KGAME_LOG) << "gameMaster " << isMaster();
501 qCDebug(KDEGAMESPRIVATE_KGAME_LOG) << "gameAdmin " << isAdmin();
502 qCDebug(KDEGAMESPRIVATE_KGAME_LOG) << "---------------------------------------------------";
503}
504
505#include "moc_kgamenetwork.cpp"
506
507/*
508 * vim: et sw=2
509 */
static bool isPlayer(quint32 id)
Checks whether a message receiver/sender is a player.
static void extractHeader(QDataStream &msg, quint32 &sender, quint32 &receiver, int &msgid)
Retrieves the information like cookie,sender,receiver,... from a message header.
static void createHeader(QDataStream &msg, quint32 sender, quint32 receiver, int msgid)
Creates a message header given cookie,sender,receiver,...
static int rawPlayerId(quint32 playerid)
Returns the raw playerid, that is, a id which does not contain the game number encoded in it.
static quint32 rawGameId(quint32 playerid)
Returns the raw game id, that is, the game id the player belongs to.
KMessageServer * messageServer() const
Don't use this unless you really know what you are doing! You might experience some strange behaviour...
bool isMaster() const
Is this the game MASTER (i.e.
KMessageClient * messageClient() const
Don't use this unless you really know what you're doing! You might experience some strange behaviour ...
bool isNetwork() const
bool isAdmin() const
The admin of a game is the one who initializes newly connected clients using negotiateNetworkGame and...
void signalClientConnected(quint32 clientID)
This signal is emitted whenever the KMessageServer sends us a message that a new client connected.
bool connectToServer(const QString &host, quint16 port)
Inits a network game as a network CLIENT.
QString hostName() const
virtual void lock()
You should call this before doing thigs like, e.g.
KGameNetwork(int cookie=42, QObject *parent=nullptr)
Create a KGameNetwork object.
virtual void networkTransmission(QDataStream &, int, quint32, quint32, quint32 clientID)=0
Called by ReceiveNetworkTransmission().
void electAdmin(quint32 clientID)
If you are the ADMIN of the game you can give the ADMIN status away to another client.
virtual void Debug()
Gives debug output of the game status.
void slotResetConnection()
Called when the network connection is terminated.
void disconnect()
Disconnect the current connection and establish a new local one.
int cookie() const
Application cookie.
bool sendMessage(const QByteArray &buffer, int msgid, quint32 receiver=0, quint32 sender=0)
Send a network message msg with a given message ID msgid to all clients.
quint32 gameId() const
The unique ID of this game.
void aboutToLoseConnection(quint32 id)
Called when the network connection is about to terminate.
bool isOfferingConnections() const
Are we still offer offering server connections - only for game MASTER.
void setMaxClients(int max)
Changes the maximal connection number of the KMessageServer to max.
bool offerConnections(quint16 port)
Inits a network game as network MASTER.
void slotAdminStatusChanged(bool isAdmin)
This KGame object receives or loses the admin status.
void signalConnectionBroken()
Our connection to the KMessageServer has broken.
virtual void unlock()
void signalNetworkErrorMessage(int error, const QString &text)
A network error occurred.
void signalClientDisconnected(quint32 clientID, bool broken)
This signal is emitted whenever the KMessageServer sends us a message that a connection to a client w...
quint16 port() const
void receiveNetworkTransmission(const QByteArray &a, quint32 clientID)
Called by KMessageClient::broadcastReceived() and will check if the message format is valid.
bool stopServerConnection()
Stops offering server connections - only for game MASTER.
void sendError(int error, const QByteArray &message, quint32 receiver=0, quint32 sender=0)
Sends a network message.
void signalAdminStatusChanged(bool isAdmin)
This client gets or loses the admin status.
bool sendSystemMessage(const QByteArray &buffer, int msgid, quint32 receiver=0, quint32 sender=0)
Sends a network message msg with a given msg id msgid to all clients.
A client to connect to a KMessageServer.
void lock()
Once this function is called no message will be received anymore.
void eventClientConnected(quint32 clientID)
This signal is emitted when another client has connected to the server.
void broadcastReceived(const QByteArray &msg, quint32 senderID)
This signal is emitted when the client receives a broadcast message from the KMessageServer,...
void eventClientDisconnected(quint32 clientID, bool broken)
This signal is emitted when the server has lost the connection to one of the clients (This could be b...
void connectionBroken()
This signal is emitted when the connection to the KMessageServer is broken.
void adminStatusChanged(bool isAdmin)
This signal is emitted when this client becomes the admin client or when it loses the admin client st...
void unlock()
Deliver every message that was delayed by lock() and actually deliver all messages that get received ...
void aboutToDisconnect(quint32 id)
This signal is emitted right before the client disconnects.
void forwardReceived(const QByteArray &msg, quint32 senderID, const QList< quint32 > &receivers)
This signal is emitted when the client receives a forward message from the KMessageServer,...
This abstract base class represents one end of a message connections between two clients.
Definition kmessageio.h:44
virtual int rtti() const
The runtime identification.
Definition kmessageio.h:61
A server for message sending and broadcasting, using TCP/IP connections.
This class implements the message communication using a TCP/IP socket.
Definition kmessageio.h:150
char * data()
int size() const const
QIODevice * device() const const
int writeRawData(const char *s, int len)
Q_EMITQ_EMIT
QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
T qobject_cast(QObject *object)
QObject * sender() const const
bool isEmpty() const const
QByteArray toLatin1() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Sun Feb 25 2024 18:43:15 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.