A Step-by-Step Tutorial of Building a Simple Peer-to-Peer WebSocket App – Part 7

In Part 1, we looked at the completed application, then reviewed the starting code in Part 2. In Part 3 we created a WebSocket connection and started sending and receiving messages in Part 4. In Part 5 we solved the problem of hearing back the echo of the same messages we were sending to the other clients. In Part 6, we added an image that resizes in all participating browsers the slider is moved.

In this part we address the problem of accumulating messages and potentially degraded performance when the slider is moved too fast.

Task 16: Defining supporting variables

In this task, we will set up a few variables that we’ll need later in this tutorial: the variable, called sending holds the state whether there are messages yet to be sent. The variable, called sliderQueue holds the accumulating messages that are yet to be sent.

[sourcecode language=”javascript” light=”true” gutter=”0″]
var sending = false;
var sliderQueue = [];
[/sourcecode]

Here’s the entire JavaScript file. Look for Task 16 in the comments to find the newly added function.

[sourcecode language=”javascript” collapse=”1″ highlight=”31,32,33,34,35,36″ wraplines=”false”]
// Variables you can change
//
var WEBSOCKET_URL = "wss://demos.kaazing.com/jms";
var TOPIC_NAME = "/topic/myTopic";
var IN_DEBUG_MODE = true;
var DEBUG_TO_SCREEN = true;

/*** Task 9 ***/
// Message Properties and Message Types
var MESSAGE_PROPERTIES = {
"messageType": "MESSAGE_TYPE",
"userId": "USERID",
"sliderPos": "SLIDER_POS"
};

var MESSAGE_TYPES = {
"sliderMoved": "SLIDER_MOVED"
};
/*** Task 9 ***/

/*** Task 10 ***/
var userId = Math.random(100000).toString();
/*** Task 10 ***/

// WebSocket and JMS variables
//
var connection;
var session;
var wsUrl;

// *** Task 16 ***
// Internal variables
//
var sending = false;
var sliderQueue = [];
// *** Task 16 ***

// Variable for log messages
//
var screenMsg = "";

// JSFiddle-specific variables
//
var runningOnJSFiddle = (window.location.hostname === "fiddle.jshell.net");

// Used for development and debugging. All logging can be turned
// off by modifying this function.
//
var consoleLog = function(text) {
if (IN_DEBUG_MODE) {
if (runningOnJSFiddle || DEBUG_TO_SCREEN) {
// Logging to the screen
screenMsg = screenMsg + text + "<br>";
$("#logMsgs").html(screenMsg);
} else {
// Logging to the browser console
console.log(text);
}
}
};

