Always On, Always Connected Week 11 - Network Sockets

Network Socket

Sockets enable inter application communication over a network. Most networking software that you use, web browsers, web servers, SSH clients, email applications leverage sockets to communicate with remote applications.

WebSockets

WebSockets are a relatively new protocol that is supported by browsers and web servers that facilitate more real time connectivity between client and server than HTTP alone has traditionally supported. WebSockets different only a bit from regular sockets so anything that supports regular socket communication can be made to support WebSockets. The benefit to going this direction is that you can write a server side application that uses WebSockets and every client under the sun should be able to work with it. Browser, Java Applet, Android app, iOS app, desktop application, PhoneGap application and so on.

Node.js is a JavaScript platform for creating applications. JavaScript is a nice way to create synchronous socket based server applications. I like to use a library called Socket.IO with Node.js.

Local Server

You can run a Node.js server on your computer and access it from the local network (as long as the network you are on allows it). Simply download Node.js, write your server and run it.

Server in the Cloud

In order to make a server written with Node.js and Socket.IO available to clients on the regular internet, it needs to be run on a server that is accessible to clients on the public internet. There are a variety of cloud based services for running Node.js servers but one that I like is Digital Ocean.

In order to get started, with Digital Ocean, you'll need to create an account.

After that, you can go ahead and create a "droplet". A "droplet" is Digital Ocean's term for a VPS. After coming up with a hostname, I would choose the smallest size, an appropriate region (one close to you, perhaps), and then in "Select image" go to the second tab, "Applications" and choose "node-v0.10.30 on Ubuntu 14.04" (the version numbers may change) and click "Create Droplet".

The Command line

Digital Ocean will email you a password to use with the default "root" account. In order to do anything, you first have to login to the newly created server via the command line to change your password. To do so, open up Terminal on your Mac (or use PuTTY if you are on a PC). To connect on from the Terminal app on the Mac, you type the following:

ssh root@ip_address
		
(The "ip_address" is in the email you get from Digital Ocean.) You will be prompted to enter the password (which is also from the email) and then prompted to enter it again to change it. Go ahead and change it to something you will remember but sufficiently complex that it will be difficult for hackers.

Once you have done that, we can move on although it is probably a good idea to keep the terminal open as we'll be using it again shortly.

Uploading

