Testing Phoenix Channels on a Mac App

Daniel Almeida
5 min readApr 13, 2018

--

In one of our current projects here at Coletiv we had the need to continuously keep the data synced between multiple mobile devices. The first solution that came to our minds to keep the state up to date was to poll the server each minute allied with push notifications.

Clearly this wasn’t the best solution in terms of performance and reliability. Since our Backend was made using Phoenix, the initial naive solution naturally evolved to use Phoenix Channels.

In this article we will describe our experience implementing a proof of concept to test Phoenix Channels using the game Slime Soccer. This is a simple game for two players where each one controls a slime and the objective is to score a goal.

Screenshot of the final version of our proof of concept

Overview

The proof of concept was implemented in Swift as a Mac app, but it could be easily implemented in any other language or platform of your choice.

The idea is to have two instances of the game running on different computers, each one will connect to a Phoenix server through Phoenix Channels. The server is responsible for managing the game score and handle the communications between the two players.

Phoenix Channel Client

There are a few Swift open source projects to help with the channel communications:

We decided to use Birdsong simply because we think that at the moment it was the project that best fits our needs. It has a clean and simple code style, performs a periodic ping to the server and allows some customisations like handling the disconnect actions.

Implementation

If you want to follow along you can find the server source code here and the client one here.

The following diagram depicts the general communication flow between the Mac clients and the server during a game.

Flow Diagram

Establishing the connection

Server

To define a new channel in Phoenix we simply add this line to UserSocket.ex:

channel (“game: *”, SuperSlimeGameWeb.GameChannel)

Our connect function is quite simple because we chose to ignore the authentication steps since it would imply additional development and that was not our goal with this test, but if you need authentication this would be a possible place to had your checks.

Client

In the connect method we define both onConnect and onDisconnect handlers. These closures will be called after the socket is connected/disconnected. In our test we will join the channel immediately after the socket is connected. When the socket is disconnected a reconnect will be performed after a short delay.

Joining a game

Server

The main part of the application is the join function where players join a game. The channel nomenclature and the payload structure are defined as follows:

Channel nomenclature

“game:” <> code

Payload structure

%{“email” => email} 

The game code game:[a_code] is used to check if the game already exists in the GameState agent process that was created to manage games. If the game already exists we will add the new player passed in the payload structure, otherwise a new game is created and a new player is added.

The GameState agent is used to save the games and match players, it is started alongside the server as a supervised child process.

To start the agent add the worker to the Application.ex like this:

worker(SuperSlimeGame.GameState, [])
Game state agent implementation

Client

Within the join method we define the channel event listeners. These listeners will be executed if a specific action is returned from the channel (i.e. player moved). It is important to verify the state of the socket to ensure that the persistence of the connection is maintained.

Player Actions

Server

When the playerAction command is received, the server will broadcast it to all players that joined the channel.

Client

Each time the scene is updated, a message is sent to the channel with the local player coordinates and actions.

Concerns and Future work

Authentication method — join vs connect

In this example we didn’t add any security within the channel connection, but this is a MUST have in a real world context.

In other projects developed by Coletiv, we need to authenticate the user to allow it to access their resources. This is usually accomplished by using Guardian + ueberauth + JWT + Comeonin. Our concern here is to either send the JWT token in the connect or join functions.

We’ve decided to use it within the connect function because without a valid token no one has access to the endpoint. Otherwise, if the token is passed in the join function, everyone will perform a successful connection, making the endpoint public.

Improve game communication/synchronisation

In this game both players have the responsibility to draw the player positions and simulate the game physics. Obviously this is not the best way to accomplish the movement synchronizations, perhaps a master-slave strategy could be a better solution.

Ensure the use of HTTPS/WSS

The server MUST accept incoming secure connections using WSS protocol. By default, most servers don’t accept this.

# Configures the endpoint
config :myapp, MyApp.Web.Endpoint,
url: [host: "localhost"],
check_origin: ["//dev.example.com", "//www.example.com"],
secret_key_base: "SECRET_KEY",
render_errors: [view: MyApp.ErrorView, accepts: ~w(json)],
pubsub: [name: MyApp.PubSub, adapter: Phoenix.PubSub.PG2]

Connection persistence

On client side its very important to maintain the socket connection alive, which led to more code complexity. We’ve tried to achieve this by implementing the reconnect() function when the connection is lost, but this is not enough if we need to rejoin the channel and/or use authentication.

Conclusion

During this experience with Phoenix Channels we’ve noticed that the message exchange process is reliable and fast. This simple game, that was not optimized in terms of communication, has a satisfactory performance.

We think that Phoenix Channels serve our purpose of maintaining the state synchronized between multiple clients.

We are implementing a similar solution in one of our current projects and we hope to write soon about our real world experience with Phoenix Channels.

Sample Code

You can find the server source code here and client one here.

--

--