diff --git a/ConditionMonitoring/asioClientServer/net_client.h b/ConditionMonitoring/asioClientServer/net_client.h new file mode 100644 index 0000000..774834c --- /dev/null +++ b/ConditionMonitoring/asioClientServer/net_client.h @@ -0,0 +1,157 @@ +/* + MMO Client/Server Framework using ASIO + "Happy Birthday Mrs Javidx9!" - javidx9 + + Videos: + Part #1: https://youtu.be/2hNdkYInj4g + Part #2: https://youtu.be/UbjxGvrDrbw + + License (OLC-3) + ~~~~~~~~~~~~~~~ + + Copyright 2018 - 2020 OneLoneCoder.com + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions or derivations of source code must retain the above + copyright notice, this list of conditions and the following disclaimer. + + 2. Redistributions or derivative works in binary form must reproduce + the above copyright notice. This list of conditions and the following + disclaimer must be reproduced in the documentation and/or other + materials provided with the distribution. + + 3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + Links + ~~~~~ + YouTube: https://www.youtube.com/javidx9 + Discord: https://discord.gg/WhwHUMV + Twitter: https://www.twitter.com/javidx9 + Twitch: https://www.twitch.tv/javidx9 + GitHub: https://www.github.com/onelonecoder + Homepage: https://www.onelonecoder.com + + Author + ~~~~~~ + David Barr, aka javidx9, ©OneLoneCoder 2019, 2020 + +*/ + +#pragma once +#include "net_common.h" +#include "net_connection.h" +namespace net +{ + template + class ClientInterface + { + public: + ClientInterface() + {} + + virtual ~ClientInterface() + { + // If the client is destroyed, always try and disconnect from server + Disconnect(); + } + + public: + // Connect to server with hostname/ip-address and port + bool Connect(const std::string& host, const uint16_t port) + { + try + { + // Resolve hostname/ip-address into tangiable physical address + asio::ip::tcp::resolver resolver(m_context); + asio::ip::tcp::resolver::results_type endpoints = resolver.resolve(host, std::to_string(port)); + + // Create connection + m_connection = std::make_unique>(Connection::Owner::CLIENT, m_context, asio::ip::tcp::socket(m_context), m_qMessagesIn); + + // Tell the connection object to connect to server + m_connection->ConnectToServer(endpoints); + + // Start Context Thread + thrContext = std::thread([this]() { m_context.run(); }); + } + catch (std::exception& e) + { + std::cerr << "Client Exception: " << e.what() << "\n"; + return false; + } + return true; + } + + // Disconnect from server + void Disconnect() + { + // If connection exists, and it's connected then... + if(IsConnected()) + { + // ...disconnect from server gracefully + m_connection->Disconnect(); + } + + // Either way, we're also done with the asio context... + m_context.stop(); + // ...and its thread + if (thrContext.joinable()) + thrContext.join(); + + // Destroy the connection object + m_connection.release(); + } + + // Check if client is actually connected to a server + bool IsConnected() + { + if (m_connection) + return m_connection->IsConnected(); + else + return false; + } + + public: + // Send message to server + void Send(const Message& msg) + { + if (IsConnected()) + m_connection->Send(msg); + } + + // Retrieve queue of messages from server + net::ts_dequeue>& Incoming() + { + return m_qMessagesIn; + } + + protected: + // asio context handles the data transfer... + asio::io_context m_context; + // ...but needs a thread of its own to execute its work commands + std::thread thrContext; + // The client has a single instance of a "connection" object, which handles data transfer + std::unique_ptr> m_connection; + + private: + // This is the thread safe queue of incoming messages from server + net::ts_dequeue> m_qMessagesIn; + }; +} diff --git a/ConditionMonitoring/asioClientServer/net_common.h b/ConditionMonitoring/asioClientServer/net_common.h new file mode 100644 index 0000000..1dbd53a --- /dev/null +++ b/ConditionMonitoring/asioClientServer/net_common.h @@ -0,0 +1,76 @@ +/* + MMO Client/Server Framework using ASIO + "Happy Birthday Mrs Javidx9!" - javidx9 + + Videos: + Part #1: https://youtu.be/2hNdkYInj4g + Part #2: https://youtu.be/UbjxGvrDrbw + + License (OLC-3) + ~~~~~~~~~~~~~~~ + + Copyright 2018 - 2020 OneLoneCoder.com + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions or derivations of source code must retain the above + copyright notice, this list of conditions and the following disclaimer. + + 2. Redistributions or derivative works in binary form must reproduce + the above copyright notice. This list of conditions and the following + disclaimer must be reproduced in the documentation and/or other + materials provided with the distribution. + + 3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + Links + ~~~~~ + YouTube: https://www.youtube.com/javidx9 + Discord: https://discord.gg/WhwHUMV + Twitter: https://www.twitter.com/javidx9 + Twitch: https://www.twitch.tv/javidx9 + GitHub: https://www.github.com/onelonecoder + Homepage: https://www.onelonecoder.com + + Author + ~~~~~~ + David Barr, aka javidx9, ©OneLoneCoder 2019, 2020 + +*/ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef _WIN32 +#define _WIN32_WINNT 0x0A00 +#endif + +#define ASIO_STANDALONE +#include +#include +#include diff --git a/ConditionMonitoring/asioClientServer/net_connection.h b/ConditionMonitoring/asioClientServer/net_connection.h new file mode 100644 index 0000000..3af8b3e --- /dev/null +++ b/ConditionMonitoring/asioClientServer/net_connection.h @@ -0,0 +1,343 @@ +/* + MMO Client/Server Framework using ASIO + "Happy Birthday Mrs Javidx9!" - javidx9 + + Videos: + Part #1: https://youtu.be/2hNdkYInj4g + Part #2: https://youtu.be/UbjxGvrDrbw + + License (OLC-3) + ~~~~~~~~~~~~~~~ + + Copyright 2018 - 2020 OneLoneCoder.com + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions or derivations of source code must retain the above + copyright notice, this list of conditions and the following disclaimer. + + 2. Redistributions or derivative works in binary form must reproduce + the above copyright notice. This list of conditions and the following + disclaimer must be reproduced in the documentation and/or other + materials provided with the distribution. + + 3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + Links + ~~~~~ + YouTube: https://www.youtube.com/javidx9 + Discord: https://discord.gg/WhwHUMV + Twitter: https://www.twitter.com/javidx9 + Twitch: https://www.twitch.tv/javidx9 + GitHub: https://www.github.com/onelonecoder + Homepage: https://www.onelonecoder.com + + Author + ~~~~~~ + David Barr, aka javidx9, ©OneLoneCoder 2019, 2020 + +*/ + +#pragma once + +#include "net_common.h" +#include "net_dequeue_ts.h" +#include "net_message.h" + + + +namespace net +{ + template + class Connection : public std::enable_shared_from_this> + { + public: + // A connection is "owned" by either a server or a client, and its + // behaviour is slightly different bewteen the two. + enum class Owner + { + SERVER, + CLIENT + }; + + public: + // Constructor: Specify Owner, connect to context, transfer the socket + // Provide reference to incoming message queue + Connection(Owner parent, asio::io_context& asioContext, asio::ip::tcp::socket socket, ts_dequeue>& qIn) + : m_socket(std::move(socket)), m_asioContext(asioContext), m_qMessagesIn(qIn) + { + m_nOwnerType = parent; + } + + virtual ~Connection() + {} + + // This ID is used system wide - its how clients will understand other clients + // exist across the whole system. + uint32_t GetID() const + { + return id; + } + + public: + void ConnectToClient(uint32_t uid = 0) + { + if (m_nOwnerType == Owner::SERVER) + { + if (m_socket.is_open()) + { + id = uid; + ReadHeader(); + } + } + } + + void ConnectToServer(const asio::ip::tcp::resolver::results_type& endpoints) + { + // Only clients can connect to servers + if (m_nOwnerType == Owner::CLIENT) + { + // Request asio attempts to connect to an endpoint + asio::async_connect(m_socket, endpoints, + [this](std::error_code ec, asio::ip::tcp::endpoint endpoint) + { + if (!ec) + { + ReadHeader(); + } + }); + } + } + + + void Disconnect() + { + if (IsConnected()) + asio::post(m_asioContext, [this]() { m_socket.close(); }); + } + + bool IsConnected() const + { + return m_socket.is_open(); + } + + // Prime the connection to wait for incoming messages + void StartListening() + { + + } + + public: + // ASYNC - Send a message, connections are one-to-one so no need to specifiy + // the target, for a client, the target is the server and vice versa + void Send(const Message& msg) + { + asio::post(m_asioContext, + [this, msg]() + { + // If the queue has a message in it, then we must + // assume that it is in the process of asynchronously being written. + // Either way add the message to the queue to be output. If no messages + // were available to be written, then start the process of writing the + // message at the front of the queue. + bool bWritingMessage = !m_qMessagesOut.empty(); + m_qMessagesOut.push_back(msg); + if (!bWritingMessage) + { + WriteHeader(); + } + }); + } + + + + private: + // ASYNC - Prime context to write a message header + void WriteHeader() + { + // If this function is called, we know the outgoing message queue must have + // at least one message to send. So allocate a transmission buffer to hold + // the message, and issue the work - asio, send these bytes + asio::async_write(m_socket, asio::buffer(&m_qMessagesOut.front().header, sizeof(MessageHeader)), + [this](std::error_code ec, std::size_t length) + { + // asio has now sent the bytes - if there was a problem + // an error would be available... + if (!ec) + { + // ... no error, so check if the message header just sent also + // has a message body... + if (m_qMessagesOut.front().body.size() > 0) + { + // ...it does, so issue the task to write the body bytes + WriteBody(); + } + else + { + // ...it didnt, so we are done with this message. Remove it from + // the outgoing message queue + m_qMessagesOut.pop_front(); + + // If the queue is not empty, there are more messages to send, so + // make this happen by issuing the task to send the next header. + if (!m_qMessagesOut.empty()) + { + WriteHeader(); + } + } + } + else + { + // ...asio failed to write the message, we could analyse why but + // for now simply assume the connection has died by closing the + // socket. When a future attempt to write to this client fails due + // to the closed socket, it will be tidied up. + std::cout << "[" << id << "] Write Header Fail.\n"; + m_socket.close(); + } + }); + } + + // ASYNC - Prime context to write a message body + void WriteBody() + { + // If this function is called, a header has just been sent, and that header + // indicated a body existed for this message. Fill a transmission buffer + // with the body data, and send it! + asio::async_write(m_socket, asio::buffer(m_qMessagesOut.front().body.data(), m_qMessagesOut.front().body.size()), + [this](std::error_code ec, std::size_t length) + { + if (!ec) + { + // Sending was successful, so we are done with the message + // and remove it from the queue + m_qMessagesOut.pop_front(); + + // If the queue still has messages in it, then issue the task to + // send the next messages' header. + if (!m_qMessagesOut.empty()) + { + WriteHeader(); + } + } + else + { + // Sending failed, see WriteHeader() equivalent for description :P + std::cout << "[" << id << "] Write Body Fail.\n"; + m_socket.close(); + } + }); + } + + // ASYNC - Prime context ready to read a message header + void ReadHeader() + { + // asio to waits until it receives + // enough bytes to form a header of a message. We know the headers are a fixed + // size, so allocate a transmission buffer large enough to store it. + //Call this function to set up an asynchronous listener for a certain Connection + asio::async_read(m_socket, asio::buffer(&m_msgTemporaryIn.header, sizeof(MessageHeader)), + [this](std::error_code ec, std::size_t length) + { + if (!ec) + { + //Full header readed + //Check for message body + if (m_msgTemporaryIn.header.size > 0) + { + //Allocate storage + m_msgTemporaryIn.body.resize(m_msgTemporaryIn.header.size); + //Read if available + ReadBody(); + } + else + { + //Bodyless message, add to queue + AddToIncomingMessageQueue(); + } + } + else + { + //Failure, probably a disconnection + std::cout << "[" << id << "] Error: " << ec.message() << std::endl; + m_socket.close(); + } + }); + } + + // ASYNC - Prime context to read a message body + void ReadBody() + { + //Called after header has been read successfully + //Read the body in the pre allocated storage + asio::async_read(m_socket, asio::buffer(m_msgTemporaryIn.body.data(), m_msgTemporaryIn.body.size()), + [this](std::error_code ec, std::size_t length) + { + if (!ec) + { + // Complete message, add to queue + AddToIncomingMessageQueue(); + } + else + { + //Failure, probably a disconnection + std::cout << "[" << id << "] Error reading body: " << ec.message() << std::endl; + m_socket.close(); + } + }); + } + + // Add a complete message to the incoming queue, with or without body + void AddToIncomingMessageQueue() + { + //Put it in the queue, put a owner to the object to let the server know who send the message (which connection) + if(m_nOwnerType == Owner::SERVER) + m_qMessagesIn.push_back({ this->shared_from_this(), m_msgTemporaryIn }); + else + m_qMessagesIn.push_back({ nullptr, m_msgTemporaryIn }); + + //Done queueing the message, now initialize a new async read to wait for next message + ReadHeader(); + } + + protected: + // unique socket to a remote + asio::ip::tcp::socket m_socket; + + // This context is shared with the whole asio instance + asio::io_context& m_asioContext; + + // This queue holds all messages to be sent to the remote side + // of this connection + ts_dequeue> m_qMessagesOut; + + // This references the incoming queue of the parent object + ts_dequeue>& m_qMessagesIn; + + // Incoming messages are constructed asynchronously, so we will + // store the part assembled message here, until it is ready + Message m_msgTemporaryIn; + + // The "owner" decides how some of the connection behaves + Owner m_nOwnerType = Owner::SERVER; + + uint32_t id = 0; + + }; +} diff --git a/ConditionMonitoring/asioClientServer/net_dequeue_ts.h b/ConditionMonitoring/asioClientServer/net_dequeue_ts.h new file mode 100644 index 0000000..69db4f0 --- /dev/null +++ b/ConditionMonitoring/asioClientServer/net_dequeue_ts.h @@ -0,0 +1,161 @@ +/* +MMO Client/Server Framework using ASIO +"Happy Birthday Mrs Javidx9!" - javidx9 + +Videos: +Part #1: https://youtu.be/2hNdkYInj4g +Part #2: https://youtu.be/UbjxGvrDrbw + +License (OLC-3) +~~~~~~~~~~~~~~~ + +Copyright 2018 - 2020 OneLoneCoder.com + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +1. Redistributions or derivations of source code must retain the above +copyright notice, this list of conditions and the following disclaimer. + +2. Redistributions or derivative works in binary form must reproduce +the above copyright notice. This list of conditions and the following +disclaimer must be reproduced in the documentation and/or other +materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its +contributors may be used to endorse or promote products derived +from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +Links +~~~~~ +YouTube: https://www.youtube.com/javidx9 +Discord: https://discord.gg/WhwHUMV +Twitter: https://www.twitter.com/javidx9 +Twitch: https://www.twitch.tv/javidx9 +GitHub: https://www.github.com/onelonecoder +Homepage: https://www.onelonecoder.com + +Author +~~~~~~ +David Barr, aka javidx9, ©OneLoneCoder 2019, 2020 + +*/ + +#pragma once + +#include "net_common.h" + + +namespace net +{ + template + class ts_dequeue + { + public: + ts_dequeue() = default; + ts_dequeue(const ts_dequeue&) = delete; + virtual ~ts_dequeue() { clear(); } + + public: + // Returns and maintains item at front of Queue + const T& front() + { + std::scoped_lock lock(muxQueue); + return deqQueue.front(); + } + + // Returns and maintains item at back of Queue + const T& back() + { + std::scoped_lock lock(muxQueue); + return deqQueue.back(); + } + + // Removes and returns item from front of Queue + T pop_front() + { + std::scoped_lock lock(muxQueue); + auto t = std::move(deqQueue.front()); + deqQueue.pop_front(); + return t; + } + + // Removes and returns item from back of Queue + T pop_back() + { + std::scoped_lock lock(muxQueue); + auto t = std::move(deqQueue.back()); + deqQueue.pop_back(); + return t; + } + + // Adds an item to back of Queue + void push_back(const T& item) + { + std::scoped_lock lock(muxQueue); + deqQueue.emplace_back(std::move(item)); + + std::unique_lock ul(muxBlocking); + cvBlocking.notify_one(); + } + + // Adds an item to front of Queue + void push_front(const T& item) + { + std::scoped_lock lock(muxQueue); + deqQueue.emplace_front(std::move(item)); + + std::unique_lock ul(muxBlocking); + cvBlocking.notify_one(); + } + + // Returns true if Queue has no items + bool empty() + { + std::scoped_lock lock(muxQueue); + return deqQueue.empty(); + } + + // Returns number of items in Queue + size_t count() + { + std::scoped_lock lock(muxQueue); + return deqQueue.size(); + } + + // Clears Queue + void clear() + { + std::scoped_lock lock(muxQueue); + deqQueue.clear(); + } + + void wait() + { + while (empty()) + { + std::unique_lock ul(muxBlocking); + cvBlocking.wait(ul); + } + } + + protected: + std::mutex muxQueue; + std::deque deqQueue; + std::condition_variable cvBlocking; + std::mutex muxBlocking; + }; +} diff --git a/ConditionMonitoring/asioClientServer/net_message.h b/ConditionMonitoring/asioClientServer/net_message.h new file mode 100644 index 0000000..c332573 --- /dev/null +++ b/ConditionMonitoring/asioClientServer/net_message.h @@ -0,0 +1,214 @@ +/* + MMO Client/Server Framework using ASIO + "Happy Birthday Mrs Javidx9!" - javidx9 + + Videos: + Part #1: https://youtu.be/2hNdkYInj4g + Part #2: https://youtu.be/UbjxGvrDrbw + + License (OLC-3) + ~~~~~~~~~~~~~~~ + + Copyright 2018 - 2020 OneLoneCoder.com + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions or derivations of source code must retain the above + copyright notice, this list of conditions and the following disclaimer. + + 2. Redistributions or derivative works in binary form must reproduce + the above copyright notice. This list of conditions and the following + disclaimer must be reproduced in the documentation and/or other + materials provided with the distribution. + + 3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + Links + ~~~~~ + YouTube: https://www.youtube.com/javidx9 + Discord: https://discord.gg/WhwHUMV + Twitter: https://www.twitter.com/javidx9 + Twitch: https://www.twitch.tv/javidx9 + GitHub: https://www.github.com/onelonecoder + Homepage: https://www.onelonecoder.com + + Author + ~~~~~~ + David Barr, aka javidx9, �OneLoneCoder 2019, 2020 + +*/ + +#pragma once +#include "net_common.h" +#include +namespace net +{ + ///[OLC_HEADERIFYIER] START "MESSAGE" + + // Message Header is sent at start of all messages. The template allows us + // to use "enum class" to ensure that the messages are valid at compile time + template + struct MessageHeader + { + T id{}; + uint32_t size = 0; + }; + + // Message Body contains a header and a std::vector, containing raw bytes + // of infomation. This way the message can be variable length, but the size + // in the header must be updated. + template + struct Message + { + // Header & Body vector + MessageHeader header{}; + std::vector body; + + + // returns size of entire message packet in bytes + size_t size() const + { + return body.size(); + } + + // Override for std::cout compatibility - produces friendly description of message + friend std::ostream& operator << (std::ostream& os, const Message& msg) + { + os << "ID:" << int(msg.header.id) << " Size:" << msg.header.size; + return os; + } + + // Convenience Operator overloads - These allow us to add and remove stuff from + // the body vector as if it were a stack, so First in, Last Out. These are a + // template in itself, because we dont know what data type the user is pushing or + // popping, so lets allow them all. NOTE: It assumes the data type is fundamentally + // Plain Old Data (POD). TLDR: Serialise & Deserialise into/from a vector + + // Pushes any POD-like data into the message buffer + template + friend Message& operator << (Message& msg, const DataType& data) + { + // Check that the type of the data being pushed is trivially copyable + static_assert(std::is_standard_layout::value, "Data is too complex to be pushed into vector"); + + // Cache current size of vector, as this will be the point we insert the data + size_t i = msg.body.size(); + + // Resize the vector by the size of the data being pushed + msg.body.resize(i + sizeof(DataType)); + + // Physically copy the data into the newly allocated vector space + std::memcpy(msg.body.data() + i, &data, sizeof(DataType)); + + // Recalculate the message size + msg.header.size = msg.size(); + + // Return the target message so it can be "chained" + return msg; + } + + //Specified template to write string + friend Message& operator << (Message& msg, const std::string& data) + { + // Cache current size of vector, as this will be the point we insert the data + size_t i = msg.body.size(); + + // Resize the vector by the size of the data being pushed + //msg.body.resize(i + sizeof(data)); + + // Physically copy the data of the string character by character + msg.body.resize(i + data.size()); + + for (size_t index = 0; index < data.size(); index++) { + msg.body[i+index] = data.at(index); + //std::memcpy(msg.body.data() + i, &c, sizeof(uint8_t)); + } + + // Recalculate the message size + msg.header.size = (uint32_t)msg.size(); + + // Return the target message so it can be "chained" + return msg; + } + + // Pulls any POD-like data form the message buffer + template + friend Message& operator >> (Message& msg, DataType& data) + { + // Check that the type of the data being pushed is trivially copyable + static_assert(std::is_standard_layout::value, "Data is too complex to be pulled from vector"); + + // Cache the location towards the end of the vector where the pulled data starts + size_t i = msg.body.size() - sizeof(DataType); + + // Physically copy the data from the vector into the user variable + std::memcpy(&data, msg.body.data() + i, sizeof(DataType)); + + // Shrink the vector to remove read bytes, and reset end position + msg.body.resize(i); + + // Recalculate the message size + msg.header.size = msg.size(); + + // Return the target message so it can be "chained" + return msg; + } + + //Specified template to read string + friend Message& operator >> (Message& msg, std::string& data) + { + // Cache the location towards the end of the vector where the pulled data starts + size_t i = 0; + + // Physically copy the data from the vector into the user variable + std::memcpy(&data, msg.body.data(), msg.body.size()); + + // Shrink the vector to remove read bytes, and reset end position + msg.body.resize(i); + + // Return the target message so it can be "chained" + return msg; + } + + }; + + + // An "owned" message is identical to a regular message, but it is associated with + // a connection. On a server, the owner would be the client that sent the message, + // on a client the owner would be the server. + + // Forward declare the connection + template + class Connection; + + template + struct OwnedMessage + { + std::shared_ptr> remote = nullptr; + Message msg; + + // Again, a friendly string maker + friend std::ostream& operator<<(std::ostream& os, const OwnedMessage& msg) + { + os << msg.msg; + return os; + } + }; + +} diff --git a/ConditionMonitoring/asioClientServer/net_server.h b/ConditionMonitoring/asioClientServer/net_server.h new file mode 100644 index 0000000..ea756d1 --- /dev/null +++ b/ConditionMonitoring/asioClientServer/net_server.h @@ -0,0 +1,276 @@ +/* + MMO Client/Server Framework using ASIO + "Happy Birthday Mrs Javidx9!" - javidx9 + + Videos: + Part #1: https://youtu.be/2hNdkYInj4g + Part #2: https://youtu.be/UbjxGvrDrbw + + License (OLC-3) + ~~~~~~~~~~~~~~~ + + Copyright 2018 - 2020 OneLoneCoder.com + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions or derivations of source code must retain the above + copyright notice, this list of conditions and the following disclaimer. + + 2. Redistributions or derivative works in binary form must reproduce + the above copyright notice. This list of conditions and the following + disclaimer must be reproduced in the documentation and/or other + materials provided with the distribution. + + 3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + Links + ~~~~~ + YouTube: https://www.youtube.com/javidx9 + Discord: https://discord.gg/WhwHUMV + Twitter: https://www.twitter.com/javidx9 + Twitch: https://www.twitch.tv/javidx9 + GitHub: https://www.github.com/onelonecoder + Homepage: https://www.onelonecoder.com + + Author + ~~~~~~ + David Barr, aka javidx9, ?OneLoneCoder 2019, 2020 + +*/ + +#pragma once +#include "net_dequeue_ts.h" +#include "net_common.h" +#include "net_message.h" +#include "net_connection.h" +#include "easylogging++.h" + +namespace net +{ + template + class ServerInterface + { + public: // Create a server, ready to listen on specified port + ServerInterface(uint16_t port) + : m_asioAcceptor(m_asioContext, asio::ip::tcp::endpoint(asio::ip::tcp::v4(), port)) { } + + virtual ~ServerInterface() { Stop(); } + + // Starts the server + virtual bool Start() + { + try + { + // Issue a task to the asio context - This is important + // as it will prime the context with "work", and stop it + // from exiting immediately. Since this is a server, we + // want it primed ready to handle clients trying to + // connect. + WaitForClientConnection(); + // Launch the asio context in its own thread + m_threadContext = std::thread([this]() { m_asioContext.run(); }); + } + catch (std::exception& e) + { + // Something prohibited the server from listening + LOG(ERROR) << "[SERVER] Exception: " << e.what(); + return false; + } + return true; + } + + bool isStopped()const { + return m_asioContext.stopped(); + } + + // Stops the server! + virtual void Stop() + { + // Request the context to close + m_asioContext.stop(); + + // Tidy up the context thread + if (m_threadContext.joinable()) m_threadContext.join(); + + // Inform someone, anybody, if they care... + LOG(INFO) << "[SERVER] Stopped!\n"; + } + + // ASYNC - Instruct asio to wait for connection + void WaitForClientConnection() + { + // Prime context with an instruction to wait until a socket connects. This + // is the purpose of an "acceptor" object. It will provide a unique socket + // for each incoming connection attempt + m_asioAcceptor.async_accept( + [this](std::error_code ec, asio::ip::tcp::socket socket) + { + // Triggered by incoming connection request + if (!ec) + { + LOG(INFO) << "[SERVER] New Connection: " << socket.remote_endpoint(); + + // Create a new connection to handle this client + std::shared_ptr> newconn = + std::make_shared>(Connection::Owner::SERVER, + m_asioContext, std::move(socket), m_qMessagesIn); + + + // Give the user server a chance to deny connection + if (OnClientConnect(newconn)) + { + // Connection allowed, so add to container of new connections + m_deqConnections.push_back(std::move(newconn)); + + // Issue a task to the connection's + // asio context to sit and wait for bytes to arrive! + m_deqConnections.back()->ConnectToClient(nIDCounter++); + + LOG(INFO) << "[" << m_deqConnections.back()->GetID() << "] Connection Approved"; + } + else + { + LOG(INFO) << "[SERVER] Connection Denied"; + + // Connection will go out of scope with no pending tasks, so will + // get destroyed automatically (smart pointer) + } + } + else + { + // Error has occurred during acceptance + LOG(INFO) << "[SERVER] New Connection Error: " << ec.message(); + } + + // Prime the asio context with more work - again simply wait for + // another connection... + WaitForClientConnection(); + }); + } + + // Send a message to a specific client + void MessageClient(std::shared_ptr> client, const Message& msg) + { + // Check client is legitimate... + if (client && client->IsConnected()) + { + // ...and post the message via the connection + client->Send(msg); + } + else + { + // If we cant communicate with client then we may as + // well remove the client - let the server know, it may + // be tracking it somehow + OnClientDisconnect(client); + + // Off you go now, bye bye! + client.reset(); + + // Then physically remove it from the container + m_deqConnections.erase( + std::remove(m_deqConnections.begin(), m_deqConnections.end(), client), m_deqConnections.end()); + } + } + + // Send message to all clients + void MessageAllClients(const Message& msg, std::shared_ptr> pIgnoreClient = nullptr) + { + + bool bInvalidClientExists = false; + + // Iterate through all clients in container + for (auto& client : m_deqConnections) + { + // Check client is connected... + if (client && client->IsConnected()) + { + // ..it is! + if(client != pIgnoreClient) + client->Send(msg); + } + else + { + // The client couldnt be contacted, so assume it has + // disconnected. + OnClientDisconnect(client); + client.reset(); + + // Set this flag to then remove dead clients from container + bInvalidClientExists = true; + } + } + + // Remove dead clients, all in one go - this way, we dont invalidate the + // container as we iterated through it. + if (bInvalidClientExists) + m_deqConnections.erase( + std::remove(m_deqConnections.begin(), m_deqConnections.end(), nullptr), m_deqConnections.end()); + } + + // Force server to respond to incoming messages + // size_t nmaxMessages: Assign -1 to unsigned to unspecify max message count + // bool bWait: if queue is empty, wait synchronously until message arrives + void Update(size_t nMaxMessages = -1, bool bWait = false) + { + if (bWait) m_qMessagesIn.wait(); + + // Process as many messages as you can up to the value + // specified + size_t nMessageCount = 0; + while (nMessageCount < nMaxMessages && !m_qMessagesIn.empty()) + { + // Grab the front message + auto msg = m_qMessagesIn.pop_front(); + + // Pass to message handler + OnMessage(msg.remote, msg.msg); + + nMessageCount++; + } + } + + protected: + //Overwritable functions to customize server behaviour + + // Called when a client connects, you can veto the connection by returning false + virtual bool OnClientConnect(std::shared_ptr> client) = 0; + // Called when a client appears to have disconnected + virtual void OnClientDisconnect(std::shared_ptr> client) = 0; + + // Called when a message arrives + virtual void OnMessage(std::shared_ptr> client, Message& msg) = 0; + + // Thread Safe Queue for incoming message packets + ts_dequeue> m_qMessagesIn; + + // Container of active validated connections + std::deque>> m_deqConnections; + + // Order of declaration is important - it is also the order of initialisation + asio::io_context m_asioContext; + std::thread m_threadContext; + + // These things need an asio context + asio::ip::tcp::acceptor m_asioAcceptor; // Handles new incoming connection attempts... + + // Clients will be identified in the "wider system" via an ID + uint32_t nIDCounter = 10000; + }; +}