WebSockets have been around for a little while now, but haven’t quite reached the point of being mainstream; to some of us old-timers, something like Socket.io seems like the fevered dream of a madman, real-time communication between a web server and a client? What!?!

Fear not, gentle readers. It will all be clear soon.

A Little Background

Java has something of a stodgy reputation nowadays, a language used by large organizations to build applications for their accounting departments. That isn’t entirely fair. Sure, a lot of Java tools can commit sins like SimpleBeanFactoryAwareAspectInstanceFactory, and can make your eyes glaze over while reading esoteric XML config files. But there are also the good parts, like the incredible library support, the highly-tuned virtual machine that runs on almost any operating system, the ability to build real self-contained apps.

When working in Java one of the components I have ended up using often is Jetty, a wonderful embeddable web server that supports pretty much every feature you need, including SSL, WebSockets, custom request handlers, etc. This tutorial shows how to use Jetty to build a WebSockets-based application.

Intro to WebSockets

WebSockets provide a mechanism for full-duplex communication between a web server and client. A client (web browser) can open a socket with a web server. After this, the two can both write data to this socket and respond to messages coming from each other through event handlers registered on each side. Additionally, the server can broadcast messages to all of its connected clients. According to Wikipedia, WebSockets started trickling into browsers early in 2010 and are now available in all of the current major browsers, except for the naggingly resilient Android browser. Chrome on Android works well, however.

A Simple Chatroom

To demonstrate how WebSockets work with Java I’ve put together a simple chatroom application and tossed it up on GitHub. In addition to the source code, I’ve included a build of the application that can be run immediately. Once cloned, just go into the root directory of the checkout and type:

java -jar dist/JavaWebSockets.jar -p [some free port number]

After this you can point your browser (and perhaps even several browsers simultaneously to see the chatroom in action) to:

http://localhost:[some free port number]

Try sending a few messages to see how it works. Notice how the messages appear in each browser window instantaneously without the need to poll. This is one of the many powers of WebSockets.

How It Works: Server

JavaChatroom.java

JavaChatroom is the application class. It is a pretty standard Java application, and most of the work happens in the startServer() method. The code below initializes the ContextHandler for the WebSocket, and sets it to listen for requests coming in to the /chat route.

ContextHandler contextHandler = new ContextHandler();<br />

contextHandler.setContextPath(“/chat”);<br />

contextHandler.setHandler(new WebSocketHandler() {<br />

@Override<br />

public void configure(WebSocketServletFactory factory) {<br />

factory.setCreator(new WebSocketCreator() {<br />

@Override<br />

public Object createWebSocket(UpgradeRequest req, UpgradeResponse resp) {<br />

String query = req.getRequestURI().toString();<br />

if ((query == null) || (query.length() <= 0)) {<br />

try {<br />

resp.sendForbidden(“Unspecified query”);<br />

} catch (IOException e) {</p>

<p> }<br />

return null;<br />

}<br />

return new ChatroomSocket();<br />

}<br />

});<br />

}<br />

});

1 ContextHandler contextHandler = new ContextHandler (); 2 contextHandler . setContextPath ( "/chat" ); 3 contextHandler . setHandler ( new WebSocketHandler () { 4 5 public void configure ( WebSocketServletFactory factory ) { 6 factory . setCreator ( new WebSocketCreator () { 7 8 public Object createWebSocket ( UpgradeRequest req , UpgradeResponse resp ) { 9 String query = req . getRequestURI (). toString (); 10 if (( query == null ) || ( query . length () <= 0 )) { 11 try { 12 resp . sendForbidden ( "Unspecified query" ); 13 } catch ( IOException e ) { 14 15 } 16 return null ; 17 } 18 return new ChatroomSocket (); 19 } 20 }); 21 } 22 });

A little further down, a second handler (a ResourceHandler) is added to the HandlerList which handles requests for the web files stored under /www in the application home directory.

ChatroomSocket.java

ChatroomSocket is the WebSocket itself. It is a subclass of WebSocketAdapter and overrides the various event handlers needed to make the WebSocket function. The interesting bits are below:

