KDEGames

kmessageserver.cpp
1/*
2 This file is part of the KDE games library
3 SPDX-FileCopyrightText: 2001 Burkhard Lehner <Burkhard.Lehner@gmx.de>
4
5 SPDX-License-Identifier: LGPL-2.0-only
6*/
7
8#include "kmessageserver.h"
9#include "kmessageserver_p.h"
10
11// own
12#include "kmessageio.h"
13#include <kdegamesprivate_kgame_logging.h>
14// Qt
15#include <QBuffer>
16#include <QDataStream>
17#include <QIODevice>
18#include <QList>
19#include <QQueue>
20#include <QTimer>
21
22// --------------- internal class KMessageServerSocket
23
24KMessageServerSocket::KMessageServerSocket(quint16 port, QObject *parent)
25 : QTcpServer(parent)
26{
27 listen(QHostAddress::Any, port);
28 connect(this, &KMessageServerSocket::newConnection, this, &KMessageServerSocket::slotNewConnection);
29}
30
31KMessageServerSocket::~KMessageServerSocket()
32{
33}
34
35void KMessageServerSocket::slotNewConnection()
36{
37 if (hasPendingConnections()) {
38 Q_EMIT newClientConnected(new KMessageSocket(nextPendingConnection()));
39 }
40}
41
42// ---------------- class for storing an incoming message
43
44class MessageBuffer
45{
46public:
47 MessageBuffer(quint32 clientID, const QByteArray &messageData)
48 : id(clientID)
49 , data(messageData)
50 {
51 }
52 ~MessageBuffer()
53 {
54 }
55 quint32 id;
56 QByteArray data;
57};
58
59// ---------------- KMessageServer's private class
60
61class KMessageServerPrivate
62{
63public:
64 KMessageServerPrivate() = default;
65
66 ~KMessageServerPrivate()
67 {
68 qDeleteAll(mClientList);
69 qDeleteAll(mMessageQueue);
70 }
71
72public:
73 int mMaxClients = -1;
74 int mGameId = 1;
75 quint16 mCookie;
76 quint32 mUniqueClientNumber = 1;
77 quint32 mAdminID = 0;
78
79 KMessageServerSocket *mServerSocket = nullptr;
80
81 QList<KMessageIO *> mClientList;
82 QQueue<MessageBuffer *> mMessageQueue;
83 QTimer mTimer;
84 bool mIsRecursive = false;
85};
86
87// ------------------ KMessageServer
88
90 : QObject(parent)
91 , d(new KMessageServerPrivate)
92{
93 d->mCookie = cookie;
95 qCDebug(KDEGAMESPRIVATE_KGAME_LOG) << "CREATE(KMessageServer=" << this << ") cookie=" << d->mCookie << "sizeof(this)=" << sizeof(KMessageServer);
96}
97
98KMessageServer::~KMessageServer()
99{
100 qCDebug(KDEGAMESPRIVATE_KGAME_LOG) << "this=" << this;
101 Debug();
102 stopNetwork();
104 qCDebug(KDEGAMESPRIVATE_KGAME_LOG) << "done";
105}
106
107//------------------------------------- TCP/IP server stuff
108
110{
111 qCDebug(KDEGAMESPRIVATE_KGAME_LOG);
112
113 if (d->mServerSocket) {
114 qCDebug(KDEGAMESPRIVATE_KGAME_LOG) << ": We were already offering connections!";
115 delete d->mServerSocket;
116 }
117
118 d->mServerSocket = new KMessageServerSocket(port);
119 d->mIsRecursive = false;
120
121 if (!d->mServerSocket || !d->mServerSocket->isListening()) {
122 qCCritical(KDEGAMESPRIVATE_KGAME_LOG) << ": Serversocket::ok() == false";
123 delete d->mServerSocket;
124 d->mServerSocket = nullptr;
125 return false;
126 }
127
128 qCDebug(KDEGAMESPRIVATE_KGAME_LOG) << ": Now listening to port " << d->mServerSocket->serverPort();
129 connect(d->mServerSocket, &KMessageServerSocket::newClientConnected, this, &KMessageServer::addClient);
130 return true;
131}
132
134{
135 if (d->mServerSocket)
136 return d->mServerSocket->serverPort();
137 else
138 return 0;
139}
140
142{
143 if (d->mServerSocket) {
144 delete d->mServerSocket;
145 d->mServerSocket = nullptr;
146 }
147}
148
150{
151 return d->mServerSocket != nullptr;
152}
153
154//----------------------------------------------- adding / removing clients
155
157{
158 QByteArray msg;
159
160 // maximum number of clients reached?
161 if (d->mMaxClients >= 0 && d->mMaxClients <= clientCount()) {
162 qCCritical(KDEGAMESPRIVATE_KGAME_LOG) << ": Maximum number of clients reached!";
163 return;
164 }
165
166 // give it a unique ID
167 client->setId(uniqueClientNumber());
168 qCDebug(KDEGAMESPRIVATE_KGAME_LOG) << ":" << client->id();
169
170 // connect its signals
171 connect(client, &KMessageIO::connectionBroken, this, &KMessageServer::removeBrokenClient);
173
174 // Tell everyone about the new guest
175 // Note: The new client doesn't get this message!
176 QDataStream(&msg, QIODevice::WriteOnly) << quint32(EVNT_CLIENT_CONNECTED) << client->id();
177 broadcastMessage(msg);
178
179 // add to our list
180 d->mClientList.push_back(client);
181
182 // tell it its ID
183 QDataStream(&msg, QIODevice::WriteOnly) << quint32(ANS_CLIENT_ID) << client->id();
184 client->send(msg);
185
186 // Give it the complete list of client IDs
187 QDataStream(&msg, QIODevice::WriteOnly) << quint32(ANS_CLIENT_LIST) << clientIDs();
188 client->send(msg);
189
190 if (clientCount() == 1) {
191 // if it is the first client, it becomes the admin
192 setAdmin(client->id());
193 } else {
194 // otherwise tell it who is the admin
195 QDataStream(&msg, QIODevice::WriteOnly) << quint32(ANS_ADMIN_ID) << adminID();
196 client->send(msg);
197 }
198
199 Q_EMIT clientConnected(client);
200}
201
202void KMessageServer::removeClient(KMessageIO *client, bool broken)
203{
204 quint32 clientID = client->id();
205 if (!d->mClientList.removeAll(client)) {
206 qCCritical(KDEGAMESPRIVATE_KGAME_LOG) << ": Deleting client that wasn't added before!";
207 return;
208 }
209
210 // tell everyone about the removed client
211 QByteArray msg;
212 QDataStream(&msg, QIODevice::WriteOnly) << quint32(EVNT_CLIENT_DISCONNECTED) << client->id() << (qint8)broken;
213 broadcastMessage(msg);
214
215 // If it was the admin, select a new admin.
216 if (clientID == adminID()) {
217 if (!d->mClientList.isEmpty())
218 setAdmin(d->mClientList.front()->id());
219 else
220 setAdmin(0);
221 }
222}
223
225{
226 qDeleteAll(d->mClientList);
227 d->mClientList.clear();
228 d->mAdminID = 0;
229}
230
231void KMessageServer::removeBrokenClient()
232{
233 KMessageIO *client = sender() ? qobject_cast<KMessageIO *>(sender()) : nullptr;
234 if (!client) {
235 qCCritical(KDEGAMESPRIVATE_KGAME_LOG) << ": sender of the signal was not a KMessageIO object!";
236 return;
237 }
238
239 Q_EMIT connectionLost(client);
240 removeClient(client, true);
241}
242
244{
245 d->mMaxClients = c;
246}
247
249{
250 return d->mMaxClients;
251}
252
254{
255 return d->mClientList.count();
256}
257
259{
260 QList<quint32> list;
261 list.reserve(d->mClientList.size());
262 for (QList<KMessageIO *>::iterator iter(d->mClientList.begin()); iter != d->mClientList.end(); ++iter)
263 list.append((*iter)->id());
264 return list;
265}
266
268{
269 if (no == 0)
270 no = d->mAdminID;
271
272 QList<KMessageIO *>::iterator iter = d->mClientList.begin();
273 while (iter != d->mClientList.end()) {
274 if ((*iter)->id() == no)
275 return (*iter);
276 ++iter;
277 }
278 return nullptr;
279}
280
282{
283 return d->mAdminID;
284}
285
286void KMessageServer::setAdmin(quint32 adminID)
287{
288 // Trying to set the client that is already admin => nothing to do
289 if (adminID == d->mAdminID)
290 return;
291
292 if (adminID > 0 && findClient(adminID) == nullptr) {
293 qCWarning(KDEGAMESPRIVATE_KGAME_LOG) << "Trying to set a new admin that doesn't exist!";
294 return;
295 }
296
297 d->mAdminID = adminID;
298
299 QByteArray msg;
300 QDataStream(&msg, QIODevice::WriteOnly) << quint32(ANS_ADMIN_ID) << adminID;
301
302 // Tell everyone about the new master
303 broadcastMessage(msg);
304}
305
306//------------------------------------------- ID stuff
307
309{
310 return d->mUniqueClientNumber++;
311}
312
313// --------------------- Messages ---------------------------
314
316{
317 for (QList<KMessageIO *>::iterator iter(d->mClientList.begin()); iter != d->mClientList.end(); ++iter)
318 (*iter)->send(msg);
319}
320
321void KMessageServer::sendMessage(quint32 id, const QByteArray &msg)
322{
323 KMessageIO *client = findClient(id);
324 if (client)
325 client->send(msg);
326}
327
329{
330 for (quint32 id : ids) {
331 sendMessage(id, msg);
332 }
333}
334
336{
337 KMessageIO *client = sender() ? qobject_cast<KMessageIO *>(sender()) : nullptr;
338 if (!client) {
339 qCCritical(KDEGAMESPRIVATE_KGAME_LOG) << ": slot was not called from KMessageIO!";
340 return;
341 }
342 // qCDebug(KDEGAMESPRIVATE_KGAME_LOG) << ": size=" << msg.size();
343 quint32 clientID = client->id();
344
345 // QByteArray *ta=new QByteArray;
346 // ta->duplicate(msg);
347 // d->mMessageQueue.enqueue (new MessageBuffer (clientID, *ta));
348
349 d->mMessageQueue.enqueue(new MessageBuffer(clientID, msg));
350 if (!d->mTimer.isActive())
351 d->mTimer.start(0); // AB: should be , TRUE i guess
352}
353
355{
356 // This shouldn't happen, since the timer should be stopped before. But only to be sure!
357 if (d->mMessageQueue.isEmpty()) {
358 d->mTimer.stop();
359 return;
360 }
361 if (d->mIsRecursive) {
362 return;
363 }
364 d->mIsRecursive = true;
365
366 MessageBuffer *msg_buf = d->mMessageQueue.head();
367
368 quint32 clientID = msg_buf->id;
369 QBuffer in_buffer(&msg_buf->data);
370 in_buffer.open(QIODevice::ReadOnly);
371 QDataStream in_stream(&in_buffer);
372
373 QByteArray out_msg;
374 QBuffer out_buffer(&out_msg);
375 out_buffer.open(QIODevice::WriteOnly);
376 QDataStream out_stream(&out_buffer);
377
378 bool unknown = false;
379
380 quint32 messageID;
381 in_stream >> messageID;
382 // qCDebug(KDEGAMESPRIVATE_KGAME_LOG) << ": got message with messageID=" << messageID;
383 switch (messageID) {
384 case REQ_BROADCAST:
385 out_stream << quint32(MSG_BROADCAST) << clientID;
386 // FIXME, compiler bug?
387 // this should be okay, since QBuffer is subclass of QIODevice! :
388 // out_buffer.write (in_buffer.readAll());
389 out_buffer.QIODevice::write(in_buffer.readAll());
390 broadcastMessage(out_msg);
391 break;
392
393 case REQ_FORWARD: {
394 QList<quint32> clients;
395 in_stream >> clients;
396 out_stream << quint32(MSG_FORWARD) << clientID << clients;
397 // see above!
398 out_buffer.QIODevice::write(in_buffer.readAll());
399 sendMessage(clients, out_msg);
400 } break;
401
402 case REQ_CLIENT_ID:
403 out_stream << quint32(ANS_CLIENT_ID) << clientID;
404 sendMessage(clientID, out_msg);
405 break;
406
407 case REQ_ADMIN_ID:
408 out_stream << quint32(ANS_ADMIN_ID) << d->mAdminID;
409 sendMessage(clientID, out_msg);
410 break;
411
412 case REQ_ADMIN_CHANGE:
413 if (clientID == d->mAdminID) {
414 quint32 newAdmin;
415 in_stream >> newAdmin;
416 setAdmin(newAdmin);
417 }
418 break;
419
420 case REQ_REMOVE_CLIENT:
421 if (clientID == d->mAdminID) {
422 QList<quint32> client_list;
423 in_stream >> client_list;
424 for (quint32 id : std::as_const(client_list)) {
425 KMessageIO *client = findClient(id);
426 if (client)
427 removeClient(client, false);
428 else
429 qCWarning(KDEGAMESPRIVATE_KGAME_LOG) << ": removing non-existing clientID";
430 }
431 }
432 break;
433
434 case REQ_MAX_NUM_CLIENTS:
435 if (clientID == d->mAdminID) {
436 qint32 maximum_clients;
437 in_stream >> maximum_clients;
438 setMaxClients(maximum_clients);
439 }
440 break;
441
442 case REQ_CLIENT_LIST: {
443 out_stream << quint32(ANS_CLIENT_LIST) << clientIDs();
444 sendMessage(clientID, out_msg);
445 } break;
446
447 default:
448 unknown = true;
449 }
450
451 // check if all the data has been used
452 if (!unknown && !in_buffer.atEnd())
453 qCWarning(KDEGAMESPRIVATE_KGAME_LOG) << ": Extra data received for message ID" << messageID;
454
455 Q_EMIT messageReceived(msg_buf->data, clientID, unknown);
456
457 if (unknown)
458 qCWarning(KDEGAMESPRIVATE_KGAME_LOG) << ": received unknown message ID" << messageID;
459
460 // remove the message, since we are ready with it
461 delete d->mMessageQueue.dequeue();
462 if (d->mMessageQueue.isEmpty())
463 d->mTimer.stop();
464 d->mIsRecursive = false;
465}
466
468{
469 qCDebug(KDEGAMESPRIVATE_KGAME_LOG) << "------------------ KMESSAGESERVER -----------------------";
470 qCDebug(KDEGAMESPRIVATE_KGAME_LOG) << "MaxClients : " << maxClients();
471 qCDebug(KDEGAMESPRIVATE_KGAME_LOG) << "NoOfClients : " << clientCount();
472 qCDebug(KDEGAMESPRIVATE_KGAME_LOG) << "---------------------------------------------------";
473}
474
475#include "moc_kmessageserver.cpp"
476#include "moc_kmessageserver_p.cpp"
This abstract base class represents one end of a message connections between two clients.
Definition kmessageio.h:44
virtual void send(const QByteArray &msg)=0
This slot sends the data block in /e msg to the connected object, that will emit /e received().
quint32 id()
Queries the ID of this object.
void connectionBroken()
This signal is emitted when the connection is closed.
void received(const QByteArray &msg)
This signal is emitted when /e send() on the connected KMessageIO object is called.
void setId(quint32 id)
Sets the ID number of this object.
void removeClient(KMessageIO *io, bool broken)
Removes the KMessageIO object from the client list and deletes it.
void clientConnected(KMessageIO *client)
A new client connected to the game.
KMessageIO * findClient(quint32 no) const
Find the KMessageIO object to the given client number.
quint32 adminID() const
Returns the clientID of the admin, if there is a admin, 0 otherwise.
void setMaxClients(int maxnumber)
sets the maximum number of clients which can connect.
virtual void Debug()
Gives debug output of the game status.
virtual void getReceivedMessage(const QByteArray &msg)
This slot receives all the messages from the KMessageIO::received signals.
quint16 serverPort() const
Returns the TCP/IP port number we are listening to for incoming connections.
void addClient(KMessageIO *)
Adds a new KMessageIO object to the communication server.
int maxClients() const
returns the maximum number of clients
void setAdmin(quint32 adminID)
Sets the admin to a new client with the given ID.
virtual void processOneMessage()
This slot is called whenever there are elements in the message queue.
quint32 uniqueClientNumber() const
bool initNetwork(quint16 port=0)
Starts the Communication server to listen for incoming TCP/IP connections.
void messageReceived(const QByteArray &data, quint32 clientID, bool &unknown)
This signal is always emitted when a message from a client is received.
void connectionLost(KMessageIO *client)
A network connection got broken.
void deleteClients()
Deletes all connections to the clients.
virtual void broadcastMessage(const QByteArray &msg)
Sends a message to all connected clients.
virtual void sendMessage(quint32 id, const QByteArray &msg)
Sends a message to a single client with the given ID.
void stopNetwork()
Stops listening for connections.
KMessageServer(quint16 cookie=42, QObject *parent=nullptr)
Create a KGameNetwork object.
bool isOfferingConnections() const
Are we still offer offering server connections?
QList< quint32 > clientIDs() const
returns a list of the unique IDs of all clients.
int clientCount() const
returns the current number of connected clients.
This class implements the message communication using a TCP/IP socket.
Definition kmessageio.h:150
virtual bool atEnd() const const override
virtual bool open(OpenMode flags) override
QByteArray readAll()
void append(QList< T > &&value)
void reserve(qsizetype size)
Q_EMITQ_EMIT
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
T qobject_cast(QObject *object)
QObject * sender() const const
QFuture< ArgsType< Signal > > connect(Sender *sender, Signal signal)
void timeout()
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Fri Nov 1 2024 18:48:56 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.