1 #define WIN32_LEAN_AND_MEAN
2
3 #include "ClientNetworking.h"
4
5 #include "../network/Message.h"
6 #include "../network/MessageQueue.h"
7
8 // boost::asio pulls in windows.h which in turn defines the macros GetMessage,
9 // SendMessage, min and max. Disabling the generation of the min and max macros
10 // and undefining those should avoid name collisions with std c++ library and
11 // FreeOrion function names.
12 #define NOMINMAX
13 #include <boost/asio.hpp>
14 #include <boost/asio/high_resolution_timer.hpp>
15 #ifdef FREEORION_WIN32
16 # undef GetMessage
17 # undef SendMessage
18 #endif
19
20 #include "../network/Networking.h"
21 #include "../util/Logger.h"
22 #include "../util/OptionsDB.h"
23
24 #include <boost/algorithm/string/predicate.hpp>
25 #include <boost/algorithm/string/erase.hpp>
26 #include <boost/thread/condition.hpp>
27 #include <boost/thread/thread.hpp>
28 #include <boost/optional/optional.hpp>
29
30 #include <thread>
31
32 using boost::asio::ip::tcp;
33 using namespace Networking;
34
35 /** In Boost 1.66, io_service was replaced with a typedef of io_context.
36 * That typedef was removed in Boost 1.70 along with other interface changes.
37 * This code uses io_context for future compatibility and adds the typedef
38 * here for old versions of Boost. */
39 #if BOOST_VERSION < 106600
40 namespace boost { namespace asio {
41 typedef io_service io_context;
42 }}
43 #endif
44
45 namespace {
46 DeclareThreadSafeLogger(network);
47
48 /** A simple client that broadcasts UDP datagrams on the local network for
49 FreeOrion servers, and reports any it finds. */
50 class ServerDiscoverer {
51 public:
52 using ServerList = std::vector<std::pair<boost::asio::ip::address, std::string>>;
53
ServerDiscoverer(boost::asio::io_context & io_context)54 ServerDiscoverer(boost::asio::io_context& io_context) :
55 m_io_context(&io_context),
56 m_timer(io_context),
57 m_socket(io_context),
58 m_recv_buf(),
59 m_receive_successful(false),
60 m_server_name()
61 {}
62
Servers() const63 const ServerList& Servers() const
64 { return m_servers; }
65
DiscoverServers()66 void DiscoverServers() {
67 using namespace boost::asio::ip;
68 udp::resolver resolver(*m_io_context);
69 udp::resolver::query query(udp::v4(), "255.255.255.255",
70 std::to_string(Networking::DiscoveryPort()),
71 resolver_query_base::address_configured |
72 resolver_query_base::numeric_service);
73 udp::resolver::iterator end_it;
74 for (udp::resolver::iterator it = resolver.resolve(query); it != end_it; ++it) {
75 udp::endpoint receiver_endpoint = *it;
76
77 m_socket.close();
78 m_socket.open(udp::v4());
79 m_socket.set_option(boost::asio::socket_base::broadcast(true));
80
81 m_socket.send_to(boost::asio::buffer(DISCOVERY_QUESTION),
82 receiver_endpoint);
83
84 m_socket.async_receive_from(
85 boost::asio::buffer(m_recv_buf),
86 m_sender_endpoint,
87 boost::bind(&ServerDiscoverer::HandleReceive,
88 this,
89 boost::asio::placeholders::error,
90 boost::asio::placeholders::bytes_transferred));
91
92 #if BOOST_VERSION >= 106600
93 m_timer.expires_after(std::chrono::seconds(2));
94 #else
95 m_timer.expires_from_now(std::chrono::seconds(2));
96 #endif
97 m_timer.async_wait(boost::bind(&ServerDiscoverer::CloseSocket, this));
98 m_io_context->run();
99 m_io_context->reset();
100 if (m_receive_successful) {
101 boost::asio::ip::address address = m_server_name == "localhost" ?
102 boost::asio::ip::address::from_string("127.0.0.1") :
103 m_sender_endpoint.address();
104 m_servers.push_back({address, m_server_name});
105 }
106 m_receive_successful = false;
107 m_server_name.clear();
108 }
109 }
110
111 private:
HandleReceive(const boost::system::error_code & error,std::size_t length)112 void HandleReceive(const boost::system::error_code& error, std::size_t length) {
113 if (error == boost::asio::error::message_size) {
114 m_socket.async_receive_from(
115 boost::asio::buffer(m_recv_buf),
116 m_sender_endpoint,
117 boost::bind(&ServerDiscoverer::HandleReceive,
118 this,
119 boost::asio::placeholders::error,
120 boost::asio::placeholders::bytes_transferred));
121
122 } else if (!error) {
123 std::string buffer_string(m_recv_buf.begin(), m_recv_buf.begin() + length);
124 if (boost::algorithm::starts_with(buffer_string, DISCOVERY_ANSWER)) {
125 m_receive_successful = true;
126 m_server_name =
127 boost::algorithm::erase_first_copy(buffer_string, DISCOVERY_ANSWER);
128 if (m_server_name == boost::asio::ip::host_name())
129 m_server_name = "localhost";
130 m_timer.cancel();
131 m_socket.close();
132 }
133 }
134 }
135
CloseSocket()136 void CloseSocket()
137 { m_socket.close(); }
138
139 boost::asio::io_context* m_io_context;
140 boost::asio::high_resolution_timer m_timer;
141 boost::asio::ip::udp::socket m_socket;
142
143 std::array<char, 1024> m_recv_buf;
144
145 boost::asio::ip::udp::endpoint m_sender_endpoint;
146 bool m_receive_successful;
147 std::string m_server_name;
148 ServerList m_servers;
149 };
150 }
151
152
153
154 class ClientNetworking::Impl {
155 public:
156 /** The type of list returned by a call to DiscoverLANServers(). */
157 using ServerList = std::vector<std::pair<boost::asio::ip::address, std::string>>;
158
159 /** \name Structors */ //@{
160 Impl();
161 //@}
162
163 /** \name Accessors */ //@{
164 /** Returns true iff the client is full duplex connected to the server. */
165 bool IsConnected() const;
166
167 /** Returns true iff the client is connected to receive from the server. */
168 bool IsRxConnected() const;
169
170 /** Returns true iff the client is connected to send to the server. */
171 bool IsTxConnected() const;
172
173 /** Returns the ID of the player on this client. */
174 int PlayerID() const;
175
176 /** Returns the ID of the host player, or INVALID_PLAYER_ID if there is no host player. */
177 int HostPlayerID() const;
178
179 /** Returns whether the indicated player ID is the host. */
180 bool PlayerIsHost(int player_id) const;
181
182 /** Checks if the client has some authorization \a role. */
183 bool HasAuthRole(Networking::RoleType role) const;
184
185 /** Returns destination address of server. */
186 const std::string& Destination() const;
187 //@}
188
189 /** \name Mutators */ //@{
190 /** Returns a list of the addresses and names of all servers on the Local
191 Area Network. */
192 ClientNetworking::ServerNames DiscoverLANServerNames();
193
194 /** Connects to the server at \a ip_address. On failure, repeated
195 attempts will be made until \a timeout seconds has elapsed. If \p
196 expect_timeout is true, timeout is not reported as an error. */
197 bool ConnectToServer(const ClientNetworking* const self,
198 const std::string& ip_address,
199 const std::chrono::milliseconds& timeout = std::chrono::seconds(10),
200 bool expect_timeout = false);
201
202 /** Connects to the server on the client's host. On failure, repeated
203 attempts will be made until \a timeout seconds has elapsed. If \p
204 expect_timeout is true, timeout is not reported as an error.*/
205 bool ConnectToLocalHostServer(
206 const ClientNetworking* const self,
207 const std::chrono::milliseconds& timeout = std::chrono::seconds(10),
208 bool expect_timeout = false);
209
210 /** Sends \a message to the server. This function actually just enqueues
211 the message for sending and returns immediately. */
212 void SendMessage(const Message& message);
213
214 /** Return the next incoming message from the server if available or boost::none.
215 Remove the message from the incoming message queue. */
216 boost::optional<Message> GetMessage();
217
218 /** Disconnects the client from the server. */
219 void DisconnectFromServer();
220
221 /** Sets player ID for this client. */
222 void SetPlayerID(int player_id);
223
224 /** Sets Host player ID. */
225 void SetHostPlayerID(int host_player_id);
226
227 /** Get authorization roles access. */
228 Networking::AuthRoles& AuthorizationRoles();
229 //@}
230
231 private:
232 void HandleException(const boost::system::system_error& error);
233 void HandleConnection(boost::asio::ip::tcp::resolver::iterator* it,
234 const boost::system::error_code& error);
235
236 void NetworkingThread(const std::shared_ptr<const ClientNetworking> self);
237 void HandleMessageBodyRead(const std::shared_ptr<const ClientNetworking>& keep_alive,
238 boost::system::error_code error, std::size_t bytes_transferred);
239 void HandleMessageHeaderRead(const std::shared_ptr<const ClientNetworking>& keep_alive,
240 boost::system::error_code error, std::size_t bytes_transferred);
241 void AsyncReadMessage(const std::shared_ptr<const ClientNetworking>& keep_alive);
242 void HandleMessageWrite(boost::system::error_code error, std::size_t bytes_transferred);
243 void AsyncWriteMessage();
244 void SendMessageImpl(Message message);
245 void DisconnectFromServerImpl();
246
247 int m_player_id;
248 int m_host_player_id;
249 Networking::AuthRoles m_roles;
250
251 boost::asio::io_context m_io_context;
252 boost::asio::ip::tcp::socket m_socket;
253
254 // m_mutex guards m_incoming_message, m_rx_connected and m_tx_connected which are written by
255 // the networking thread and read by the main thread to check incoming messages and connection
256 // status. As those read and write operations are not atomic, shared access has to be
257 // protected to prevent unpredictable results.
258 mutable boost::mutex m_mutex;
259
260 bool m_rx_connected; // accessed from multiple threads
261 bool m_tx_connected; // accessed from multiple threads
262
263 MessageQueue m_incoming_messages; // accessed from multiple threads, but its interface is threadsafe
264 std::list<Message> m_outgoing_messages;
265
266 Message::HeaderBuffer m_incoming_header;
267 Message m_incoming_message;
268 Message::HeaderBuffer m_outgoing_header;
269
270 std::string m_destination;
271 };
272
273
274 ////////////////////////////////////////////////
275 // ClientNetworking Impl
276 ////////////////////////////////////////////////
Impl()277 ClientNetworking::Impl::Impl() :
278 m_player_id(Networking::INVALID_PLAYER_ID),
279 m_host_player_id(Networking::INVALID_PLAYER_ID),
280 m_io_context(),
281 m_socket(m_io_context),
282 m_rx_connected(false),
283 m_tx_connected(false),
284 m_incoming_messages(m_mutex)
285 {}
286
IsConnected() const287 bool ClientNetworking::Impl::IsConnected() const {
288 boost::mutex::scoped_lock lock(m_mutex);
289 return m_rx_connected && m_tx_connected;
290 }
291
IsRxConnected() const292 bool ClientNetworking::Impl::IsRxConnected() const {
293 boost::mutex::scoped_lock lock(m_mutex);
294 return m_rx_connected;
295 }
296
IsTxConnected() const297 bool ClientNetworking::Impl::IsTxConnected() const {
298 boost::mutex::scoped_lock lock(m_mutex);
299 return m_tx_connected;
300 }
301
PlayerID() const302 int ClientNetworking::Impl::PlayerID() const
303 { return m_player_id; }
304
HostPlayerID() const305 int ClientNetworking::Impl::HostPlayerID() const
306 { return m_host_player_id; }
307
PlayerIsHost(int player_id) const308 bool ClientNetworking::Impl::PlayerIsHost(int player_id) const {
309 if (player_id == Networking::INVALID_PLAYER_ID)
310 return false;
311 return player_id == m_host_player_id;
312 }
313
HasAuthRole(Networking::RoleType role) const314 bool ClientNetworking::Impl::HasAuthRole(Networking::RoleType role) const
315 { return m_roles.HasRole(role); }
316
DiscoverLANServerNames()317 ClientNetworking::ServerNames ClientNetworking::Impl::DiscoverLANServerNames() {
318 if (!IsConnected())
319 return ServerNames();
320 ServerDiscoverer discoverer(m_io_context);
321 discoverer.DiscoverServers();
322 ServerNames names;
323 for (const auto& server : discoverer.Servers()) {
324 names.push_back(server.second);
325 }
326 return names;
327 }
328
Destination() const329 const std::string& ClientNetworking::Impl::Destination() const
330 { return m_destination; }
331
ConnectToServer(const ClientNetworking * const self,const std::string & ip_address,const std::chrono::milliseconds & timeout,bool expect_timeout)332 bool ClientNetworking::Impl::ConnectToServer(
333 const ClientNetworking* const self,
334 const std::string& ip_address,
335 const std::chrono::milliseconds& timeout/* = std::chrono::seconds(10)*/,
336 bool expect_timeout /*=false*/)
337 {
338 using Clock = std::chrono::high_resolution_clock;
339 Clock::time_point start_time = Clock::now();
340 auto deadline = start_time + timeout;
341
342 using namespace boost::asio::ip;
343 tcp::resolver resolver(m_io_context);
344 tcp::resolver::query query(ip_address,
345 std::to_string(Networking::MessagePort()),
346 boost::asio::ip::resolver_query_base::numeric_service);
347
348 tcp::resolver::iterator end_it;
349
350 DebugLogger(network) << "Attempt to connect to server at one of these addresses:";
351 for (tcp::resolver::iterator it = resolver.resolve(query); it != end_it; ++it) {
352 DebugLogger(network) << " tcp::resolver::iterator host_name: " << it->host_name()
353 << " address: " << it->endpoint().address()
354 << " port: " << it->endpoint().port();
355 }
356
357 try {
358 while(!IsConnected() && Clock::now() < deadline) {
359 for (tcp::resolver::iterator it = resolver.resolve(query); it != end_it; ++it) {
360 try {
361 m_socket.close();
362 } catch (const std::exception& e) {
363 ErrorLogger(network) << "ConnectToServer() : unable to close socket due to exception: " << e.what();
364 m_socket = boost::asio::ip::tcp::socket(m_io_context);
365 }
366
367 m_socket.async_connect(*it, boost::bind(&ClientNetworking::Impl::HandleConnection, this,
368 &it,
369 boost::asio::placeholders::error));
370 m_io_context.run();
371 m_io_context.reset();
372
373 auto connection_time = Clock::now() - start_time;
374
375 if (IsConnected()) {
376 DebugLogger(network) << "Connected to server at host_name: " << it->host_name()
377 << " address: " << it->endpoint().address()
378 << " port: " << it->endpoint().port();
379
380 //DebugLogger(network) << "ConnectToServer() : Client using "
381 // << ((GetOptionsDB().Get<bool>("save.format.binary.enabled")) ? "binary": "xml")
382 // << " serialization.";
383
384 // Prepare the socket
385
386 // linger option has different meanings on different platforms. It affects the
387 // behavior of the socket.close(). It can do the following:
388 // - close both send and receive immediately,
389 // - finish sending any pending send packets and wait up to SOCKET_LINGER_TIME for
390 // ACKs,
391 // - finish sending pending sent packets and wait up to SOCKET_LINGER_TIME for ACKs
392 // and for the other side of the connection to close,
393 // linger may/may not cause close() to block until the linger time has elapsed.
394 m_socket.set_option(boost::asio::socket_base::linger(true, SOCKET_LINGER_TIME));
395
396 // keep alive is an OS dependent option that will keep the TCP connection alive and
397 // then deliver an OS dependent error when/if the other side of the connection
398 // times out or closes.
399 m_socket.set_option(boost::asio::socket_base::keep_alive(true));
400 DebugLogger(network) << "Connecting to server took "
401 << std::chrono::duration_cast<std::chrono::milliseconds>(connection_time).count() << " ms.";
402
403 DebugLogger(network) << "ConnectToServer() : starting networking thread";
404 boost::thread(boost::bind(&ClientNetworking::Impl::NetworkingThread, this, self->shared_from_this()));
405 break;
406 } else {
407 TraceLogger(network) << "Failed to connect to host_name: " << it->host_name()
408 << " address: " << it->endpoint().address()
409 << " port: " << it->endpoint().port();
410 if (timeout < connection_time && !expect_timeout) {
411 ErrorLogger(network) << "Timed out ("
412 << std::chrono::duration_cast<std::chrono::milliseconds>(connection_time).count() << " ms."
413 << ") attempting to connect to server.";
414 }
415 }
416 }
417 }
418 if (!IsConnected())
419 DebugLogger(network) << "ConnectToServer() : failed to connect to server.";
420
421 } catch (const std::exception& e) {
422 ErrorLogger(network) << "ConnectToServer() : unable to connect to server at "
423 << ip_address << " due to exception: " << e.what();
424 }
425 if (IsConnected())
426 m_destination = ip_address;
427 return IsConnected();
428 }
429
ConnectToLocalHostServer(const ClientNetworking * const self,const std::chrono::milliseconds & timeout,bool expect_timeout)430 bool ClientNetworking::Impl::ConnectToLocalHostServer(
431 const ClientNetworking* const self,
432 const std::chrono::milliseconds& timeout/* = std::chrono::seconds(10)*/,
433 bool expect_timeout /*=false*/)
434 {
435 bool retval = false;
436 #if FREEORION_WIN32
437 try {
438 #endif
439 retval = ConnectToServer(self, "127.0.0.1", timeout, expect_timeout);
440 #if FREEORION_WIN32
441 } catch (const boost::system::system_error& e) {
442 if (e.code().value() != WSAEADDRNOTAVAIL)
443 throw;
444 }
445 #endif
446 return retval;
447 }
448
DisconnectFromServer()449 void ClientNetworking::Impl::DisconnectFromServer() {
450 bool is_open(false);
451
452 { // Create a scope for the mutex
453 boost::mutex::scoped_lock lock(m_mutex);
454 is_open = m_rx_connected || m_tx_connected;
455 }
456
457 if (is_open)
458 m_io_context.post(boost::bind(&ClientNetworking::Impl::DisconnectFromServerImpl, this));
459 }
460
SetPlayerID(int player_id)461 void ClientNetworking::Impl::SetPlayerID(int player_id) {
462 DebugLogger(network) << "ClientNetworking::SetPlayerID: player id set to: " << player_id;
463 m_player_id = player_id;
464 }
465
SetHostPlayerID(int host_player_id)466 void ClientNetworking::Impl::SetHostPlayerID(int host_player_id)
467 { m_host_player_id = host_player_id; }
468
AuthorizationRoles()469 Networking::AuthRoles& ClientNetworking::Impl::AuthorizationRoles()
470 { return m_roles; }
471
SendMessage(const Message & message)472 void ClientNetworking::Impl::SendMessage(const Message& message) {
473 if (!IsTxConnected()) {
474 ErrorLogger(network) << "ClientNetworking::SendMessage can't send message when not transmit connected";
475 return;
476 }
477 TraceLogger(network) << "ClientNetworking::SendMessage() : sending message " << message;
478 m_io_context.post(boost::bind(&ClientNetworking::Impl::SendMessageImpl, this, message));
479 }
480
GetMessage()481 boost::optional<Message> ClientNetworking::Impl::GetMessage() {
482 const auto message = m_incoming_messages.PopFront();
483 if (message)
484 TraceLogger(network) << "ClientNetworking::GetMessage() : received message "
485 << *message;
486 return message;
487 }
488
HandleConnection(tcp::resolver::iterator * it,const boost::system::error_code & error)489 void ClientNetworking::Impl::HandleConnection(tcp::resolver::iterator* it,
490 const boost::system::error_code& error)
491 {
492 if (error) {
493 TraceLogger(network) << "ClientNetworking::HandleConnection : connection "
494 << "error #"<<error.value()<<" \"" << error.message() << "\""
495 << "... retrying";
496 } else {
497 TraceLogger(network) << "ClientNetworking::HandleConnection : connected";
498
499 boost::mutex::scoped_lock lock(m_mutex);
500 m_rx_connected = true;
501 m_tx_connected = true;
502 }
503 }
504
HandleException(const boost::system::system_error & error)505 void ClientNetworking::Impl::HandleException(const boost::system::system_error& error) {
506 if (error.code() == boost::asio::error::eof) {
507 DebugLogger(network) << "Client connection disconnected by EOF from server.";
508 m_socket.close();
509 }
510 else if (error.code() == boost::asio::error::connection_reset)
511 DebugLogger(network) << "Client connection disconnected, due to connection reset from server.";
512 else if (error.code() == boost::asio::error::operation_aborted)
513 DebugLogger(network) << "Client connection closed by client.";
514 else {
515 ErrorLogger(network) << "ClientNetworking::NetworkingThread() : Networking thread will be terminated "
516 << "due to unhandled exception error #" << error.code().value() << " \""
517 << error.code().message() << "\"";
518 }
519 }
520
NetworkingThread(const std::shared_ptr<const ClientNetworking> self)521 void ClientNetworking::Impl::NetworkingThread(const std::shared_ptr<const ClientNetworking> self) {
522 auto protect_from_destruction_in_other_thread = self;
523 try {
524 if (!m_outgoing_messages.empty())
525 AsyncWriteMessage();
526 AsyncReadMessage(protect_from_destruction_in_other_thread);
527 m_io_context.run();
528 } catch (const boost::system::system_error& error) {
529 HandleException(error);
530 }
531 m_outgoing_messages.clear();
532 m_io_context.reset();
533 { // Mutex scope
534 boost::mutex::scoped_lock lock(m_mutex);
535 m_rx_connected = false;
536 m_tx_connected = false;
537 }
538 TraceLogger(network) << "ClientNetworking::NetworkingThread() : Networking thread terminated.";
539 }
540
HandleMessageBodyRead(const std::shared_ptr<const ClientNetworking> & keep_alive,boost::system::error_code error,std::size_t bytes_transferred)541 void ClientNetworking::Impl::HandleMessageBodyRead(const std::shared_ptr<const ClientNetworking>& keep_alive,
542 boost::system::error_code error, std::size_t bytes_transferred)
543 {
544 if (error)
545 throw boost::system::system_error(error);
546
547 assert(static_cast<int>(bytes_transferred) <= m_incoming_header[Message::Parts::SIZE]);
548 if (static_cast<int>(bytes_transferred) == m_incoming_header[Message::Parts::SIZE]) {
549 m_incoming_messages.PushBack(m_incoming_message);
550 AsyncReadMessage(keep_alive);
551 }
552 }
553
HandleMessageHeaderRead(const std::shared_ptr<const ClientNetworking> & keep_alive,boost::system::error_code error,std::size_t bytes_transferred)554 void ClientNetworking::Impl::HandleMessageHeaderRead(const std::shared_ptr<const ClientNetworking>& keep_alive,
555 boost::system::error_code error, std::size_t bytes_transferred)
556 {
557 if (error)
558 throw boost::system::system_error(error);
559 assert(bytes_transferred <= Message::HeaderBufferSize);
560 if (bytes_transferred != Message::HeaderBufferSize)
561 return;
562
563 BufferToHeader(m_incoming_header, m_incoming_message);
564 m_incoming_message.Resize(m_incoming_header[Message::Parts::SIZE]);
565 // Intentionally not checked for open. We expect (header, body) pairs.
566 boost::asio::async_read(
567 m_socket,
568 boost::asio::buffer(m_incoming_message.Data(), m_incoming_message.Size()),
569 boost::bind(&ClientNetworking::Impl::HandleMessageBodyRead,
570 this, keep_alive,
571 boost::asio::placeholders::error,
572 boost::asio::placeholders::bytes_transferred));
573 }
574
AsyncReadMessage(const std::shared_ptr<const ClientNetworking> & keep_alive)575 void ClientNetworking::Impl::AsyncReadMessage(const std::shared_ptr<const ClientNetworking>& keep_alive) {
576 // If keep_alive's count < 2 the networking thread is orphaned so shut down
577 if (keep_alive.use_count() < 2)
578 DisconnectFromServerImpl();
579
580 if (m_socket.is_open())
581 boost::asio::async_read(
582 m_socket, boost::asio::buffer(m_incoming_header),
583 boost::bind(&ClientNetworking::Impl::HandleMessageHeaderRead, this, keep_alive,
584 boost::asio::placeholders::error,
585 boost::asio::placeholders::bytes_transferred));
586 }
587
HandleMessageWrite(boost::system::error_code error,std::size_t bytes_transferred)588 void ClientNetworking::Impl::HandleMessageWrite(boost::system::error_code error, std::size_t bytes_transferred) {
589 if (error) {
590 throw boost::system::system_error(error);
591 return;
592 }
593
594 assert(static_cast<int>(bytes_transferred) <= static_cast<int>(Message::HeaderBufferSize) + m_outgoing_header[Message::Parts::SIZE]);
595 if (static_cast<int>(bytes_transferred) != static_cast<int>(Message::HeaderBufferSize) + m_outgoing_header[Message::Parts::SIZE])
596 return;
597
598 m_outgoing_messages.pop_front();
599 if (!m_outgoing_messages.empty())
600 AsyncWriteMessage();
601
602 // Check if finished sending last pending write while shutting down.
603 else {
604 bool should_shutdown(false);
605 { // Scope for the mutex
606 boost::mutex::scoped_lock lock(m_mutex);
607 should_shutdown = !m_tx_connected;
608 }
609 if (should_shutdown) {
610 DisconnectFromServerImpl();
611 }
612 }
613 }
614
AsyncWriteMessage()615 void ClientNetworking::Impl::AsyncWriteMessage() {
616 if (!m_socket.is_open()) {
617 ErrorLogger(network) << "Socket is closed. Dropping message.";
618 return;
619 }
620
621 HeaderToBuffer(m_outgoing_messages.front(), m_outgoing_header);
622 std::vector<boost::asio::const_buffer> buffers;
623 buffers.push_back(boost::asio::buffer(m_outgoing_header));
624 buffers.push_back(boost::asio::buffer(m_outgoing_messages.front().Data(),
625 m_outgoing_messages.front().Size()));
626 boost::asio::async_write(m_socket, buffers,
627 boost::bind(&ClientNetworking::Impl::HandleMessageWrite, this,
628 boost::asio::placeholders::error,
629 boost::asio::placeholders::bytes_transferred));
630 }
631
SendMessageImpl(Message message)632 void ClientNetworking::Impl::SendMessageImpl(Message message) {
633 bool start_write = m_outgoing_messages.empty();
634 m_outgoing_messages.push_back(Message());
635 swap(m_outgoing_messages.back(), message);
636 if (start_write)
637 AsyncWriteMessage();
638 }
639
DisconnectFromServerImpl()640 void ClientNetworking::Impl::DisconnectFromServerImpl() {
641 // Depending behavior of linger on OS's of the sending and receiving machines this call to close could
642 // - immediately disconnect both send and receive channels
643 // - immediately disconnect send, but continue receiving until all pending sent packets are
644 // received and acknowledged.
645 // - send pending packets and wait for the receive side to terminate the connection.
646
647 // The shutdown steps:
648 // 1. Finish sending pending tx messages. These may include final panic messages
649 // 2. After the final tx call DisconnectFromServerImpl again from the tx handler.
650 // 3. shutdown the connection
651 // 4. server end acknowledges with a 0 length packet
652 // 5. close the connection in the rx handler
653
654 // Stop sending new packets
655 { // Scope for the mutex
656 boost::mutex::scoped_lock lock(m_mutex);
657 m_tx_connected = false;
658 m_rx_connected = m_socket.is_open();
659 }
660
661 if (!m_outgoing_messages.empty()) {
662 return;
663 }
664
665 // Note: m_socket.is_open() may be independently true/false on each of these checks.
666 if (m_socket.is_open())
667 m_socket.shutdown(boost::asio::ip::tcp::socket::shutdown_both);
668 }
669
670
671
672 ////////////////////////////////////////////////
673 // ClientNetworking
674 ////////////////////////////////////////////////
ClientNetworking()675 ClientNetworking::ClientNetworking() :
676 m_impl(new ClientNetworking::Impl())
677 {}
678
679 ClientNetworking::~ClientNetworking() = default;
680
IsConnected() const681 bool ClientNetworking::IsConnected() const
682 { return m_impl->IsConnected(); }
683
IsRxConnected() const684 bool ClientNetworking::IsRxConnected() const
685 { return m_impl->IsRxConnected(); }
686
IsTxConnected() const687 bool ClientNetworking::IsTxConnected() const
688 { return m_impl->IsTxConnected(); }
689
PlayerID() const690 int ClientNetworking::PlayerID() const
691 { return m_impl->PlayerID(); }
692
HostPlayerID() const693 int ClientNetworking::HostPlayerID() const
694 { return m_impl->HostPlayerID(); }
695
PlayerIsHost(int player_id) const696 bool ClientNetworking::PlayerIsHost(int player_id) const
697 { return m_impl->PlayerIsHost(player_id); }
698
HasAuthRole(Networking::RoleType role) const699 bool ClientNetworking::HasAuthRole(Networking::RoleType role) const
700 { return m_impl->HasAuthRole(role); }
701
Destination() const702 const std::string& ClientNetworking::Destination() const
703 { return m_impl->Destination(); }
704
DiscoverLANServerNames()705 ClientNetworking::ServerNames ClientNetworking::DiscoverLANServerNames()
706 { return m_impl->DiscoverLANServerNames(); }
707
ConnectToServer(const std::string & ip_address,const std::chrono::milliseconds & timeout)708 bool ClientNetworking::ConnectToServer(
709 const std::string& ip_address,
710 const std::chrono::milliseconds& timeout/* = std::chrono::seconds(10)*/)
711 { return m_impl->ConnectToServer(this, ip_address, timeout); }
712
ConnectToLocalHostServer(const std::chrono::milliseconds & timeout)713 bool ClientNetworking::ConnectToLocalHostServer(
714 const std::chrono::milliseconds& timeout/* = std::chrono::seconds(10)*/)
715 { return m_impl->ConnectToLocalHostServer(this, timeout); }
716
PingServer(const std::string & ip_address,const std::chrono::milliseconds & timeout)717 bool ClientNetworking::PingServer(
718 const std::string& ip_address,
719 const std::chrono::milliseconds& timeout/* = std::chrono::seconds(10)*/)
720 { return m_impl->ConnectToServer(this, ip_address, timeout, true /*expect_timeout*/); }
721
PingLocalHostServer(const std::chrono::milliseconds & timeout)722 bool ClientNetworking::PingLocalHostServer(
723 const std::chrono::milliseconds& timeout/* = std::chrono::seconds(10)*/)
724 { return m_impl->ConnectToLocalHostServer(this, timeout, true /*expect_timeout*/); }
725
DisconnectFromServer()726 void ClientNetworking::DisconnectFromServer()
727 { return m_impl->DisconnectFromServer(); }
728
SetPlayerID(int player_id)729 void ClientNetworking::SetPlayerID(int player_id)
730 { return m_impl->SetPlayerID(player_id); }
731
SetHostPlayerID(int host_player_id)732 void ClientNetworking::SetHostPlayerID(int host_player_id)
733 { return m_impl->SetHostPlayerID(host_player_id); }
734
AuthorizationRoles()735 Networking::AuthRoles& ClientNetworking::AuthorizationRoles()
736 { return m_impl->AuthorizationRoles(); }
737
SendMessage(const Message & message)738 void ClientNetworking::SendMessage(const Message& message)
739 { return m_impl->SendMessage(message); }
740
GetMessage()741 boost::optional<Message> ClientNetworking::GetMessage()
742 { return m_impl->GetMessage(); }
743