In order to upload to your server, we need to use a tool that will allow us to connect via SSH to transfer the files. I have found that Cyberduck and Fetch (available for free from NYU's ITD department) are good tools.

To connect, choose "SFTP (SSH File Transfer Protocol)", under "Server" use the IP address emailed to you by Digital Ocean. The "username" is "root" and the "password" is what you just created in Terminal.

Here is a quick example (from http://itp.nyu.edu/~sve204/liveweb_fall2013/week3.html)


// HTTP Portion
var http = require('http');
var fs = require('fs'); // Using the filesystem module
var httpServer = http.createServer(requestHandler);
httpServer.listen(8080);

// Any normal http request
function requestHandler(req, res) {
	// Read index.html
	fs.readFile(__dirname + '/index.html', 
		// Callback function for reading
		function (err, data) {
			// if there is an error
			if (err) {
				res.writeHead(500);
				return res.end('Error loading index.html');
			}
			// Otherwise, send the data, the contents of the file
			res.writeHead(200);
			res.end(data);
  		}
  	);
}


// WebSocket Portion
// WebSockets work with the HTTP server
var io = require('socket.io').listen(httpServer);

// Register a callback function to run when we have an individual connection
// This is run for each individual user that connects
io.sockets.on('connection', 
	// We are given a websocket object in our function
	function (socket) {
	
		console.log("We have a new client: " + socket.id);
	
		// When this user "send" from clientside javascript, we get a "message"
		// client side: socket.send("the message");  or socket.emit('message', "the message");
		socket.on('message', 
			// Run this function when a message is sent
			function (data) {
				console.log("message: " + data);
				
				// Call "broadcast" to send it to all clients (except sender), this is equal to
				// socket.broadcast.emit('message', data);
				socket.broadcast.send(data);
				
				// To all clients, on io.sockets instead
				// io.sockets.emit('message', "this goes to everyone");
			}
		);
		
		// When this user emits, client side: socket.emit('otherevent',some data);
		socket.on('otherevent', function(data) {
			// Data comes in as whatever was sent, including objects
			console.log("Received: 'otherevent' " + data);
		});
		
		
		socket.on('disconnect', function() {
			console.log("Client has disconnected");
		});
	}
);
Save this file as whatever.js and upload it to a new directory on your server.

Here is the corresponding index.html file that should be uploaded to the same directory. (Don't forget to replace the SERVER_NAME with your server's name or address.).

On cordova/phonegap, you'll need to install this plugin: plugin-socket.io and change the code below to use "plugin.socket.io.connect" instead of "io.connect".
<html>
	<head>
		<script src="/socket.io/socket.io.js"></script>
		<script>
			var socket = io.connect('http://SERVER_NAME:8080/');
			
			socket.on('connect', function() {
				console.log("Connected");
			});

			// Receive a message
			socket.on('message', function(data) {
				console.log("Got: " + data);
				document.getElementById('messages').innerHTML += data;
			});

			// Receive from any event
			socket.on('otherevent', function (data) {
				console.log(data);
			});
			
			var sendmessage = function() {
				var message = document.getElementById('message').value;
				console.log("Sending: " + message);
				
				// Send a messaage
				socket.send(message);
			};
			
			var sendother = function() {
				var othermessage = document.getElementById('message').value;
				console.log("sending: " + othermessage);
				
				// Send any kind of data with a custom event
				//socket.emit('otherevent',{ othermessage: othermessage });
				socket.emit('otherevent', othermessage);
			};
		</script>
	</head>
	<body>
 		<div id="messages">
			No Messages Yet
		</div>
		<div>
			<input type="text" id="message" name="message">
			<input type="button" value="message" onclick="sendmessage();">
			<input type="button" value="other" onclick="sendother();">
		</div>
	</body>
</html>

More documentation and examples: http://socket.io/

As you can see, sockets programming is very event driven (as is JavaScript). When a message comes in a function is called to handle that message.

PhoneGap + WebSockets

Of course, since websockets are JavaScript based, they just work in our PhoneGap applications.

Here is an example PhoneGap application which sends acceleromter data from the phone

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8" />
        <meta name="viewport" content="user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1, width=device-width, height=device-height, target-densitydpi=device-dpi" />
        <title>Hello World</title>
        <script type="text/javascript" src="cordova.js"></script>
        <!-- Use Socket.IO from the CDN or locally -->
        <script src="https://cdn.socket.io/socket.io-1.3.4.js"></script>
        <script type="text/javascript">

			var socket = io.connect('http://SERVER_IP:8080/');
			
			socket.on('connect', function() {
				console.log("Connected");
				window.addEventListener('devicemotion', ondevicemotion, false);
			});

			// Receive a message
			socket.on('message', function(data) {
				console.log("Got: " + data);
			});			

			function ondevicemotion(e) {
				var x = e.acceleration.x;
				var y = e.acceleration.y;
				var z = e.acceleration.z;

				console.log(x + " " + y + " " + z);
				
				var message = {x: x, y: y, z: z};				
				socket.send(message);
			}
        </script>
    </head>
    <body>

    </body>
</html>	
	
and the corresponding server:
// HTTP Portion
var http = require('http');
var fs = require('fs'); // Using the filesystem module
var httpServer = http.createServer(requestHandler);
httpServer.listen(8080);

// Any normal http request
function requestHandler(req, res) {
        // Read index.html
        fs.readFile(__dirname + '/screen.html',
                // Callback function for reading
                function (err, data) {
                        // if there is an error
                        if (err) {
                                res.writeHead(500);
                                return res.end('Error loading screen.html');
                        }
                        // Otherwise, send the data, the contents of the file
                        res.writeHead(200);
                        res.end(data);
                }
        );
}

// WebSocket Portion
// WebSockets work with the HTTP server
var io = require('socket.io').listen(httpServer);

// Register a callback function to run when we have an individual connection
// This is run for each individual user that connects
io.sockets.on('connection',
        // We are given a websocket object in our function
        function (socket) {

                console.log("We have a new client: " + socket.id);

                // When this user "send" from clientside javascript, we get a "message"
                // client side: socket.send("the message");  or socket.emit('message', "the message");
                socket.on('message',
                        // Run this function when a message is sent
                        function (data) {
                                console.log("message: " + data.x + " " + data.y + " " + data.z);

                                // Call "broadcast" to send it to all clients (except sender), this is equal to
                                // socket.broadcast.emit('message', data);
                                socket.broadcast.send(data);

                                // To all clients, on io.sockets instead
                                // io.sockets.emit('message', "this goes to everyone");
                        }
                );

                socket.on('disconnect', function() {
                        console.log("Client has disconnected");
                });
        }
);

	
Finally, here is a screen.html file which can be used to receive the events and visualize them. It uses p5.js and Socket.io.
<html>
	<head>
		<script language="javascript" type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/p5.js/0.3.11/p5.min.js"></script>
		<script src="https://cdn.socket.io/socket.io-1.3.4.js"></script>
		<script type="text/javascript">
			var socket = io.connect('http://SERVER_IP:8080/');

			socket.on('connect', function() {
					console.log("Connected");
					window.addEventListener('devicemotion', ondevicemotion, false);
			});

			// Receive a message
			socket.on('message', function(data) {
					console.log("Got: " + data);
					x = x + data.x;
					y = y + data.y;

					if (x > 600) { x = 599; }
					if (x < 0) { x = 0; }
					if (y > 400) { y = 399; }
					if (y < 0) { y = 1; }
			});

			var x = 0;
			var y = 0;

			function setup() {
				createCanvas(600, 400);
			}

			function draw() {
				ellipse(x,y,80,80);
			}
        </script>
	</head>
	<body>
	</body>
</html>