var handleException = function(e) {
consoleLog("EXCEPTION: " + e);
};
// *** Task 6a ***
var handleTopicMessage = function(message) {
// *** Task 12 ***
if (message.getStringProperty(MESSAGE_PROPERTIES.userId) != userId) {
consoleLog("Message received: " + message.getText());
// *** Task 8b ***
$("#slider").val(message.getText());
// *** Task 8b ***
// *** Task 15 ***
$("#pic").width(message.getText());
// *** Task 15 ***
}
// *** Task 12 ***
};
// *** Task 6a ***
// *** Task 2 ***
var sliderChange = function(sliderValue) {
consoleLog("Slider changed: " + sliderValue);
// *** Task 14 ***
$("#pic").width(sliderValue);
// *** Task 14 ***
// *** Task 8a ***
doSend(session.createTextMessage(sliderValue));
// *** Task 8a ***
};
// *** Task 2 ***
// *** Task 7a ***
// Send a message to the topic.
//
var doSend = function(message) {
/*** Task 11 ***/
message.setStringProperty(MESSAGE_PROPERTIES.userId, userId);
/*** Task 11 ***/
topicProducer.send(null, message, DeliveryMode.NON_PERSISTENT, 3, 1, function() {

});
consoleLog("Message sent: " + message.getText());
};
// *** Task 7a ***
// Connecting…
//
var doConnect = function() {
// Connect to JMS, create a session and start it.
//
var jmsConnectionFactory = new JmsConnectionFactory(WEBSOCKET_URL);
try {
var connectionFuture = jmsConnectionFactory.createConnection(”, ”, function() {
if (!connectionFuture.exception) {
try {
connection = connectionFuture.getValue();
connection.setExceptionListener(handleException);

consoleLog("Connected to " + WEBSOCKET_URL);
session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);

// *** Task 3 ***
// Creating topic
var myTopic = session.createTopic(TOPIC_NAME);
consoleLog("Topic created…");
// *** Task 3 ***
// *** Task 4 ***
// Creating topic Producer
topicProducer = session.createProducer(myTopic);
consoleLog("Topic producer created…");
// *** Task 4 ***
// *** Task 5 ***
// Creating topic Consumer
topicConsumer = session.createConsumer(myTopic);
consoleLog("Topic consumer created…");
// *** Task 5 ***
// *** Task 6b ***
topicConsumer.setMessageListener(handleTopicMessage);
// *** Task 6b ***
connection.start(function() {
// Put any callback logic here.
//
consoleLog("JMS session created");
// *** Task 7b ***
doSend(session.createTextMessage("Hello world…"));
// *** Task 7b ***
});
} catch (e) {
handleException(e);
}
} else {
handleException(connectionFuture.exception);
}
});
} catch (e) {
handleException(e);
}
};
[/sourcecode]

Task 17: Dealing with accumulating messages

In case the network cannot keep up with the message rate, you can follow a number of different strategies. Given the nature of our application, we’ll implement a solution in which we throw away all the accumulated slider states, except for the very last one, that we send over. If you move your slider constantly, and your network latency is sufficiently big, you will be able to observe that the image resize actions in the remote browser are somewhat granular or choppy.

In this task we write a function, called sendFromQueue that performs the following logic: If we have messages in the sliderQueue array, then we send the last message from the queue and clear the queue. Otherwise (if there are no messages in the sliderQueue), we switch to “not sending” state.

Here’s the source of our sendFromQueue() function:

[sourcecode language=”javascript” light=”true”]
var sendFromQueue = function() {
if (sliderQueue.length >; 0) {
consoleLog("Sending last element from queue: " + sliderQueue[sliderQueue.length-1]);
var msg = sliderQueue[sliderQueue.length-1];
sliderQueue = [];
doSend(session.createTextMessage(msg));
}
else {
sending = false;
}
};
[/sourcecode]

Here’s the entire JavaScript file. Look for Task 17 in the comments to find the newly added function.

[sourcecode language=”javascript” collapse=”1″ highlight=”105,106,107,108,109,110,111,112,113,114,115,116″ wraplines=”false”]
// Variables you can change
//
var WEBSOCKET_URL = "wss://demos.kaazing.com/jms";
var TOPIC_NAME = "/topic/myTopic";
var IN_DEBUG_MODE = true;
var DEBUG_TO_SCREEN = true;

/*** Task 9 ***/
// Message Properties and Message Types
var MESSAGE_PROPERTIES = {
"messageType": "MESSAGE_TYPE",
"userId": "USERID",
"sliderPos": "SLIDER_POS"
};

var MESSAGE_TYPES = {
"sliderMoved": "SLIDER_MOVED"
};
/*** Task 9 ***/

/*** Task 10 ***/
var userId = Math.random(100000).toString();
/*** Task 10 ***/

// WebSocket and JMS variables
//
var connection;
var session;
var wsUrl;

// *** Task 16 ***
// Internal variables
//
var sending = false;
var sliderQueue = [];
// *** Task 16 ***

// Variable for log messages
//
var screenMsg = "";

// JSFiddle-specific variables
//
var runningOnJSFiddle = (window.location.hostname === "fiddle.jshell.net");

