Skip to content

Network Architecture Archived

MarjoleinKaal edited this page May 6, 2020 · 1 revision

Not applicable anymore due to project realignment

UDP Library

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.

Low-Level Network Abstraction Layer

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.


Structure overview

net::Address

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);

net::Host

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);

Note:

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);
}

net::Peer

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.

net::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);

net::NetEventHandler

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);
}