Browse Source

Initial commit

master
Michael Stuffer 3 years ago
parent
commit
aa81a70ef2

+ 157
- 0
ConditionMonitoring/asioClientServer/net_client.h View File

/*
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 <typename T>
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<T>>(Connection<T>::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<T>& msg)
{
if (IsConnected())
m_connection->Send(msg);
}

// Retrieve queue of messages from server
net::ts_dequeue<OwnedMessage<T>>& 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<Connection<T>> m_connection;
private:
// This is the thread safe queue of incoming messages from server
net::ts_dequeue<OwnedMessage<T>> m_qMessagesIn;
};
}

+ 76
- 0
ConditionMonitoring/asioClientServer/net_common.h View File

/*
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 <memory>
#include <thread>
#include <mutex>
#include <deque>
#include <optional>
#include <vector>
#include <iostream>
#include <algorithm>
#include <chrono>
#include <cstdint>
#ifdef _WIN32
#define _WIN32_WINNT 0x0A00
#endif

#define ASIO_STANDALONE
#include <asio.hpp>
#include <asio/ts/buffer.hpp>
#include <asio/ts/internet.hpp>

+ 343
- 0
ConditionMonitoring/asioClientServer/net_connection.h View File

/*
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<typename T>
class Connection : public std::enable_shared_from_this<Connection<T>>
{
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<OwnedMessage<T>>& 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<T>& 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<T>)),
[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<T>)),
[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<Message<T>> m_qMessagesOut;

// This references the incoming queue of the parent object
ts_dequeue<OwnedMessage<T>>& m_qMessagesIn;

// Incoming messages are constructed asynchronously, so we will
// store the part assembled message here, until it is ready
Message<T> m_msgTemporaryIn;

// The "owner" decides how some of the connection behaves
Owner m_nOwnerType = Owner::SERVER;

uint32_t id = 0;

};
}

+ 161
- 0
ConditionMonitoring/asioClientServer/net_dequeue_ts.h View File

/*
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<typename T>
class ts_dequeue
{
public:
ts_dequeue() = default;
ts_dequeue(const ts_dequeue<T>&) = 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<std::mutex> 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<std::mutex> 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<std::mutex> ul(muxBlocking);
cvBlocking.wait(ul);
}
}

protected:
std::mutex muxQueue;
std::deque<T> deqQueue;
std::condition_variable cvBlocking;
std::mutex muxBlocking;
};
}

+ 214
- 0
ConditionMonitoring/asioClientServer/net_message.h View File

/*
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 <string>
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 <typename T>
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 <typename T>
struct Message
{
// Header & Body vector
MessageHeader<T> header{};
std::vector<uint8_t> 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<T>& 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<typename DataType>
friend Message<T>& operator << (Message<T>& msg, const DataType& data)
{
// Check that the type of the data being pushed is trivially copyable
static_assert(std::is_standard_layout<DataType>::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<T>& operator << (Message<T>& 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<typename DataType>
friend Message<T>& operator >> (Message<T>& msg, DataType& data)
{
// Check that the type of the data being pushed is trivially copyable
static_assert(std::is_standard_layout<DataType>::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<T>& operator >> (Message<T>& 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 <typename T>
class Connection;

template <typename T>
struct OwnedMessage
{
std::shared_ptr<Connection<T>> remote = nullptr;
Message<T> msg;

// Again, a friendly string maker
friend std::ostream& operator<<(std::ostream& os, const OwnedMessage<T>& msg)
{
os << msg.msg;
return os;
}
};

}

+ 276
- 0
ConditionMonitoring/asioClientServer/net_server.h View File

/*
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<typename T>
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<Connection<T>> newconn =
std::make_shared<Connection<T>>(Connection<T>::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<Connection<T>> client, const Message<T>& 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<T>& msg, std::shared_ptr<Connection<T>> 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<Connection<T>> client) = 0;
// Called when a client appears to have disconnected
virtual void OnClientDisconnect(std::shared_ptr<Connection<T>> client) = 0;

// Called when a message arrives
virtual void OnMessage(std::shared_ptr<Connection<T>> client, Message<T>& msg) = 0;

// Thread Safe Queue for incoming message packets
ts_dequeue<OwnedMessage<T>> m_qMessagesIn;

// Container of active validated connections
std::deque<std::shared_ptr<Connection<T>>> 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;
};
}

Loading…
Cancel
Save