Link to Part I of the Article: http://ruminationsontechnology.blogspot.com.es/2014/10/websockets-and-real-time-web-part-i.html
Link to Part III of the Article: http://ruminationsontechnology.blogspot.com/2014/10/websockets-and-real-time-web-part-iii.html
The Server Side
After a little bit of history to support the motivation behind the creation of WebSockets, let us move forward and experiment with a pseudo-real-world code example.
For a change, I will not be presenting the "Hello, world!" echo app or the proverbial chat example, no disregard for those classics, they are very important steps in the journey to learn WebSockets, but there are simply too many incarnations of those projects on any imaginable supporting platform and language (human or otherwise).
240.000 results for the search of "websockets chat example" (your mileage may vary.)
Instead, our little project for the day will be a parking lot occupancy monitor. In this example we will create a WebSocket-based service that reports, on real-time, the space availability of all the public parking lots owned by the fictional company A-Lotta-Parking.
A-Lotta-Parking Usage Monitor in action
As you may already be familiar with, the WebSocket client and server implementations, when writing the client part in JavaScript, the same implementation can be used with all the supporting clients. On the server side, however, there are a few libraries implementing JSR-356, but since Oracle included java native WebSocket support starting with Java EE 7, and as Tomcat 8 already supports that implementation, it is the one we will be using as our web server in this example.
I am going to focus on the WebSocket related classes in this example, so you may want to download the complete project from Github, for Eclipse Luna or NetBeans 8.0.1. The Github projects also come with some hopefully helpful comments.
Without further ado, let the coding begin!
WebSocket: The Server Side
The application we are going to develop is a simple monitor for a number of parking lots that belong to the same company, the goal is to monitor, in real-time, the used capacity of each of the parking lots.
The basic building block of the application is a small java class that contains the information related to one of the parking venues, we will call it ParkingLotRec. This class contains the name, the total capacity and the used capacity of the parking lot.
A list of objects of this class, wrapped in an instance of ParkingLotsUpdate is the payload that will be transmitted to the client monitoring the operation.
A list of objects of this class, wrapped in an instance of ParkingLotsUpdate is the payload that will be transmitted to the client monitoring the operation.
Listing 1: ParkingLotRec.java
package com.pmarquezh.alottaparking.parkinglot; public class ParkingLotRec { private String plName; private int plCapacity; private int plUsedCapacity; public ParkingLotRec ( String plName, int plCapacity, int plUsedCapacity ) { this.plName = plName; this.plCapacity = plCapacity; this.plUsedCapacity = plUsedCapacity; } public String getPlName() { return plName; } public void setPlName(String plName) { this.plName = plName; } public int getPlCapacity() { return plCapacity; } public void setPlCapacity(int plCapacity) { this.plCapacity = plCapacity; } public int getPlUsedCapacity() { return plUsedCapacity; } public void setPlUsedCapacity(int plUsedCapacity) { this.plUsedCapacity = plUsedCapacity; } }
At the heart of any Java WebSocket application is the Server Endpoint, and now that we know the type of information that we want to push over the connection, let's see how and when it will flow.
As you may already know, Java Server Endpoints can be implemented by subclassing javax.websocket.Endpoint or by annotating an otherwise ordinary POJO. Since the annotated approach is a bit simpler, we will implement ours that way.
Listing 2: ParkingLotMonitorEndpoint.java
package com.pmarquezh.alottaparking.parkinglot; import java.io.IOException; import javax.websocket.CloseReason; import javax.websocket.EncodeException; import javax.websocket.EndpointConfig; import javax.websocket.OnClose; import javax.websocket.OnError; import javax.websocket.OnMessage; import javax.websocket.OnOpen; import javax.websocket.Session; import javax.websocket.server.ServerEndpoint; @ServerEndpoint ( value = "/parkingLotUpdates", encoders = { MonitorUpdateEncoder.class } ) public class ParkingLotMonitorEndpoint implements ParkingLotsDataSourceListener { private Session session; private ParkingLotsDataSource dataSource; @OnOpen public void openConnection ( Session session, EndpointConfig config ) { this.session = session; this.dataSource = new ParkingLotsDataSource ( ); this.dataSource.addParkingLotDataSourceListener ( this ); } @OnMessage public void handleCommand ( String command ) { System.out.println ( "@OnMessage: " + command ); switch ( command ) { case "startMonitor": System.out.println ( "command: " + command ); this.dataSource.start ( ); break; case "stopMonitor": System.out.println ( "command: " + command ); this.dataSource.stop ( ); break; default: System.out.println ( "command: " + command ); throw new RuntimeException ( "Command " + command + " not found." ); } } @OnError public void handleError ( Throwable t ) { System.out.println ( "Error: " + t.getMessage ( ) ); } @OnClose public void closeConnection ( CloseReason reason ) { dataSource.stop ( ); } public void handleCommandResponse ( String response ) { try { session.getBasicRemote ( ).sendText ( response ); } catch ( IOException ioe ) { this.handleError ( ioe ); } } @Override public void handleParkingLotUsageData ( ParkingLotsUpdate plu ) { try { session.getBasicRemote ( ).sendObject ( plu ); } catch ( IOException | EncodeException ioe ) { this.handleError ( ioe ); } } }
Noteworthy
@ServerEndpoint ( value = "/parkingLotUpdates", encoders = { MonitorUpdateEncoder.class } )At the top of the class, we find the familiar @ServerEndpoint annotation with a value of /parkingLotUpdates, the name under which the service will be published, but we also see a less common encoders parameter with a value of: MonitorUpdateEncoder.class.
Encoders and Decoders provide support for converting between WebSocket messages and custom Java types. In our case, the MonitorUpdateEncoder class has the noble job of transforming ParkingLotsUpdate objects (a list of ParkingLotRec objects) generated by our monitor engine into a JSON representation, suitable for consumption by the client.
Listing 3: MonitorUpdateEncoder.java
package com.pmarquezh.alottaparking.parkinglot; import javax.json.Json; import javax.json.JsonArrayBuilder; import javax.json.JsonObject; import javax.websocket.*; /** * This Encoder transforms the list of update objects into a JSON representation. * * I used Java's own JSONObject library for this task. Included from Java EE7 and on. */ public class MonitorUpdateEncoder implements Encoder.TextOur Encoder implementation uses the javax.json.Json classes that were also released with Java EE 7 but are not bundled with Tomcat 8, so I included them in the Gitbub projects (got the.jar from the mvn repository).{ @Override public void init ( EndpointConfig config ) { } @Override public void destroy ( ) { } @Override public String encode ( ParkingLotsUpdate plu ) { JsonArrayBuilder ab = Json.createArrayBuilder ( ); for ( ParkingLotRec r : plu.getLots ( ) ) { ab.add ( Json.createObjectBuilder ( ) .add ( "plName", r.getPlName ( ) ) .add ( "plCapacity", r.getPlCapacity ( ) ) .add ( "plUsedCapacity", r.getPlUsedCapacity ( ) ) ); } JsonObject parkingInfo = Json.createObjectBuilder ( ) .add ( "lotsMonitor", ab ).build ( ); return parkingInfo.toString ( ); } }
Noteworthy
public class ParkingLotMonitorEndpoint implements ParkingLotsDataSourceListener {
Our endpoint class implements ParkingLotsDataSourceListener, which is an interface that defines a single method: handleParkingLotUsageData, the method to invoke when there is new data to be pushed to the client.
For our purpose, new usage data is produced by a thread running in the ParkingLotsDataSource Class and, when ready, it is pushed to all the registered listeners in the form of an instance of ParkingLotsUpdate.
Listing 4: ParkingLotsDataSource.java (Relevant methods)
// Create a new thread to simulate incoming data from the different parking lots. public void start ( ) { if ( this.updateThread == null ) { shouldUpdate = true; updateThread = new Thread ( ) { @Override public void run ( ) { while ( shouldUpdate ) { doUpdate ( ); try { Thread.sleep ( generateRandomParkingLotUpdatePeriod ( ) ); } catch ( InterruptedException ie ) { System.out.println ( "updateThread.run ( ): " + ie.getMessage ( ) ); shouldUpdate = false; } } } }; updateThread.start ( ); } } // Update the parking lot usage. Invoked from "updateThread" public void doUpdate ( ) { this.updateUsage ( ); this.notifyListeners ( parkingLotsUsage ); } // Broadcast the new data to registered listeners private void notifyListeners ( Listlots ) { for ( ParkingLotsDataSourceListener l : this.listeners ) { ParkingLotsUpdate plu = new ParkingLotsUpdate ( lots ); l.handleParkingLotUsageData ( plu ); } } // Notify the simulation thread that we are closing shop. public void stop ( ) { this.updateThread.interrupt ( ); }
As you can see, the start method creates a thread called updateThread, which in turn calls doUpdate () repeatedly at randomly spaced intervals (just to spice up a bit the simulation.)
doUpdate is responsible for gathering the new data and then posting it to all registered listeners via notifyListeners ().
To wrap up the server side implementation, when our listener (ParkingLotMonitorEndpoint) receives the new data (an instance of ParkingLotsUpdate) , it kindly pushes it to the registered clients through the WebSocket connection, but not before encoding it into a JSON representation that can be happily consumed at the other endpoint (remember the MonitorUpdateEncoder viewed a bit earlier).
And so this is, my dear reader, one of the ways to implement a server side WebSocket based monitor, which will diligently push new data continuously to the clients until the connection is explicitly closed (or a nasty network error occurs.)
For the visual magic part, the client side implementation, please go on to the third part of this article, where we will be working with JSON, jQuery, HTML5 and one of the coolest JavaScript charting libraries I know: ZingChart.
What would you have done differently?
Sunrise at the Santa Cruz Sea Port, Tenerife
Photo Credit: Paulo Márquez Herrero
PM/pm
No comments:
Post a Comment