olcPixelGameEngine v2.28
The official distribution of olcPixelGameEngine, a tool used in javidx9's YouTube videos and projects
Loading...
Searching...
No Matches
olcPGEX_Network.h
Go to the documentation of this file.
1/*
2 ASIO Based Networking olcPixelGameEngine Extension v1.0
3
4 Videos:
5 Part #1: https://youtu.be/2hNdkYInj4g
6 Part #2: https://youtu.be/UbjxGvrDrbw
7 Part #3: https://youtu.be/hHowZ3bWsio
8 Part #4: https://youtu.be/f_1lt9pfaEo
9
10 License (OLC-3)
11 ~~~~~~~~~~~~~~~
12
13 Copyright 2018 - 2021 OneLoneCoder.com
14
15 Redistribution and use in source and binary forms, with or without
16 modification, are permitted provided that the following conditions
17 are met:
18
19 1. Redistributions or derivations of source code must retain the above
20 copyright notice, this list of conditions and the following disclaimer.
21
22 2. Redistributions or derivative works in binary form must reproduce
23 the above copyright notice. This list of conditions and the following
24 disclaimer must be reproduced in the documentation and/or other
25 materials provided with the distribution.
26
27 3. Neither the name of the copyright holder nor the names of its
28 contributors may be used to endorse or promote products derived
29 from this software without specific prior written permission.
30
31 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
32 "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
33 LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
34 A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
35 HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
36 SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
37 LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
38 DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
39 THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
40 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
41 OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
42
43 Links
44 ~~~~~
45 YouTube: https://www.youtube.com/javidx9
46 Discord: https://discord.gg/WhwHUMV
47 Twitter: https://www.twitter.com/javidx9
48 Twitch: https://www.twitch.tv/javidx9
49 GitHub: https://www.github.com/onelonecoder
50 Homepage: https://www.onelonecoder.com
51
52 Author
53 ~~~~~~
54 David Barr, aka javidx9, ©OneLoneCoder 2019, 2020, 2021
55
56*/
57
58#pragma once
59
60#include <memory>
61#include <thread>
62#include <mutex>
63#include <deque>
64#include <optional>
65#include <vector>
66#include <iostream>
67#include <algorithm>
68#include <chrono>
69#include <cstdint>
70
71#ifdef _WIN32
72#ifndef _WIN32_WINNT
73#define _WIN32_WINNT 0x0A00
74#endif
75#endif
76
77#define _WINSOCK_DEPRECATED_NO_WARNINGS
78#define ASIO_STANDALONE
79#include <asio.hpp>
80#include <asio/ts/buffer.hpp>
81#include <asio/ts/internet.hpp>
82
83namespace olc
84{
85 namespace net
86 {
87 // Message
88
89 // Message Header is sent at start of all messages. The template allows us
90 // to use "enum class" to ensure that the messages are valid at compile time
91 template <typename T>
93 {
94 T id{};
95 uint32_t size = 0;
96 };
97
98 // Message Body contains a header and a std::vector, containing raw bytes
99 // of infomation. This way the message can be variable length, but the size
100 // in the header must be updated.
101 template <typename T>
102 struct message
103 {
104 // Header & Body vector
106 std::vector<uint8_t> body;
107
108 // returns size of entire message packet in bytes
109 size_t size() const
110 {
111 return body.size();
112 }
113
114 // Override for std::cout compatibility - produces friendly description of message
115 friend std::ostream& operator << (std::ostream& os, const message<T>& msg)
116 {
117 os << "ID:" << int(msg.header.id) << " Size:" << msg.header.size;
118 return os;
119 }
120
121 // Convenience Operator overloads - These allow us to add and remove stuff from
122 // the body vector as if it were a stack, so First in, Last Out. These are a
123 // template in itself, because we dont know what data type the user is pushing or
124 // popping, so lets allow them all. NOTE: It assumes the data type is fundamentally
125 // Plain Old Data (POD). TLDR: Serialise & Deserialise into/from a vector
126
127 // Pushes any POD-like data into the message buffer
128 template<typename DataType>
129 friend message<T>& operator << (message<T>& msg, const DataType& data)
130 {
131 // Check that the type of the data being pushed is trivially copyable
132 static_assert(std::is_standard_layout<DataType>::value, "Data is too complex to be pushed into vector");
133
134 // Cache current size of vector, as this will be the point we insert the data
135 size_t i = msg.body.size();
136
137 // Resize the vector by the size of the data being pushed
138 msg.body.resize(msg.body.size() + sizeof(DataType));
139
140 // Physically copy the data into the newly allocated vector space
141 std::memcpy(msg.body.data() + i, &data, sizeof(DataType));
142
143 // Recalculate the message size
144 msg.header.size = msg.size();
145
146 // Return the target message so it can be "chained"
147 return msg;
148 }
149
150 // Pulls any POD-like data form the message buffer
151 template<typename DataType>
152 friend message<T>& operator >> (message<T>& msg, DataType& data)
153 {
154 // Check that the type of the data being pushed is trivially copyable
155 static_assert(std::is_standard_layout<DataType>::value, "Data is too complex to be pulled from vector");
156
157 // Cache the location towards the end of the vector where the pulled data starts
158 size_t i = msg.body.size() - sizeof(DataType);
159
160 // Physically copy the data from the vector into the user variable
161 std::memcpy(&data, msg.body.data() + i, sizeof(DataType));
162
163 // Shrink the vector to remove read bytes, and reset end position
164 msg.body.resize(i);
165
166 // Recalculate the message size
167 msg.header.size = msg.size();
168
169 // Return the target message so it can be "chained"
170 return msg;
171 }
172 };
173
174
175 // An "owned" message is identical to a regular message, but it is associated with
176 // a connection. On a server, the owner would be the client that sent the message,
177 // on a client the owner would be the server.
178
179 // Forward declare the connection
180 template <typename T>
181 class connection;
182
183 template <typename T>
185 {
186 std::shared_ptr<connection<T>> remote = nullptr;
188
189 // Again, a friendly string maker
190 friend std::ostream& operator<<(std::ostream& os, const owned_message<T>& msg)
191 {
192 os << msg.msg;
193 return os;
194 }
195 };
196
197
198 // Queue
199 template<typename T>
201 {
202 public:
203 tsqueue() = default;
204 tsqueue(const tsqueue<T>&) = delete;
205 virtual ~tsqueue() { clear(); }
206
207 public:
208 // Returns and maintains item at front of Queue
209 const T& front()
210 {
211 std::scoped_lock lock(muxQueue);
212 return deqQueue.front();
213 }
214
215 // Returns and maintains item at back of Queue
216 const T& back()
217 {
218 std::scoped_lock lock(muxQueue);
219 return deqQueue.back();
220 }
221
222 // Removes and returns item from front of Queue
224 {
225 std::scoped_lock lock(muxQueue);
226 auto t = std::move(deqQueue.front());
227 deqQueue.pop_front();
228 return t;
229 }
230
231 // Removes and returns item from back of Queue
233 {
234 std::scoped_lock lock(muxQueue);
235 auto t = std::move(deqQueue.back());
236 deqQueue.pop_back();
237 return t;
238 }
239
240 // Adds an item to back of Queue
241 void push_back(const T& item)
242 {
243 std::scoped_lock lock(muxQueue);
244 deqQueue.emplace_back(std::move(item));
245
246 std::unique_lock<std::mutex> ul(muxBlocking);
247 cvBlocking.notify_one();
248 }
249
250 // Adds an item to front of Queue
251 void push_front(const T& item)
252 {
253 std::scoped_lock lock(muxQueue);
254 deqQueue.emplace_front(std::move(item));
255
256 std::unique_lock<std::mutex> ul(muxBlocking);
257 cvBlocking.notify_one();
258 }
259
260 // Returns true if Queue has no items
261 bool empty()
262 {
263 std::scoped_lock lock(muxQueue);
264 return deqQueue.empty();
265 }
266
267 // Returns number of items in Queue
268 size_t count()
269 {
270 std::scoped_lock lock(muxQueue);
271 return deqQueue.size();
272 }
273
274 // Clears Queue
275 void clear()
276 {
277 std::scoped_lock lock(muxQueue);
278 deqQueue.clear();
279 }
280
281 void wait()
282 {
283 while (empty())
284 {
285 std::unique_lock<std::mutex> ul(muxBlocking);
286 cvBlocking.wait(ul);
287 }
288 }
289
290 protected:
291 std::mutex muxQueue;
292 std::deque<T> deqQueue;
293 std::condition_variable cvBlocking;
294 std::mutex muxBlocking;
295 };
296
297 // Connection
298 // Forward declare
299 template<typename T>
300 class server_interface;
301
302 template<typename T>
303 class connection : public std::enable_shared_from_this<connection<T>>
304 {
305 public:
306 // A connection is "owned" by either a server or a client, and its
307 // behaviour is slightly different bewteen the two.
308 enum class owner
309 {
310 server,
311 client
312 };
313
314 public:
315 // Constructor: Specify Owner, connect to context, transfer the socket
316 // Provide reference to incoming message queue
317 connection(owner parent, asio::io_context& asioContext, asio::ip::tcp::socket socket, tsqueue<owned_message<T>>& qIn)
318 : m_asioContext(asioContext), m_socket(std::move(socket)), m_qMessagesIn(qIn)
319 {
320 m_nOwnerType = parent;
321
322 // Construct validation check data
324 {
325 // Connection is Server -> Client, construct random data for the client
326 // to transform and send back for validation
327 m_nHandshakeOut = uint64_t(std::chrono::system_clock::now().time_since_epoch().count());
328
329 // Pre-calculate the result for checking when the client responds
331 }
332 else
333 {
334 // Connection is Client -> Server, so we have nothing to define,
335 m_nHandshakeIn = 0;
336 m_nHandshakeOut = 0;
337 }
338 }
339
340 virtual ~connection()
341 {}
342
343 // This ID is used system wide - its how clients will understand other clients
344 // exist across the whole system.
345 uint32_t GetID() const
346 {
347 return id;
348 }
349
350 public:
352 {
354 {
355 if (m_socket.is_open())
356 {
357 id = uid;
358
359 // Was: ReadHeader();
360
361 // A client has attempted to connect to the server, but we wish
362 // the client to first validate itself, so first write out the
363 // handshake data to be validated
364 WriteValidation();
365
366 // Next, issue a task to sit and wait asynchronously for precisely
367 // the validation data sent back from the client
368 ReadValidation(server);
369 }
370 }
371 }
372
373 void ConnectToServer(const asio::ip::tcp::resolver::results_type& endpoints)
374 {
375 // Only clients can connect to servers
377 {
378 // Request asio attempts to connect to an endpoint
379 asio::async_connect(m_socket, endpoints,
380 [this](std::error_code ec, asio::ip::tcp::endpoint endpoint)
381 {
382 if (!ec)
383 {
384 // Was: ReadHeader();
385
386 // First thing server will do is send packet to be validated
387 // so wait for that and respond
388 ReadValidation();
389 }
390 });
391 }
392 }
393
394
396 {
397 if (IsConnected())
398 asio::post(m_asioContext, [this]() { m_socket.close(); });
399 }
400
401 bool IsConnected() const
402 {
403 return m_socket.is_open();
404 }
405
406 // Prime the connection to wait for incoming messages
408 {
409
410 }
411
412 public:
413 // ASYNC - Send a message, connections are one-to-one so no need to specifiy
414 // the target, for a client, the target is the server and vice versa
415 void Send(const message<T>& msg)
416 {
417 asio::post(m_asioContext,
418 [this, msg]()
419 {
420 // If the queue has a message in it, then we must
421 // assume that it is in the process of asynchronously being written.
422 // Either way add the message to the queue to be output. If no messages
423 // were available to be written, then start the process of writing the
424 // message at the front of the queue.
425 bool bWritingMessage = !m_qMessagesOut.empty();
426 m_qMessagesOut.push_back(msg);
427 if (!bWritingMessage)
428 {
429 WriteHeader();
430 }
431 });
432 }
433
434
435
436 private:
437 // ASYNC - Prime context to write a message header
438 void WriteHeader()
439 {
440 // If this function is called, we know the outgoing message queue must have
441 // at least one message to send. So allocate a transmission buffer to hold
442 // the message, and issue the work - asio, send these bytes
443 asio::async_write(m_socket, asio::buffer(&m_qMessagesOut.front().header, sizeof(message_header<T>)),
444 [this](std::error_code ec, std::size_t length)
445 {
446 // asio has now sent the bytes - if there was a problem
447 // an error would be available...
448 if (!ec)
449 {
450 // ... no error, so check if the message header just sent also
451 // has a message body...
452 if (m_qMessagesOut.front().body.size() > 0)
453 {
454 // ...it does, so issue the task to write the body bytes
455 WriteBody();
456 }
457 else
458 {
459 // ...it didnt, so we are done with this message. Remove it from
460 // the outgoing message queue
461 m_qMessagesOut.pop_front();
462
463 // If the queue is not empty, there are more messages to send, so
464 // make this happen by issuing the task to send the next header.
465 if (!m_qMessagesOut.empty())
466 {
467 WriteHeader();
468 }
469 }
470 }
471 else
472 {
473 // ...asio failed to write the message, we could analyse why but
474 // for now simply assume the connection has died by closing the
475 // socket. When a future attempt to write to this client fails due
476 // to the closed socket, it will be tidied up.
477 std::cout << "[" << id << "] Write Header Fail.\n";
478 m_socket.close();
479 }
480 });
481 }
482
483 // ASYNC - Prime context to write a message body
484 void WriteBody()
485 {
486 // If this function is called, a header has just been sent, and that header
487 // indicated a body existed for this message. Fill a transmission buffer
488 // with the body data, and send it!
489 asio::async_write(m_socket, asio::buffer(m_qMessagesOut.front().body.data(), m_qMessagesOut.front().body.size()),
490 [this](std::error_code ec, std::size_t length)
491 {
492 if (!ec)
493 {
494 // Sending was successful, so we are done with the message
495 // and remove it from the queue
496 m_qMessagesOut.pop_front();
497
498 // If the queue still has messages in it, then issue the task to
499 // send the next messages' header.
500 if (!m_qMessagesOut.empty())
501 {
502 WriteHeader();
503 }
504 }
505 else
506 {
507 // Sending failed, see WriteHeader() equivalent for description :P
508 std::cout << "[" << id << "] Write Body Fail.\n";
509 m_socket.close();
510 }
511 });
512 }
513
514 // ASYNC - Prime context ready to read a message header
515 void ReadHeader()
516 {
517 // If this function is called, we are expecting asio to wait until it receives
518 // enough bytes to form a header of a message. We know the headers are a fixed
519 // size, so allocate a transmission buffer large enough to store it. In fact,
520 // we will construct the message in a "temporary" message object as it's
521 // convenient to work with.
522 asio::async_read(m_socket, asio::buffer(&m_msgTemporaryIn.header, sizeof(message_header<T>)),
523 [this](std::error_code ec, std::size_t length)
524 {
525 if (!ec)
526 {
527 // A complete message header has been read, check if this message
528 // has a body to follow...
529 if (m_msgTemporaryIn.header.size > 0)
530 {
531 // ...it does, so allocate enough space in the messages' body
532 // vector, and issue asio with the task to read the body.
533 m_msgTemporaryIn.body.resize(m_msgTemporaryIn.header.size);
534 ReadBody();
535 }
536 else
537 {
538 // it doesn't, so add this bodyless message to the connections
539 // incoming message queue
540 AddToIncomingMessageQueue();
541 }
542 }
543 else
544 {
545 // Reading form the client went wrong, most likely a disconnect
546 // has occurred. Close the socket and let the system tidy it up later.
547 std::cout << "[" << id << "] Read Header Fail.\n";
548 m_socket.close();
549 }
550 });
551 }
552
553 // ASYNC - Prime context ready to read a message body
554 void ReadBody()
555 {
556 // If this function is called, a header has already been read, and that header
557 // request we read a body, The space for that body has already been allocated
558 // in the temporary message object, so just wait for the bytes to arrive...
559 asio::async_read(m_socket, asio::buffer(m_msgTemporaryIn.body.data(), m_msgTemporaryIn.body.size()),
560 [this](std::error_code ec, std::size_t length)
561 {
562 if (!ec)
563 {
564 // ...and they have! The message is now complete, so add
565 // the whole message to incoming queue
566 AddToIncomingMessageQueue();
567 }
568 else
569 {
570 // As above!
571 std::cout << "[" << id << "] Read Body Fail.\n";
572 m_socket.close();
573 }
574 });
575 }
576
577 // "Encrypt" data
578 uint64_t scramble(uint64_t nInput)
579 {
580 uint64_t out = nInput ^ 0xDEADBEEFC0DECAFE;
581 out = (out & 0xF0F0F0F0F0F0F0) >> 4 | (out & 0x0F0F0F0F0F0F0F) << 4;
582 return out ^ 0xC0DEFACE12345678;
583 }
584
585 // ASYNC - Used by both client and server to write validation packet
586 void WriteValidation()
587 {
588 asio::async_write(m_socket, asio::buffer(&m_nHandshakeOut, sizeof(uint64_t)),
589 [this](std::error_code ec, std::size_t length)
590 {
591 if (!ec)
592 {
593 // Validation data sent, clients should sit and wait
594 // for a response (or a closure)
595 if (m_nOwnerType == owner::client)
596 ReadHeader();
597 }
598 else
599 {
600 m_socket.close();
601 }
602 });
603 }
604
605 void ReadValidation(olc::net::server_interface<T>* server = nullptr)
606 {
607 asio::async_read(m_socket, asio::buffer(&m_nHandshakeIn, sizeof(uint64_t)),
608 [this, server](std::error_code ec, std::size_t length)
609 {
610 if (!ec)
611 {
612 if (m_nOwnerType == owner::server)
613 {
614 // Connection is a server, so check response from client
615
616 // Compare sent data to actual solution
617 if (m_nHandshakeIn == m_nHandshakeCheck)
618 {
619 // Client has provided valid solution, so allow it to connect properly
620 std::cout << "Client Validated" << std::endl;
621 server->OnClientValidated(this->shared_from_this());
622
623 // Sit waiting to receive data now
624 ReadHeader();
625 }
626 else
627 {
628 // Client gave incorrect data, so disconnect
629 std::cout << "Client Disconnected (Fail Validation)" << std::endl;
630 m_socket.close();
631 }
632 }
633 else
634 {
635 // Connection is a client, so solve puzzle
636 m_nHandshakeOut = scramble(m_nHandshakeIn);
637
638 // Write the result
639 WriteValidation();
640 }
641 }
642 else
643 {
644 // Some biggerfailure occured
645 std::cout << "Client Disconnected (ReadValidation)" << std::endl;
646 m_socket.close();
647 }
648 });
649 }
650
651 // Once a full message is received, add it to the incoming queue
652 void AddToIncomingMessageQueue()
653 {
654 // Shove it in queue, converting it to an "owned message", by initialising
655 // with the a shared pointer from this connection object
656 if(m_nOwnerType == owner::server)
657 m_qMessagesIn.push_back({ this->shared_from_this(), m_msgTemporaryIn });
658 else
659 m_qMessagesIn.push_back({ nullptr, m_msgTemporaryIn });
660
661 // We must now prime the asio context to receive the next message. It
662 // wil just sit and wait for bytes to arrive, and the message construction
663 // process repeats itself. Clever huh?
664 ReadHeader();
665 }
666
667 protected:
668 // Each connection has a unique socket to a remote
669 asio::ip::tcp::socket m_socket;
670
671 // This context is shared with the whole asio instance
672 asio::io_context& m_asioContext;
673
674 // This queue holds all messages to be sent to the remote side
675 // of this connection
677
678 // This references the incoming queue of the parent object
680
681 // Incoming messages are constructed asynchronously, so we will
682 // store the part assembled message here, until it is ready
684
685 // The "owner" decides how some of the connection behaves
686 owner m_nOwnerType = owner::server;
687
688 // Handshake Validation
689 uint64_t m_nHandshakeOut = 0;
690 uint64_t m_nHandshakeIn = 0;
691 uint64_t m_nHandshakeCheck = 0;
692
693
694 bool m_bValidHandshake = false;
695 bool m_bConnectionEstablished = false;
696
697 uint32_t id = 0;
698
699 };
700
701 // Client
702 template <typename T>
704 {
705 public:
708
710 {
711 // If the client is destroyed, always try and disconnect from server
712 Disconnect();
713 }
714
715 public:
716 // Connect to server with hostname/ip-address and port
717 bool Connect(const std::string& host, const uint16_t port)
718 {
719 try
720 {
721 // Resolve hostname/ip-address into tangiable physical address
722 asio::ip::tcp::resolver resolver(m_context);
723 asio::ip::tcp::resolver::results_type endpoints = resolver.resolve(host, std::to_string(port));
724
725 // Create connection
726 m_connection = std::make_unique<connection<T>>(connection<T>::owner::client, m_context, asio::ip::tcp::socket(m_context), m_qMessagesIn);
727
728 // Tell the connection object to connect to server
729 m_connection->ConnectToServer(endpoints);
730
731 // Start Context Thread
732 thrContext = std::thread([this]() { m_context.run(); });
733 }
734 catch (std::exception& e)
735 {
736 std::cerr << "Client Exception: " << e.what() << "\n";
737 return false;
738 }
739 return true;
740 }
741
742 // Disconnect from server
744 {
745 // If connection exists, and it's connected then...
746 if(IsConnected())
747 {
748 // ...disconnect from server gracefully
749 m_connection->Disconnect();
750 }
751
752 // Either way, we're also done with the asio context...
753 m_context.stop();
754 // ...and its thread
755 if (thrContext.joinable())
756 thrContext.join();
757
758 // Destroy the connection object
759 m_connection.release();
760 }
761
762 // Check if client is actually connected to a server
764 {
765 if (m_connection)
766 return m_connection->IsConnected();
767 else
768 return false;
769 }
770
771 public:
772 // Send message to server
773 void Send(const message<T>& msg)
774 {
775 if (IsConnected())
776 m_connection->Send(msg);
777 }
778
779 // Retrieve queue of messages from server
781 {
782 return m_qMessagesIn;
783 }
784
785 protected:
786 // asio context handles the data transfer...
787 asio::io_context m_context;
788 // ...but needs a thread of its own to execute its work commands
789 std::thread thrContext;
790 // The client has a single instance of a "connection" object, which handles data transfer
791 std::unique_ptr<connection<T>> m_connection;
792
793 private:
794 // This is the thread safe queue of incoming messages from server
795 tsqueue<owned_message<T>> m_qMessagesIn;
796 };
797
798 // Server
799 template<typename T>
801 {
802 public:
803 // Create a server, ready to listen on specified port
804 server_interface(uint16_t port)
805 : m_asioAcceptor(m_asioContext, asio::ip::tcp::endpoint(asio::ip::tcp::v4(), port))
806 {
807
808 }
809
811 {
812 // May as well try and tidy up
813 Stop();
814 }
815
816 // Starts the server!
817 bool Start()
818 {
819 try
820 {
821 // Issue a task to the asio context - This is important
822 // as it will prime the context with "work", and stop it
823 // from exiting immediately. Since this is a server, we
824 // want it primed ready to handle clients trying to
825 // connect.
826 WaitForClientConnection();
827
828 // Launch the asio context in its own thread
829 m_threadContext = std::thread([this]() { m_asioContext.run(); });
830 }
831 catch (std::exception& e)
832 {
833 // Something prohibited the server from listening
834 std::cerr << "[SERVER] Exception: " << e.what() << "\n";
835 return false;
836 }
837
838 std::cout << "[SERVER] Started!\n";
839 return true;
840 }
841
842 // Stops the server!
843 void Stop()
844 {
845 // Request the context to close
846 m_asioContext.stop();
847
848 // Tidy up the context thread
849 if (m_threadContext.joinable()) m_threadContext.join();
850
851 // Inform someone, anybody, if they care...
852 std::cout << "[SERVER] Stopped!\n";
853 }
854
855 // ASYNC - Instruct asio to wait for connection
857 {
858 // Prime context with an instruction to wait until a socket connects. This
859 // is the purpose of an "acceptor" object. It will provide a unique socket
860 // for each incoming connection attempt
861 m_asioAcceptor.async_accept(
862 [this](std::error_code ec, asio::ip::tcp::socket socket)
863 {
864 // Triggered by incoming connection request
865 if (!ec)
866 {
867 // Display some useful(?) information
868 std::cout << "[SERVER] New Connection: " << socket.remote_endpoint() << "\n";
869
870 // Create a new connection to handle this client
871 std::shared_ptr<connection<T>> newconn =
872 std::make_shared<connection<T>>(connection<T>::owner::server,
873 m_asioContext, std::move(socket), m_qMessagesIn);
874
875
876
877 // Give the user server a chance to deny connection
878 if (OnClientConnect(newconn))
879 {
880 // Connection allowed, so add to container of new connections
881 m_deqConnections.push_back(std::move(newconn));
882
883 // And very important! Issue a task to the connection's
884 // asio context to sit and wait for bytes to arrive!
885 m_deqConnections.back()->ConnectToClient(this, nIDCounter++);
886
887 std::cout << "[" << m_deqConnections.back()->GetID() << "] Connection Approved\n";
888 }
889 else
890 {
891 std::cout << "[-----] Connection Denied\n";
892
893 // Connection will go out of scope with no pending tasks, so will
894 // get destroyed automagically due to the wonder of smart pointers
895 }
896 }
897 else
898 {
899 // Error has occurred during acceptance
900 std::cout << "[SERVER] New Connection Error: " << ec.message() << "\n";
901 }
902
903 // Prime the asio context with more work - again simply wait for
904 // another connection...
905 WaitForClientConnection();
906 });
907 }
908
909 // Send a message to a specific client
910 void MessageClient(std::shared_ptr<connection<T>> client, const message<T>& msg)
911 {
912 // Check client is legitimate...
913 if (client && client->IsConnected())
914 {
915 // ...and post the message via the connection
916 client->Send(msg);
917 }
918 else
919 {
920 // If we cant communicate with client then we may as
921 // well remove the client - let the server know, it may
922 // be tracking it somehow
923 OnClientDisconnect(client);
924
925 // Off you go now, bye bye!
926 client.reset();
927
928 // Then physically remove it from the container
929 m_deqConnections.erase(
930 std::remove(m_deqConnections.begin(), m_deqConnections.end(), client), m_deqConnections.end());
931 }
932 }
933
934 // Send message to all clients
935 void MessageAllClients(const message<T>& msg, std::shared_ptr<connection<T>> pIgnoreClient = nullptr)
936 {
937 bool bInvalidClientExists = false;
938
939 // Iterate through all clients in container
940 for (auto& client : m_deqConnections)
941 {
942 // Check client is connected...
943 if (client && client->IsConnected())
944 {
945 // ..it is!
946 if(client != pIgnoreClient)
947 client->Send(msg);
948 }
949 else
950 {
951 // The client couldnt be contacted, so assume it has
952 // disconnected.
953 OnClientDisconnect(client);
954 client.reset();
955
956 // Set this flag to then remove dead clients from container
957 bInvalidClientExists = true;
958 }
959 }
960
961 // Remove dead clients, all in one go - this way, we dont invalidate the
962 // container as we iterated through it.
963 if (bInvalidClientExists)
964 m_deqConnections.erase(
965 std::remove(m_deqConnections.begin(), m_deqConnections.end(), nullptr), m_deqConnections.end());
966 }
967
968 // Force server to respond to incoming messages
969 void Update(size_t nMaxMessages = -1, bool bWait = false)
970 {
971 if (bWait) m_qMessagesIn.wait();
972
973 // Process as many messages as you can up to the value
974 // specified
975 size_t nMessageCount = 0;
976 while (nMessageCount < nMaxMessages && !m_qMessagesIn.empty())
977 {
978 // Grab the front message
979 auto msg = m_qMessagesIn.pop_front();
980
981 // Pass to message handler
982 OnMessage(msg.remote, msg.msg);
983
984 nMessageCount++;
985 }
986 }
987
988 protected:
989 // This server class should override thse functions to implement
990 // customised functionality
991
992 // Called when a client connects, you can veto the connection by returning false
993 virtual bool OnClientConnect(std::shared_ptr<connection<T>> client)
994 {
995 return false;
996 }
997
998 // Called when a client appears to have disconnected
999 virtual void OnClientDisconnect(std::shared_ptr<connection<T>> client)
1000 {
1001
1002 }
1003
1004 // Called when a message arrives
1005 virtual void OnMessage(std::shared_ptr<connection<T>> client, message<T>& msg)
1006 {
1007
1008 }
1009
1010 public:
1011 // Called when a client is validated
1012 virtual void OnClientValidated(std::shared_ptr<connection<T>> client)
1013 {
1014
1015 }
1016
1017
1018 protected:
1019 // Thread Safe Queue for incoming message packets
1021
1022 // Container of active validated connections
1023 std::deque<std::shared_ptr<connection<T>>> m_deqConnections;
1024
1025 // Order of declaration is important - it is also the order of initialisation
1026 asio::io_context m_asioContext;
1027 std::thread m_threadContext;
1028
1029 // These things need an asio context
1030 asio::ip::tcp::acceptor m_asioAcceptor; // Handles new incoming connection attempts...
1031
1032 // Clients will be identified in the "wider system" via an ID
1033 uint32_t nIDCounter = 10000;
1034 };
1035 }
1036}
1037
1038
Definition olcPGEX_Network.h:704
std::thread thrContext
Definition olcPGEX_Network.h:789
bool Connect(const std::string &host, const uint16_t port)
Definition olcPGEX_Network.h:717
tsqueue< owned_message< T > > & Incoming()
Definition olcPGEX_Network.h:780
void Disconnect()
Definition olcPGEX_Network.h:743
bool IsConnected()
Definition olcPGEX_Network.h:763
asio::io_context m_context
Definition olcPGEX_Network.h:787
virtual ~client_interface()
Definition olcPGEX_Network.h:709
std::unique_ptr< connection< T > > m_connection
Definition olcPGEX_Network.h:791
client_interface()
Definition olcPGEX_Network.h:706
void Send(const message< T > &msg)
Definition olcPGEX_Network.h:773
Definition olcPGEX_Network.h:304
void Send(const message< T > &msg)
Definition olcPGEX_Network.h:415
uint32_t GetID() const
Definition olcPGEX_Network.h:345
uint64_t m_nHandshakeOut
Definition olcPGEX_Network.h:689
void StartListening()
Definition olcPGEX_Network.h:407
uint64_t m_nHandshakeIn
Definition olcPGEX_Network.h:690
tsqueue< owned_message< T > > & m_qMessagesIn
Definition olcPGEX_Network.h:679
uint32_t id
Definition olcPGEX_Network.h:697
void ConnectToClient(olc::net::server_interface< T > *server, uint32_t uid=0)
Definition olcPGEX_Network.h:351
bool IsConnected() const
Definition olcPGEX_Network.h:401
connection(owner parent, asio::io_context &asioContext, asio::ip::tcp::socket socket, tsqueue< owned_message< T > > &qIn)
Definition olcPGEX_Network.h:317
void ConnectToServer(const asio::ip::tcp::resolver::results_type &endpoints)
Definition olcPGEX_Network.h:373
virtual ~connection()
Definition olcPGEX_Network.h:340
owner
Definition olcPGEX_Network.h:309
asio::ip::tcp::socket m_socket
Definition olcPGEX_Network.h:669
message< T > m_msgTemporaryIn
Definition olcPGEX_Network.h:683
void Disconnect()
Definition olcPGEX_Network.h:395
asio::io_context & m_asioContext
Definition olcPGEX_Network.h:672
uint64_t m_nHandshakeCheck
Definition olcPGEX_Network.h:691
owner m_nOwnerType
Definition olcPGEX_Network.h:686
tsqueue< message< T > > m_qMessagesOut
Definition olcPGEX_Network.h:676
Definition olcPGEX_Network.h:801
void MessageClient(std::shared_ptr< connection< T > > client, const message< T > &msg)
Definition olcPGEX_Network.h:910
void Update(size_t nMaxMessages=-1, bool bWait=false)
Definition olcPGEX_Network.h:969
asio::io_context m_asioContext
Definition olcPGEX_Network.h:1026
virtual bool OnClientConnect(std::shared_ptr< connection< T > > client)
Definition olcPGEX_Network.h:993
void WaitForClientConnection()
Definition olcPGEX_Network.h:856
virtual void OnClientValidated(std::shared_ptr< connection< T > > client)
Definition olcPGEX_Network.h:1012
std::deque< std::shared_ptr< connection< T > > > m_deqConnections
Definition olcPGEX_Network.h:1023
std::thread m_threadContext
Definition olcPGEX_Network.h:1027
asio::ip::tcp::acceptor m_asioAcceptor
Definition olcPGEX_Network.h:1030
virtual ~server_interface()
Definition olcPGEX_Network.h:810
virtual void OnClientDisconnect(std::shared_ptr< connection< T > > client)
Definition olcPGEX_Network.h:999
virtual void OnMessage(std::shared_ptr< connection< T > > client, message< T > &msg)
Definition olcPGEX_Network.h:1005
server_interface(uint16_t port)
Definition olcPGEX_Network.h:804
bool Start()
Definition olcPGEX_Network.h:817
tsqueue< owned_message< T > > m_qMessagesIn
Definition olcPGEX_Network.h:1020
void MessageAllClients(const message< T > &msg, std::shared_ptr< connection< T > > pIgnoreClient=nullptr)
Definition olcPGEX_Network.h:935
void Stop()
Definition olcPGEX_Network.h:843
Definition olcPGEX_Network.h:201
void wait()
Definition olcPGEX_Network.h:281
std::condition_variable cvBlocking
Definition olcPGEX_Network.h:293
std::deque< T > deqQueue
Definition olcPGEX_Network.h:292
std::mutex muxQueue
Definition olcPGEX_Network.h:291
void push_back(const T &item)
Definition olcPGEX_Network.h:241
void clear()
Definition olcPGEX_Network.h:275
bool empty()
Definition olcPGEX_Network.h:261
std::mutex muxBlocking
Definition olcPGEX_Network.h:294
T pop_back()
Definition olcPGEX_Network.h:232
virtual ~tsqueue()
Definition olcPGEX_Network.h:205
tsqueue(const tsqueue< T > &)=delete
const T & back()
Definition olcPGEX_Network.h:216
size_t count()
Definition olcPGEX_Network.h:268
void push_front(const T &item)
Definition olcPGEX_Network.h:251
const T & front()
Definition olcPGEX_Network.h:209
T pop_front()
Definition olcPGEX_Network.h:223
Definition olcPixelGameEngine.h:593
@ T
Definition olcPixelGameEngine.h:974
Definition olcPGEX_Network.h:93
uint32_t size
Definition olcPGEX_Network.h:95
Definition olcPGEX_Network.h:103
friend std::ostream & operator<<(std::ostream &os, const message< T > &msg)
Definition olcPGEX_Network.h:115
size_t size() const
Definition olcPGEX_Network.h:109
std::vector< uint8_t > body
Definition olcPGEX_Network.h:106
message_header< T > header
Definition olcPGEX_Network.h:105
friend message< T > & operator>>(message< T > &msg, DataType &data)
Definition olcPGEX_Network.h:152
Definition olcPGEX_Network.h:185
friend std::ostream & operator<<(std::ostream &os, const owned_message< T > &msg)
Definition olcPGEX_Network.h:190
message< T > msg
Definition olcPGEX_Network.h:187
std::shared_ptr< connection< T > > remote
Definition olcPGEX_Network.h:186