Multiplayer: node.js, socket.io, and Latency

(return)  (time to code)

data flowThe demo page now contains multiplayer functionality. The approach is to collect mouse and keyboard events from the client device and deliver them to the host computer. All rendering is done on the host's screen (everyone must be in one physical room). Client mouse activity is detected when the cursor is over the blank canvas on the client's page.

This is like a game console experience: social, multiple controllers, and with viewing on one screen.

The diagram here shows the overall dataflow from the network clients, to the node server, to the client objects in the game module on the host computer. Finally, the output of the physics engine is rendered to the HTML 5 Canvas and displayed on the host's monitor.

A host that has a large monitor or projection screen is always nice. Note you can host multiple rooms on one screen, side by side. Just open the demo (host) page in another browser window and start a new room for that second window.

The best performance, lowest latency, comes with installing a local node server on the host computer. The installation instructions are short (see section below). This local-node approach is the configuration shown in the diagram.

And if you're wondering about peer-to-peer connections and protocols like WebRTC, that will be introduced at the end of this page.

The magic is contained mostly in two files for the node server: package.json and server.js, and one file used by the host and client pages: hostAndClient.js.

A Quick How-To

There's a remote node server running at Heroku. You can try this multi-player stuff (without installing your own node server) by connecting to the one at Heroku. It goes like this:
  1. From the demo page, click the multi-player toggle (on the right).
  2. Type in a short name for your "Room."
  3. Click connect. (It can take up to 10 seconds to wake up the Heroku application.) You should see a chat message from the server indicating you are the host of that room.
  4. Now open a client page in a new window. The easiest way to try this out is to stay on one computer. But it's probably best to fire up the laptop (or a second laptop) at this point and use it for the client page. Use a link here or the one below the multi-player toggle (on the right side of the demo page).
  5. Enter the same "Room" name on this client page and click connect. You'll get connection confirmation in the chat panel. Also the server field will be populated with the address of the Heroku node application.
  6. Now move your mouse over the blank rectangle (Canvas) on the client page. You should see your mouse position rendered as a small circle (your new cursor) on the host's canvas. Try each of the three mouse buttons to get differing mouse-spring strength (middle button is the weakest).

Note there are only mouse events captured at this time. I'm not yet capturing keyboard events...

Latency

The Heroku server is in California or on the East coast somewhere. Speed-of-light limitations will be noticeable from the Midwest. You can measure the round-trip time with the ping features in the node-server and client scripts. Type the word "ping" into the chat field and then enter that or click the "chat" button. Here is a screen shot of a test from Minnesota to the Heroku server.

ping 

And here is a ping test to a node server running on a computer (a Raspberry Pi) on my local network.

ping

It takes about half the time (31ms compared to 56ms). You'll notice that reduced latency especially with mouse movements. If you install the local node server, as described below, you may also feel a more continuous, less jumpy rendering (fewer dropped packets).

If you enter "ping:host" in the chat field you'll get the time for a round-trip time to the host and back (via the server). That's a four legged path, client to server to host to server to client. Here's an example of that type of ping where the client and host are on one computer and the node server is at Heroku:

ping

That takes twice as long as the base test and is about what is expected for a trip that's twice as far. This is the more rigorous test in that it travels the actual path to the host. If you divide this number by two you get the best estimate of the latency involved in our one-way communications with the host.

Installation of a Node Server

The following sections provide instructions for installing and running a local node server on Windows and Linux, and a remote server on the Linux environment at Heroku.

(Note that the local install offers the best performance if the node server is put on the host computer. But that's not absolutely necessary. It can go on any computer on your local network. I sometimes have three node servers running in my house and just point the host and client pages at a particular server's IP address during testing.)

On Windows

Download the node installer for Windows here. Run the installer.

Download the node-server.zip file from here (this works best if you login to Google). Unzip it and copy or move the three files (package.json, server.js, and links.html) to a working directory where you wish to keep your node-server content.

Open a Windows command prompt and go to your working directory. Then enter the following:

