Introduction

At Leaning Technologies, we provide solutions for porting traditional desktop applications to the Web. Our C++ compiler Cheerp generates a combination of WebAssembly and JavaScript, allowing both easy interoperability with the browser and performance.

As a showcase project we decided to port a multiplayer game to the Web, and we opted for Teeworlds. Teeworlds is a retro multiplayer 2D shooting game, with a small but passionate community of players (including me!). It is lightweight in terms of both assets to download and CPU and GPU requirements. It was a perfect candidate.

Teeworlds running in a browser

We decided to use this project to experiment general solutions for porting networking code to the Web. Usually, the main routes to do so are:

XMLHttpRequest/fetch , if networking consists only of HTTP requests, or

, if networking consists only of HTTP requests, or WebSockets.

Both solutions would require to host the server component server-side, and neither of them allows UDP as the transport protocol. This is important for real-time applications like video conferencing and gaming because the delivery and ordering guarantees of TCP can get in the way of low latency.

There is a third way of using the network from the browser: WebRTC.

RTCDataChannel supports both reliable and unreliable transmission (the latter tries to use UDP as the transport protocol if possible) and can be used for communicating both with a remote server and between browsers. This means that we can port the whole application to the browser, server component included!

There is some additional complexity involved though: before two WebRTC peers can exchange data, they need to perform a relatively complex connection handshake, which involves multiple third party entities (a signalling server and one or more STUN/TURN servers).

Ideally, we would want to design a network API that uses WebRTC internally but is as close as possible to the connection-less interface of UDP Sockets.

This would allow us to reap the benefits of WebRTC, without exposing the complicated details to the application code (that we want to modify as little as possible with our porting effort).

WebRTC primer

WebRTC is a set of APIs available in browsers for peer-to-peer communication of audio, video and arbitrary data.

The connection between peers is established even in the presence of NAT on either or both sides with the help of STUN and/or TURN servers, through a mechanism called ICE. The peers exchange the ICE information and channel parameters through an SDP offer and answer.

Wow! A lot of acronyms all in one go. Let’s have a very quick review of those terms:

Session Traversal Utilities for NAT ( STUN ) is a protocol for traversing NATs and obtaining an (IP, port) pair to communicate directly with a host. If it succeeds, the peers can then communicate with each other by themselves.

( ) is a protocol for traversing NATs and obtaining an (IP, port) pair to communicate directly with a host. If it succeeds, the peers can then communicate with each other by themselves. Traversal Using Relays around NAT ( TURN ) is also used for traversing NATs, but it does so by relaying the data through a proxy visible by both peers. It adds latency and it is more expensive to run compared to STUN (because it is used for the whole duration of the connection), but sometimes it is the only option.

( ) is also used for traversing NATs, but it does so by relaying the data through a proxy visible by both peers. It adds latency and it is more expensive to run compared to STUN (because it is used for the whole duration of the connection), but sometimes it is the only option. Interactive Connectivity Establishment ( ICE ) is used to decide the best possible method to connect two peers, given the information obtained through direct communication between the peers, and the one obtained by any number of STUN and TURN servers.

( ) is used to decide the best possible method to connect two peers, given the information obtained through direct communication between the peers, and the one obtained by any number of STUN and TURN servers. Session Description Protocol (SDP) is a format for describing the parameters of the communication channel, like the ICE candidates, the media codecs (in case of an audio/video channel), etc… One of the peers will send an SDP Offer, and the other will reply with an SDP Answer. After that, the channel is established.

In order to achieve the connection, the peers need to gather the ICE information they receive from the STUN and TURN servers and exchange it with each other.

The problem is that they have no way of communicating directly yet, so there needs to be an out-of-band mechanism for exchanging this data: a signalling server.

The signalling server can be very simple since its only job is to relay data between the peers in the handshake phase (as you can see in the following diagram).

Simplified sequence diagram of a WebRTC handshake

Teeworlds networking overview

The networking architecture of Teeworlds is straightforward:

The server and client components are two separate programs.

Clients join a game by connecting to one of several servers, each hosting a single game at a time.

All communication in a game passes through the server.

A special master server is used to collect a list of all public servers, which are shown in the game client.

By using WebRTC for communication, we can move the server component of the game to the browser, just like the client. This gives us a nice opportunity..

Going Serverless

Having no server-side logic has a nice advantage: we can deploy the whole application as static content on Github Pages, or on our own hardware behind Cloudflare, to ensure fast downloads and high uptime for free. We can basically forget about it, and if by any chance it will become popular, we don’t need to upgrade our infrastructure.

We still need to use some external infrastructure in order to make it work though:

One or more STUN servers: There are several free options here.

At least one TURN server: There are no free options available, so we can either run our own or pay for the service. Hopefully most of the time the connection can be established via the STUN servers (and be truly p2p), but this is needed as a fallback.

A signalling server: Signaling is not standardized, as opposed to the other two. What a signalling server actually needs to do depends somewhat on the application. In our case, we really just need a way to exchange a small amount of data between two peers before the actual connection.

A Teeworlds master server: this is used by the other servers to advertise themselves, and by the clients to find public servers. While not strictly needed (clients can always manually connect to a known server), it is nice to have so that player can enter games with random people.

We decided to use the free Google STUN servers, and we deployed one TURN server ourselves.

For the last two points, we used Firebase: