Topic 19: Introduction to WebSocket
Real-time Updates
Really interactive, real-time web applications feature
real-time updates.
For instance, real-time chat applications update with messages contributed by
any user in the chat, or a real-time train map needs to be updated which each
train's current position.
We can implement these real-time applications using the technologies we already
know through the use of polling, in which we schedule an AJAX
request to be sent to the server every so often (every 5 seconds, every
minute, etc). However this has the disadvantage that if we want frequent
updates, we have to either
send a message to the server very frequently, potentially
overloading the server (and this would be wasteful if the data is not changing)
or send it less frequently, meaning updates would not truly be real-time.
Push Technology
What we need instead is push technology. Normal HTTP requests
are pull requests: they pull data from the server.
Push requests, however, involve the server sending messages to
the client without receiving a request. WebSocket is
a protocol which allows servers to send push requests to clients.
How does WebSocket work?
WebSocket has a detailed specification available
here which explains it
in detail; this Mozilla article also goes into some detail
and is easier to understand than the full specification. But to summarise:
- A WebSocket server runs as an independent process, on an
independent port (typically 8080), to Apache.
- A WebSocket client, running in the browser and typically
written in JavaScript, listens to requests coming in from the WebSocket
server and can send messages back. Similarly, the WebSocket server can
listen to, and respond to, messages coming from the client.
Writing a WebSocket client
Writing a WebSocket client is quite easy because JavaScript provides
native support. Here is an example:
function init() {
var ws = new WebSocket('ws://example.com:8080');
ws.onopen = e => {
//Handle the web socket being opened...
alert('Websocket was opened!');
ws.send('ephp001');
}
ws.onmessage = e => {
console.log('Websocket sent back... ' + e.data);
}
ws.onerror = e => {
alert('Error with websocket communication: is the server running?');
}
}
Note how we create a WebSocket object and
specify the server and port (example.com and 8080 respectively,
here). We then specify three different event handlers:
- onopen - this event handler will run when the connection
to the web socket is made. In this example we use the send()
method of the WebSocket object to send a username back to the server
(so that, as soon as we have connected to the server, we let the server
know who we are).
JSON is typically used as a format to transmit data between client
and server.
- onmessage - this event handler will run when the client
receives a push message from the server. The data property of
the event object will contain the data sent back. Here, we just display
it on the console.
- onerror - this will run if an error of some kind occurs.
Typical errors include the server not running, or the port being blocked
by a firewall.
Writing a WebSocket server
Various server side languages can deal with websockets using third party
libraries. Here is an example with the ws module in Node.
const wsock = require('ws');
const server = new wsock.Server({port:8080});
// When the server receives a new connection...
server.on('connection', clientConn=> {
console.log('New connection');
// Listen for messages from this connection...
clientConn.on('message', msg=> {
console.log(`Received a message: ${msg}`);
server.clients.forEach ( curClient=> {
curClient.send(msg);
});
});
});
Note how we:
- Create a new web socket server and listen on port 8080.
- Handle the 'connection' event. This occurs when a new client
connects to the web socket server.
- Inside the 'connection' event handler, set up an event handler for
receiving messages from this client (the 'message' event handler)
- Inside the 'message' event handler, process messages received. The
msg parameter will be the message sent from the client. In this
example, every time we receive a message, we loop through the collection
of currently-connected clients (server.clients) and send the
message received to each one (using the client's send() method).
Exercise
Write a simple chat application, similar to the one demonstrated in
class. To do this:
- Create a web page with a text field to add a message, and a div to
show all messages so far. The message should be sent to the web socket
server when a button is clicked.
Upload this to your public_html folder on Neptune.
- Write a corresponding websocket server and upload this to your personal
directory. A div should update each
time anyone sends a new message, as above.
- Run your websocket server from the Neptune console.
-
Enhance your application so that it works like a real chat system;
each time a new message is sent back from the server, the user who wrote that
message should be displayed. To do this you will need a user ID field in
the front end and send the user ID with the chat message. Send a JSON
message with two fields, one for the user ID and one for the chat message;
you can use JSON.stringify() to encode a JavaScript object as
JSON.
- ADVANCED: you can avoid having to send the user ID each
time as follows. The clientConn object is just a plain JavaScript
object, so you can add properties to it.
- Users should have to login first, by specifying a user ID and clicking
a button. The user ID will then be sent to the server. Note that you
should send it as a piece of JSON data including a message type field,
so that the server can tell that this is a user ID and not a chat
message. When the server receives this message, attach the username to the
current clientConn as a property, e.g.
clientConn.user = msgData.user;
- When the user sends a chat message, this should be again sent as
JSON so that the server can tell that it is a chat message and not a
user ID.
- Work out which user sent the chat message. This can be done using
the property of the clientConn object (current client connection) that
you set in step 1.