Initial Commit
This commit is contained in:
commit
6088fc29ed
978 changed files with 353647 additions and 0 deletions
46
include/Helpers/Networking/ByteStream.cpp
Normal file
46
include/Helpers/Networking/ByteStream.cpp
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
#include "ByteStream.hpp"
|
||||
#include <cereal/archives/portable_binary.hpp>
|
||||
#include <cereal/types/string.hpp>
|
||||
#include "NetLibrary.hpp"
|
||||
|
||||
ByteMemBuf::ByteMemBuf(char* p, size_t l) {
|
||||
setg(p, p, p + l);
|
||||
|
||||
}
|
||||
|
||||
ByteMemStream::ByteMemStream(char* p, size_t l):
|
||||
std::istream(&buffer),
|
||||
buffer(p, l)
|
||||
{
|
||||
rdbuf(&buffer);
|
||||
}
|
||||
|
||||
std::vector<std::shared_ptr<std::stringstream>> fragment_message(std::string_view v, uint64_t stride) {
|
||||
if(v.length() <= stride)
|
||||
return {};
|
||||
uint64_t nextByte = 0;
|
||||
std::vector<std::shared_ptr<std::stringstream>> toRet;
|
||||
toRet.emplace_back(std::make_shared<std::stringstream>());
|
||||
{
|
||||
cereal::PortableBinaryOutputArchive m(*toRet.back());
|
||||
m((MessageCommandType)0, (uint64_t)v.length(), cereal::binary_data(v.data() + nextByte, stride));
|
||||
}
|
||||
nextByte += stride;
|
||||
for(;;) {
|
||||
if((v.length() - nextByte) <= stride) {
|
||||
toRet.emplace_back(std::make_shared<std::stringstream>());
|
||||
{
|
||||
cereal::PortableBinaryOutputArchive m(*toRet.back());
|
||||
m((MessageCommandType)0, cereal::binary_data(v.data() + nextByte, (uint64_t)(v.length() - nextByte)));
|
||||
}
|
||||
break;
|
||||
}
|
||||
toRet.emplace_back(std::make_shared<std::stringstream>());
|
||||
{
|
||||
cereal::PortableBinaryOutputArchive m(*toRet.back());
|
||||
m((MessageCommandType)0, cereal::binary_data(v.data() + nextByte, stride));
|
||||
}
|
||||
nextByte += stride;
|
||||
}
|
||||
return toRet;
|
||||
}
|
||||
19
include/Helpers/Networking/ByteStream.hpp
Normal file
19
include/Helpers/Networking/ByteStream.hpp
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
#pragma once
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
// https://tuttlem.github.io/2014/08/18/getting-istream-to-work-off-a-byte-array.html
|
||||
class ByteMemBuf : public std::basic_streambuf<char> {
|
||||
public:
|
||||
ByteMemBuf(char* p, size_t l);
|
||||
};
|
||||
class ByteMemStream : public std::istream {
|
||||
public:
|
||||
ByteMemStream(char* p, size_t l);
|
||||
private:
|
||||
ByteMemBuf buffer;
|
||||
};
|
||||
|
||||
std::vector<std::shared_ptr<std::stringstream>> fragment_message(std::string_view v, uint64_t stride);
|
||||
165
include/Helpers/Networking/NetClient.cpp
Normal file
165
include/Helpers/Networking/NetClient.cpp
Normal file
|
|
@ -0,0 +1,165 @@
|
|||
#include "NetClient.hpp"
|
||||
#include "NetLibrary.hpp"
|
||||
#include "NetServer.hpp"
|
||||
#include <chrono>
|
||||
#include <Helpers/Logger.hpp>
|
||||
|
||||
NetClient::NetClient(const std::string& serverFullID) {
|
||||
receiveQueue = std::make_shared<ReceiveQueue>();
|
||||
this->serverFullID = serverFullID;
|
||||
lastMessageTime = std::chrono::steady_clock::now();
|
||||
}
|
||||
|
||||
NetClient::NetClient(NetServer& server) {
|
||||
receiveQueue = std::make_shared<ReceiveQueue>();
|
||||
directConnectServer = &server;
|
||||
server.directConnectClient = this;
|
||||
server.directConnectClientData = std::make_shared<NetServer::ClientData>();
|
||||
server.directConnectClientData->lastMessageTime = std::chrono::steady_clock::now();
|
||||
server.clients.emplace_back(server.directConnectClientData);
|
||||
lastMessageTime = std::chrono::steady_clock::now();
|
||||
}
|
||||
|
||||
void NetClient::init_channel(const std::string& channelName, std::shared_ptr<rtc::DataChannel> channel) {
|
||||
if(isDisconnected)
|
||||
return;
|
||||
auto commandChannelOnMessage = [channelName, rQueueWeak = make_weak_ptr(receiveQueue)](rtc::message_variant data) {
|
||||
auto rQueue = rQueueWeak.lock();
|
||||
if(rQueue) {
|
||||
std::scoped_lock rQueueLock(rQueue->mut);
|
||||
rQueue->messages.emplace(channelName, std::get<rtc::binary>(data));
|
||||
}
|
||||
};
|
||||
channel->onMessage(commandChannelOnMessage);
|
||||
channel->onClosed([cWeak = make_weak_ptr(shared_from_this())]() {
|
||||
auto cLock = cWeak.lock();
|
||||
if(cLock)
|
||||
cLock->isDisconnected = true;
|
||||
});
|
||||
channels[channelName] = channel;
|
||||
Logger::get().log("INFO", "Client channel setup: " + channelName);
|
||||
lastMessageTime = std::chrono::steady_clock::now();
|
||||
}
|
||||
|
||||
void NetClient::update() {
|
||||
if(isDisconnected)
|
||||
return;
|
||||
if(std::chrono::steady_clock::now() - lastMessageTime >= NetLibrary::TIMEOUT_DURATION) {
|
||||
Logger::get().log("INFO", "Connection timed out");
|
||||
isDisconnected = true;
|
||||
return;
|
||||
}
|
||||
parse_received_messages();
|
||||
send_queued_messages();
|
||||
}
|
||||
|
||||
void NetClient::parse_received_messages() {
|
||||
std::scoped_lock rQueueLock(receiveQueue->mut);
|
||||
while(!receiveQueue->messages.empty()) {
|
||||
auto& receivedMessage = receiveQueue->messages.front();
|
||||
|
||||
lastMessageTime = std::chrono::steady_clock::now();
|
||||
|
||||
size_t messageOrderDisplacement = 0;
|
||||
if(receivedMessage.channel == UNRELIABLE_COMMAND_CHANNEL || receivedMessage.channel == RELIABLE_COMMAND_CHANNEL) {
|
||||
MessageOrder messageOrder = NetLibrary::get_message_order(receivedMessage.data);
|
||||
if(nextMessageOrderToExpect > messageOrder && receivedMessage.channel == UNRELIABLE_COMMAND_CHANNEL) {
|
||||
receiveQueue->messages.pop();
|
||||
continue;
|
||||
}
|
||||
nextMessageOrderToExpect = std::max(messageOrder + 1, nextMessageOrderToExpect);
|
||||
messageOrderDisplacement = sizeof(MessageOrder);
|
||||
}
|
||||
|
||||
ByteMemStream strm((char*)(receivedMessage.data.data() + messageOrderDisplacement), receivedMessage.data.size() - messageOrderDisplacement);
|
||||
cereal::PortableBinaryInputArchive inArchive(strm);
|
||||
|
||||
auto& spfm = pfm[receivedMessage.channel];
|
||||
|
||||
MessageCommandType commandID;
|
||||
inArchive(commandID);
|
||||
|
||||
if(commandID == 0) {
|
||||
if(spfm.partialFragmentMessage.size() == 0) {
|
||||
spfm.partialFragmentMessageLoc = 0;
|
||||
uint64_t messageSize;
|
||||
inArchive(messageSize);
|
||||
spfm.partialFragmentMessage.resize(messageSize);
|
||||
inArchive(cereal::binary_data(spfm.partialFragmentMessage.data() + spfm.partialFragmentMessageLoc, NetLibrary::FRAGMENT_MESSAGE_STRIDE));
|
||||
spfm.partialFragmentMessageLoc += NetLibrary::FRAGMENT_MESSAGE_STRIDE;
|
||||
}
|
||||
else if(spfm.partialFragmentMessage.size() - spfm.partialFragmentMessageLoc <= NetLibrary::FRAGMENT_MESSAGE_STRIDE) {
|
||||
inArchive(cereal::binary_data(spfm.partialFragmentMessage.data() + spfm.partialFragmentMessageLoc, spfm.partialFragmentMessage.size() - spfm.partialFragmentMessageLoc));
|
||||
ByteMemStream completeStrm(spfm.partialFragmentMessage.data(), spfm.partialFragmentMessage.size());
|
||||
cereal::PortableBinaryInputArchive completeArchive(completeStrm);
|
||||
MessageCommandType completeCommandID;
|
||||
completeArchive(completeCommandID);
|
||||
recvCallbacks[completeCommandID](completeArchive);
|
||||
spfm.partialFragmentMessage.clear();
|
||||
spfm.partialFragmentMessageLoc = 0;
|
||||
}
|
||||
else {
|
||||
inArchive(cereal::binary_data(spfm.partialFragmentMessage.data() + spfm.partialFragmentMessageLoc, NetLibrary::FRAGMENT_MESSAGE_STRIDE));
|
||||
spfm.partialFragmentMessageLoc += NetLibrary::FRAGMENT_MESSAGE_STRIDE;
|
||||
}
|
||||
}
|
||||
else
|
||||
recvCallbacks[commandID](inArchive);
|
||||
|
||||
receiveQueue->messages.pop();
|
||||
}
|
||||
}
|
||||
|
||||
void NetClient::send_queued_messages() {
|
||||
for(auto& [channelName, messageQueue] : messageQueues) {
|
||||
bool addMessageOrder = channelName == UNRELIABLE_COMMAND_CHANNEL || channelName == RELIABLE_COMMAND_CHANNEL;
|
||||
if(directConnectServer) {
|
||||
auto& c = directConnectServer->directConnectClientData;
|
||||
std::scoped_lock rMessLock(c->receivedMessagesMutex);
|
||||
while(!messageQueue.empty()) {
|
||||
std::string toInsert;
|
||||
if(addMessageOrder)
|
||||
toInsert = NetLibrary::attach_message_order_and_update(nextMessageOrderToSend, messageQueue.front()->str());
|
||||
else
|
||||
toInsert = messageQueue.front()->str();
|
||||
rtc::binary b((std::byte*)toInsert.c_str(), (std::byte*)(toInsert.c_str() + toInsert.length()));
|
||||
c->receivedMessages.emplace(channelName, b);
|
||||
messageQueue.pop();
|
||||
}
|
||||
}
|
||||
else {
|
||||
auto& channel = channels[channelName];
|
||||
if(channel) {
|
||||
if(channel->isOpen()) {
|
||||
while(!messageQueue.empty()) {
|
||||
#ifdef __EMSCRIPTEN__
|
||||
if(channel->bufferedAmount() >= NetLibrary::MAX_BUFFERED_DATA_PER_CHANNEL)
|
||||
break;
|
||||
#endif
|
||||
if(addMessageOrder)
|
||||
send_str_as_bytes(channel, NetLibrary::attach_message_order_and_update(nextMessageOrderToSend, messageQueue.front()->str()));
|
||||
else
|
||||
send_str_as_bytes(channel, messageQueue.front()->str());
|
||||
messageQueue.pop();
|
||||
}
|
||||
#ifdef __EMSCRIPTEN__
|
||||
if(channel->bufferedAmount() >= NetLibrary::MAX_BUFFERED_DATA_PER_CHANNEL && channelName == UNRELIABLE_COMMAND_CHANNEL)
|
||||
messageQueue = std::queue<std::shared_ptr<std::stringstream>>();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool NetClient::is_disconnected() {
|
||||
return isDisconnected;
|
||||
}
|
||||
|
||||
void NetClient::send_str_as_bytes(std::shared_ptr<rtc::DataChannel> channel, const std::string& str) {
|
||||
channel->send((const std::byte*)str.c_str(), str.size());
|
||||
}
|
||||
|
||||
void NetClient::add_recv_callback(MessageCommandType commandID, const NetClientRecvCallback& callback) {
|
||||
recvCallbacks[commandID] = callback;
|
||||
}
|
||||
83
include/Helpers/Networking/NetClient.hpp
Normal file
83
include/Helpers/Networking/NetClient.hpp
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
#pragma once
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <rtc/rtc.hpp>
|
||||
#include <cereal/cereal.hpp>
|
||||
#include <unordered_map>
|
||||
#include <functional>
|
||||
#include <cereal/cereal.hpp>
|
||||
#include <cereal/archives/portable_binary.hpp>
|
||||
#include <queue>
|
||||
#include "ByteStream.hpp"
|
||||
#include "NetLibrary.hpp"
|
||||
|
||||
typedef std::function<void(cereal::PortableBinaryInputArchive&)> NetClientRecvCallback;
|
||||
|
||||
class NetClient : public std::enable_shared_from_this<NetClient> {
|
||||
public:
|
||||
NetClient(const std::string& serverFullID);
|
||||
NetClient(NetServer& server);
|
||||
template <typename... Args> void send_items_to_server(const std::string& channel, Args&&... items) {
|
||||
if(isDisconnected)
|
||||
return;
|
||||
auto ss(std::make_shared<std::stringstream>());
|
||||
{
|
||||
cereal::PortableBinaryOutputArchive m(*ss);
|
||||
(m(items), ...);
|
||||
}
|
||||
std::vector<std::shared_ptr<std::stringstream>> fragmentedMessage = fragment_message(ss->view(), NetLibrary::FRAGMENT_MESSAGE_STRIDE);
|
||||
auto& messageQueue = messageQueues[channel];
|
||||
if(fragmentedMessage.empty())
|
||||
messageQueue.emplace(ss);
|
||||
else if(channel != UNRELIABLE_COMMAND_CHANNEL) { // Just drop unreliable messages that are fragmented
|
||||
for(auto& ss2 : fragmentedMessage)
|
||||
messageQueue.emplace(ss2);
|
||||
}
|
||||
}
|
||||
void update();
|
||||
void add_recv_callback(MessageCommandType commandID, const NetClientRecvCallback& callback);
|
||||
bool is_disconnected();
|
||||
|
||||
private:
|
||||
std::string localID;
|
||||
void init_channel(const std::string& channelName, std::shared_ptr<rtc::DataChannel> channel);
|
||||
void parse_received_messages();
|
||||
void send_queued_messages();
|
||||
void send_str_as_bytes(std::shared_ptr<rtc::DataChannel> channel, const std::string& str);
|
||||
|
||||
std::unordered_map<std::string, std::queue<std::shared_ptr<std::stringstream>>> messageQueues;
|
||||
|
||||
struct ReceiveQueue {
|
||||
std::mutex mut;
|
||||
struct ReceivedMessage {
|
||||
std::string channel;
|
||||
rtc::binary data;
|
||||
};
|
||||
std::queue<ReceivedMessage> messages;
|
||||
};
|
||||
std::shared_ptr<ReceiveQueue> receiveQueue;
|
||||
|
||||
std::unordered_map<std::string, std::shared_ptr<rtc::DataChannel>> channels;
|
||||
|
||||
struct PartialFragmentMessage {
|
||||
std::string partialFragmentMessage;
|
||||
uint64_t partialFragmentMessageLoc = 0;
|
||||
};
|
||||
std::unordered_map<std::string, PartialFragmentMessage> pfm;
|
||||
|
||||
std::unordered_map<MessageCommandType, NetClientRecvCallback> recvCallbacks;
|
||||
|
||||
NetServer* directConnectServer = nullptr;
|
||||
std::atomic<bool> isDisconnected = false;
|
||||
|
||||
MessageOrder nextMessageOrderToSend = 0;
|
||||
MessageOrder nextMessageOrderToExpect = 0;
|
||||
|
||||
std::string serverFullID;
|
||||
bool fullyRegistered = false;
|
||||
|
||||
std::chrono::steady_clock::time_point lastMessageTime;
|
||||
|
||||
friend class NetLibrary;
|
||||
friend class NetServer;
|
||||
};
|
||||
389
include/Helpers/Networking/NetLibrary.cpp
Normal file
389
include/Helpers/Networking/NetLibrary.cpp
Normal file
|
|
@ -0,0 +1,389 @@
|
|||
#include "NetLibrary.hpp"
|
||||
#include <fstream>
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <rtc/peerconnection.hpp>
|
||||
#include "../Logger.hpp"
|
||||
#include <rtc/rtc.hpp>
|
||||
#include <variant>
|
||||
#include <vector>
|
||||
#include <Helpers/Random.hpp>
|
||||
#include "NetClient.hpp"
|
||||
#include "NetServer.hpp"
|
||||
#include <bit>
|
||||
|
||||
std::atomic<bool> NetLibrary::alreadyInitialized = false;
|
||||
std::string NetLibrary::signalingAddr;
|
||||
std::shared_ptr<rtc::WebSocket> NetLibrary::ws;
|
||||
std::unordered_map<std::string, std::shared_ptr<NetLibrary::PeerData>> NetLibrary::peers;
|
||||
std::string NetLibrary::globalID;
|
||||
rtc::Configuration NetLibrary::config;
|
||||
std::mutex NetLibrary::serverListMutex;
|
||||
std::mutex NetLibrary::clientListMutex;
|
||||
std::mutex NetLibrary::peerListMutex;
|
||||
std::vector<std::weak_ptr<NetClient>> NetLibrary::clients;
|
||||
std::vector<std::weak_ptr<NetServer>> NetLibrary::servers;
|
||||
std::promise<void> NetLibrary::wsPromise;
|
||||
|
||||
void NetLibrary::init() {
|
||||
if(alreadyInitialized)
|
||||
return;
|
||||
|
||||
using json = nlohmann::json;
|
||||
std::ifstream f("data/config/p2p.json");
|
||||
|
||||
std::vector<std::string> stunServerList;
|
||||
std::vector<std::string> turnServerList;
|
||||
|
||||
rtc::InitLogger(rtc::LogLevel::Info, [](rtc::LogLevel level, std::string message) {
|
||||
Logger::get().log("INFO", "[LibDataChannel Log Level " + std::to_string(static_cast<int>(level)) + "] " + message);
|
||||
});
|
||||
|
||||
if(f.is_open()) {
|
||||
try {
|
||||
json j;
|
||||
f >> j;
|
||||
|
||||
j.at("signalingServer").get_to<std::string>(signalingAddr);
|
||||
|
||||
std::vector<std::string> stunList = j.at("stunList").get<std::vector<std::string>>();
|
||||
for(std::string& s : stunList) {
|
||||
if(s.substr(0, 5).compare("stun:"))
|
||||
s.insert(0, "stun:");
|
||||
config.iceServers.emplace_back(s);
|
||||
}
|
||||
}
|
||||
catch(...) {
|
||||
throw std::runtime_error("[NetLibrary::init] Invalid config/p2p.json");
|
||||
}
|
||||
}
|
||||
else
|
||||
throw std::runtime_error("[NetLibrary::init] Could not open config/p2p.json");
|
||||
|
||||
f.close();
|
||||
|
||||
|
||||
// Not sure why, but TLS verification fails on mac
|
||||
// Should be fixed later, but for now this is fine, as the signaling server doesn't have
|
||||
// critical information
|
||||
#ifdef __APPLE__
|
||||
rtc::WebSocket::Configuration wsConfig;
|
||||
wsConfig.disableTlsVerification = true;
|
||||
ws = std::make_shared<rtc::WebSocket>(wsConfig);
|
||||
#else
|
||||
ws = std::make_shared<rtc::WebSocket>();
|
||||
#endif
|
||||
|
||||
auto wsFuture = wsPromise.get_future();
|
||||
|
||||
ws->onOpen([]() {
|
||||
Logger::get().log("INFO", "Websocket connected, signaling ready");
|
||||
wsPromise.set_value();
|
||||
});
|
||||
|
||||
ws->onError([](std::string s) {
|
||||
Logger::get().log("INFO", "Websocket error: " + s);
|
||||
wsPromise.set_exception(std::make_exception_ptr(std::runtime_error(s)));
|
||||
});
|
||||
|
||||
ws->onClosed([]() {
|
||||
Logger::get().log("INFO", "Websocket closed");
|
||||
destroy();
|
||||
});
|
||||
|
||||
ws->onMessage([wws = make_weak_ptr(ws)](auto data) {
|
||||
if(!std::holds_alternative<std::string>(data))
|
||||
return;
|
||||
|
||||
json message = json::parse(std::get<std::string>(data));
|
||||
|
||||
auto itID = message.find("id");
|
||||
if(itID == message.end())
|
||||
return;
|
||||
auto id = itID->get<std::string>();
|
||||
|
||||
auto itType = message.find("type");
|
||||
auto type = itType->get<std::string>();
|
||||
|
||||
std::shared_ptr<rtc::PeerConnection> peerConnection;
|
||||
{
|
||||
std::scoped_lock peerListLock(peerListMutex);
|
||||
auto peerIt = peers.find(id);
|
||||
if(peerIt != peers.end())
|
||||
peerConnection = peerIt->second->connection;
|
||||
else if(type == "offer") {
|
||||
Logger::get().log("INFO", "Websocket answering to " + id);
|
||||
auto peer = setup_peer_connection(config, wws, id);
|
||||
peers[id] = peer;
|
||||
peerConnection = peer->connection;
|
||||
}
|
||||
}
|
||||
|
||||
if(!peerConnection)
|
||||
return;
|
||||
|
||||
if(type == "offer" || type == "answer") {
|
||||
auto sdp = message["description"].get<std::string>();
|
||||
peerConnection->setRemoteDescription(rtc::Description(sdp, type));
|
||||
}
|
||||
else if(type == "candidate") {
|
||||
auto sdp = message["candidate"].get<std::string>();
|
||||
auto mid = message["mid"].get<std::string>();
|
||||
peerConnection->addRemoteCandidate(rtc::Candidate(sdp, mid));
|
||||
}
|
||||
});
|
||||
|
||||
get_global_id();
|
||||
|
||||
Logger::get().log("INFO", "NetLibrary global id: " + globalID);
|
||||
|
||||
const std::string wsPrefix = signalingAddr.find("://") == std::string::npos ? "ws://" : "";
|
||||
const std::string url = wsPrefix + signalingAddr + "/" + globalID;
|
||||
|
||||
Logger::get().log("INFO", "Websocket URL is: " + url);
|
||||
ws->open(url);
|
||||
|
||||
Logger::get().log("INFO", "NetLibrary waiting for signaling to be connected...");
|
||||
//wsFuture.get();
|
||||
|
||||
alreadyInitialized = true;
|
||||
}
|
||||
|
||||
std::string NetLibrary::get_random_server_local_id() {
|
||||
return Random::get().alphanumeric_str(LOCALID_LEN);
|
||||
}
|
||||
|
||||
const std::string& NetLibrary::get_global_id() {
|
||||
if(globalID.empty())
|
||||
globalID = Random::get().alphanumeric_str(GLOBALID_LEN);
|
||||
return globalID;
|
||||
}
|
||||
|
||||
void NetLibrary::register_server(std::shared_ptr<NetServer> server) {
|
||||
std::scoped_lock serverListLock(serverListMutex);
|
||||
servers.emplace_back(server);
|
||||
}
|
||||
|
||||
void NetLibrary::register_client(std::shared_ptr<NetClient> client) {
|
||||
client->localID = Random::get().alphanumeric_str(LOCALID_LEN);
|
||||
std::scoped_lock serverListLock(clientListMutex);
|
||||
clients.emplace_back(client);
|
||||
}
|
||||
|
||||
void NetLibrary::finish_client_registration(std::shared_ptr<NetClient> client) {
|
||||
try {
|
||||
std::string serverGlobalID = client->serverFullID.substr(0, GLOBALID_LEN);
|
||||
std::string serverLocalID = client->serverFullID.substr(GLOBALID_LEN);
|
||||
std::scoped_lock peerListLock(peerListMutex);
|
||||
if(!peers.contains(serverGlobalID)) {
|
||||
auto peer = setup_peer_connection(config, ws, serverGlobalID);
|
||||
peers[serverGlobalID] = peer;
|
||||
peer->channel = peer->connection->createDataChannel(MAIN_NETLIBRARY_CHANNEL);
|
||||
setup_main_data_channel(peer, serverGlobalID);
|
||||
}
|
||||
peers[serverGlobalID]->messageQueue.emplace(client->localID + serverLocalID);
|
||||
client->fullyRegistered = true;
|
||||
}
|
||||
catch(const std::exception& e) {
|
||||
Logger::get().log("INFO", "Invalid client registration: " + std::string(e.what()));
|
||||
client->isDisconnected = true;
|
||||
}
|
||||
}
|
||||
|
||||
void NetLibrary::update() {
|
||||
{
|
||||
std::scoped_lock clientListLock(clientListMutex);
|
||||
std::erase_if(clients, [](auto& cWeak) {
|
||||
auto cLock = cWeak.lock();
|
||||
if(!cLock)
|
||||
return true;
|
||||
if(!cLock->fullyRegistered && ws->isOpen())
|
||||
finish_client_registration(cLock);
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
{
|
||||
std::scoped_lock serverListLock(serverListMutex);
|
||||
std::erase_if(servers, [](auto& sWeak) {
|
||||
return !sWeak.lock();
|
||||
});
|
||||
}
|
||||
|
||||
{
|
||||
std::scoped_lock peerListLock(peerListMutex);
|
||||
std::erase_if(peers, [](auto& a) {
|
||||
auto& p = a.second;
|
||||
rtc::PeerConnection::State s = p->connection->state();
|
||||
if(s == rtc::PeerConnection::State::Disconnected || s == rtc::PeerConnection::State::Closed || s == rtc::PeerConnection::State::Failed)
|
||||
return true;
|
||||
if(p->channel) {
|
||||
if(p->channel->isOpen()) {
|
||||
while(!p->messageQueue.empty()) {
|
||||
p->channel->send(p->messageQueue.front());
|
||||
Logger::get().log("INFO", "Sent main channel message: " + p->messageQueue.front());
|
||||
p->messageQueue.pop();
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<NetLibrary::PeerData> NetLibrary::setup_peer_connection(const rtc::Configuration& config, std::weak_ptr<rtc::WebSocket> wws, std::string id) {
|
||||
using json = nlohmann::json;
|
||||
auto peer = std::make_shared<PeerData>();
|
||||
peer->connection = std::make_shared<rtc::PeerConnection>(config);
|
||||
|
||||
peer->connection->onLocalDescription([wws, id](rtc::Description description) {
|
||||
json message = {{"id", id},
|
||||
{"type", description.typeString()},
|
||||
{"description", std::string(description)}};
|
||||
|
||||
if (auto ws = wws.lock())
|
||||
ws->send(message.dump());
|
||||
});
|
||||
|
||||
peer->connection->onLocalCandidate([wws, id](rtc::Candidate candidate) {
|
||||
json message = {{"id", id},
|
||||
{"type", "candidate"},
|
||||
{"candidate", std::string(candidate)},
|
||||
{"mid", candidate.mid()}};
|
||||
|
||||
if (auto ws = wws.lock())
|
||||
ws->send(message.dump());
|
||||
});
|
||||
|
||||
peer->connection->onDataChannel([id, peerWeak = make_weak_ptr(peer)](std::shared_ptr<rtc::DataChannel> dc) {
|
||||
auto peer = peerWeak.lock();
|
||||
if(peer) {
|
||||
if(!peer->channel) {
|
||||
if(dc->label() == MAIN_NETLIBRARY_CHANNEL) {
|
||||
peer->channel = dc;
|
||||
setup_main_data_channel(peer, id);
|
||||
}
|
||||
else {
|
||||
Logger::get().log("INFO", "Client connected with incorrect first channel name: " + dc->label() + ". Could be from a different version of the program");
|
||||
dc->send("REFUSE");
|
||||
peer->connection->close();
|
||||
}
|
||||
}
|
||||
else
|
||||
channel_created_in_client(dc);
|
||||
}
|
||||
});
|
||||
|
||||
return peer;
|
||||
}
|
||||
|
||||
void NetLibrary::setup_main_data_channel(std::shared_ptr<PeerData> peer, const std::string& id) {
|
||||
peer->channel->onOpen([id]() {
|
||||
Logger::get().log("INFO", "Main data channel has opened on " + id);
|
||||
});
|
||||
peer->channel->onClosed([id]() {
|
||||
Logger::get().log("INFO", "Main data channel has closed on " + id);
|
||||
});
|
||||
peer->channel->onMessage([id, pConWeak = make_weak_ptr(peer->connection)](auto data) {
|
||||
if(!std::holds_alternative<std::string>(data)) { // Main channel messages will only carry text
|
||||
Logger::get().log("INFO", "Main data channel received binary message, which is not expected");
|
||||
return;
|
||||
}
|
||||
std::string message = std::get<std::string>(data);
|
||||
if(message == "REFUSE") {
|
||||
Logger::get().log("USERINFO", "Refused connection. Could be on a different version");
|
||||
auto pConLock = pConWeak.lock();
|
||||
if(pConLock)
|
||||
pConLock->close();
|
||||
}
|
||||
else {
|
||||
std::string clientLocalID = message.substr(0, LOCALID_LEN);
|
||||
std::string serverLocalID = message.substr(LOCALID_LEN);
|
||||
Logger::get().log("INFO", "Main data channel received message " + message);
|
||||
auto pConLock = pConWeak.lock();
|
||||
if(pConLock)
|
||||
assign_client_connection_to_server(serverLocalID, clientLocalID, pConLock);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void NetLibrary::assign_client_connection_to_server(const std::string& serverLocalID, const std::string& clientLocalID, std::shared_ptr<rtc::PeerConnection> connection) {
|
||||
std::scoped_lock serverListLock(serverListMutex);
|
||||
auto serverIt = std::find_if(servers.begin(), servers.end(), [&serverLocalID](auto& s) {
|
||||
auto sLock = s.lock();
|
||||
if(!sLock)
|
||||
return false;
|
||||
return sLock->localID == serverLocalID;
|
||||
});
|
||||
if(serverIt != servers.end()) {
|
||||
auto sLock = serverIt->lock();
|
||||
if(sLock)
|
||||
sLock->client_connected(connection, clientLocalID);
|
||||
}
|
||||
}
|
||||
|
||||
void NetLibrary::channel_created_in_client(std::shared_ptr<rtc::DataChannel> channel) {
|
||||
std::string clientID = channel->label().substr(0, LOCALID_LEN);
|
||||
std::string channelName = channel->label().substr(LOCALID_LEN);
|
||||
std::scoped_lock clientListLock(clientListMutex);
|
||||
auto clientIt = std::find_if(clients.begin(), clients.end(), [&clientID](auto& c) {
|
||||
auto cLock = c.lock();
|
||||
if(!cLock)
|
||||
return false;
|
||||
return cLock->localID == clientID;
|
||||
});
|
||||
if(clientIt != clients.end()) {
|
||||
auto cLock = clientIt->lock();
|
||||
if(cLock)
|
||||
cLock->init_channel(channelName, channel);
|
||||
}
|
||||
}
|
||||
|
||||
std::string NetLibrary::attach_message_order_and_update(MessageOrder& order, const std::string& message) {
|
||||
union {
|
||||
MessageOrder o;
|
||||
char b[sizeof(MessageOrder)];
|
||||
} u;
|
||||
u.o = (std::endian::native == std::endian::little) ? std::byteswap(order) : order;
|
||||
order++;
|
||||
return std::string(u.b, sizeof(MessageOrder)) + message;
|
||||
}
|
||||
|
||||
MessageOrder NetLibrary::get_message_order(rtc::binary& message) {
|
||||
union {
|
||||
MessageOrder o;
|
||||
char b[sizeof(MessageOrder)];
|
||||
} u;
|
||||
std::memcpy(u.b, message.data(), sizeof(MessageOrder));
|
||||
if(std::endian::native == std::endian::little)
|
||||
u.o = std::byteswap(u.o);
|
||||
return u.o;
|
||||
}
|
||||
|
||||
void NetLibrary::destroy() {
|
||||
ws = nullptr;
|
||||
{
|
||||
std::scoped_lock peerListLock(peerListMutex);
|
||||
peers.clear();
|
||||
}
|
||||
{
|
||||
std::scoped_lock clientListLock(clientListMutex);
|
||||
for(auto& c : clients) {
|
||||
auto cLock = c.lock();
|
||||
if(cLock)
|
||||
cLock->isDisconnected = true;
|
||||
}
|
||||
clients.clear();
|
||||
}
|
||||
{
|
||||
std::scoped_lock serverListLock(serverListMutex);
|
||||
for(auto& s : servers) {
|
||||
auto sLock = s.lock();
|
||||
if(sLock)
|
||||
sLock->isDisconnected = true;
|
||||
}
|
||||
servers.clear();
|
||||
}
|
||||
wsPromise = std::promise<void>();
|
||||
//globalID.clear(); // Do we need to do this? (Causes problems when global ID changes from the user's side after they copy a lobby address
|
||||
alreadyInitialized = false;
|
||||
}
|
||||
73
include/Helpers/Networking/NetLibrary.hpp
Normal file
73
include/Helpers/Networking/NetLibrary.hpp
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
#pragma once
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <rtc/rtc.hpp>
|
||||
#include <queue>
|
||||
|
||||
typedef uint64_t MessageOrder;
|
||||
class NetClient;
|
||||
class NetServer;
|
||||
|
||||
template <class T> std::weak_ptr<T> make_weak_ptr(std::shared_ptr<T> ptr) { return ptr; }
|
||||
|
||||
#define RELIABLE_COMMAND_CHANNEL "reliableCommand"
|
||||
#define UNRELIABLE_COMMAND_CHANNEL "unreliableCommand"
|
||||
#define RESOURCE_COMMAND_CHANNEL "resourceCommand"
|
||||
#define MAIN_NETLIBRARY_CHANNEL "main00001"
|
||||
|
||||
typedef uint8_t MessageCommandType;
|
||||
|
||||
class NetLibrary {
|
||||
public:
|
||||
static void init();
|
||||
static void update();
|
||||
static std::string get_random_server_local_id();
|
||||
static const std::string& get_global_id();
|
||||
|
||||
static void register_server(std::shared_ptr<NetServer> server);
|
||||
static void register_client(std::shared_ptr<NetClient> client);
|
||||
|
||||
static void destroy();
|
||||
|
||||
static constexpr size_t LOCALID_LEN = 5;
|
||||
static constexpr size_t GLOBALID_LEN = 20;
|
||||
private:
|
||||
static constexpr size_t FRAGMENT_MESSAGE_STRIDE = 4096;
|
||||
static constexpr size_t MAX_BUFFERED_DATA_PER_CHANNEL = 65500;
|
||||
static constexpr std::chrono::seconds TIMEOUT_DURATION = std::chrono::seconds(15);
|
||||
|
||||
static void finish_client_registration(std::shared_ptr<NetClient> client);
|
||||
|
||||
static void assign_client_connection_to_server(const std::string& serverLocalID, const std::string& clientLocalID, std::shared_ptr<rtc::PeerConnection> connection);
|
||||
static void channel_created_in_client(std::shared_ptr<rtc::DataChannel> channel);
|
||||
|
||||
static std::string attach_message_order_and_update(MessageOrder& order, const std::string& message);
|
||||
static MessageOrder get_message_order(rtc::binary& message);
|
||||
|
||||
struct PeerData {
|
||||
std::shared_ptr<rtc::PeerConnection> connection;
|
||||
std::shared_ptr<rtc::DataChannel> channel;
|
||||
std::queue<std::string> messageQueue;
|
||||
};
|
||||
|
||||
static void setup_main_data_channel(std::shared_ptr<PeerData> peer, const std::string& id);
|
||||
static std::shared_ptr<PeerData> setup_peer_connection(const rtc::Configuration& config, std::weak_ptr<rtc::WebSocket> wws, std::string id);
|
||||
|
||||
static std::atomic<bool> alreadyInitialized;
|
||||
static std::string signalingAddr;
|
||||
static std::shared_ptr<rtc::WebSocket> ws;
|
||||
static std::unordered_map<std::string, std::shared_ptr<PeerData>> peers;
|
||||
static std::string globalID;
|
||||
static rtc::Configuration config;
|
||||
|
||||
static std::mutex clientListMutex;
|
||||
static std::mutex serverListMutex;
|
||||
static std::mutex peerListMutex;
|
||||
static std::vector<std::weak_ptr<NetClient>> clients;
|
||||
static std::vector<std::weak_ptr<NetServer>> servers;
|
||||
|
||||
friend class NetClient;
|
||||
friend class NetServer;
|
||||
|
||||
static std::promise<void> wsPromise;
|
||||
};
|
||||
201
include/Helpers/Networking/NetServer.cpp
Normal file
201
include/Helpers/Networking/NetServer.cpp
Normal file
|
|
@ -0,0 +1,201 @@
|
|||
#include "NetServer.hpp"
|
||||
#include "NetLibrary.hpp"
|
||||
#include <chrono>
|
||||
#include <rtc/peerconnection.hpp>
|
||||
#include <Helpers/Random.hpp>
|
||||
#include <Helpers/Logger.hpp>
|
||||
#include "NetClient.hpp"
|
||||
|
||||
NetServer::NetServer(const std::string& serverLocalID) {
|
||||
localID = serverLocalID;
|
||||
Logger::get().log("INFO", "[NetServer::NetServer] Server has local id: " + serverLocalID);
|
||||
}
|
||||
|
||||
void NetServer::update() {
|
||||
if(isDisconnected)
|
||||
return;
|
||||
std::erase_if(clients, [&](auto& client) {
|
||||
bool timedOut = std::chrono::steady_clock::now() - client->lastMessageTime > NetLibrary::TIMEOUT_DURATION;
|
||||
if(client->setToDisconnect || timedOut) {
|
||||
if(disconnectCallback)
|
||||
disconnectCallback(*client);
|
||||
if(timedOut)
|
||||
Logger::get().log("INFO", "Connection timed out");
|
||||
return true;
|
||||
}
|
||||
client->parse_received_messages(*this);
|
||||
client->send_queued_messages(*this);
|
||||
return false;
|
||||
});
|
||||
|
||||
std::scoped_lock clientAddLock(clientsToAddMutex);
|
||||
clients.insert(clients.begin(), clientsToAdd.begin(), clientsToAdd.end());
|
||||
clientsToAdd.clear();
|
||||
}
|
||||
|
||||
bool NetServer::is_disconnected() {
|
||||
return isDisconnected;
|
||||
}
|
||||
|
||||
void NetServer::client_connected(std::shared_ptr<rtc::PeerConnection> connection, const std::string& clientLocalID) {
|
||||
if(isDisconnected)
|
||||
return;
|
||||
auto newClient = std::make_shared<ClientData>();
|
||||
newClient->lastMessageTime = std::chrono::steady_clock::now();
|
||||
|
||||
{
|
||||
std::scoped_lock clientAddLock(clientsToAddMutex);
|
||||
clientsToAdd.emplace_back(newClient);
|
||||
}
|
||||
|
||||
Logger::get().log("INFO", "Client connected and creating channels for: " + clientLocalID);
|
||||
newClient->channels.emplace(RELIABLE_COMMAND_CHANNEL, connection->createDataChannel(clientLocalID + RELIABLE_COMMAND_CHANNEL));
|
||||
newClient->channels.emplace(RESOURCE_COMMAND_CHANNEL, connection->createDataChannel(clientLocalID + RESOURCE_COMMAND_CHANNEL));
|
||||
rtc::DataChannelInit unreliableCommandInit;
|
||||
unreliableCommandInit.reliability.maxPacketLifeTime = std::chrono::milliseconds(2000);
|
||||
unreliableCommandInit.reliability.unordered = true;
|
||||
newClient->channels.emplace(UNRELIABLE_COMMAND_CHANNEL, connection->createDataChannel(clientLocalID + UNRELIABLE_COMMAND_CHANNEL, unreliableCommandInit));
|
||||
|
||||
newClient->channels[RELIABLE_COMMAND_CHANNEL]->onMessage([weakClient = make_weak_ptr(newClient)](rtc::message_variant data) {
|
||||
std::shared_ptr<ClientData> clientLock = weakClient.lock();
|
||||
if(clientLock) {
|
||||
std::scoped_lock recvMessLock(clientLock->receivedMessagesMutex);
|
||||
clientLock->receivedMessages.emplace(RELIABLE_COMMAND_CHANNEL, std::get<rtc::binary>(data));
|
||||
}
|
||||
});
|
||||
// If any of the local channels are closed, we can assume the client has disconnected
|
||||
newClient->channels[RELIABLE_COMMAND_CHANNEL]->onClosed([weakClient = make_weak_ptr(newClient)]() {
|
||||
std::shared_ptr<ClientData> clientLock = weakClient.lock();
|
||||
if(clientLock)
|
||||
clientLock->setToDisconnect = true;
|
||||
});
|
||||
newClient->channels[UNRELIABLE_COMMAND_CHANNEL]->onMessage([weakClient = make_weak_ptr(newClient)](rtc::message_variant data) {
|
||||
std::shared_ptr<ClientData> clientLock = weakClient.lock();
|
||||
if(clientLock) {
|
||||
std::scoped_lock recvMessLock(clientLock->receivedMessagesMutex);
|
||||
clientLock->receivedMessages.emplace(UNRELIABLE_COMMAND_CHANNEL, std::get<rtc::binary>(data));
|
||||
}
|
||||
});
|
||||
newClient->channels[RESOURCE_COMMAND_CHANNEL]->onMessage([weakClient = make_weak_ptr(newClient)](rtc::message_variant data) {
|
||||
std::shared_ptr<ClientData> clientLock = weakClient.lock();
|
||||
if(clientLock) {
|
||||
std::scoped_lock recvMessLock(clientLock->receivedMessagesMutex);
|
||||
clientLock->receivedMessages.emplace(RESOURCE_COMMAND_CHANNEL, std::get<rtc::binary>(data));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void NetServer::ClientData::send_queued_messages(NetServer& server) {
|
||||
for(auto& [channelName, messageQueue] : messageQueues) {
|
||||
bool addMessageOrder = channelName == UNRELIABLE_COMMAND_CHANNEL || channelName == RELIABLE_COMMAND_CHANNEL;
|
||||
if(this == server.directConnectClientData.get()) {
|
||||
NetClient* c = server.directConnectClient;
|
||||
std::scoped_lock recvQueueLock(c->receiveQueue->mut);
|
||||
while(!messageQueue.empty()) {
|
||||
std::string toInsert;
|
||||
if(addMessageOrder)
|
||||
toInsert = NetLibrary::attach_message_order_and_update(nextMessageOrderToSend, messageQueue.front()->str());
|
||||
else
|
||||
toInsert = messageQueue.front()->str();
|
||||
rtc::binary b((std::byte*)toInsert.c_str(), (std::byte*)(toInsert.c_str() + toInsert.length()));
|
||||
c->receiveQueue->messages.emplace(channelName, b);
|
||||
messageQueue.pop();
|
||||
}
|
||||
}
|
||||
else {
|
||||
auto& channel = channels[channelName];
|
||||
if(channel) {
|
||||
if(channel->isOpen()) {
|
||||
while(!messageQueue.empty()) {
|
||||
#ifdef __EMSCRIPTEN__
|
||||
if(channel->bufferedAmount() >= NetLibrary::MAX_BUFFERED_DATA_PER_CHANNEL)
|
||||
break;
|
||||
#endif
|
||||
if(addMessageOrder)
|
||||
send_str_as_bytes(channel, NetLibrary::attach_message_order_and_update(nextMessageOrderToSend, messageQueue.front()->str()));
|
||||
else
|
||||
send_str_as_bytes(channel, messageQueue.front()->str());
|
||||
messageQueue.pop();
|
||||
}
|
||||
#ifdef __EMSCRIPTEN__
|
||||
if(channel->bufferedAmount() >= NetLibrary::MAX_BUFFERED_DATA_PER_CHANNEL && channelName == UNRELIABLE_COMMAND_CHANNEL)
|
||||
messageQueue = std::queue<std::shared_ptr<std::stringstream>>();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void NetServer::ClientData::send_str_as_bytes(std::shared_ptr<rtc::DataChannel> channel, const std::string& str) {
|
||||
channel->send((const std::byte*)str.c_str(), str.size());
|
||||
}
|
||||
|
||||
void NetServer::ClientData::parse_received_messages(NetServer& server) {
|
||||
std::scoped_lock recvMessLock(receivedMessagesMutex);
|
||||
while(!receivedMessages.empty()) {
|
||||
auto& receivedMessage = receivedMessages.front();
|
||||
lastMessageTime = std::chrono::steady_clock::now();
|
||||
|
||||
size_t messageOrderDisplacement = 0;
|
||||
if(receivedMessage.channel == UNRELIABLE_COMMAND_CHANNEL || receivedMessage.channel == RELIABLE_COMMAND_CHANNEL) {
|
||||
MessageOrder messageOrder = NetLibrary::get_message_order(receivedMessage.data);
|
||||
if(nextMessageOrderToExpect > messageOrder && receivedMessage.channel == UNRELIABLE_COMMAND_CHANNEL) {
|
||||
receivedMessages.pop();
|
||||
continue;
|
||||
}
|
||||
nextMessageOrderToExpect = std::max(messageOrder + 1, nextMessageOrderToExpect);
|
||||
messageOrderDisplacement = sizeof(MessageOrder);
|
||||
}
|
||||
|
||||
ByteMemStream strm((char*)(receivedMessage.data.data() + messageOrderDisplacement), receivedMessage.data.size() - messageOrderDisplacement);
|
||||
cereal::PortableBinaryInputArchive inArchive(strm);
|
||||
|
||||
auto& spfm = pfm[receivedMessage.channel];
|
||||
|
||||
MessageCommandType commandID;
|
||||
inArchive(commandID);
|
||||
|
||||
if(commandID == 0) {
|
||||
if(spfm.partialFragmentMessage.size() == 0) {
|
||||
spfm.partialFragmentMessageLoc = 0;
|
||||
uint64_t messageSize;
|
||||
inArchive(messageSize);
|
||||
spfm.partialFragmentMessage.resize(messageSize);
|
||||
inArchive(cereal::binary_data(spfm.partialFragmentMessage.data() + spfm.partialFragmentMessageLoc, NetLibrary::FRAGMENT_MESSAGE_STRIDE));
|
||||
spfm.partialFragmentMessageLoc += NetLibrary::FRAGMENT_MESSAGE_STRIDE;
|
||||
}
|
||||
else if(spfm.partialFragmentMessage.size() - spfm.partialFragmentMessageLoc <= NetLibrary::FRAGMENT_MESSAGE_STRIDE) {
|
||||
inArchive(cereal::binary_data(spfm.partialFragmentMessage.data() + spfm.partialFragmentMessageLoc, spfm.partialFragmentMessage.size() - spfm.partialFragmentMessageLoc));
|
||||
ByteMemStream completeStrm(spfm.partialFragmentMessage.data(), spfm.partialFragmentMessage.size());
|
||||
cereal::PortableBinaryInputArchive completeArchive(completeStrm);
|
||||
MessageCommandType completeCommandID;
|
||||
completeArchive(completeCommandID);
|
||||
server.recvCallbacks[completeCommandID](*this, completeArchive);
|
||||
spfm.partialFragmentMessage.clear();
|
||||
spfm.partialFragmentMessageLoc = 0;
|
||||
}
|
||||
else {
|
||||
inArchive(cereal::binary_data(spfm.partialFragmentMessage.data() + spfm.partialFragmentMessageLoc, NetLibrary::FRAGMENT_MESSAGE_STRIDE));
|
||||
spfm.partialFragmentMessageLoc += NetLibrary::FRAGMENT_MESSAGE_STRIDE;
|
||||
}
|
||||
}
|
||||
else
|
||||
server.recvCallbacks[commandID](*this, inArchive);
|
||||
|
||||
receivedMessages.pop();
|
||||
}
|
||||
}
|
||||
|
||||
void NetServer::add_recv_callback(MessageCommandType commandID, const NetServerRecvCallback& callback) {
|
||||
recvCallbacks[commandID] = callback;
|
||||
}
|
||||
|
||||
void NetServer::add_disconnect_callback(const NetServerDisconnectCallback& callback) {
|
||||
disconnectCallback = callback;
|
||||
}
|
||||
|
||||
NetServer::~NetServer() {
|
||||
if(directConnectClient)
|
||||
directConnectClient->isDisconnected = true;
|
||||
}
|
||||
134
include/Helpers/Networking/NetServer.hpp
Normal file
134
include/Helpers/Networking/NetServer.hpp
Normal file
|
|
@ -0,0 +1,134 @@
|
|||
#pragma once
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <rtc/rtc.hpp>
|
||||
#include <mutex>
|
||||
#include <unordered_map>
|
||||
#include <cereal/archives/portable_binary.hpp>
|
||||
#include <cereal/cereal.hpp>
|
||||
#include "ByteStream.hpp"
|
||||
#include "NetLibrary.hpp"
|
||||
|
||||
class NetServer : public std::enable_shared_from_this<NetServer> {
|
||||
public:
|
||||
struct ClientData;
|
||||
typedef std::function<void(ClientData&, cereal::PortableBinaryInputArchive&)> NetServerRecvCallback;
|
||||
typedef std::function<void(ClientData&)> NetServerConnectCallback;
|
||||
typedef std::function<void(ClientData&)> NetServerDisconnectCallback;
|
||||
struct ClientData {
|
||||
std::unordered_map<std::string, std::queue<std::shared_ptr<std::stringstream>>> messages;
|
||||
struct PartialFragmentMessage {
|
||||
std::string partialFragmentMessage;
|
||||
uint64_t partialFragmentMessageLoc = 0;
|
||||
};
|
||||
std::unordered_map<std::string, PartialFragmentMessage> pfm;
|
||||
|
||||
MessageOrder nextMessageOrderToSend = 0;
|
||||
MessageOrder nextMessageOrderToExpect = 0;
|
||||
|
||||
std::unordered_map<std::string, std::shared_ptr<rtc::DataChannel>> channels;
|
||||
|
||||
std::mutex receivedMessagesMutex;
|
||||
struct ReceivedMessage {
|
||||
std::string channel;
|
||||
rtc::binary data;
|
||||
};
|
||||
std::queue<ReceivedMessage> receivedMessages;
|
||||
|
||||
std::unordered_map<std::string, std::queue<std::shared_ptr<std::stringstream>>> messageQueues;
|
||||
|
||||
uint64_t customID;
|
||||
|
||||
void send_queued_messages(NetServer& server);
|
||||
void parse_received_messages(NetServer& server);
|
||||
void send_str_as_bytes(std::shared_ptr<rtc::DataChannel> channel, const std::string& str);
|
||||
|
||||
std::atomic<bool> setToDisconnect = false;
|
||||
std::chrono::steady_clock::time_point lastMessageTime;
|
||||
|
||||
friend class NetServer;
|
||||
friend class NetClient;
|
||||
};
|
||||
|
||||
NetServer(const std::string& serverLocalID);
|
||||
~NetServer();
|
||||
void update();
|
||||
template <typename... Args> void send_items_to_client(ClientData& client, const std::string& channel, Args&&... items) {
|
||||
auto ss(std::make_shared<std::stringstream>());
|
||||
{
|
||||
cereal::PortableBinaryOutputArchive m(*ss);
|
||||
(m(items), ...);
|
||||
}
|
||||
|
||||
std::vector<std::shared_ptr<std::stringstream>> fragmentedMessage = fragment_message(ss->view(), NetLibrary::FRAGMENT_MESSAGE_STRIDE);
|
||||
auto& messageQueue = client.messageQueues[channel];
|
||||
if(fragmentedMessage.empty())
|
||||
messageQueue.emplace(ss);
|
||||
else if(channel != UNRELIABLE_COMMAND_CHANNEL) { // Just drop unreliable messages that are fragmented
|
||||
for(auto& ss2 : fragmentedMessage)
|
||||
messageQueue.emplace(ss2);
|
||||
}
|
||||
}
|
||||
template <typename... Args> void send_items_to_all_clients(const std::string& channel, Args&&... items) {
|
||||
send_items_to_client_if([&](const ClientData& c) {
|
||||
return true;
|
||||
}, channel, items...);
|
||||
}
|
||||
template <typename... Args> void send_items_to_all_clients_except(ClientData& client, const std::string& channel, Args&&... items) {
|
||||
send_items_to_client_if([&](const ClientData& c) {
|
||||
return (&c) != (&client);
|
||||
}, channel, items...);
|
||||
}
|
||||
template <typename... Args> void send_items_to_client_if(std::function<bool(const ClientData&)> clientChecker, const std::string& channel, Args&&... items) {
|
||||
auto ss(std::make_shared<std::stringstream>());
|
||||
{
|
||||
cereal::PortableBinaryOutputArchive m(*ss);
|
||||
(m(items), ...);
|
||||
}
|
||||
|
||||
|
||||
std::vector<std::shared_ptr<std::stringstream>> fragmentedMessage = fragment_message(ss->view(), NetLibrary::FRAGMENT_MESSAGE_STRIDE);
|
||||
|
||||
for(auto& client : clients) {
|
||||
if(clientChecker(*client)) {
|
||||
auto& messageQueue = client->messageQueues[channel];
|
||||
if(fragmentedMessage.empty())
|
||||
messageQueue.emplace(ss);
|
||||
else if(channel != UNRELIABLE_COMMAND_CHANNEL) { // Just drop unreliable messages that are fragmented
|
||||
for(auto& ss2 : fragmentedMessage)
|
||||
messageQueue.emplace(ss2);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool is_disconnected();
|
||||
|
||||
void add_recv_callback(MessageCommandType commandID, const NetServerRecvCallback& callback);
|
||||
void add_disconnect_callback(const NetServerDisconnectCallback& callback);
|
||||
private:
|
||||
void parse_received_messages();
|
||||
void send_queued_messages();
|
||||
void client_connected(std::shared_ptr<rtc::PeerConnection> connection, const std::string& clientLocalID);
|
||||
|
||||
std::atomic<bool> isDisconnected = false;
|
||||
|
||||
std::string localID;
|
||||
|
||||
std::mutex clientList;
|
||||
|
||||
std::vector<std::shared_ptr<ClientData>> clients;
|
||||
|
||||
// Adding an add list, because i dont want to block the entire clients vector with a mutex
|
||||
std::mutex clientsToAddMutex;
|
||||
std::vector<std::shared_ptr<ClientData>> clientsToAdd;
|
||||
|
||||
std::unordered_map<MessageCommandType, NetServerRecvCallback> recvCallbacks;
|
||||
NetServerDisconnectCallback disconnectCallback;
|
||||
|
||||
NetClient* directConnectClient = nullptr;
|
||||
std::shared_ptr<ClientData> directConnectClientData;
|
||||
|
||||
friend class NetLibrary;
|
||||
friend class NetClient;
|
||||
};
|
||||
Loading…
Add table
Add a link
Reference in a new issue