// Used for development and debugging. All logging can be turned
// off by modifying this function.
//
var consoleLog = function(text) {
if (IN_DEBUG_MODE) {
if (runningOnJSFiddle || DEBUG_TO_SCREEN) {
// Logging to the screen
screenMsg = screenMsg + text + "<br>";
$("#logMsgs").html(screenMsg);
} else {
// Logging to the browser console
console.log(text);
}
}
};

var handleException = function(e) {
consoleLog("EXCEPTION: " + e);
};
// *** Task 6a ***
var handleTopicMessage = function(message) {
// *** Task 12 ***
if (message.getStringProperty(MESSAGE_PROPERTIES.userId) != userId) {
consoleLog("Message received: " + message.getText());
// *** Task 8b ***
$("#slider").val(message.getText());
// *** Task 8b ***
// *** Task 15 ***
$("#pic").width(message.getText());
// *** Task 15 ***
}
// *** Task 12 ***
};
// *** Task 6a ***
// *** Task 2 ***
var sliderChange = function(sliderValue) {
consoleLog("Slider changed: " + sliderValue);
// *** Task 14 ***
$("#pic").width(sliderValue);
// *** Task 14 ***
// *** Task 8a ***
doSend(session.createTextMessage(sliderValue));
// *** Task 8a ***
};
// *** Task 2 ***
// *** Task 7a ***
// Send a message to the topic.
//
var doSend = function(message) {
/*** Task 11 ***/
message.setStringProperty(MESSAGE_PROPERTIES.userId, userId);
/*** Task 11 ***/
topicProducer.send(null, message, DeliveryMode.NON_PERSISTENT, 3, 1, function() {

});
consoleLog("Message sent: " + message.getText());
};
// *** Task 7a ***

// *** Task 17 ***
var sendFromQueue = function() {
if (sliderQueue.length > 0) {
consoleLog("Sending last element from queue: " + sliderQueue[sliderQueue.length – 1]);
var msg = sliderQueue[sliderQueue.length – 1];
sliderQueue = [];
doSend(session.createTextMessage(msg));
} else {
sending = false;
}
};
// *** Task 17 ***
// Connecting…
//
var doConnect = function() {
// Connect to JMS, create a session and start it.
//
var jmsConnectionFactory = new JmsConnectionFactory(WEBSOCKET_URL);
try {
var connectionFuture = jmsConnectionFactory.createConnection(”, ”, function() {
if (!connectionFuture.exception) {
try {
connection = connectionFuture.getValue();
connection.setExceptionListener(handleException);

consoleLog("Connected to " + WEBSOCKET_URL);
session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);

// *** Task 3 ***
// Creating topic
var myTopic = session.createTopic(TOPIC_NAME);
consoleLog("Topic created…");
// *** Task 3 ***
// *** Task 4 ***
// Creating topic Producer
topicProducer = session.createProducer(myTopic);
consoleLog("Topic producer created…");
// *** Task 4 ***
// *** Task 5 ***
// Creating topic Consumer
topicConsumer = session.createConsumer(myTopic);
consoleLog("Topic consumer created…");
// *** Task 5 ***
// *** Task 6b ***
topicConsumer.setMessageListener(handleTopicMessage);
// *** Task 6b ***
connection.start(function() {
// Put any callback logic here.
//
consoleLog("JMS session created");
// *** Task 7b ***
doSend(session.createTextMessage("Hello world…"));
// *** Task 7b ***
});
} catch (e) {
handleException(e);
}
} else {
handleException(connectionFuture.exception);
}
});
} catch (e) {
handleException(e);
}
};
[/sourcecode]

Task 18: Adding sendFromQueue() as the callback function of doSend()

Earlier on we wrote a doSend() function that is responsible for sending the messages every time the slider is moved. Back then we left the callback empty. Now that we wrote the sendFromQueue() function, let’s add it in there. This will ensure that when done with sending a message, sendFromQueue() is invoked, allowing us to send the last element from the queue and clear the rest of the messages from it.
We’re adding a single line of code to the doSend() function: sendFromQueue().

