{"id":40975940,"url":"https://github.com/edgegap/distributed-relay-examples","last_synced_at":"2026-01-22T06:52:02.219Z","repository":{"id":153041438,"uuid":"624547444","full_name":"edgegap/distributed-relay-examples","owner":"edgegap","description":"Examples of implementation for Edgegap's Distributed Relays","archived":false,"fork":false,"pushed_at":"2024-11-04T14:37:47.000Z","size":178,"stargazers_count":5,"open_issues_count":2,"forks_count":3,"subscribers_count":2,"default_branch":"main","last_synced_at":"2024-11-04T15:35:51.913Z","etag":null,"topics":["distributed","edge","edge-computing","edgegap","relay"],"latest_commit_sha":null,"homepage":"https://edgegap.com","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/edgegap.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-04-06T18:02:31.000Z","updated_at":"2024-11-04T14:37:57.000Z","dependencies_parsed_at":"2024-04-23T15:54:34.863Z","dependency_job_id":"2755c88f-1de6-4670-85cb-c00067d22e2d","html_url":"https://github.com/edgegap/distributed-relay-examples","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/edgegap/distributed-relay-examples","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/edgegap%2Fdistributed-relay-examples","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/edgegap%2Fdistributed-relay-examples/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/edgegap%2Fdistributed-relay-examples/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/edgegap%2Fdistributed-relay-examples/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/edgegap","download_url":"https://codeload.github.com/edgegap/distributed-relay-examples/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/edgegap%2Fdistributed-relay-examples/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28657368,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-22T01:17:37.254Z","status":"online","status_checked_at":"2026-01-22T02:00:07.137Z","response_time":144,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["distributed","edge","edge-computing","edgegap","relay"],"created_at":"2026-01-22T06:52:02.037Z","updated_at":"2026-01-22T06:52:02.213Z","avatar_url":"https://github.com/edgegap.png","language":"C#","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Edgegap Distributed Relay\n\n\nThe Edgegap Distributed Relay helps connect player hosted games.\n\nThe Relay is compatible with all game engines and all netcodes.\u003c/br\u003e\nThis repository contains a detailed manual on how to implement it for your project.\u003c/br\u003e\nIt also contains integrations for popular game engines \u0026 netcodes.\u003c/br\u003e\n\nFeel free to share implementations for other engines \u0026 netcodes by opening pull requests!\nIf you have trouble integrating the relay manually, please reach out or check one of the default integrations.\n\nFear not, this is very easy to implement!\n\n---\n\n## Relay Implementation Manual\n\n### Requirements\n\nRelay works with any game engine and any programming language.\n\nThe implementation guide is in C# for readability, but works just as well with C++, Java, Python, Rust, Go, etc.\n\nThe only requirement is access to low level UDP send/recv.\n\nFor example, some Unity netcodes may use native DLLs for their transports.\u003c/br\u003e\nThe relay needs to be implemented on the socket layer, which may be difficult when using third-party native DLLs.\u003c/br\u003e\n\n### Overview\n\nUnlike competing relays with large SDKs, the Edgegap relay is extremely easy to integrate.\u003c/br\u003e\nFirst, let's start with a high-level overview of the necessary components.\n\n1. **Edgegap Login:** a **sessionAuthorizationToken** and **userAuthorizationToken** are needed in order to redirect game traffic over the relay. After matchmaking / lobby, those ids will be assigned to players from the Edgegap authentication.\n2. **Redirect Traffic**: game clients and game servers no longer communicate with each other. Instead, both talk to the Relay directly. This is very easy to change, essentially all we need to do is connect to the relay and preprend some metadata to our messages.\n\nThe relay protocol is intentionally kept extremely simple, in order to target a wide range of game engines \u0026 netcodes.\n\n### Relay Protocol\n\nThe Relay communicates over unreliable UDP messages.\n\n- There is one UDP port for game servers.\n- There is one UDP port for game clients.\n- Max message size (MTU) is **1200** bytes.\n- Relay adds up to 13 bytes of message overhead.\n- Which leaves the max usable payload at 1200-13 = **1187** bytes.\n\nReliability, Fragmentation and Encryption differ between game engines \u0026 netcodes.\u003c/br\u003e\nRelay simply sends unreliable UDP messages.\u003c/br\u003e\nEach engine \u0026 netcode may implement its own logic on top of it.\u003c/br\u003e\n\nNote that your game may currently have the game server act as a UDP Server.\u003c/br\u003e\nInstead, both game client \u0026 game server need to act as UDP Clients.\u003c/br\u003e\nWhich makes everything even easier.\u003c/br\u003e\n\nThe following is an overview of the complete relay protocol.\u003c/br\u003e\nAs promised, it's extremely simple.\u003c/br\u003e\n\n### Constants\n\nUDP netcode always defines a max message size (MTU). Usually of around 1200-1400 bytes.\u003c/br\u003e\nRelay communication needs to prepend some metadata, so we need to define two different MTUs:\n\n```cs\n// relay prepends up to 13 bytes of meta-data\nconst int RELAY_OVERHEAD = 13;\n\n// 1200 is the relay limit.\nconst int MTU = 1200;\n\n// game may send up to MAX_PAYLOAD bytes\nconst int MAX_PAYLOAD = MTU - RELAY_OVERHEAD;\n```\n\nIn other words, you may send up to **MAX_PAYLOAD** bytes.\u003c/br\u003e\nWe then prepend some relay metadata, for a total of up to **1200** bytes\u003c/br\u003e\n\nTypically your netcode implements fragmentation on top of this, so you can send large messages.\u003c/br\u003e\n\nFor example, a 10 KB message would be split into multiple MAX_PAYLOAD fragments, each fragment is sent to the relay with some metadata.\u003c/br\u003e\n\nFragmentation, Reliability, and Encryption are implemented by the game engine's netcode transport. Some games may implement this themselves. Edgegap relay is transport agnostic, so it works with any game engine and netcode. In other words, it handles low-level MTU sized UDP messages, nothing else.\n\n### Enums\n\nEvery message contains a message type byte:\n\n```cs\nenum MsgType : byte {\n    PING = 1,\n    DATA = 2\n}\n```\n\nSome messages contain a state to notify the relay user:\n\n```cs\nenum State : byte {\n    Disconnected = 0,   // until the user calls connect()\n    Checking = 1,       // recently connected, validation in progress\n    Valid = 2,          // validation succeeded\n    Invalid = 3,        // validation rejected by tower\n    SessionTimeout = 4, // session owner timed out\n    Error = 5,          // other error\n}\n```\n\nBoth the game server and game client need to have a state variable:\n\n```cs\n// will be set by PING messages.\n// show this in a GUI to indicate the connection state.\nState state = State.Disconnected;\n```\n\n### Session Id \u0026 User Id\n\nBefore relaying game traffic, we need to get the sessionAuthorizationToken and userAuthorizationToken.\u003c/br\u003e\nThe game server \u0026 clients will get this after the match is made / lobby is started.\u003c/br\u003e\n\n```cs\n// from matchmaking / lobby\nuint sessionAuthorizationToken;\nuint userAuthorizationToken;\n\nThe ids will be serialized in **little endian** byte order.\n\n---\n\n## Game Server communication with Relay\n\nBefore we start, it's worth understanding that game server usually implement **UDP Servers**.\u003c/br\u003e\nIn other words, they can handle multiple UDP client connects.\u003c/br\u003e\n\nWhen talking to a relay, the game server becomes a **UDP Client**.\u003c/br\u003e\nIt only ever talks to the one relay.\u003c/br\u003e\n\nFirst, the game server needs to connect the UDP socket to the relay.\u003c/br\u003e\nAfter matchmaking / lobby, Edgegap will assign the relay address \u0026 port of a nearby relay.\u003c/br\u003e\n\n```cs\n// from matchmaking/lobby\nsocket.Connect(relayAddress, relayServerPort)\n```\n\nWhile UDP sockets are connectionless, calling Connect() allows us to use Send() and Recv() instead of passing the address every time in SendTo() and RecvFrom().\n\nThe game server needs to send a Ping message roughly every 500 ms.\u003c/br\u003e\nThe ping messages are used both as keep-alive and as authentication.\u003c/br\u003e\nPing messages need to be sent immediately after connecting.\u003c/br\u003e\n\n```cs\n// start sending pings immediately after connect, every 500 ms.\n// faster sends make logins faster, but cost more bandwidth.\nvoid SendPing() {\n    // binary writer for convenience.\n    // use what the language / netcode offer.\n    writer = new NetworkWriter();\n    writer.WriteUInt(userAuthorizationToken);       // 4 bytes little endian\n    writer.WriteUInt(sessionAuthorizationToken);    // 4 bytes little endian\n    writer.WriteByte(MsgType.PING);\n    socket.Send(writer.Content());\n}\n```\n\nThe game server also sends Data messages. These are the messages that your game currently sends to the game client. Clients are identified by a connectionId. For security reasons, the relay will never expose a game client's IP address.\n\n```cs\n// messages that the game server sends to the game client with connectionId.\n// wait for validation before sending them.\n// connectionIds come from DATA messages.\nvoid SendData(byte[] message, int connectionId) {\n    // ensure max size\n    if (message.Length \u003e MAX_PAYLOAD) throw;\n\n    // binary writer for convenience.\n    // use what the language / netcode offer.\n    writer = new NetworkWriter();\n    writer.WriteUInt(userAuthorizationToken);       // 4 bytes little endian\n    writer.WriteUInt(sessionAuthorizationToken);    // 4 bytes little endian\n    writer.WriteByte(MsgType.DATA);\n    writer.WriteUInt(connectionId); // 4 bytes little endian\n    writer.WriteBytes(bytes);\n    socket.Send(writer.Content());\n}\n```\n\nThe game server can receive two types of messages from the relay:\n\n```cs\nvoid ProcessMessage() {\n    // check for new udp message.\n    byte[] buffer = new byte[MTU];\n    int size = socket.Receive(buffer);\n\n    // binary reader to parse 'size' bytes.\n    // use what the language / netcode offer.\n    reader = new NetworkReader(buffer, size);\n\n    // read message type\n    MsgType msgType = reader.ReadByte();\n    switch (msgType) {\n        case MsgType.PING: {\n            // read connection state.\n            // after 1-2s, this will turn to Valid.\n            // then we can send DATA messages.\n            State state = reader.ReadByte();\n            break;\n        }\n        case MsgType.DATA: {\n            // connectionId indicates which game client this is from.\n            uint connectionId = reader.ReadUInt();\n            // remaining bytes are a game message\n            byte[] payload = reader.ReadBytes(reader.remaining);\n            // handle the game message as before\n            HandleMessage(payload, connectionId);\n            break;\n        }\n    }\n}\n```\n\nThat's it for the game server. Pretty simple.\n\n---\n\n## Game Client communication with Relay\n\nFirst, the game client needs to connect the UDP socket to the relay.\u003c/br\u003e\nAfter matchmaking / lobby, Edgegap will assign the relay address \u0026 port of a nearby relay.\u003c/br\u003e\n\n```cs\n// from matchmaking/lobby\nsocket.Connect(relayAddress, relayClientPort)\n```\n\nWhile UDP sockets are connectionless, calling Connect() allows us to use Send() and Recv() instead of passing the address every time in SendTo() and RecvFrom().\n\nThe game client needs to send a Ping message roughly every 500 ms.\u003c/br\u003e\nThe ping messages are used both as keep-alive and as authentication.\u003c/br\u003e\nPing messages need to be sent immediately after connecting.\u003c/br\u003e\n\n```cs\n// start sending pings immediately after connecting, every 500 ms.\n// faster sends make logins faster but cost more bandwidth.\nvoid SendPing() {\n    // binary writer for convenience.\n    // use what the language / netcode offer.\n    writer = new NetworkWriter();\n    writer.WriteUInt(userAuthorizationToken);       // 4 bytes little endian\n    writer.WriteUInt(sessionAuthorizationToken);    // 4 bytes little endian\n    writer.WriteByte(MsgType.PING);\n    socket.Send(writer.Content());\n}\n```\n\nThe game client also sends Data messages. These are the messages that your game currently sends to the game server:\n\n```cs\n// messages that the game client sends to the game server.\n// wait for validation before sending them.\nvoid SendData(byte[] message) {\n    // ensure max size\n    if (message.Length \u003e MAX_PAYLOAD) throw;\n\n    // binary writer for convenience.\n    // use what the language / netcode offer.\n    writer = new NetworkWriter();\n    writer.WriteUInt(userAuthorizationToken);       // 4 bytes little endian\n    writer.WriteUInt(sessionAuthorizationToken);    // 4 bytes little endian\n    writer.WriteByte(MsgType.DATA);\n    writer.WriteBytes(bytes);\n    socket.Send(writer.Content());\n}\n```\n\nThe game client can receive two types of messages from the relay:\n\n```cs\nvoid ProcessMessage() {\n    // check for new UDP message.\n    byte[] buffer = new byte[MTU];\n    int size = socket.Receive(buffer);\n\n    // binary reader to parse 'size' bytes.\n    // use what the language / netcode offer.\n    reader = new NetworkReader(buffer, size);\n\n    // read message type\n    MsgType msgType = reader.ReadByte();\n    switch (msgType) {\n        case MsgType.PING: {\n            // read connection state.\n            // after 1-2s, this will turn to Valid.\n            // then we can send DATA messages.\n            State state = reader.ReadByte();\n            break;\n        }\n        case MsgType.DATA: {\n            // remaining bytes are a game message\n            byte[] payload = reader.ReadBytes(reader.remaining);\n            // handle the game message as before\n            OnMessage(payload);\n            break;\n        }\n    }\n}\n```\n\nThat's it for the game client. Pretty simple as well.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fedgegap%2Fdistributed-relay-examples","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fedgegap%2Fdistributed-relay-examples","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fedgegap%2Fdistributed-relay-examples/lists"}