The Building of Yeww: A WebRTC ProjectSocket.io

Posted on September 18, 2016 at 6:40 PM by Lynn Walker

Socket.io is a javascript library created to support WebSocket programming. The WebSocket protocol was standardized in 2011 and the WebSocket API is currently being standardized by the W3C.

Socket.io just recently hit 1.0, and now supports binary streaming. Yeww-hoo!

WebSockets

WebSockets allow for bi-directional communication between a client and a server. The web was originally designed around HTTP request-response. This allows for only one way communication, as the client must always ask before it receives anything from the server.

Methods have been devised for allowing the server to contact the client as soon as it receives information it knows the client would like to receive. Push, or comet technology uses long-polling or other techniques to keep an HTTP connection open between the client and server. The WebSocket’s specification opens a persistent TCP socket connection between the client and server over which both can send data at any time, making full-duplex, real-time communication possible.

Socket.io as a node.js module

Socket.io is written in Node.js and distributed through node package manager (npm).

Install node locally

Visit http://nodejs.org/ to download and install node.js on your local system.

For those wondering why we didn’t do this to install node.js on Ubuntu in the previous lesson, instead of using a browser and GUI interface on our Ubuntu instance at AWS, we used SSH, a command-line tool to install everything with scripts.

Server and client

Socket.io is composed of two parts, a server integrated with node.js, (socket.io), and a client library, that loads into the page in the browser (socket.io.client). Install the server like this:

npm install socket.io

Then you simply require the module in your node script to make use of it.

var io = require ('socket.io');

The client can reference the library by including the socket.io.js file directly from the instantiated server, which automatically provides the client with access, like this:

<script src="http://your-io-server/socket.io/socket.io.js"></script>

To include the socket.io.client in a web page you would add this reference to the io global, establishing a socket connection:

<script> var socket = io.connect(); </script>

or

<script> var socket = io( [URL_TO_SOCKET_SERVER] ); </script>

Socket.io application first steps – establish connection

You’ll need three files, “app.js”, which defines the server, “index.html”, which is the client, a web application running in the browser, and “package.json”, which is the project file.

Package.json

{
    "name": "connect",
    "version": "0.0.1",
    "dependencies": {
        "express": "^4.9.1",
        "socket.io": "^1.1.0"
    }
}

We will use this same package.json as the project file for each of today's examples.

The project file stores the project name, version number, and two dependencies, “express”, version 4.9.1 and “socket.io”, version 1.1.0. In later lessons we will expand package.json with more fields, especially scripts, which are useful in automation.

App.js

First we’ll build the server using a popular node module named “express”.

var app = require('express')();
var server = require('http').Server(app);
var io = require('socket.io')(server);
server.listen(8000);

app.get('/', function(req, res){
	res.sendFile(__dirname + '/index.html');
});

io.sockets.on('connection', function(socket){
    console.log("connection established");
});

The first three lines are a tidy short-hand of six straight-forward statements, three require and three function call expressions.

Express creates a web application, with the http module, an http server and socket.io, which provides websocket or a fall-back technology for real-time communication. The http server is listening on port 8000.

All requests at this root will receive a file, “index.html”, in response.

A listener is created so that when the server receives a websocket ‘connection’ event, the console message “connection established” is displayed.

Index.html

<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Socket.IO Connection Example</title>
    <script src="/socket.io/socket.io.js"></script>
    <script>
        var socket = io('http://localhost');
    </script>
</head>
<body>
    Welcome to Socket.io
</body>
</html>

Displays a page title and a single line of text on the page. Includes the socket.io.client library and opens a socket by instantiating a socket.io object with the server’s url.

Run the application

First the dependencies must be installed. From the directory with the package.json file, execute this:

npm install

You will see messages scroll across the terminal display, downloading and installing the packages and their dependencies. When it is complete you will have a node_modules directory with subdirectories containing the project dependencies.

If you receive errors you may need to rerun the command with administrative privileges, as we did on the Ubuntu instance like this:

sudo npm install

Start the server

Launch the server with the command:

node app.js

This server has no visible output except when a connection is made with a client, or some form of failure occurs, such as port in use or module not found. No message, just a blinking cursor, means we’ve started the server successfully.

Launch the client

Open a browser and enter this url into the address bar:

http://localhost:8000

Your browser should display the message:

Welcome to Socket.io

The page title should display as “Socket.IO Connection Example”.

There will be no visible signs of a connection on the client (browser), but the node.js server should have displayed the line:

connection established

Each time you refresh the page, or visit that url from a new browser, the server will print that line again.

Troubleshooting

If you don’t see the line “connection established”, use Chrome as the browser and open the Developer Tools ([Ctrl]-[Shift]-[I] on Windows). Various connection errors will be caught and reported in the console and network tabs.

Version related errors

Express has deprecated functions you may find in other examples, using “createServer”, for instance. When experiencing version related errors, try deleting the node_modules directory and reinstalling the dependencies again from the package.json file with “npm install”. The dependency versions in the package.json are compatible with the project’s code. If you installed the dependencies manually you may have obtained a different version.

Stop Server

Issue the break command with the keyboard pair [Ctrl]-[C]. This will stop the node server.

Simple chat application

We’re going to build a chat application, one step at a time, so it is easy to understand the details. For the first step we will take the previous sample where a server and client formed a websocket connection and add a disconnect event.

Presence

With event listeners for connect and disconnect events we are able to track user “presence” and update that information in real-time for other users of the system.

App.js

Expand this:

io.sockets.on('connection', function(socket){
    console.log("connection established");
});

to this:

io.sockets.on('connection', function(socket){
    console.log("connection established");
    
    socket.on('disconnect', function(data){
        console.log("connection dropped");
    }); 
});

Start the server

In addition to the “connection established” message, you should now see a “connection dropped” when the browser is closed or the session ends. We are now tracking the presence of unidentified users.

Send Message

Next we will add the ability to send a message from one client to all other connected clients. This is considered “broadcasting” a message. As we move forward, sending a message will eventually mean to a specific user or socket. Broadcasting, on the other hand, means sending a message to everyone, either in a room or the entire system.

How it works

Socket.io allows us to send many types of messages, and pass data along with them. We are going to start with two types of messages, “send message” and “new message”. The client starts by sending a “send message” to the server which turns around and sends a “new message” to each of the connected clients. The client appends “new messages” to the message list when received.

Index.html

We’ll make the first changes in the client, as it is the client that initiates a “send message” action.

<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Socket.IO Send Message Example</title>
</head>
<body>
    <div id="messages" style="height:300px;"></div>
    <form id="send-message">
	<input size="35" id="message"></input>
	 <input type="submit"></input>
    </form>

    <script src="http://code.jquery.com/jquery-latest.min.js"></script>
    <script src="/socket.io/socket.io.js"></script>
    <script>
        jQuery(function($){
            var socket = io('http://localhost');
            var $messageForm = $('#send-message');
            var $messageBox = $('#message');
            var $messages = $('#messages');

            $messageForm.submit(function(e){
                e.preventDefault();
                socket.emit('send message', $messageBox.val());
                $messageBox.val('');
            });

            socket.on('new message', function(data){
                $messages.append(data + "<br/>");
            });
        });
    </script>
</body>
</html>

From top to bottom, the previous static text message has been removed and replaced with an empty div and a form. The form contains a single input box and a submit button.

The page loads the same socket.io.client library but this time it also loads jQuery from a CDN. When the document is ready the websocket connection is made, three variables are declared and two event listeners are created. Since the event listeners are created in the same scope as the variables, a closure is created and the value of those variables at the time those event listeners are called is available to the event listener.

One variable is associated with each of three page elements, the empty div, which will display messages, both sent and received, the input field, which is where messages will be typed to send, and the form, which will send messages on its submit event.