[sourcecode language=”javascript” light=”true”]
var doSend = function(message) {
/*** Task 11 ***/
message.setStringProperty(MESSAGE_PROPERTIES.userId, userId);
/*** Task 11 ***/
topicProducer.send(null, message, DeliveryMode.NON_PERSISTENT, 3, 1, function() {
// *** Task 18 ***
sendFromQueue();
// *** Task 18 ***
});
consoleLog("Message sent: " + message.getText());
};
[/sourcecode]

Here’s the entire JavaScript file. Look for Task 18 in the comments to find the newly added function.

[sourcecode language=”javascript” collapse=”1″ highlight=”99,100,101″ wraplines=”false”]
// Variables you can change
//
var WEBSOCKET_URL = "wss://demos.kaazing.com/jms";
var TOPIC_NAME = "/topic/myTopic";
var IN_DEBUG_MODE = true;
var DEBUG_TO_SCREEN = true;

/*** Task 9 ***/
// Message Properties and Message Types
var MESSAGE_PROPERTIES = {
"messageType": "MESSAGE_TYPE",
"userId": "USERID",
"sliderPos": "SLIDER_POS"
};

var MESSAGE_TYPES = {
"sliderMoved": "SLIDER_MOVED"
};
/*** Task 9 ***/

/*** Task 10 ***/
var userId = Math.random(100000).toString();
/*** Task 10 ***/

// WebSocket and JMS variables
//
var connection;
var session;
var wsUrl;

// *** Task 16 ***
// Internal variables
//
var sending = false;
var sliderQueue = [];
// *** Task 16 ***

// Variable for log messages
//
var screenMsg = "";

// JSFiddle-specific variables
//
var runningOnJSFiddle = (window.location.hostname === "fiddle.jshell.net");

// Used for development and debugging. All logging can be turned
// off by modifying this function.
//
var consoleLog = function(text) {
if (IN_DEBUG_MODE) {
if (runningOnJSFiddle || DEBUG_TO_SCREEN) {
// Logging to the screen
screenMsg = screenMsg + text + "<br>";
$("#logMsgs").html(screenMsg);
} else {
// Logging to the browser console
console.log(text);
}
}
};

var handleException = function(e) {
consoleLog("EXCEPTION: " + e);
};
// *** Task 6a ***
var handleTopicMessage = function(message) {
// *** Task 12 ***
if (message.getStringProperty(MESSAGE_PROPERTIES.userId) != userId) {
consoleLog("Message received: " + message.getText());
// *** Task 8b ***
$("#slider").val(message.getText());
// *** Task 8b ***
// *** Task 15 ***
$("#pic").width(message.getText());
// *** Task 15 ***
}
// *** Task 12 ***
};
// *** Task 6a ***
// *** Task 2 ***
var sliderChange = function(sliderValue) {
consoleLog("Slider changed: " + sliderValue);
// *** Task 14 ***
$("#pic").width(sliderValue);
// *** Task 14 ***
// *** Task 8a ***
doSend(session.createTextMessage(sliderValue));
// *** Task 8a ***
};
// *** Task 2 ***
// *** Task 7a ***
// Send a message to the topic.
//
var doSend = function(message) {
/*** Task 11 ***/
message.setStringProperty(MESSAGE_PROPERTIES.userId, userId);
/*** Task 11 ***/
topicProducer.send(null, message, DeliveryMode.NON_PERSISTENT, 3, 1, function() {
// *** Task 18 ***
sendFromQueue();
// *** Task 18 ***
});
consoleLog("Message sent: " + message.getText());
};
// *** Task 7a ***

