All code and slides can be found on GitHub: https://github.com/thoward333/websockets-demo
Know When You Need Real-time
Real-time applications need instant updates from the server, often when an external event triggers the need to receive new data. The most popular solution involved long polling, where the page makes an AJAX call to the server that remains open until the server sends information and then a new AJAX call is made to restart the process. Note the difference between this and short polling, where an AJAX is made at a regular interval and immediately receives data. Also note the performance/scalability difference between long polling and short polling. Long polling puts a bigger strain on the server since the connections are held open for long periods of time. That is, the benefit of real-time updates comes at the cost of scalability. WebSockets compete with long polling, not short polling. Short polling is a valid pattern, but it’s not relevant for our discussion since it is so different from WebSockets.
Why Not Long Polling?
Long polling is a tried and true solution (there are even libraries), but there are several drawbacks:
- HTTP request per message: the client must make a new AJAX call each time it receives data.
- Had to deal with timeouts: Sometimes the AJAX call would timeout before the server sends data, requiring that a new AJAX call be made.
- HTTP header / latency: There is overhead for each AJAX call, such as the HTTP header and the network latency of establishing the connection. In an application where the message payloads are small it is common for this overhead to create more network traffic than the payload.
Why WebSockets?
In a word: Efficiency. Long polling was a workaround for HTTP not supporting persistent connections and WebSockets provide a way to maintain the connection between the client and server. This Stack Overflow post has some great visuals emphasizing the difference between long polling and WebSockets. And websockets.org has a benchmark showing the dramatic difference in scalability between long polling and WebSockets, particularly with large message volumes.
Note that in WebSockets, we do not necessarily have one request per response (often we don’t). This is a stark difference compared to traditional client/server communication, long polling or otherwise.
Look at chat.html in GitHub to see an example of how to create a WebSocket:
var webSocket = new WebSocket('ws://localhost:8080/websockets-demo/mysocket');
webSocket.onopen = function(message) { ... }
webSocket.onclose = function(message) { ... }
webSocket.onmessage = function(message) { ... }
webSocket.onerror = function(message) { ... }
Note that like most HTML5 features, not all browsers support WebSockets. It is supported by most modern browsers (IE10+): http://caniuse.com/websockets
Introducing Java WebSockets
Java Enterprise Edition 7 introduced JSR 356, the Java API for WebSockets. The API supports an annotation-driven and interface-driven approach. The interface-driven approach requires that you inherit an interface and implement the provided methods. This article will focus on the annotation-driven approach, which does not rely on polymorphism, instead using annotations to declare which Java methods to use for the WebSocket’s lifecycle.
Look at ChatSocket.java in GitHub to see a Java WebSocket implementation. Note the use of annotations (@ServerEndpoint, @OnOpen, @OnClose, @OnMessage, @OnError) to declaratively define the behavior. Let’s focus on the @OnMessage behavior:
@OnMessage
public void handleMessage(String message, Session session) {
log(String.format("Received message from %s: %s", user, message));
if (message.startsWith("login@")) {
// each client stores its username in this.user (stateful socket!)
this.user = message.substring("login@".length());
} else {
for (Session s : session.getOpenSessions()) {
if (s.isOpen()) {
try {
s.getBasicRemote().sendObject(user + '@' + message);
} catch (Exception e) {
log("Error sending message to session: " + e.getMessage());
e.printStackTrace();
}
}
}
}
}
Note that the ChatSocket class has a private field ‘user’. The first message sent by the client contains a username. In a real chat client, we’d probably want some sort of authentication, but this is good enough for demonstration purposes. This username is assigned to ChatSocket.user, leveraging the fact that Java WebSockets are stateful. Each instance of ChatSocket represents a connection.
Deploy the application to a EE7-compliant server (eg, Glassfish 4.0). Follow these instructions to configure Glassfish 4.0 in Eclipse.
Once the server is running. open the following pages in different tabs:
- http://localhost:8080/websockets-demo/chat.html?user=Ethan
- http://localhost:8080/websockets-demo/chat.html?user=Jane
- http://localhost:8080/websockets-demo/chat.html?user=John
Enter a message in one tab and you can see that message in all 3 tabs.
Java WebSocket Client API
With the addition of 2 libraries, any Java application (eg, standalone desktop app) can create a WebSocket connection to a server. Here are those 2 libraries:
- https://tyrus.java.net/dependencies.html
- http://mvnrepository.com/artifact/javax.websocket/javax.websocket-api
Look at ChatClient.java in GitHub for an example of how to use this libraries to open the WebSocket connection. This demo runs as a command line application with the username as a command line argument. ChatClient sends a ‘Hello’ message and then 10 ‘Ping’ messages (1 per second). It writes all messages received to System.out.
Let’s see it in action. You still have those chat.html tabs open, right? Now execute ChatClient with the argument ‘Lilah’ and you’ll see Lilah’s messages appear in all 3 chat.html tabs. While it’s running, send some messages from any of the chat.html tabs and you will see them in the ChatClient console.