Initial commit
This commit is contained in:
parent
ff408db8de
commit
aa81a70ef2
157
ConditionMonitoring/asioClientServer/net_client.h
Normal file
157
ConditionMonitoring/asioClientServer/net_client.h
Normal file
@ -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 <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
ConditionMonitoring/asioClientServer/net_common.h
Normal file
76
ConditionMonitoring/asioClientServer/net_common.h
Normal file
@ -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 <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
ConditionMonitoring/asioClientServer/net_connection.h
Normal file
343
ConditionMonitoring/asioClientServer/net_connection.h
Normal file
@ -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<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
ConditionMonitoring/asioClientServer/net_dequeue_ts.h
Normal file
161
ConditionMonitoring/asioClientServer/net_dequeue_ts.h
Normal file
@ -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<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
ConditionMonitoring/asioClientServer/net_message.h
Normal file
214
ConditionMonitoring/asioClientServer/net_message.h
Normal file
@ -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, <EFBFBD>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
ConditionMonitoring/asioClientServer/net_server.h
Normal file
276
ConditionMonitoring/asioClientServer/net_server.h
Normal file
@ -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<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…
x
Reference in New Issue
Block a user