// *** Task 17 ***
var sendFromQueue = function() {
if (sliderQueue.length > 0) {
consoleLog("Sending last element from queue: " + sliderQueue[sliderQueue.length – 1]);
var msg = sliderQueue[sliderQueue.length – 1];
sliderQueue = [];
doSend(session.createTextMessage(msg));
} else {
sending = false;
}
};
// *** Task 17 ***
// Connecting…
//
var doConnect = function() {
// Connect to JMS, create a session and start it.
//
var jmsConnectionFactory = new JmsConnectionFactory(WEBSOCKET_URL);
try {
var connectionFuture = jmsConnectionFactory.createConnection(”, ”, function() {
if (!connectionFuture.exception) {
try {
connection = connectionFuture.getValue();
connection.setExceptionListener(handleException);

consoleLog("Connected to " + WEBSOCKET_URL);
session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);

// *** Task 3 ***
// Creating topic
var myTopic = session.createTopic(TOPIC_NAME);
consoleLog("Topic created…");
// *** Task 3 ***
// *** Task 4 ***
// Creating topic Producer
topicProducer = session.createProducer(myTopic);
consoleLog("Topic producer created…");
// *** Task 4 ***
// *** Task 5 ***
// Creating topic Consumer
topicConsumer = session.createConsumer(myTopic);
consoleLog("Topic consumer created…");
// *** Task 5 ***
// *** Task 6b ***
topicConsumer.setMessageListener(handleTopicMessage);
// *** Task 6b ***
connection.start(function() {
// Put any callback logic here.
//
consoleLog("JMS session created");
// *** Task 7b ***
doSend(session.createTextMessage("Hello world…"));
// *** Task 7b ***
});
} catch (e) {
handleException(e);
}
} else {
handleException(connectionFuture.exception);
}
});
} catch (e) {
handleException(e);
}
};
[/sourcecode]

Task 19: Incorporating sliderQueue into the message sending logic

In this final task, let’s integrate the sliderQueue with our message sending logic. With this logic, when the slider changes, we’re either in the middle of sending a message or idle. If the app is busy sending a message, the message should be appended to the sliderQueue. If the app is not sending a message, then we should switch to sending mode, and send the message.

[sourcecode language=”javascript” light=”true”]
var sliderChange = function(sliderValue) {
consoleLog("Slider changed: " + sliderValue);
// *** Task 14 ***
$("#pic").width(sliderValue);
// *** Task 14 ***

// *** Task 19 ***
if (!sending) {
sending = true;
// *** Task 8a ***
doSend(session.createTextMessage(sliderValue));
// *** Task 8a ***
}
else {
sliderQueue.push(sliderValue);
consoleLog("Busy sending, pushing to slider queue: " + sliderValue);
}
// *** Task 19 ***
};
[/sourcecode]

Here’s the entire JavaScript file. Look for Task 19 in the comments to find the newly added function.

[sourcecode language=”javascript” collapse=”1″ highlight=”86,87,88,89,90,91,92,93,94,95,96″ wraplines=”false”]
// Variables you can change
//
var WEBSOCKET_URL = "wss://demos.kaazing.com/jms";
var TOPIC_NAME = "/topic/myTopic";
var IN_DEBUG_MODE = true;
var DEBUG_TO_SCREEN = true;

/*** Task 9 ***/
// Message Properties and Message Types
var MESSAGE_PROPERTIES = {
"messageType": "MESSAGE_TYPE",
"userId": "USERID",
"sliderPos": "SLIDER_POS"
};

var MESSAGE_TYPES = {
"sliderMoved": "SLIDER_MOVED"
};
/*** Task 9 ***/

/*** Task 10 ***/
var userId = Math.random(100000).toString();
/*** Task 10 ***/

// WebSocket and JMS variables
//
var connection;
var session;
var wsUrl;

// *** Task 16 ***
// Internal variables
//
var sending = false;
var sliderQueue = [];
// *** Task 16 ***

// Variable for log messages
//
var screenMsg = "";

// JSFiddle-specific variables
//
var runningOnJSFiddle = (window.location.hostname === "fiddle.jshell.net");

// Used for development and debugging. All logging can be turned
// off by modifying this function.
//
var consoleLog = function(text) {
if (IN_DEBUG_MODE) {
if (runningOnJSFiddle || DEBUG_TO_SCREEN) {
// Logging to the screen
screenMsg = screenMsg + text + "<br>";
$("#logMsgs").html(screenMsg);
} else {
// Logging to the browser console
console.log(text);
}
}
};

