Relay
Generic Relay Server
brainCloud Relay Server is updated to a new Version 2 Protocol in our 4.5.6 release -- which are more reliable, more feature-rich, and faster to launch than our original Relay Servers.
Purpose
The brainCloud relay server's only purpose is to relay packet data between peer. It is not meant to be an authoritative server, except for the owner migration part.
It is designed to handle clients from various connection types: UDP, TCP or WS. So if I client plays in a webgl version of a game, using WS, he can communicate with a client that plays in UDP on desktop.
The maximum number of players supported by the relay server is 40. But it is highly recommended to keep it low, around 8 players. Peer to Peer generates a lot of traffic.
Packets
A client that wishes to talk to the relay server is required to follow the brainCloud Relay-Server (BCRS) base protocol.
All the integer values in the following tables are unsigned and in network byte order (Big Endian).
Ping
Pings will be immediately responded by PONG. The client uses this to calculate their ping. Should send frequently, like every second or so. It is important to pass valid previous ping number because this is used to guess the next best lobby owner, should the original owner leave the game.
The first PING sent should be accompanied with a round trip time (RTT) of 999ms. A common mistake would be to send zero first. The server will use the lowest value to transfer ownership. Someone that just joined should initialize high.
If the PING or PONG packets are dropped, make sure to mark your RTT value in the next PING as high as possible, or 999, instead of the previously known RTT. That is a packet drop, and it should hurt the player's ping.
RSMG Acknowledge
This is only used by UDP connections. If a Relay-Server Message (RSMG) is sent to the client, it should be responded with an RSMG Ack. Including the packet id that was sent in the original message. RSMG packets need to be reliable. The Relay-Server will resend those every 200ms until acknowledged.
Connection request
brainCloud gave the information about the lobby to the relay server. So it knows who should connect, and each client was given a passcode to use to connect. This should be the first packet sent. A UDP client needs to resend that until a confirmation is received that they are connected. A subsequent connection request will just be ignored if already connected. Re-transmission doesn't need to be high frequency. A suitable interval could be 500ms.
Disconnection
This is a clean way to disconnect from the server. If possible, clients should always send this to properly leave a game. Of course, if a game crashes or the browser's tab is killed, it can't do this. There is a timeout in place for those situations.
Relay-Server Messages (RSMG)
The relay server will sometimes communicate important system messages to the clients. Those are in json string format. Relay server will keep resending that packet to UDP clients every 200 milliseconds until client acknowledge.
Owner migration
If the owner left or never connected in a timely manner, the relay-server will migrate the role to the next member with the best ping. If no one else is currently connected yet, it will be transferred to the next member in the lobby members' list. This last scenario can only occur if the owner connected first, then quickly disconnected. Leaving only unconnected lobby members.
End Match
Normal procedure for match termination is to wait for all the participants to disconnect before terminating the instance. This is insufficient however in the case of long-lived lobbies because when a client disconnects from an instance associated with a long-running lobby we send a notification to the brainCloud API servers that said client has prematurely left and the API servers will remove them from any lobbies they are currently a member of. This is not ideal for a long-lived lobby since the clients are meant to stay in it even after the relay server instance has terminated. For this scenario, in release 4.14.0 we have introduced a new END_MATCH message that the instance owner can use to signal that the match is over and can be safely terminated leaving all the clients joined to the lobby instance.
UDP Reliables
- 1 bit to specify if the packet is reliable.
- 1 bit to specify if the packet should be in order.
- 2 bit for channel id. 0-3.
The combination of those 4 bits creates what we call a channel idx. Each combination of reliable/ordered, times the 4 channels gives us 16 channels total.
Channels are used to define retransmission speed of reliable packets. Also, each channel will have its own retransmission queue. Packet Ids are unique within a channel only. You can receive/send a packet with id=133 on channel 0, and also another one with the same id=133 but on channel 1.
- Channel 0: fastest retransmission rate (Use for things like bullets)
- Channel 1: fastest retransmission rate (Same, but offer the separated channel. So a dropped bullet packet, for example, wouldn't block something else important.)
- Channel 2: medium retransmission rate (Game events that are important, but not crucial. Like player picked up a gun)
- Channel 3: slow retransmission rate (Use for things like chat messages)
Channel is irrelevant for unreliable packets. Ordering is still relevant in that case, but it just means if a packet arrives out of order, it will be dropped.
Useful Constants
For anyone implementing the protocol, here are the internally defined constants that will be useful:
static const auto MAX_RESEND_INTERVAL = 500ms;
static const auto CHANNEL_HIGH_PRIORITY_1 = 0;
static const auto CHANNEL_HIGH_PRIORITY_2 = 1;
static const auto CHANNEL_NORMAL_PRIORITY = 2;
static const auto CHANNEL_LOW_PRIORITY = 3;
static const int MIN_PACKET_SIZE = 3;
static const int MAX_PACKET_SIZE = 1024;
static const uint8_t MAX_PLAYERS = 40;
static const uint8_t INVALID_NET_ID = MAX_PLAYERS;
static const uint64_t ALL_PLAYERS_MASK = 0xFFFFFFFFFFFFFFFF;
// Messages send from Client to Relay-Server
static const uint8_t CL2RS_CONNECT = 0;
static const uint8_t CL2RS_DISCONNECT = 1;
static const uint8_t CL2RS_RELAY = 2;
static const uint8_t CL2RS_ACK = 3;
static const uint8_t CL2RS_PING = 4;
static const uint8_t CL2RS_RSMG_ACK = 5;
static const uint8_t CL2RS_END_MATCH = 6;
// Messages sent from Relay-Server to Client
static const uint8_t RS2CL_RSMG = 0;
static const uint8_t RS2CL_DISCONNECT = 1; // CL2RS_DISCONNECT
static const uint8_t RS2CL_RELAY = 2; // CL2RS_RELAY
static const uint8_t RS2CL_ACK = 3; // CL2RS_ACK
static const uint8_t RS2CL_PONG = 4; // CL2RS_PING
static const int ACK_ID_BYTES_SIZE = 8;
We've slightly modified the relay server behaviour when a player disconnects in protocol version v3. We now send the leaver a DISCONNECT (i.e. RS2CL_DISCONNECT) message and everybody else gets a SYSTEM DISCONNECT (i.e. RS2CL_RSMG) message. In v2, everybody got the 2nd one (i.e. RS2CL_RSMG).
And here are the channel resend intervals:
static const Duration RELIABLE_RESEND_INTERVALS[] = {
50ms, 50ms, // High priority 1 and 2
150ms, // Normal priority
500ms // Low priority
};
static const auto RELIABLE_CHANNEL_HIGH_PRIORITY_1 = 0;
static const auto RELIABLE_CHANNEL_HIGH_PRIORITY_2 = 1;
static const auto RELIABLE_CHANNEL_NORMAL_PRIORITY = 2;
static const auto RELIABLE_CHANNEL_LOW_PRIORITY = 3;
Resend Interval Rate
For each reliable packets, the resend rate slows down by 25% each time. This might be tweaked later. So for channel 1, resend at 50ms interface, it will go like this until timeout (10sec):
interval | total time | frequency |
---|---|---|
50ms | 0.1s | 20 |
62ms | 0.1s | 16 |
77ms | 0.2s | 13 |
96ms | 0.3s | 10 |
120ms | 0.4s | 8 |
150ms | 0.5s | 7 |
187ms | 0.7s | 5 |
233ms | 1.0s | 4 |
291ms | 1.3s | 3 |
363ms | 1.6s | 3 |
453ms | 2.0s | 2 |
500ms | 2.5s | 2 |
500ms | 3.0s | 2 |
500ms | 3.5s | 2 |
500ms | 4.0s | 2 |
500ms | 4.5s | 2 |
500ms | 5.0s | 2 |
500ms | 5.5s | 2 |
500ms | 6.0s | 2 |
500ms | 6.5s | 2 |
500ms | 7.0s | 2 |
500ms | 7.5s | 2 |
500ms | 8.0s | 2 |
500ms | 8.5s | 2 |
500ms | 9.0s | 2 |
500ms | 9.5s | 2 |
500ms | 10.0s | 2 |
timeout | too many packets lost |
Custom Environment Variables
The following environment variables are used by the relay server which can be configured via Relay server settings page in the dashboard.
- PACKET_LOG_ENABLED: Logs packets sent/received. Shouldn't be enabled for production (very spammy). Defaults to false.
- MAX_PLAYERS: Maximum number of players allowed to connect to the server. Defaults to 40.
- ALLOW_REJOIN_BY_PROFILE_ID: Allows players to reconnect to the server instance if their connection drops (even if they have a new RTT connection as well). Only applies for short-lived lobbies (i.e. where "Disband on start" is true). Defaults to false.
- CONNECTION_TIMEOUT: Amount of time in seconds that a player is allowed to be connected to the server without sending a packet before being kicked. Defaults to 10 secs.
- MEMBER_TIMEOUT: Amount of time to wait in seconds for a player to connect to the server before assuming they aren't coming at all. Defaults to 30 secs.
- END_MATCH_TIMEOUT: Amount of time in seconds to stay up before exiting after receiving the END_MATCH packet. Defaults to 2 secs.
- PACKET_LOG_ENABLED_WITH_MD5: Allows for an MD5 hash logging of incoming and outgoing relay packet payloads.
- PAYLOAD_LOG_AS_HEX: Logs the incoming packet payload as a hex dump rather than an ASCII string.
All the timeout settings are in seconds.
{
"PACKET_LOG_ENABLED": "true",
"CONNECTION_TIMEOUT": "240",
"MEMBER_TIMEOUT": "120",
"END_MATCH_TIMEOUT": "600",
"MAX_PLAYERS": "40",
"ALLOW_REJOIN_BY_PROFILE_ID": "true",
"PACKET_LOG_ENABLED_WITH_MD5": "true",
"PAYLOAD_LOG_AS_HEX": "true"
}
Note: brainCloud will pass these environment variables by default for any room/relay server it spins up. Below is an example list of environment variables from a launched server.
// passed by default from brainCloud
APP_ID = 13469
CONTAINER_EXPOSED_PORTS = 7777/tcp,7777/udp
EXTERNAL_IP = 3.99.186.192
EXTERNAL_PORTS = 9001/tcp,9001/udp
HOSTNAME = 029ec2d7bc98
LOBBY_ID = 13469:CustomGame:15
SERVER_NAME = CMSU
SERVER_HOST = api.braincloudservers.com
SERVER_PORT = 443
SERVER_SECRET = 99ef5e85-9a99-4140-8b0d-292b4b13807d
// from server itself
HOME = /root
PATH = /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
// from brainCloud server settings of portal server page
PACKET_LOG_ENABLED = true
CONNECTION_TIMEOUT = 240
Relay Flow
Client Send Implementation
Client Receive Implementation
Exit Codes
The following table describes the known exit codes for the relay server.
Code | Description |
---|---|
0 | Exited cleanly |
1 | Failed to initiliaze GameLift (GameLift only) |
2 | Unused |
3 | Missing expected environment variables |
4 | Failed to initialize S2S context |
5 | Missing expected lobby instance configuration |
6 | Failed to retrieve lobby instance configuration |
7 | Failed to process lobby instance configuration |
8 | Failed to initialize websocket server |
9 | Failed to initialize TCP server |
10 | Failed to initialize UDP server |
11 | No players connected before MEMBER_TIMEOUT is reached |
API Summary
Relay
- Connect - Connect to relay server.
- Disconnect - Disconnect from the relay server.
- IsConnected - Returns whether or not we have a successful connection with the relay server.
- GetPing - Get the current ping for our user.
- SetPingInterval - Set the ping interval. Ping allows to keep the connection alive, but also inform the player of his current ping.
- GetOwnerProfileId - Get the lobby's owner profile Id.
- GetOwnerCxId - Get the lobby's owner Connection Id.
- GetNetIdForProfileId - Returns the netId associated with a profileId.
- GetCxIdForNetId - Returns the connection Id associated with a netId.
- GetNetIdForCxId - Returns the netId associated with a connection Id.
- Send - Send a packet to peer(s).
- SendToPlayers - Send a packet to any players by using a mask.
- SendToAll - Send a packet to all except yourself.
- EndMatch - Terminate the match instance by the owner.
Callbacks
- RegisterRelayCallback - Register callback for relay messages coming from peers.
- DeregisterRelayCallback - Deregisters callback for relay messages coming from peers.
- RegisterSystemCallback - Register callback for RelayServer system messages.
- DeregisterSystemCallback - Deregister callback for RelayServer system messages.
📄️ Connect
Start off a connection, based off connection type to 's Relay Servers. Connect options come in from "ROOM_ASSIGNED" lobby callback
📄️ DeregisterRelayCallback
Deregisters callback for relay messages coming from peers.
📄️ DeregisterSystemCallback
Deregister callback for RelayServer system messages.
📄️ Disconnect
Disconnect from the relay server.
📄️ EndMatch
Terminate the match instance by the owner.
📄️ GetCxIdForNetId
Returns the RTT connection Id associated with a netId.
📄️ GetNetIdForCxId
Returns the netId associated with a connection Id.
📄️ GetNetIdForProfileId
Returns the netId associated with a profileId.
📄️ GetOwnerCxId
Get the lobby's owner Connection Id.
📄️ GetOwnerProfileId
Get the lobby's owner profile Id.
📄️ GetPing
Get the current ping for our user. Note: Pings are not distributed among other members. Your game will have to bundle it inside a packet and distribute to other peers.
📄️ GetProfileIdForNetId
Returns the profileId associated with a netId.
📄️ IsConnected
Returns whether or not we have a successful connection with the relay server.
📄️ RegisterRelayCallback
Register callback for relay messages coming from peers.
📄️ RegisterSystemCallback
Register callback for RelayServer system messages.
📄️ Send
Send a packet to peer(s).
📄️ SendToAll
Send a packet to all except yourself.
📄️ SendToPlayers
Send a packet to any players by using a mask.
📄️ SetPingInterval
Set the ping interval.