That form’s submit event is the first of the created event listeners. First the handler will stop the default form behavior so that we can override that with our own. We don’t want to redirect to anther page, posting data. We want to have our client socket send a message to the server, which the server will then broadcast to all sockets (connected users). Sending this message is performed by the socket’s “emit” function.

The message it sends is a “send message” type. This message also passes along the contents of the message input text box as data. Then we clear the text box contents. In the case of a “send message” type message, the server will broadcast that data as this client’s message to the other clients.

This is all we need in order to send messages from the client. In order to receive those messages sent by other clients, we need to set up one more event listener, to handle the receiving of a “new message” event.

The “new message” event appends the accompanying data to the messages div to update the chat history.

App.js

Add this beneath the disconnect event handler, still within the connect event handler:

	socket.on('send message', function(data){
		io.sockets.emit('new message', data);
	});

This creates an event handler that responds whenever the server receives a “send message” event with accompanying data over one of the websockets. The server responds by broadcasting a “new message” along with that data.

It now looks like this:

var app = require('express')();
var server = require('http').Server(app);
var io = require('socket.io')(server);
server.listen(8000);

app.get('/', function(req, res){
    res.sendFile(__dirname + '/index.html');
});

io.sockets.on('connection', function(socket){
    console.log("connection established");
    
    socket.on('disconnect', function(data){
        console.log("connection dropped");
    });

    socket.on('send message', function(data){
        io.sockets.emit('new message', data);
    });
});

We’ll use the same package.json as in the previous example.

Run “send message” example

Start the server. Open two different browsers, pointing each to http://localhost:8000. The server will report as each of them connects. Type text into the input on one window and hit submit. The message appears on both browsers. Type text in the other browser, same result. The messages are anonymous, we haven’t associated a name with a connection, yet. That’s coming in the next step.

We are sending messages from one client to another. We want to test this on our AMS server, but first let’s add usernames to our code so that we can see who is sending a message.

Taking names

Chat isn’t personal until it uses names, or at least nicknames. Our next step will be to take the user’s nickname on a first page and on the chat page we will label this person’s messages with their nickname, both on his own client as well as on all other connected clients.

How this works

The client will make use of two divs that wrap page sections, with only one section showing at a time. The first page section will contain a form to collect the user’s nickname. Once submitted this div is hidden and the other div displaying the chat controls will be displayed. The chat controls include a list of connected users along with the chat history and the message box.

The server persists an array of nicknames, updating them each time a user connects or disconnects.

Index.html

<!doctype html>
<html lang="en">
<head>
	<title>Socket.io Username Example</title>
	<style>
		#messages{ height:300px; }
		#contentWrap{ display: none; }
		#messageWrap{ float: left; border: 1px #000 solid; }
	</style>
<head>
<body>
	<div id="nickWrap">
		<p>Enter a username:</p>
		<p id="nickError"></p>
		<form id="setNick"&
        gt;
			<input size="35" id="nickname"></input>
			<input type="submit"></input>
		</form>
	</div>

	<div id="contentWrap">
		<div id="messageWrap">
			<div id="messages"></div>
			<form id="send-message">
				<input size="35" id="message"></input>
				<input type="submit"></input>
			</form>
		</div>
		<div id="users"></div>
	</div>
	
	<script src="http://code.jquery.com/jquery-latest.min.js"></script>
	<script src="/socket.io/socket.io.js"></script>
	<script>
		jQuery(function($){
            			var socket = io('http://localhost');
			var $nickForm = $('#setNick');
			var $nickError = $('#nickError');
			var $nickBox = $('#nickname');
			var $users = $('#users');
			var $messageForm = $('#send-message');
			var $messageBox = $('#message');
			var $messages = $('#messages');
			
			$nickForm.submit(function(e){
				e.preventDefault();
				socket.emit('new user', $nickBox.val(), function(data){
					if(data){
						$('#nickWrap').hide();
						$('#contentWrap').show();
					} else{
						$nickError.html('That username is already taken!  Try again.');
					}
				});
				$nickBox.val('');
			});
			
			socket.on('usernames', function(data){
				var html = '';
				for(i=0; i < data.length; i++){
					html += data[i] + '<br/>';
				}
				$users.html(html);
			});
			
			$messageForm.submit(function(e){
				e.preventDefault();
				socket.emit('send message', $messageBox.val());
				$messageBox.val('');
			});
			
			socket.on('new message', function(data){
				$messages.append('<b>' + data.nick + ': </b>' + data.msg + "<br/>");
			});
		});
	</script>