var handleException = function(e) {
consoleLog("EXCEPTION: " + e);
};
// *** Task 6a ***
var handleTopicMessage = function(message) {
// *** Task 12 ***
if (message.getStringProperty(MESSAGE_PROPERTIES.userId) != userId) {
consoleLog("Message received: " + message.getText());
// *** Task 8b ***
$("#slider").val(message.getText());
// *** Task 8b ***
// *** Task 15 ***
$("#pic").width(message.getText());
// *** Task 15 ***
}
// *** Task 12 ***
};
// *** Task 6a ***
// *** Task 2 ***
var sliderChange = function(sliderValue) {
consoleLog("Slider changed: " + sliderValue);
// *** Task 14 ***
$("#pic").width(sliderValue);
// *** Task 14 ***
// *** Task 19 ***
if (!sending) {
sending = true;
// *** Task 8a ***
doSend(session.createTextMessage(sliderValue));
// *** Task 8a ***
} else {
sliderQueue.push(sliderValue);
consoleLog("Busy sending, pushing to slider queue: " + sliderValue);
}
// *** Task 19 ***
};
// *** Task 2 ***
// *** Task 7a ***
// Send a message to the topic.
//
var doSend = function(message) {
/*** Task 11 ***/
message.setStringProperty(MESSAGE_PROPERTIES.userId, userId);
/*** Task 11 ***/
topicProducer.send(null, message, DeliveryMode.NON_PERSISTENT, 3, 1, function() {
// *** Task 18 ***
sendFromQueue();
// *** Task 18 ***
});
consoleLog("Message sent: " + message.getText());
};
// *** Task 7a ***

// *** Task 17 ***
var sendFromQueue = function() {
if (sliderQueue.length > 0) {
consoleLog("Sending last element from queue: " + sliderQueue[sliderQueue.length – 1]);
var msg = sliderQueue[sliderQueue.length – 1];
sliderQueue = [];
doSend(session.createTextMessage(msg));
} else {
sending = false;
}
};
// *** Task 17 ***
// Connecting…
//
var doConnect = function() {
// Connect to JMS, create a session and start it.
//
var jmsConnectionFactory = new JmsConnectionFactory(WEBSOCKET_URL);
try {
var connectionFuture = jmsConnectionFactory.createConnection(”, ”, function() {
if (!connectionFuture.exception) {
try {
connection = connectionFuture.getValue();
connection.setExceptionListener(handleException);

consoleLog("Connected to " + WEBSOCKET_URL);
session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);

// *** Task 3 ***
// Creating topic
var myTopic = session.createTopic(TOPIC_NAME);
consoleLog("Topic created…");
// *** Task 3 ***
// *** Task 4 ***
// Creating topic Producer
topicProducer = session.createProducer(myTopic);
consoleLog("Topic producer created…");
// *** Task 4 ***
// *** Task 5 ***
// Creating topic Consumer
topicConsumer = session.createConsumer(myTopic);
consoleLog("Topic consumer created…");
// *** Task 5 ***
// *** Task 6b ***
topicConsumer.setMessageListener(handleTopicMessage);
// *** Task 6b ***
connection.start(function() {
// Put any callback logic here.
//
consoleLog("JMS session created");
// *** Task 7b ***
doSend(session.createTextMessage("Hello world…"));
// *** Task 7b ***
});
} catch (e) {
handleException(e);
}
} else {
handleException(connectionFuture.exception);
}
});
} catch (e) {
handleException(e);
}
};
[/sourcecode]

To see and run the updated code in JSFiddle, point your browser to the completed app at the Task 19 state: http://jsfiddle.net/R5qc3/19.

Congratulations! You have completed the Building a Simple Peer-to-Peer WebSocket App tutorial.

WebSocket: The best thing since sliced bread