public void onWebSocketConnect(Session session) {<br />

System.out.println(“+++ Websocket Connect from ” + session.getRemoteAddress().getAddress());<br />

this.session = session;<br />

JavaChatroom.getChatroom().addParticipant(session);<br />

try {<br />

this.session.getRemote().sendString(JavaChatroom.getChatroom().print(3));<br />

} catch (IOException ex) {<br />

System.out.println(“+++ Websocket Error ” + ex.getMessage());<br />

}<br />

}</p>

<p>public void onWebSocketText(String message) {<br />

if(message != null && !message.equals(“keep-alive”)) {<br />

ChatroomMessage crm = new ChatroomMessage(session.getRemoteAddress().getAddress().toString().substring(1), message);<br />

JavaChatroom.getChatroom().addMessage(crm);<br />

}<br />

}

1 public void onWebSocketConnect ( Session session ) { 2 System . out . println ( "+++ Websocket Connect from " + session . getRemoteAddress (). getAddress ()); 3 this . session = session ; 4 JavaChatroom . getChatroom (). addParticipant ( session ); 5 try { 6 this . session . getRemote (). sendString ( JavaChatroom . getChatroom (). print ( 3 )); 7 } catch ( IOException ex ) { 8 System . out . println ( "+++ Websocket Error " + ex . getMessage ()); 9 } 10 } 11 12 public void onWebSocketText ( String message ) { 13 if ( message != null && ! message . equals ( "keep-alive" )) { 14 ChatroomMessage crm = new ChatroomMessage ( session . getRemoteAddress (). getAddress (). toString (). substring ( 1 ), message ); 15 JavaChatroom . getChatroom (). addMessage ( crm ); 16 } 17 }

The onWebSocketConnect() method saves the Session for this socket to a local variable for later use, and also registers it as a participant with the Chatroom object so that messages from other users can be broadcast over this socket.

The onWebSocketText() method handles incoming text messages from the client, and adds them to the chatroom conversation.

Chatroom.java

The Chatroom class represents the chatroom itself, maintaining the history of the conversation and as well as the list of participants. In this simple implementation, there is a single instance of the Chatroom class, and a reference to it can be obtained from a static method on the JavaChatroom object.

public Chatroom() {<br />

ScheduledExecutorService ses = Executors.newSingleThreadScheduledExecutor();</p>

<p> ses.scheduleAtFixedRate(new Runnable() {<br />

@Override<br />

public void run() {<br />

for (Session participant : JavaChatroom.getChatroom().participants) {<br />

try {<br />

participant.getRemote().sendString(“keep-alive”);<br />

} catch (IOException ex) {<br />

JavaChatroom.getChatroom().participants.remove(participant);<br />

System.out.println(“Websocket Error ” + ex.getMessage());<br />

}<br />

}<br />

}<br />

}, 15, 15, TimeUnit.SECONDS);<br />

}</p>

<p>public void addMessage(ChatroomMessage crm) {<br />

this.messages.add(crm);<br />

Vector<Session> sessionsToRemove = new Vector();<br />

for (Session participant : this.participants) {<br />

if (participant.isOpen()) {<br />

try {<br />

participant.getRemote().sendString(crm.print());<br />

} catch (IOException ex) {<br />

this.participants.remove(participant);<br />

System.out.println(“Websocket Error ” + ex.getMessage());<br />

}<br />

} else {<br />

sessionsToRemove.add(participant);<br />

}<br />

}<br />

for(Session participant : sessionsToRemove) {<br />

this.participants.remove(participant);<br />

}<br />

}<br />



1 public Chatroom () { 2 ScheduledExecutorService ses = Executors . newSingleThreadScheduledExecutor (); 3 4 ses . scheduleAtFixedRate ( new Runnable () { 5 6 public void run () { 7 for ( Session participant : JavaChatroom . getChatroom (). participants ) { 8 try { 9 participant . getRemote (). sendString ( "keep-alive" ); 10 } catch ( IOException ex ) { 11 JavaChatroom . getChatroom (). participants . remove ( participant ); 12 System . out . println ( "Websocket Error " + ex . getMessage ()); 13 } 14 } 15 } 16 }, 15 , 15 , TimeUnit . SECONDS ); 17 } 18 19 public void addMessage ( ChatroomMessage crm ) { 20 this . messages . add ( crm ); 21 Vector < Session > sessionsToRemove = new Vector (); 22 for ( Session participant : this . participants ) { 23 if ( participant . isOpen ()) { 24 try { 25 participant . getRemote (). sendString ( crm . print ()); 26 } catch ( IOException ex ) { 27 this . participants . remove ( participant ); 28 System . out . println ( "Websocket Error " + ex . getMessage ()); 29 } 30 } else { 31 sessionsToRemove . add ( participant ); 32 } 33 } 34 for ( Session participant : sessionsToRemove ) { 35 this . participants . remove ( participant ); 36 } 37 } 38