</body>
</html>

There are styles defined in the page head and a new page section dedicated to collecting the user’s nickname. A list of users is added to the chat section, with divs to wrap all of the controls. There are four new jQuery variables associated with page elements and two new event listeners.

The first new event listener handles the submit event from the nickname form. It begins by stopping the default form action. It then sends a message to the server of the “new user” type, along with the value of the nickname text box and a callback function.

The server is going to check the provided nickname against the list of connected users. If it matches an existing nickname the server calls the client’s callback with a false value and the client will display an error message indicating the nickname has already been taken. If the nickname is unique the server adds the nickname to the list, names the socket with this nickname and sends a message to update the connected user lists on all clients. On the client the nickname form disappears and the chat controls appear, which now includes a list of connected users.

The client also gets a new event listener for the message “usernames”. Each time a client receives this event it will update the connected users displayed on the page with the list that has just changed.

Finally, the event listener for the “new message” event will update the chat history with the nickname of message sender.

App.js

var app = require('express')();
var server = require('http').Server(app);
var io = require('socket.io')(server);
server.listen(8000);
var nicknames = [];

app.get('/', function(req, res){
	res.sendFile(__dirname + '/index.html');
});

io.sockets.on('connection', function(socket){
	socket.on('new user', function(data, callback){
		if (nicknames.indexOf(data) != -1){
			callback(false);
		} else{
			callback(true);
			socket.nickname = data;
			nicknames.push(socket.nickname);
			updateNicknames();
		}
	});
	
	function updateNicknames(){
		io.sockets.emit('usernames', nicknames);
	}

	socket.on('send message', function(data){
		io.sockets.emit('new message', {msg: data, nick: socket.nickname});
	});
	
	socket.on('disconnect', function(data){
		if(!socket.nickname) return;
		nicknames.splice(nicknames.indexOf(socket.nickname), 1);
		updateNicknames();
	});
});

The server has added an event listener for the “new user” event, taking data and a function to callback. It checks the data against the nickname list and handles the user management if it is unique.

There is a new function to send the “usernames” event to each client when a new user connects or a user disconnects.

The “send message” event listener is updated to send the nickname along with each “send message” type message.

The disconnect event listener is updated to remove the user’s nickname from the nicknames array and trigger the update of each client’s connected user list.

Run the application

Start the server and launch two browsers, pointing each to http://localhost:8000. One at a time, enter a username and submit the form for each browser.

After a username is given and the submit button clicked the page updates with chat controls replacing the username form.

After both users have a named socket connection the messages can go back and forth, with the nickname attached to the sender of the message.

Finally, when one user closes out the session, by closing the browser, for instance, the remaining client’s user list is updated to reflect the disconnection.

Wrap up

  • We demonstrated the minimum code necessary to create a server and client that connect.
  • We added a disconnect event handler giving us full presence detection.
  • We added message sending in real-time between browsers over websockets.
  • We added nicknames to our application so that we can track users and their messages by name


Acknowledgements

There are numerous examples of chat code on the web. I find these three approaches using socket.io to be very instructive.

The socket.io based code we are using borrows heavily from these three sources.

  • A four part series by Smitha Milli provides a clear description of implementing basic chat features.
  • A multi part series by Tamas Piros, provides a few features on the way to an advanced chat application plus an example in AngularJS.
  • An article by Brian Ford describes creating a chat application with an AngularJS front-end.