npm install

Now start your node server by entering the following command (while still in that working directory):

node server.js

The first time you start the server you'll be asked for permission to go through the firewall. Agree to this. You're now ready to connect to your local server. Refer to the "After Installation" section below.

On Linux

First, install node.js on linux:

curl -sL https://deb.nodesource.com/setup_6.x | sudo -E bash -
sudo apt-get install -y nodejs

Download the node-server.zip file from here (this works best if you login to Google). Unzip it and copy or move the three files (package.json, server.js, and links.html) to a working directory where you wish to keep your node-server content.

Then enter the following command (from that directory):

npm install

Then run the node-server script (while still in that same directory).

node server.js

You're now ready to connect to your local server. Refer to the "After Installation" section below.

At Heroku

For many game situations a remote node server is just fine. The extra latency won't be noticed especially if you're not controlling objects with mouse movements. The same three files that are used for the local installations can also be used at Heroku. Here's how:

First, open a free account at Heroku.

Download and Install the CLI (command line interface). Links to the Windows 32 and 64bit installers are on this page.

If you haven't already setup a working directory, download the node-server.zip file from here (this works best if you login to Google). Unzip it and copy or move the three files (package.json, server.js, and links.html) to a working directory where you wish to keep your node-server content.

Open the CLI using the Git CMD icon. Then go to your working directory (where you have the server.js, package.json, and links.html files). The little screenshot below shows what that looked like for me (I have a Git CMD icon on my desktop).

heroku

At that command prompt, login to heroku:

heroku login

Run these commands to initialize a local Git repository and commit your files to it:

git init
git add .
git commit -am "another update"

Run this command to create your new (blank) node application at Heroku:

heroku create

Finally, push your local code out to Heroku:

git push heroku master

That should do it. Now you can run the following command to open a browser and a little page being hosted by your node server. That page simply has links to the host and client. Make note of the URL for that page (it will be something like xxxxx-xxxxx-nnnnnn.herokuapp.com). That's the secret sauce you need to paste into the "Server" input box on the host and client pages.

heroku open

Updates to your Heroku application can be published by using these three commands. I like to put these in a little batch file so it's easy to push out changes.

git add .
git commit -am "another update"
git push heroku master

After Installation

The server.js script will activate all the socket.io event listeners that are needed to receive input from the clients.

Now you should able to point the host or client pages to the IP address of the computer on which you installed the node server. Do that by entering the node-server's IP address and port into the "Server" input box on the host or client page (e.g. 192.168.1.106:3000). You can also see this in the screen shots in the Latency section above. Then click the "Connect" button.

For the Heroku server, use the URL of the page that opens up after issuing the "heroku open" command. Copy everything after the "https://"; it should be something like xxxxx-xxxxx-nnnnn.herokuapp.com. Then paste that into the "Server" input box and click the "Connect" button.

You can restart your local node server at any time from the command line (in your working directory). Simply issue control-c twice. That should bring you back to the normal command line prompt. Then to start it again, just type "node server.js" as you did during the installation. After a server restart you will need to reconnect the host and clients. Always start with the host, then the clients.

WebRTC

WebRTC is a new technology that supports peer-to-peer (P2P) connections. It still requires an external server to be host to a signaling process in which the peer computers exchange credentials. Maybe the most compelling aspect about WebRTC is that you can use UDP (instead of TCP). That might offer better gaming performance especially with mouse movement. However, at this time it is significantly more complex for a developer to get WebRTC up and running. My target reader here is someone like a teacher that is working with a small group of students. And the local node server offers very good performance and is fairly straightforward to install. If you're developing a site similar to this one, you may want to stick with Node.js and socket.io for starters.

Having said that, in September of 2017 I added a WebRTC (P2P) layer on top of the socket.io approach described above. This exists, in the code, mainly side-by-side with the socket.io functionality; by that I mean I don't think it confuses the work described above. The WebRTC depends on socket.io for use in the signaling process, but doesn't change the socket.io implementation here. Here's a new page that discusses this...