BF2MC-Matchmaker
server.cpp
1 #include <cstdio>
2 #include <cstdlib>
3 #include <string>
4 #include <iostream>
5 #include <unistd.h>
6 #include <thread>
7 #include <netinet/in.h>
8 #include <sys/socket.h>
9 #include <arpa/inet.h>
10 #include <algorithm>
11 
12 #include <logger.h>
13 #include <settings.h>
14 #include <gpsp/client.h>
15 #include <gpcm/client.h>
16 #include <webserver/client.h>
17 #include <browsing/client.h>
18 #include <gamestats/client.h>
19 #include <qr/client.h>
20 #include <websocket/client.h>
21 
22 #include <server.h>
23 
25 {
26  std::shared_lock<std::shared_mutex> guard(g_settings_mutex); // settings lock (read)
27 
28  int port = -1;
29  int socket_type = SOCK_STREAM;
30 
31  // socket options
32  int opt_reuse = 1; // Enable reuse option
33 
34  this->_type = type;
35 
36  // setting
37  switch(type)
38  {
39  case Server::Type::QR:
40  port = g_settings["qr"]["port"].asInt();
41  socket_type = SOCK_DGRAM;
42  break;
43  case Server::Type::GPSP:
44  port = g_settings["gpsp"]["port"].asInt();
45  break;
46  case Server::Type::GPCM:
47  port = g_settings["gpcm"]["port"].asInt();
48  break;
49  case Server::Type::Webserver:
50  port = g_settings["webserver"]["port"].asInt();
51  break;
52  case Server::Type::Browsing:
53  port = g_settings["browsing"]["port"].asInt();
54  break;
55  case Server::Type::GameStats:
56  port = g_settings["gamestats"]["port"].asInt();
57  break;
58  case Server::Type::Websocket:
59  port = g_settings["websocket"]["port"].asInt();
60  break;
61  }
62 
63  if ((this->_socket = socket(AF_INET, socket_type, 0)) < 0)
64  {
65  Logger::error("Server::Server() at socket", this->_type);
66  exit(EXIT_FAILURE);
67  }
68 
69  if (setsockopt(this->_socket, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt_reuse, sizeof(opt_reuse)))
70  {
71  Logger::error("Server::Server() at setsockopt with opt_reuse", this->_type);
72  exit(EXIT_FAILURE);
73  }
74 
75  this->_address.sin_family = AF_INET;
76  this->_address.sin_addr.s_addr = INADDR_ANY;
77  this->_address.sin_port = htons(port);
78 
79  if (bind(this->_socket, (struct sockaddr*)&this->_address, sizeof(this->_address)) < 0)
80  {
81  Logger::error("Server::Server() at bind", this->_type);
82  exit(EXIT_FAILURE);
83  }
84 }
85 
86 std::vector<std::shared_ptr<Net::Socket>> Server::GetClients()
87 {
88  std::lock_guard<std::mutex> guard(this->_mutex); // server lock
89 
90  return this->_clients;
91 }
92 
94 {
95  int client_socket;
96  struct sockaddr_in client_address;
97  socklen_t client_address_len = sizeof(client_address);
98 
99  if (listen(this->_socket, 3) < 0)
100  {
101  Logger::error("Server::Listen() on listen", this->_type);
102  return;
103  }
104 
105  this->onServerListen();
106 
107  while(true)
108  {
109  if ((client_socket = accept(this->_socket, (struct sockaddr*)&client_address, &client_address_len)) < 0)
110  {
111  Logger::error("Server::Listen() on accept", this->_type);
112  return;
113  }
114 
115  switch(this->_type)
116  {
117  case Server::Type::GPSP:
118  {
119  std::lock_guard<std::mutex> guard(this->_mutex); // server lock
120 
121  this->_clients.push_back(std::make_shared<GPSP::Client>(client_socket, client_address));
122 
123  std::shared_ptr<Net::Socket> client = this->_clients.back();
124 
125  this->onClientConnect(client);
126 
127  std::thread t([client]() {
128  static_cast<GPSP::Client*>(client.get())->Listen();
129  });
130  t.detach();
131  }
132  break;
133  case Server::Type::GPCM:
134  {
135  std::lock_guard<std::mutex> guard(this->_mutex); // server lock
136 
137  this->_clients.push_back(std::make_shared<GPCM::Client>(client_socket, client_address));
138 
139  std::shared_ptr<Net::Socket> client = this->_clients.back();
140 
141  this->onClientConnect(client);
142 
143  std::thread t([client]() {
144  static_cast<GPCM::Client*>(client.get())->Listen();
145  });
146  t.detach();
147  }
148  break;
149  case Server::Type::Browsing:
150  {
151  std::lock_guard<std::mutex> guard(this->_mutex); // server lock
152 
153  this->_clients.push_back(std::make_shared<Browsing::Client>(client_socket, client_address));
154 
155  std::shared_ptr<Net::Socket> client = this->_clients.back();
156 
157  this->onClientConnect(client);
158 
159  std::thread t([client]() {
160  static_cast<Browsing::Client*>(client.get())->Listen();
161  });
162  t.detach();
163  }
164  break;
165  case Server::Type::Webserver:
166  {
167  std::lock_guard<std::mutex> guard(this->_mutex); // server lock
168 
169  this->_clients.push_back(std::make_shared<Webserver::Client>(client_socket, client_address));
170 
171  std::shared_ptr<Net::Socket> client = this->_clients.back();
172 
173  this->onClientConnect(client);
174 
175  std::thread t([client]() {
176  static_cast<Webserver::Client*>(client.get())->Listen();
177  });
178  t.detach();
179  }
180  break;
181  case Server::Type::GameStats:
182  {
183  std::lock_guard<std::mutex> guard(this->_mutex); // server lock
184 
185  this->_clients.push_back(std::make_shared<GameStats::Client>(client_socket, client_address));
186 
187  std::shared_ptr<Net::Socket> client = this->_clients.back();
188 
189  this->onClientConnect(client);
190 
191  std::thread t([client]() {
192  static_cast<GameStats::Client*>(client.get())->Listen();
193  });
194  t.detach();
195  }
196  break;
197  case Server::Type::Websocket:
198  {
199  std::lock_guard<std::mutex> guard(this->_mutex); // server lock
200 
201  this->_clients.push_back(std::make_shared<Websocket::Client>(client_socket, client_address));
202 
203  std::shared_ptr<Net::Socket> client = this->_clients.back();
204 
205  this->onClientConnect(client);
206 
207  std::thread t([client]() {
208  static_cast<Websocket::Client*>(client.get())->Listen();
209  });
210  t.detach();
211  }
212  break;
213  }
214  }
215 }
216 
217 //
218 // ChatGPT:
219 // You are correct that UDP is a stateless protocol, and there is no inherent mechanism to determine
220 // if a client is still connected in the same way as TCP. In UDP, you won't receive disconnection notifications,
221 // and you can't rely on the absence of incoming packets to detect disconnections, as you would in TCP.
222 //
223 
225 {
226  struct sockaddr_in client_address;
227  socklen_t client_address_len = sizeof(client_address);
228 
229  this->onServerListen();
230 
231  while (true)
232  {
233  std::vector<unsigned char> buffer(2048, 0);
234 
235  // recieve data from udp connection
236  ssize_t recv_size = recvfrom(
237  this->_socket, // Listening socket
238  &(buffer[0]), 2048, // Data
239  0, // Flags
240  (struct sockaddr*)&client_address, &client_address_len // Address
241  );
242 
243  if (recv_size > 0)
244  {
245  buffer.resize(recv_size);
246 
247  switch(this->_type)
248  {
249  case Server::Type::QR:
250  QR::Client client = QR::Client(this->_socket, client_address);
251 
252  this->onClientConnect(client);
253 
254  client.onRequest(buffer);
255 
256  this->onClientDisconnect(client);
257  break;
258  }
259  }
260  else
261  {
262  Logger::error("Server::UDPListen() on recvfrom with state \"" + std::to_string(recv_size) + "\"", this->_type);
263  return;
264  }
265  }
266 }
267 
269 {
270  for(std::shared_ptr<Net::Socket> client : this->_clients)
271  {
272  switch(this->_type)
273  {
274  case Server::Type::GPSP:
275  dynamic_cast<GPSP::Client*>(client.get())->Disconnect();
276  break;
277  case Server::Type::GPCM:
278  dynamic_cast<GPCM::Client*>(client.get())->Disconnect();
279  break;
280  case Server::Type::Browsing:
281  dynamic_cast<Browsing::Client*>(client.get())->Disconnect();
282  break;
283  case Server::Type::Webserver:
284  dynamic_cast<Webserver::Client*>(client.get())->Disconnect();
285  break;
286  case Server::Type::GameStats:
287  dynamic_cast<GameStats::Client*>(client.get())->Disconnect();
288  break;
289  }
290  }
291 }
292 
294 {
295  shutdown(this->_socket, SHUT_RDWR);
296 
298 }
299 
300 /*
301  Events
302 */
303 
305 {
306  Logger::info("Server is now listening on " + this->GetAddress() + " " + this->GetSocketType(), this->_type);
307 }
308 
310 {
311  Logger::info("Server shutdown", this->_type);
312 }
313 
314 void Server::onClientConnect(const Net::Socket& client) const
315 {
316  std::shared_lock<std::shared_mutex> guard2(g_settings_mutex); // settings lock (read)
317 
318  if ((g_logger_mode & Logger::Mode::Development) != 0)
319  {
320  Logger::info("Client " + client.GetAddress() + " connected",
321  this->_type, g_settings["show_client_connect"].asBool());
322  }
323 }
324 
325 void Server::onClientConnect(const std::shared_ptr<Net::Socket>& client) const
326 {
327  std::shared_lock<std::shared_mutex> guard2(g_settings_mutex); // settings lock (read)
328 
329  if ((g_logger_mode & Logger::Mode::Development) != 0)
330  {
331  Logger::info("Client " + client->GetAddress() + " connected",
332  this->_type, g_settings["show_client_connect"].asBool());
333  }
334 }
335 
337 {
338  std::shared_lock<std::shared_mutex> guard2(g_settings_mutex); // settings lock (read)
339 
340  if(this->GetSocketType() == "tcp")
341  {
342  std::lock_guard<std::mutex> guard(this->_mutex); // server lock
343 
344  // Find shared pointer in clients list
345  auto it = std::find_if(this->_clients.begin(), this->_clients.end(),
346  [rawPtrToSearch = const_cast<Net::Socket*>(&client)](const std::shared_ptr<Net::Socket>& ptr)
347  {
348  return ptr.get() == rawPtrToSearch;
349  }
350  );
351 
352  // When found remove client
353  if (it != this->_clients.end())
354  {
355  if ((g_logger_mode & Logger::Mode::Development) != 0)
356  {
357  Logger::info("Client " + client.GetAddress() + " disconnected",
358  this->_type, g_settings["show_client_disconnect"].asBool());
359  }
360 
361  this->_clients.erase(it);
362  }
363  }
364  else
365  {
366  if ((g_logger_mode & Logger::Mode::Development) != 0)
367  {
368  Logger::info("Client " + client.GetAddress() + " disconnected",
369  this->_type, g_settings["show_client_disconnect"].asBool());
370  }
371  }
372 }
373 
Client class for handling browsing requests.
void Listen()
Listens for incoming requests from the client.
Represents a GPCM client.
Definition: gpcm/client.h:34
void Listen()
Listens for incoming messages from the GPCM client.
Definition: gpcm/client.cpp:45
Client class for GPSP protocol.
Definition: gpsp/client.h:13
void Listen()
Listen for incoming messages.
Definition: gpsp/client.cpp:37
Represents a client for game statistics.
void Listen()
Start listening for client requests.
A base class representing a network socket.
Definition: socket.h:15
struct sockaddr_in _address
Definition: socket.h:18
std::string GetSocketType() const
Gets the socket type.
Definition: socket.cpp:49
std::string GetAddress() const
Gets the full address (IP:Port) associated with the socket.
Definition: socket.cpp:44
int _socket
Definition: socket.h:17
Represents a client for QR protocol.
Definition: qr/client.h:13
void onRequest(const std::vector< unsigned char > &request)
Event handler for incoming requests.
Definition: qr/client.cpp:37
std::mutex _mutex
Definition: server.h:30
std::vector< std::shared_ptr< Net::Socket > > GetClients()
Get the vector of client sockets connected to this server.
Definition: server.cpp:86
void Close()
Close the server and stop listening for incoming connections.
Definition: server.cpp:293
void onClientConnect(const Net::Socket &client) const
Called when a client connects to the server.
Definition: server.cpp:314
std::vector< std::shared_ptr< Net::Socket > > _clients
Definition: server.h:28
Server(Server::Type type)
Constructor for the Server class.
Definition: server.cpp:24
void UDPListen()
Start listening for incoming UDP packets on the server.
Definition: server.cpp:224
Type
Enum defining the types of servers.
Definition: server.h:16
void onClientDisconnect(const Net::Socket &client)
Called when a client disconnects from the server.
Definition: server.cpp:336
Server::Type _type
Definition: server.h:29
void DisconnectAllClients()
Disconnect all connected clients from the server.
Definition: server.cpp:268
void onServerListen() const
Called when the server starts listening for incoming connections.
Definition: server.cpp:304
void onServerShutdown() const
Called when the server is shutting down.
Definition: server.cpp:309
void Listen()
Start listening for incoming connections on the server.
Definition: server.cpp:93
void Listen()
Start listening for incoming requests.