Thursday Nov 29, 2012

Collaborative Whiteboard using WebSocket in GlassFish 4 - Text/JSON and Binary/ArrayBuffer Data Transfer (TOTD #189)


This blog has published a few blogs on using JSR 356 Reference Implementation (Tyrus) as its integrated in GlassFish 4 promoted builds.
  • TOTD #183: Getting Started with WebSocket in GlassFish
  • TOTD #184: Logging WebSocket Frames using Chrome Developer Tools, Net-internals and Wireshark
  • TOTD #185: Processing Text and Binary (Blob, ArrayBuffer, ArrayBufferView) Payload in WebSocket
  • TOTD #186: Custom Text and Binary Payloads using WebSocket
One of the typical usecase for WebSocket is online collaborative games. This Tip Of The Day (TOTD) explains a sample that can be used to build such games easily.

The application is a collaborative whiteboard where different shapes can be drawn in multiple colors. The shapes drawn on one browser are automatically drawn on all other peer browsers that are connected to the same endpoint. The shape, color, and coordinates of the image are transfered using a JSON structure. A browser may opt-out of sharing the figures. Alternatively any browser can send a snapshot of their existing whiteboard to all other browsers. Take a look at this video to understand how the application work and the underlying code.



The Java API for WebSocket has evolved since this sample was first created. Here is the source code corresponding to different builds of GlassFish:
GlassFish4 4 Build Source Code
66 here
74 here
76 here
78 here
84 here
The code in the application is explained below.

The web page (index.jsp) has a HTML5 Canvas as shown:
<canvas id="myCanvas" width="150" height="150" style="border:1px solid #000000;"></canvas>
And some radio buttons to choose the color and shape. By default, the shape, color, and coordinates of any figure drawn on the canvas are put in a JSON structure and sent as a message to the WebSocket endpoint. The JSON structure looks like:
{
"shape": "square",
"color": "#FF0000",
"coords": {
"x": 31.59999942779541,
"y": 49.91999053955078
}
}
The endpoint definition looks like:
@WebSocketEndpoint(value = "websocket",
encoders = {FigureDecoderEncoder.class},
decoders = {FigureDecoderEncoder.class})
public class Whiteboard {

As you can see, the endpoint has decoder and encoder registered that decodes JSON to a Figure (a POJO class) and vice versa respectively. The decode method looks like:
public Figure decode(String string) throws DecodeException {
try {
JSONObject jsonObject = new JSONObject(string);
return new Figure(jsonObject);
} catch (JSONException ex) {
throw new DecodeException("Error parsing JSON", ex.getMessage(), ex.fillInStackTrace());
}
}
And the encode method looks like:
public String encode(Figure figure) throws EncodeException {
return figure.getJson().toString();
}
FigureDecoderEncoder implements both decoder and encoder functionality but thats purely for convenience. But the recommended design pattern is to keep them in separate classes. In certain cases, you may even need only one of them.

On the client-side, the Canvas is initialized as:
var canvas = document.getElementById("myCanvas");
var context = canvas.getContext("2d");
canvas.addEventListener("click", defineImage, false);

The defineImage method constructs the JSON structure as shown above and sends it to the endpoint using websocket.send().

An instant snapshot of the canvas is sent using binary transfer with WebSocket. The WebSocket is initialized as:
var wsUri = "ws://localhost:8080/whiteboard/websocket";
var websocket = new WebSocket(wsUri);
websocket.binaryType = "arraybuffer";

The important part is to set the binaryType property of WebSocket to arraybuffer. This ensures that any binary transfers using WebSocket are done using ArrayBuffer as the default type seem to be blob. The actual binary data transfer is done using the following:

var image = context.getImageData(0, 0, canvas.width, canvas.height);
var buffer = new ArrayBuffer(image.data.length);
var bytes = new Uint8Array(buffer);
for (var i=0; i<bytes.length; i++) {
bytes[i] = image.data[i];
}
websocket.send(bytes);

This comprehensive sample shows the following features of JSR 356 API:
  • Annotation-driven endpoints
  • Send/receive text and binary payload in WebSocket
  • Encoders/decoders for custom text payload

In addition, it also shows how images can be captured and drawn using HTML5 Canvas in a JSP.

How could this be turned in to an online game ? Imagine drawing a Tic-tac-toe board on the canvas with two players playing and others watching. Then you can build access rights and controls within the application itself. Instead of sending a snapshot of the canvas on demand, a new peer joining the game could be automatically transferred the current state as well. Do you want to build this game ?

I built a similar game a few years ago. Do somebody want to rewrite the game using WebSocket APIs ? :-)

Many thanks to Jitu and Akshay for helping through the WebSocket internals!

Here are some references for you:
Subsequent blogs will discuss the following topics (not necessary in that order) ...
  • Error handling
  • Interface-driven WebSocket endpoint
  • Java client API
  • Client and Server configuration
  • Security
  • Subprotocols
  • Extensions
  • Other topics from the API
About

profile image
Arun Gupta is a technology enthusiast, a passionate runner, author, and a community guy who works for Oracle Corp.


Java EE 7 Samples

Stay Connected

Search

Archives
« November 2012 »
SunMonTueWedThuFriSat
    
2
3
5
6
8
9
10
12
13
14
16
17
18
19
20
22
23
24
25
28
30
 
       
Today