1 /** @file serversystem.cpp Subsystem for tending to clients.
2 *
3 * @authors Copyright © 2013-2017 Jaakko Keränen <jaakko.keranen@iki.fi>
4 * @authors Copyright © 2014 Daniel Swanson <danij@dengine.net>
5 *
6 * @par License
7 * GPL: http://www.gnu.org/licenses/gpl.html
8 *
9 * <small>This program is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by the
11 * Free Software Foundation; either version 2 of the License, or (at your
12 * option) any later version. This program is distributed in the hope that it
13 * will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
14 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
15 * Public License for more details. You should have received a copy of the GNU
16 * General Public License along with this program; if not, see:
17 * http://www.gnu.org/licenses</small>
18 */
19
20 #include "serversystem.h"
21
22 #include <de/c_wrapper.h>
23 #include <de/timer.h>
24 #include <de/Address>
25 #include <de/Beacon>
26 #include <de/ByteRefArray>
27 #include <de/Garbage>
28 #include <de/ListenSocket>
29 #include <de/TextApp>
30
31 #include "api_console.h"
32
33 #include "serverapp.h"
34 #include "shellusers.h"
35 #include "remoteuser.h"
36 #include "remotefeeduser.h"
37
38 #include "server/sv_def.h"
39 #include "server/sv_frame.h"
40
41 #include "network/net_main.h"
42 #include "network/net_buf.h"
43 #include "network/net_event.h"
44 #include "network/monitor.h"
45 #include "network/masterserver.h"
46
47 #include "dd_main.h"
48 #include "dd_loop.h"
49 #include "sys_system.h"
50 #include "world/map.h"
51 #include "world/p_players.h"
52
53 using namespace de;
54
55 char *nptIPAddress = (char *) ""; ///< Public domain for clients to connect to (cvar).
56 int nptIPPort = 0; ///< Server TCP port (cvar).
57
Server_ListenPort()58 static de::duint16 Server_ListenPort()
59 {
60 return (!nptIPPort ? DEFAULT_TCP_PORT : nptIPPort);
61 }
62
DENG2_PIMPL(ServerSystem)63 DENG2_PIMPL(ServerSystem)
64 {
65 bool inited = false;
66
67 /// Beacon for informing clients that a server is present.
68 Beacon beacon = { DEFAULT_UDP_PORT };
69 Time lastBeaconUpdateAt;
70
71 ListenSocket *serverSock = nullptr;
72
73 QHash<Id, RemoteUser *> users;
74 ShellUsers shellUsers;
75 Users remoteFeedUsers;
76
77 Impl(Public *i) : Base(i) {}
78 ~Impl() { deinit(); }
79
80 bool isStarted() const
81 {
82 return serverSock != nullptr;
83 }
84
85 bool init(duint16 port)
86 {
87 // Note: re-initialization is allowed, so we don't check for inited now.
88
89 LOG_NET_NOTE("Server listening on TCP port %i") << port;
90
91 deinit();
92
93 // Open a listening TCP socket. It will accept client connections.
94 DENG2_ASSERT(!serverSock);
95 if (!(serverSock = new ListenSocket(port)))
96 return false;
97
98 QObject::connect(serverSock, SIGNAL(incomingConnection()), thisPublic, SLOT(handleIncomingConnection()));
99
100 // Update the beacon with the new port.
101 beacon.start(port);
102
103 App_World().audienceForMapChange() += shellUsers;
104
105 inited = true;
106 return true;
107 }
108
109 void clearUsers()
110 {
111 // Clear the client nodes.
112 for (RemoteUser *u : users.values())
113 {
114 delete u;
115 }
116 DENG2_ASSERT(users.isEmpty());
117 }
118
119 void deinit()
120 {
121 if (!inited) return;
122 inited = false;
123
124 if (ServerApp::appExists())
125 {
126 App_World().audienceForMapChange() -= shellUsers;
127 }
128
129 beacon.stop();
130
131 // Close the listening socket.
132 delete serverSock;
133 serverSock = 0;
134
135 clearUsers();
136 }
137
138 RemoteUser &findUser(Id const &id) const
139 {
140 DENG2_ASSERT(users.contains(id));
141 return *users[id];
142 }
143
144 void updateBeacon(Clock const &clock)
145 {
146 if (lastBeaconUpdateAt.since() > 0.5)
147 {
148 lastBeaconUpdateAt = clock.time();
149
150 // Update the status message in the server's presence beacon.
151 if (serverSock && App_World().hasMap())
152 {
153 Block msg;
154 de::Writer(msg).withHeader() << ServerApp::currentServerInfo().strippedForBroadcast();
155 beacon.setMessage(msg);
156 }
157 }
158 }
159
160 /**
161 * The client is removed from the game immediately. This is used when
162 * the server needs to terminate a client's connection abnormally.
163 */
164 void terminateNode(Id const &id)
165 {
166 if (id)
167 {
168 DENG2_ASSERT(users.contains(id));
169
170 delete users[id];
171
172 DENG2_ASSERT(!users.contains(id));
173 }
174 }
175
176 void printStatus()
177 {
178 if (serverSock)
179 {
180 LOG_NOTE("SERVER: Listening on TCP port %i") << serverSock->port();
181 }
182 else
183 {
184 LOG_NOTE("SERVER: No server socket open");
185 }
186
187 int first = true;
188 for (int i = 1; i < DDMAXPLAYERS; ++i)
189 {
190 player_t *plr = DD_Player(i);
191 if (plr->remoteUserId)
192 {
193 DENG2_ASSERT(users.contains(plr->remoteUserId));
194
195 RemoteUser *user = users[plr->remoteUserId];
196 if (first)
197 {
198 LOG_MSG(_E(m) "P# Name: Nd Jo Hs Rd Gm Age:");
199 first = false;
200 }
201
202 LOG_MSG(_E(m) "%2i %-10s %2i %c %c %c %c %f sec")
203 << i << plr->name << plr->remoteUserId
204 << (user->isJoined()? '*' : ' ')
205 << (plr->handshake? '*' : ' ')
206 << (plr->ready? '*' : ' ')
207 << (plr->publicData().inGame? '*' : ' ')
208 << (Timer_RealSeconds() - plr->enterTime);
209 }
210 }
211 if (first)
212 {
213 LOG_MSG("No clients connected");
214 }
215
216 if (shellUsers.count())
217 {
218 LOG_MSG("%i shell user%s")
219 << shellUsers.count()
220 << DENG2_PLURAL_S(shellUsers.count());
221 }
222
223 if (remoteFeedUsers.count())
224 {
225 LOG_MSG("%i remote file system user%s")
226 << remoteFeedUsers.count()
227 << DENG2_PLURAL_S(remoteFeedUsers.count());
228 }
229
230 N_PrintBufferInfo();
231
232 LOG_MSG(_E(b) "Configuration:");
233 LOG_MSG(" Port for hosting games (net-ip-port): %i") << Con_GetInteger("net-ip-port");
234 LOG_MSG(" Shell password (server-password): \"%s\"") << netPassword;
235 }
236 };
237
ServerSystem()238 ServerSystem::ServerSystem() : d(new Impl(this))
239 {}
240
start(duint16 port)241 void ServerSystem::start(duint16 port)
242 {
243 d->init(port);
244 }
245
stop()246 void ServerSystem::stop()
247 {
248 d->deinit();
249 }
250
isListening() const251 bool ServerSystem::isListening() const
252 {
253 return d->isStarted();
254 }
255
terminateNode(Id const & id)256 void ServerSystem::terminateNode(Id const &id)
257 {
258 d->terminateNode(id);
259 }
260
user(Id const & id) const261 RemoteUser &ServerSystem::user(Id const &id) const
262 {
263 if (!d->users.contains(id))
264 {
265 throw IdError("ServerSystem::user", "User " + id.asText() + " does not exist");
266 }
267 return *d->users[id];
268 }
269
isUserAllowedToJoin(RemoteUser &) const270 bool ServerSystem::isUserAllowedToJoin(RemoteUser &/*user*/) const
271 {
272 if (!CVar_Byte(Con_FindVariable("server-allowjoin"))) return false;
273 // If the server is full, attempts to connect are canceled.
274 return (Sv_GetNumConnected() < svMaxPlayers);
275 }
276
convertToShellUser(RemoteUser * user)277 void ServerSystem::convertToShellUser(RemoteUser *user)
278 {
279 DENG2_ASSERT(user);
280 LOG_AS("convertToShellUser");
281
282 Socket *socket = user->takeSocket();
283
284 LOGDEV_NET_VERBOSE("Remote user %s converted to shell user") << user->id();
285 user->deleteLater();
286
287 d->shellUsers.add(new ShellUser(socket));
288 }
289
convertToRemoteFeedUser(RemoteUser * user)290 void ServerSystem::convertToRemoteFeedUser(RemoteUser *user)
291 {
292 DENG2_ASSERT(user);
293
294 Socket *socket = user->takeSocket();
295 LOGDEV_NET_VERBOSE("Remote user %s converted to remote file system user") << user->id();
296 user->deleteLater();
297
298 d->remoteFeedUsers.add(new RemoteFeedUser(socket));
299 }
300
userCount() const301 int ServerSystem::userCount() const
302 {
303 return d->remoteFeedUsers.count() +
304 d->shellUsers.count() +
305 d->users.size();
306 }
307
timeChanged(Clock const & clock)308 void ServerSystem::timeChanged(Clock const &clock)
309 {
310 if (Sys_IsShuttingDown())
311 return; // Shouldn't run this while shutting down.
312
313 Garbage_Recycle();
314
315 // Adjust loop rate depending on whether users are connected.
316 DENG2_TEXT_APP->loop().setRate(userCount()? 35 : 3);
317
318 Loop_RunTics();
319
320 // Update clients at regular intervals.
321 Sv_TransmitFrame();
322
323 d->updateBeacon(clock);
324
325 /// @todo There's no need to queue packets via net_buf, just handle
326 /// them right away.
327 Sv_GetPackets();
328
329 /// @todo Kick unjoined nodes who are silent for too long.
330 }
331
handleIncomingConnection()332 void ServerSystem::handleIncomingConnection()
333 {
334 LOG_AS("ServerSystem");
335 forever
336 {
337 Socket *sock = d->serverSock->accept();
338 if (!sock) break;
339
340 RemoteUser *user = new RemoteUser(sock);
341 connect(user, SIGNAL(userDestroyed()), this, SLOT(userDestroyed()));
342 d->users.insert(user->id(), user);
343
344 // Immediately handle pending messages, if there are any.
345 user->handleIncomingPackets();
346 }
347 }
348
userDestroyed()349 void ServerSystem::userDestroyed()
350 {
351 RemoteUser *u = static_cast<RemoteUser *>(sender());
352
353 LOG_AS("ServerSystem");
354 LOGDEV_NET_VERBOSE("Removing user %s") << u->id();
355
356 d->users.remove(u->id());
357
358 LOG_NET_VERBOSE("Remaining user count: %i remote, %i shell, %i filesys")
359 << d->users.size()
360 << d->shellUsers.count()
361 << d->remoteFeedUsers.count();
362 }
363
printStatus()364 void ServerSystem::printStatus()
365 {
366 d->printStatus();
367 }
368
App_ServerSystem()369 ServerSystem &App_ServerSystem()
370 {
371 return ServerApp::serverSystem();
372 }
373
374 //---------------------------------------------------------------------------
375
Server_Register()376 void Server_Register()
377 {
378 C_VAR_CHARPTR("net-ip-address", &nptIPAddress, 0, 0, 0);
379 C_VAR_INT ("net-ip-port", &nptIPPort, CVF_NO_MAX, 0, 0);
380
381 #ifdef _DEBUG
382 C_CMD("netfreq", NULL, NetFreqs);
383 #endif
384 }
385
N_ServerOpen()386 dd_bool N_ServerOpen()
387 {
388 App_ServerSystem().start(Server_ListenPort());
389
390 // The game module may have something that needs doing before we actually begin.
391 if (gx.NetServerStart)
392 {
393 gx.NetServerStart(true);
394 }
395
396 Sv_StartNetGame();
397
398 // The game DLL might want to do something now that the server is started.
399 if (gx.NetServerStart)
400 {
401 gx.NetServerStart(false);
402 }
403
404 if (serverPublic)
405 {
406 // Let the master server know that we are running a public server.
407 N_MasterAnnounceServer(true);
408 }
409
410 return true;
411 }
412
N_ServerClose()413 dd_bool N_ServerClose()
414 {
415 if (!App_ServerSystem().isListening()) return true;
416
417 if (serverPublic)
418 {
419 // Bye-bye, master server.
420 N_MAClear();
421 N_MasterAnnounceServer(false);
422 }
423
424 if (gx.NetServerStop)
425 {
426 gx.NetServerStop(true);
427 }
428
429 Net_StopGame();
430 Sv_StopNetGame();
431
432 if (gx.NetServerStop)
433 {
434 gx.NetServerStop(false);
435 }
436
437 App_ServerSystem().stop();
438 return true;
439 }
440
N_PrintNetworkStatus()441 void N_PrintNetworkStatus()
442 {
443 App_ServerSystem().printStatus();
444 }
445