Symphony Of Empires
network.cpp
Go to the documentation of this file.
1 // Eng3D - General purpouse game engine
2 // Copyright (C) 2021, Eng3D contributors
3 //
4 // This program is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU General Public License as published by
6 // the Free Software Foundation, either version 3 of the License, or
7 // (at your option) any later version.
8 //
9 // This program is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 // GNU General Public License for more details.
13 //
14 // You should have received a copy of the GNU General Public License
15 // along with this program. If not, see <https://www.gnu.org/licenses/>.
16 //
17 // ----------------------------------------------------------------------------
18 // Name:
19 // network.cpp
20 //
21 // Abstract:
22 // Server and Client networking functions, along with primitive ones such
23 // as recv and send. This abstracts away all the "OS-dependant" parts of
24 // the networking code for the game.
25 // ----------------------------------------------------------------------------
26 
27 #include <cstring>
28 #include <cstdio>
29 #include <cstdlib>
30 #include <mutex>
31 #include <numeric>
32 
33 #include <glm/glm.hpp>
34 // Visual Studio does not know about UNISTD.H, Mingw does through
35 #ifndef _MSC_VER
36 # include <unistd.h>
37 #endif
38 #ifdef E3D_TARGET_UNIX
39 # define _XOPEN_SOURCE_EXTENDED 1
40 # include <sys/socket.h>
41 # include <netinet/in.h>
42 # ifndef INVALID_SOCKET
43 # define INVALID_SOCKET -1
44 # endif
45 # include <netdb.h>
46 # include <arpa/inet.h>
47 # include <poll.h>
48 # include <signal.h>
49 # include <fcntl.h>
50 #define NETWORK_FLAG 0/*MSG_DONTWAIT*/
51 #elif defined E3D_TARGET_WINDOWS
52 # define _WINSOCK_DEPRECATED_NO_WARNINGS 1
53 # ifndef WINSOCK2_IMPORTED
54 # define WINSOCK2_IMPORTED
55 # include <winsock2.h>
56 # include <ws2tcpip.h>
57 # endif
58 # include <windows.h>
59 #define NETWORK_FLAG MSG_DONTROUTE
60 #endif
61 #include <sys/types.h>
62 
63 #include "eng3d/network.hpp"
64 #include "eng3d/log.hpp"
65 #include "eng3d/serializer.hpp"
66 #include "eng3d/utils.hpp"
67 
68 constexpr static int max_tries = 10; // 10 * 100ms = 10 seconds
69 constexpr static int tries_ms = 100;
70 
71 //
72 // Socket stream
73 //
74 bool Eng3D::Networking::SocketStream::send(const void* data, size_t size, std::function<bool()> pred) {
75  const auto* c_data = reinterpret_cast<const char*>(data);
76  auto tries = max_tries;
77  for(size_t i = 0; i < size; ) {
78  if(pred && !pred()) // If (any) predicate fails then return immediately
79  break;
80  int r = ::send(fd, &c_data[i], glm::min<std::size_t>(1024, size - i), NETWORK_FLAG);
81  if(r <= 0) {
82  if(!tries)
83  return false;
84  tries--;
85  std::this_thread::sleep_for(std::chrono::milliseconds(tries_ms));
86  continue;
87  }
88  i += static_cast<std::size_t>(r);
89  tries = max_tries;
90  }
91  return true;
92 }
93 
94 bool Eng3D::Networking::SocketStream::recv(void* data, size_t size, std::function<bool()> pred) {
95  auto* c_data = reinterpret_cast<char*>(data);
96  std::memset(c_data, 0, size);
97  auto tries = max_tries;
98  for(size_t i = 0; i < size; ) {
99  if(pred && !pred()) // If (any) predicate fails then return immediately
100  break;
101  int r = ::recv(fd, &c_data[i], glm::min<std::size_t>(1024, size - i), NETWORK_FLAG);
102  if(r <= 0) {
103  if(!tries)
104  return false;
105  tries--;
106  std::this_thread::sleep_for(std::chrono::milliseconds(tries_ms));
107  continue;
108  }
109  i += static_cast<std::size_t>(r);
110  tries = max_tries;
111  }
112  return true;
113 }
114 
116  // See https://stackoverflow.com/questions/2876024/linux-is-there-a-read-or-recv-from-socket-with-timeout
117 #ifdef E3D_TARGET_UNIX
118  struct timeval tv;
119  tv.tv_sec = seconds;
120  tv.tv_usec = 0;
121  setsockopt(this->fd, SOL_SOCKET, SO_RCVTIMEO, reinterpret_cast<const char*>(&tv), sizeof tv);
122 #elif defined E3D_TARGET_WINDOWS
123  DWORD timeout = seconds * 1000;
124  setsockopt(this->fd, SOL_SOCKET, SO_RCVTIMEO, reinterpret_cast<const char*>(&timeout), sizeof timeout);
125 #endif
126 }
127 
129  // Check if we need to read packets
130 #ifdef E3D_TARGET_UNIX
131  struct pollfd pfd{};
132  pfd.fd = this->fd;
133  pfd.events = POLLIN;
134  int has_pending = poll(&pfd, 1, 10);
135  return (pfd.revents & POLLIN) != 0 || has_pending;
136 #elif defined E3D_TARGET_WINDOWS
137  u_long has_pending = 0;
138  int test = ioctlsocket(this->fd, FIONREAD, &has_pending);
139  return has_pending;
140 #endif
141 }
142 
144 #ifdef E3D_TARGET_UNIX
145  int flags = fcntl(fd, F_GETFL, 0);
146  if(flags == -1) {
147  Eng3D::Log::debug("socket_stream", translate("Can't set socket as non_blocking"));
148  return;
149  }
150  flags = blocking ? (flags & (~O_NONBLOCK)) : (flags | O_NONBLOCK);
151  fcntl(fd, F_SETFL, flags);
152 #elif defined E3D_TARGET_WINDOWS
153  u_long mode = blocking ? 0 : 1;
154  ioctlsocket(fd, FIONBIO, &mode);
155 #endif
156 }
157 
158 //
159 // Packet
160 //
162  assert(n_data > 0);
163 
164  const uint16_t net_code = htons(static_cast<uint16_t>(code));
165  if(!stream.send(&net_code, sizeof(net_code), pred))
166  return false;
167 
168  const uint16_t net_size = htons(n_data);
169  if(!stream.send(&net_size, sizeof(net_size), pred))
170  return false;
171 
172  if(!stream.send(buffer.data(), n_data, pred))
173  return false;
174 
175  const uint16_t eof_marker = htons(0xFE0F);
176  if(!stream.send(&eof_marker, sizeof(eof_marker), pred))
177  return false;
178  return true;
179 }
180 
182  uint16_t net_code;
183  if(!stream.recv(&net_code, sizeof(net_code), pred))
184  return false;
185  code = static_cast<PacketCode>(ntohs(net_code));
186 
187  uint16_t net_size;
188  if(!stream.recv(&net_size, sizeof(net_size), pred))
189  return false;
190  n_data = static_cast<size_t>(ntohs(net_size));
191  if(!n_data)
192  return false;
193 
194  buffer.resize(n_data);
195  if(!stream.recv(buffer.data(), buffer.size(), pred))
196  return false;
197 
198  uint16_t eof_marker;
199  if(!stream.recv(&eof_marker, sizeof(eof_marker), pred))
200  return false;
201  assert(ntohs(eof_marker) == 0xFE0F);
202  if(ntohs(eof_marker) != 0xFE0F)
203  return false;
204  return true;
205 }
206 
207 //
208 // Server client
209 //
211  if(this->thread && this->thread->joinable())
212  this->thread->join();
213 }
214 
216  sockaddr_in client;
217  socklen_t len = sizeof(client);
218  conn_fd = accept(fd, reinterpret_cast<sockaddr*>(&client), &len);
219  if(conn_fd == INVALID_SOCKET)
220  return 0;
221 
222  // At this point the client's connection was accepted - so we only have to check
223  // Then we check if the server is running and we throw accordingly
224  is_connected = true;
225  Eng3D::Log::debug("server", translate("New client connection established"));
226  return conn_fd;
227 }
228 
231 
232 }
233 
236 }
237 
238 //
239 // Server
240 //
241 Eng3D::Networking::Server::Server(const unsigned port, const unsigned max_conn)
242  : n_clients{ static_cast<std::size_t>(max_conn) }
243 {
244 #ifdef E3D_TARGET_WINDOWS
245  WSADATA data;
246  WSAStartup(MAKEWORD(2, 2), &data);
247 #endif
248 
249  addr = {};
250  addr.sin_family = AF_INET;
251  addr.sin_addr.s_addr = htonl(INADDR_ANY);
252  addr.sin_port = htons(port);
253 
254  fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
255  if(fd == INVALID_SOCKET)
256  CXX_THROW(Eng3D::Networking::SocketException, translate("Cannot create server socket"));
257 #ifdef E3D_TARGET_UNIX
258  int enable = 1;
259  setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(int));
260  setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, &enable, sizeof(int));
261 #endif
262  if(bind(fd, reinterpret_cast<sockaddr*>(&addr), sizeof(addr)) != 0)
264  if(listen(fd, max_conn) != 0)
265  CXX_THROW(Eng3D::Networking::SocketException, translate("Cannot listen in specified number of concurrent connections"));
266 #ifdef E3D_TARGET_UNIX
267  // Allow non-blocking operations on this socket (we don't want to block on multi-listener servers)
268  fcntl(fd, F_SETFL, fcntl(fd, F_GETFD, 0) | O_NONBLOCK);
269  // We need to ignore pipe signals since any client disconnecting **will** kill the server
270  signal(SIGPIPE, SIG_IGN);
271 #endif
272  this->run = true;
273  Eng3D::Log::debug("server", Eng3D::translate_format("Server listening on IP port *::%u", port));
274 }
275 
277  this->run = false;
278 #ifdef E3D_TARGET_UNIX
279  close(fd);
280 #elif defined E3D_TARGET_WINDOWS
281  closesocket(fd);
282  WSACleanup();
283 #endif
284  // Join all threads before deletion
285  for(size_t i = 0; i < this->n_clients; i++)
286  if(this->clients[i].thread && this->clients[i].thread->joinable())
287  this->clients[i].thread->join();
288  delete[] this->clients;
289 }
290 
293  for(size_t i = 0; i < n_clients; i++)
294  if(clients[i].is_connected == true)
295  clients[i].packets.push(packet);
296 }
297 
298 void Eng3D::Networking::Server::do_netloop(std::function<void(int i)> on_wake_thread, int id) {
299  auto& cl = clients[id];
300  int conn_fd = 0;
301  try {
302  cl.is_connected = false;
303  while(!cl.is_connected) {
304  conn_fd = cl.try_connect(fd);
305  // Perform a 5 second delay between connection tries
306  const auto delta = std::chrono::seconds{ 5 };
307  const auto start_time = std::chrono::system_clock::now();
308  std::this_thread::sleep_until(start_time + delta);
309  if(!this->run)
311  }
312 
313  Eng3D::Networking::Packet packet(conn_fd);
314  packet.pred = ([this]() {return this->run == true;});
315 
316  player_count++;
317  // Wake up another thread
318  for(size_t i = 0; i < n_clients; i++) {
319  if(clients[i].thread == nullptr) {
320  on_wake_thread(i);
321  break;
322  }
323  }
324  on_connect(conn_fd, id); // Read the data from client
326  while(this->run && cl.is_connected == true) {
327  cl.flush_packets();
328  if(cl.has_pending()) { // Check if we need to read packets
329  if(!packet.recv())
330  continue;
331  ar.set_buffer(packet.data(), packet.size());
332  ar.rewind();
333  Eng3D::Log::debug("server", translate_format("Receiving %zuB from #%i", packet.size(), id));
334  handler(packet, ar, id);
335  }
336  ar.buffer.clear();
337  ar.rewind();
338 
339  // After reading everything we will send our queue appropriately to the client
340  Eng3D::Networking::Packet tosend_packet;
341  while(cl.packets.try_pop(tosend_packet)) {
342  tosend_packet.stream = Eng3D::Networking::SocketStream(conn_fd);
343  Eng3D::Log::debug("server", translate_format("Sending package of %zuB", tosend_packet.size()));
344  if(!tosend_packet.send())
345  continue;
346  }
347  cl.packets.clear();
348  }
350  Eng3D::Log::error("server", std::string() + "Eng3D::Networking::Server::Exception: " + e.what());
352  Eng3D::Log::error("server", std::string() + "Eng3D::Networking::SocketException: " + e.what());
353  } catch(Eng3D::Deser::Exception& e) {
354  Eng3D::Log::error("server", std::string() + "Eng3D::Deser::Exception: " + e.what());
355  }
356 
357  player_count--;
358 
359  // Unlock mutexes so we don't end up with weird situations... like deadlocks
360  cl.is_connected = false;
361  cl.packets.clear();
362 
363  on_disconnect();
364 #ifdef E3D_TARGET_WINDOWS
365  Eng3D::Log::error("server", Eng3D::translate_format("WSA Code: %i", WSAGetLastError()));
366  WSACleanup();
367 #endif
368  Eng3D::Log::debug("server", "Client disconnected");
369 #ifdef E3D_TARGET_WINDOWS
370  shutdown(conn_fd, SD_BOTH);
371 #elif defined E3D_TARGET_UNIX && !defined E3D_TARGET_SWITCH
372  // Switch doesn't support shutting down sockets
373  shutdown(conn_fd, SHUT_RDWR);
374 #endif
375 }
376 
377 //
378 // Client
379 //
380 Eng3D::Networking::Client::Client(std::string host, const unsigned port) {
381  // Initialize WSA
382 #ifdef E3D_TARGET_WINDOWS
383  WSADATA data;
384  if(WSAStartup(MAKEWORD(2, 2), &data) != 0) {
385  Eng3D::Log::error("network", Eng3D::translate_format("WSA code %s", WSAGetLastError()));
386  CXX_THROW(Eng3D::Networking::SocketException, "Can't start WSA subsystem");
387  }
388 #endif
389 
390  addr = {};
391  addr.sin_family = AF_INET;
392  addr.sin_addr.s_addr = inet_addr(host.c_str());
393  addr.sin_port = htons(port);
394 
395  fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
396  if(fd == INVALID_SOCKET) {
397 #ifdef E3D_TARGET_WINDOWS
398  Eng3D::Log::error("network", Eng3D::translate_format("WSA code %s", WSAGetLastError()));
399  WSACleanup();
400 #endif
401  CXX_THROW(Eng3D::Networking::SocketException, "Can't create client socket");
402  }
403 
404  if(connect(fd, reinterpret_cast<sockaddr*>(&addr), sizeof(addr)) != 0) {
405 #ifdef E3D_TARGET_UNIX
406  close(fd);
407 #elif defined E3D_TARGET_WINDOWS
408  Eng3D::Log::error("network", Eng3D::translate_format("WSA code %s", WSAGetLastError()));
409  closesocket(fd);
410 #endif
411  CXX_THROW(Eng3D::Networking::SocketException, "Can't connect to server");
412  }
413 }
414 
416 #ifdef E3D_TARGET_WINDOWS
417  closesocket(fd);
418  WSACleanup();
419 #else
420  close(fd);
421 #endif
422 }
423 
424 void Eng3D::Networking::Client::do_netloop(std::function<bool()> cond, std::function<void(const Packet& packet, Eng3D::Deser::Archive& ar)> handler) try {
426  Eng3D::Networking::Packet packet(fd);
427  packet.pred = cond;
428  while(cond()) {
429  // Conditional of above statements
430  // When we are on host_mode we discard all potential packets sent by the server
431  // (because our data is already synchronized since WE ARE the server)
432  while(stream.has_pending() && cond()) { // Obtain the action from the server
433  if(packet.recv()) {
435  ar.set_buffer(packet.data(), packet.size());
436  ar.rewind();
437  Eng3D::Log::debug("client", translate_format("Receiving package of %zuB", packet.size()));
438  handler(packet, ar);
439  }
440  }
441 
442  // Client will also flush it's queue to the server
443  Eng3D::Networking::Packet tosend_packet;
444  while(packets.try_pop(tosend_packet)) {
445  tosend_packet.stream = Eng3D::Networking::SocketStream(fd);
446  tosend_packet.pred = cond;
447  Eng3D::Log::debug("client", translate_format("Sending package of %zuB", tosend_packet.size()));
448  if(!tosend_packet.send())
449  continue;
450  }
451  }
453  Eng3D::Log::error("client", translate_format("Exception: %s", e.what()));
455  Eng3D::Log::error("client", translate_format("Exception: %s", e.what()));
456 }
The purpouse of the serializer is to serialize objects onto a byte stream that can be transfered onto...
Definition: serializer.hpp:51
virtual const char * what() const noexcept
Definition: network.hpp:203
Client(std::string host, const unsigned port)
Definition: network.cpp:380
void do_netloop(std::function< bool()> cond, std::function< void(const Packet &packet, Eng3D::Deser::Archive &ar)> handler)
Definition: network.cpp:424
std::function< bool()> pred
Definition: network.hpp:138
size_t size() const
Definition: network.hpp:125
virtual const char * what() const noexcept
Definition: network.hpp:171
void flush_packets()
TODO: flush packets.
Definition: network.cpp:230
struct sockaddr_in addr
Definition: network.hpp:159
void do_netloop(std::function< void(int i)> on_wake_thread, int id)
Definition: network.cpp:298
Server(unsigned port, unsigned max_conn)
Definition: network.cpp:241
std::atomic< bool > run
Definition: network.hpp:161
void broadcast(const Eng3D::Networking::Packet &packet)
This will broadcast the given packet to all clients currently on the server.
Definition: network.cpp:292
virtual const char * what() const noexcept
Definition: network.hpp:74
bool recv(void *data, size_t size, std::function< bool()> pred=0)
Definition: network.cpp:94
void set_timeout(int seconds)
Definition: network.cpp:115
bool send(const void *data, size_t size, std::function< bool()> pred)
Definition: network.cpp:74
void set_blocking(bool value)
Definition: network.cpp:143
std::string translate(const std::string_view str)
Definition: string.cpp:76
void error(const std::string_view category, const std::string_view msg)
Definition: log.cpp:68
void debug(const std::string_view category, const std::string_view msg)
Definition: log.cpp:58
std::string translate_format(const std::string_view format, Args &&... args)
String formatter, with translation.
Definition: string.hpp:128
Definition: utils.hpp:35
Base class that serves as archiver, stores (in memory) the data required for serialization/deserializ...
Definition: serializer.hpp:64
void set_buffer(const void *buf, size_t size)
Definition: serializer.hpp:88
#define CXX_THROW(class,...)
Definition: utils.hpp:98