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

KDE's Doxygen guidelines are available online.