-
Notifications
You must be signed in to change notification settings - Fork 1
Network Architecture Archived
The Eyos Networkwork Architecture is built around the small C Reliable UDP networking library ENet. The main reason for this is that it guarantees package delivery and has a very easy to use interface. Besides this, it is easy to wrap around and extensible and cross-platform.
The low-level network abstraction layer is modeled after the ENet model. This means we have C++ wrapper classes/structs around the ENet structs and free functions. This design choice allows for an easy change of the library if necessary. The network layer needs to be initilized and deinilitzed:
#include <engine/net/NetFwd.hpp>
// ...
int main(){
net::InitializeNetwork();
// ...
net::DeinitializeNetwork();
}
Therefore we have the following classes:
class Address{}; // wrapper for ENetAddress struct http://enet.bespin.org/structENetAddress.html
class Host{}; // wrapper for ENetHost struct http://enet.bespin.org/structENetHost.html
class NetEventHandler{}; // No enet equviavlent
class Packet{}; // wrapper for ENetPacket http://enet.bespin.org/structENetpacket.html
class Peer{}; // wrapper for ENetPeer http://enet.bespin.org/structENetPeer.html
All those classes can be found in the general namespace eyos::net
. They cam with the Engine.dll/so
For detailed information about the ENet structs please checkout the documentation here.
Provides the following constructors:
Address(ENetAddress*);
Address(const ENetAddress&);
Address(std::uint16_t port = 49200, std::uint32_t host = ENET_HOST_ANY);
Address(const std::string& hostName, std::uint16_t port);
Address(const Address&) = default;
Address(Address&&) = default;
Address& operator=(const Address&) = default;
Address& operator=(Address&&) = default;
Provides the following utility functions:
bool ResolveHostName(const std::string& hostname);
bool ResolveHostIP(const std::string& ipaddress);
[[nodiscard]] std::string HostStr(std::size_t hostLength = 64);
[[nodiscard]] std::uint32_t Host();
[[nodiscard]] std::string Ip4();
[[nodiscard]] std::string Ip6();
[[nodiscard]] std::string Ip(std::size_t length);
[[nodiscard]] std::uint16_t Port();
This class has the following utility functions associated with it:
[[nodiscard]] EYOS_API net::Address CreateAddress(std::uint16_t port, std::uint32_t host = ENET_HOST_ANY);
[[nodiscard]] EYOS_API net::Address CreateAddress(std::uint16_t port, const std::string& hostName);
The reason why the class has no modification functionality is that an Address does not change usually during its lifetime.
Example:
auto client{ Server() };
auto address{ net::CreateAddress(1234,"127.0.0.1") };
Lobbies lobbyServer{ *client };
client->Connect(address, 2, 42);
Provides the following constructors:
Host(std::size_t peerCount,
std::size_t channelLimit,
std::uint32_t incomingBandwidth,
std::uint32_t outgoingBandwidth);
Host(std::size_t peerCount,
std::size_t channelLimit,
std::uint32_t incomingBandwidth,
std::uint32_t outgoingBandwidth,
const Address& address);
Host(std::size_t peerCount,
std::size_t channelLimit,
std::uint32_t incomingBandwidth,
std::uint32_t outgoingBandwidth,
Address&& address);
Host(const Host&) = delete;
Host(Host&& host) = default;
Host& operator=(const Host& other) = delete;
Host& operator=(Host&& other) = default;
~Host();
Provides the following functions:
// Connects the client to an Address. It returns a Peer. The peer is not alive (or valid/connected) to 100% till NetEventHandler::Poll(host) ran.
// Why? see http://enet.bespin.org/group__host.html#ga23b3ac206326b84f42fa91673f12fca9 ( enet_host_connect()) function.
Peer Connect(const Address& address, std::size_t channelCount, std::uint32_t data) const noexcept;
// returns all the Peers which are connected to the Host
std::size_t CountConnectedPeers() const noexcept;
// returns the count of all peers which state is equal to ENetPeerState (http://enet.bespin.org/enet_8h.html#a058bc368c507eb86cb47f3946f38d558)
std::size_t CountPeers(ENetPeerState state) const noexcept;
// returns an of Peers
auto GetConnectedPeers() const noexcept;
// based on the peer index (see Peer class) you can retrive the Peer as well.
Peer GetPeer(std::size_t peerIdx);
// check if a Peer is NOT in the Zombie or any none CONNECT_ state
bool IsPeerValid(const Peer& peer);
The class has a friend relationship with the void Broadcast(const Host& peer, Packet&& packet, std::uint8_t channelID);
which will send to all peers a packet.
Besides this the following factory functions are provided:
// engine/net/common.hpp
[[nodiscard]] EYOS_API Host_ptr CreateHost(
size_t peerCount,
size_t channelLimit,
std::uint32_t incomingBandwidth,
std::uint32_t outgoingBandwidth);
[[nodiscard]] EYOS_API Host_ptr CreateServer(
std::uint16_t port,
size_t peerCount = 32,
size_t channelLimit = 2,
std::uint32_t incomingBandwidth = 0,
std::uint32_t outgoingBandwidth = 0);
[[nodiscard]] EYOS_API Host_ptr CreateClient(
size_t peerCount = 1,
size_t channelLimit = 2,
std::uint32_t incomingBandwidth = 0,
std::uint32_t outgoingBandwidth = 0);
A Server and a Client are conceptually the same the main difference is just that a client is a Host which is connected to a Server and a Server listens to a port. To create a Server or a Client we have to do the following:
// use the easy to use functions:
auto server{ CreateServer(1234) }; // server will listen to port 1234 on the ENET_HOST_ANY defined host
auto client {CreateClient()}; // creates a default client which will listen to any port and any host.
auto address{ net::CreateAddress(1234,"127.0.0.1") }; // but only if they come form this server
client->Connect(address, 2, 42); // where we connect to
//... code
bool run{ true };
while (run) {
//updates the client:
eventHandler.Poll(client);
}
The peer class wraps around the ENetPeer
struct and encapsulates its functionality, mainly. In can be constructed as following:
class Peer{
public:
Peer(ENetPeer*);
//...
}
It contains four important member functions:
net::Address Address();
template <typename Data_>
std::optional<Data_> Data();
template <typename Data>
void SetData(Data data);
bool IsConnected();
The class is friend with two globally accessible free function:
bool SendPacket(const Peer& peer, Packet&& packet);
void Broadcast(const Host& host, Packet&& packet, std::uint8_t channelID);
There functionality is equal to their equavilvanet in ENet
: the SendPacket()
sends a packet to the peer itself and the Broadcast()
to all peers connected to a host.
Note: see the Packet
class for more information about the packet.
This class wraps around the ENet
Packet
struct.
class Packet {
public:
Packet(ENetPacket* packet);
//...
}
It comes with the following member functions:
void Destroy(); // IMPORTANT: Should only be called if this packet has NOT been send!
template <typename PacketHeaderType>
PacketHeaderType Type(); // recive the command Enum it has been send with. This function will assert if the PacketHeaderType is NOT an ENUM class!
The class is a friend with the following free functions:
template <typename Data>
Packet&& AppendToPacket(Packet&& packet, const Data&, std::size_t length);
template <typename Data>
Packet&& AppendToPacket(Packet&& packet, Data&&, std::size_t length);
bool SendPacket(const Peer&, Packet&& packet);
void Broadcast(const Host& peer, Packet&& packet, std::uint8_t channelID);
This is the event handler for network events. ENet
handles in its core three events: Connected
, Disconnected
and Receive
. The event handler will invoke provided callbacks on those events.
Example:
auto eventHandler{ net::NetEventHandler{} };
auto callback = [](net::Peer&& who, [[maybe_unused]] std::uint32_t eventData)->void {
std::cout << "Connected Lambda\n";
};
eventHandler.AddCallback(client, net::NetEvents::Connect,&lobbyServer,&Lobbies::OnConnect);
eventHandler.AddCallback(client, net::NetEvents::Connect, std::function<void(net::Peer && who, [[maybe_unused]] std::uint32_t eventData)>{callback });
eventHandler.AddCallback(client, net::NetEvents::Connect, &OnConnect);
eventHandler.AddCallback(client, net::NetEvents::Disconnect,&OnDisconnect);
eventHandler.AddCallback(client, net::NetEvents::Receive, &OnReceive);
bool run{ true };
while (run) {
//updates the client:
eventHandler.Poll(client);
}
Class symbiosis
class NetEventHandler {
public:
NetEventHandler() = default;
~NetEventHandler() = default;
private:
using PacketCallback = void(net::Packet && packet, [[maybe_unused]] std::uint32_t eventData);
using PacketCallback_ = void(*)(net::Packet && packet, [[maybe_unused]] std::uint32_t eventData);
using ConnectCallback = void(net::Peer && who, [[maybe_unused]] std::uint32_t eventData);
using ConnectCallback_ = void(*)(net::Peer && who, [[maybe_unused]] std::uint32_t eventData);
template <typename Member>
using MemberPacketCallback = void (Member::*)(net::Packet && packet, [[maybe_unused]] std::uint32_t eventData);
template <typename Member>
using MemberConnectCallback = void (Member::*)(net::Peer && who, [[maybe_unused]] std::uint32_t eventData);
public:
template <typename CallbackType>
bool AddCallback(const Host_ptr& host, net::NetEvents netEvent, std::function<CallbackType>&& callback);
template <typename This, typename CallbackType>
bool AddCallback(const net::Host_ptr& host, net::NetEvents netEvent, This* this_ptr, CallbackType&& callback);
template <typename CallbackType>
bool AddCallback(const net::Host_ptr& host, net::NetEvents netEvent, CallbackType&& callback);
template <typename CallbackType>
bool AddCallback(const Host& host, NetEvents netEvent, CallbackType&& callback);
template <typename This, typename CallbackType>
bool AddCallback(const net::Host& host, net::NetEvents netEvent, This* this_ptr, CallbackType&& callback);
void Poll(const net::Host& host, std::uint32_t timeout = 5000);
void Poll(const net::Host_ptr& host, std::uint32_t timeout = 5000);
}