Skip to content

Networking

SFilinsky edited this page Aug 15, 2022 · 43 revisions

Networking layer is abstraction that provides customizable layer to transfer data between Server and Client (or different Clients).

It's not aware of data it transfers or any other layers, Engine itself uses this layer to connect different parts or instances. Networking layer is responsible both for translating Server data to Client and Client events to Server. It allows data to cycle though Engine.

Networking layer should ensure packet-loss and connection-loss security. Those features can be not needed in some protocols or implemented differently, so it should be done inside of Adapter.

Network Adapter is entity that actually implements networking process. Since in some Networking implementation parties can have equal relationship, Adapter is same API for Client and Server. If Adapter should have different features on Server and Client, it can be implemented for each party separately with slightly different features.

Networking layer transfers data using abstract protocol. Because of that, NetworkingAdapter API should support many connections (not only between Client and Server) directly to allow PTP protocols (like WebRTC).

Networking Adapter should be implemented generally for Server and Client. NetworkServer and NetworkClient are ones who define the difference between Server and Client.

Engine should not call send methods directly, instead it just uses abstract methods of Network Server and Client which do the job (like sendSnapshot())

Though API should allow to transport any data, Engine parts should mainly exchange Events with each other to notify about some action or event.

Features

List of features to be implemented in networking layer:

  • Custom network adapters injection
  • Support of several active connections at once (WebRTC-like)
  • Additional validation for messages both in client and server adapters
  • Distinguishing different message types and handling them appropriately
  • Customizable authentication strategies

ConnectionId vs PlayerId

It's important to defer ConnectionIds, PlayerIds

PlayerId is unique identifier for every Player that is playing the game. Every player has separate input and separate world state slice.

ConnectionId is assined to every established Connection to map network traffic accordingly.

The point is that several ConnectionId can resolve to one PlayerId.

Adapter

Adapter is API object responsible for transporting data between parties. It provides API which is same for client and server.

The edge between Adapter and Network API is that Network implements high-level API object interaction and organizes data flow in Engine network part. Adapter implements lower-level features described below.

Connection list

Though connection list depends on Adapter, connections should be known to Engine and so to Simulation, because there can be some user-specific logic on client side.

To keep Engine updated on new users, Network should notify it when new user connects or disconnects through some API.

Features

  • Data transfer protocol
  • Connection establishment
  • Automatic ping test (which updates current ping values on server every second)
  • Packet loss safety

Validation

Event sent via network should be validated before actually applying them to world state. Mostly it's needed on Server when receiving user input, but can also be useful on Client side when having multiple connections.

Validation is the part that can be used by Engine user to customize application networking and security. It allows to check incoming packages and map them into custom or built in Events (for example, to pass Command to Simulation). It can be espacially powerful when using custom Command handlers in Simulation.

Basically, Validation implementation takes messages recieved from Client and Server to translate them into server-side events.

Why Validation is used

  • Network security
  • Custom Message reactions
  • Separate Client event input validation and game world logic

Translation

Networking adapters are only responsible for interchanging data between Client and Server, data itself should be translated (transformed) into messages by separate abstraction layer. This layer takes data and converts it into different types of messages or decodes messages back to data.

It's needed to allow customization and optimization of networking messages.

Implementation is used both on Client and Server and should meet following criterias:

  • Be pure so it works in any environment and doesn't create side effects
  • Any plain object should be encoded and decoded back without changes

Translation is needed only in several adapter implementations and passed directly to it as dependency.

Message targets

In different cases and implementations network messages can have different targets.

Server can target every client or some specific one for example. Clients can send message to server, but also to all other clients when working with multi-connection protocols.

This is why data is passed with target info (id) or rank to networking API. All needed connection data is contained inside networking so it will automatically find needed connection and use it.

Connection ranks

Connection rank is label assigned to each connection. It's needed to define received package priorities and securely handle them.

Ranks help to abstractly work with multiple connections in network implementation.

Those ranks can play role when there are more then 2 parties in networking. In most cases server and client ranks are used, but Adapter implementation can define some custom ranks additionally.

On Client-side Engine can define different handlers for different rank package senders (when using WebRTC for example). This is needed to avoid cheating or breaking client world state.

Connection

Engine does not control how connection is established since it can greatly differ depending on exact networking protocol or implementation. This is why Adapter should only have methods for data transportation.

How connection establishes is defined by Adapter creator or it's constructor arguments. Engine just takes Adapter instance passed and uses it.

It's possible to inject different implementation on Client and Server in cases when they handle connection differently.

Connection info

Sometimes Engine needs to get information about established connections. In this case Adapter implements special method which returns information about all connections. This information should include:

  • Connection rank
  • Unique connection id

Api

Adapter API

  • onMessage: (messageInfo: any) => void - callback that will be called to handle received messages
  • sendMessage(target, messageData): void - (all, by Id, by rank)
  • getConnectionList(): ConnectionInfo[]
  • isReady(): Promise<void> - resolved when Adapter is ready to use

Example of Webscoket networking adapter

    
// Server side
const serverAdapter = new WebsocketServerAdapter(
  new WebsocketNetworkingAdapter({ port: 11112 }),
);

// Client side
const clientAdapter = new NetworkingClient(
  new WebsocketNetworkingAdapter({ url: 'some-server-url', port: 11112 }),
);

Example of Local networking adapter

// Server side
const serverAdapter = new LocalServerAdapter({});

// Client side
const clientAdapter = new LocalClientAdapter({
  // Since client has direct link to server instance, they can bind to each other to pass data immidiately
  server: serverAdapter
});