Understanding Socket.IO
It's important to provide timely feedback to users in your web application. It all started with the introduction of XMLHttpRequest by Microsoft which became what we now know as AJAX. AJAX long-polling used to be the standard way to fetch server-sent data for an application, though it wasn't the most ideal solution. Long-polling involves sending periodic HTTP requests for data, introducing latency and increasing server load.
The IETF standardised WebSockets in 2011, providing a way for developers to send and receive data through a TCP socket. All major browsers began to roll out support for the standard, and developers started to use it in their projects.
Socket.IO is an event-based bi-directional communication layer for realtime web applications, built atop Engine.IO. It abstracts many transports, including AJAX long-polling and WebSockets, into a single API. It allows developers to send and receive data without worrying about cross-browser compatibility.
First Major Release
Socket.IO finally reached version 1.0 on the 28th of May, 2014. The Socket.IO project contained two parts before 1.0: a transport handling implementation, and a high-level API. Transport handling has been moved out into a separate, framework-agnostic project: Engine.IO. This allows other developers to build new APIs and projects for the realtime web without reinventing the wheel.
Apart from architectural changes, Socket.IO 1.0 introduces many user-facing changes, including:
- Binary streaming support
- Improved support for horizontal scaling
- Removal of cluttered debug messages in the console by default
- Support for Node.js streams via the
socket.io-stream
module
In this article, we'll take a quick look at how Socket.io can be used to send and receive data in realtime.
The Basics
Socket.IO provides both server-side and client-side components with similar APIs.
Server-side
On the server-side, Socket.IO works by adding event listeners to an instance of http.Server
. To add Socket.IO support to a http.Server
instance, you'd write
var server = require("net").createServer();
var io = require("socket.io")(server);
var handleClient = function (socket) {
// we've got a client connection
socket.emit("tweet", {user: "nodesource", text: "Hello, world!"});
};
io.on("connection", handleClient);
server.listen(8080);
With this versatility, it's possible to attach a Socket.IO server to other HTTP frameworks. For example, to use Socket.IO with Express:
var app = require("express");
var server = require("http").Server(app);
var io = require("socket.io")(server);
io.on("connection", handleClient);
app.listen(8080);
It's also possible to use Socket.IO with Hapi:
var server = require("hapi").createServer(8080);
var io = require("socket.io")(server.listener);
io.on("connection", handleClient);
server.start();
Socket.IO is compatible with most frameworks that expose their http.Server
instance. If you're not sure, consult the documentation.
Client-side
The HTTP server will begin to serve the client library at /socket.io/socket.io.js
. To connect to our Socket.IO server, we need to put the following in our body
tag:
<script src="/socket.io/socket.io.js"></script>
<script>
var socket = io.connect("http://localhost");
</script>
The global socket
variable is an EventEmitter
-like object. We can attach a listener to fire when we've connected to the server like so:
socket.on("connect", function () {
console.log("Connected!");
});
Sending and Receiving Objects
Since both the server and client's Socket
object act as EventEmitter
s, you can emit and listen for events in a bi-directional manner. For example, we can emit a "tweet"
event on the server and listen for it on the client side.
io.on("connection", function (socket) {
var tweet = {user: "nodesource", text: "Hello, world!"};
// to make things interesting, have it send every second
var interval = setInterval(function () {
socket.emit("tweet", tweet);
}, 1000);
socket.on("disconnect", function () {
clearInterval(interval);
});
});
To consume the data in the browser, we need to listen for the "tweet"
event.
socket.on("tweet", function(tweet) {
// todo: add the tweet as a DOM node
console.log("tweet from", tweet.username);
console.log("contents:", tweet.text);
});
We can send any JSON serialisable object to and from the server. This includes strings, numbers, arrays and booleans. We can also send Node.js Buffer objects starting with Socket.io 1.0.
If we wanted to send tweets from the browser and let the server consume them, we could do the following on the server:
io.on("connection", function(socket) {
socket.on("tweet", function (tweet) {
// we received a tweet from the browser
console.log(tweet);
});
});
In our client-side code, we can send tweets to the server like so:
var tweet = {user: "nodesource", text: "Hello, world!"};
socket.emit("tweet", tweet);
As you can see, both the server and client components of Socket.IO behave similarly.
Streaming Data
In addition to sending JSON serialisable objects, we can pipe a Readable stream to browsers from the server. If we wanted to send the contents of the server-side script, we could do the following:
var fs = require("fs");
var ss = require("socket.io-stream");
io.on("connection", function (socket) {
ss(socket).emit("script", fs.createReadStream(__filename));
});
To consume each chunk of data in the browser, we can listen for the "data"
event on the resulting object.
socket.on("script", function (stream) {
var buffer = "";
stream.on("data", function (data) {
buffer += data.toString();
});
stream.on("end", function () {
console.log(buffer);
});
});
Further Reading
Socket.IO has played an important role in the popular adoption of Node.js by making WebSockets both accessible and reliable. Version 1.0 represents a major step in its evolution and the extraction of Engine.IO has opened up some interesting possibilities for the Node.js ecosystem.
We've only covered some of the features in Socket.IO in this article, you can read about the rest at the Socket.IO website: http://socket.io/docs/.