The constructor starts up a ScheduledExecutorService that messages each participant a keep-alive message every 15 seconds. This ensures that the clients stay connected and continue to receive conversation messages.

The addMessage() method first adds the new message to the conversation history, then iterates over the participant sessions and sends the new message to each in turn. Finally, it removes any participants who are no longer connected.

How It Works: Client

chat.js

$(document).ready(function() {<br />

// connect the socket<br />

var connection = new WebSocket(‘ws://’ + document.location.host + ‘/chat/’);<br />

connection.onopen = function() {<br />

console.log(‘Connection open!’);<br />

}<br />

connection.onmessage = function(msg) {<br />

if (msg.data !== ‘keep-alive’) {<br />

$(“.conversation”).append(msg.data);<br />

$(‘.conversation-window’).scrollTop($(‘.conversation-window’)[0].scrollHeight);<br />

}<br />

}<br />

connection.onclose = function() {<br />

console.log(‘Connection closed!’);<br />

}</p>

<p> // wire up the button<br />

$(‘.send-button’).on(‘click’, function(e) {<br />

e.preventDefault();<br />

if ($(‘.message’).val() != ”) {<br />

try {<br />

connection.send($(‘.message’).val());<br />

$(‘.message’).val(”);<br />

} catch (exception) {<br />

console.log(‘Websocket error: ‘ + exception.message);<br />

}<br />

}<br />

});<br />

$(‘.message’).keypress(function(event) {<br />

if (event.keyCode == ’13’) {<br />

$(‘.send-button’).trigger(‘click’);<br />

}<br />

});<br />

});<br />



1 $ ( document ). ready ( function () { 2 3 var connection = new WebSocket ( 'ws://' + document . location . host + '/chat/' ); 4 connection . onopen = function () { 5 console . log ( 'Connection open!' ); 6 } 7 connection . onmessage = function ( msg ) { 8 if ( msg . data !== 'keep-alive' ) { 9 $ ( ".conversation" ). append ( msg . data ); 10 $ ( '.conversation-window' ). scrollTop ( $ ( '.conversation-window' )[ 0 ]. scrollHeight ); 11 } 12 } 13 connection . onclose = function () { 14 console . log ( 'Connection closed!' ); 15 } 16 17 18 $ ( '.send-button' ). on ( 'click' , function ( e ) { 19 e . preventDefault (); 20 if ( $ ( '.message' ). val () != '' ) { 21 try { 22 connection . send ( $ ( '.message' ). val ()); 23 $ ( '.message' ). val ( '' ); 24 } catch ( exception ) { 25 console . log ( 'Websocket error: ' + exception . message ); 26 } 27 } 28 }); 29 $ ( '.message' ). keypress ( function ( event ) { 30 if ( event . keyCode == '13' ) { 31 $ ( '.send-button' ). trigger ( 'click' ); 32 } 33 }); 34 }); 35

The web application first connects to the server through a new instance of the WebSocket class and registers the event handlers. The onmessage() handler listens for incoming messages from the server and appends them to the conversation, while filtering out the “keep-alive” messages.

Finally, event handles are registered for clicking the Send button or hitting the Enter key. In either case, the message that is typed in the chat text box is sent via the socket to the server and the text box is cleared.

Wrapping Up

There you have it, a simple application demonstrating how to use WebSockets in Java.

There are many other ways in which these same concepts can be extended to create amazing applications. Imagine a client application that is notified in real-time whenever an object on the server side is updated, or a cooperative browser-based game in which each players’ position is relayed to their teammates instantaneously.

WebSockets are an enabling technology, giving us developers capabilities that just didn’t exist before.

Use them wisely!