Skip to content

Commit

Permalink
Use WebSocket binary data (no base64 enc/dec) if available.
Browse files Browse the repository at this point in the history
If typed arrays (arraybuffers) are available and the WebSocket
implementation supports them, then send and receive direct binary
frames and skip base64 encode/decode. Otherwise we just fallback to
the current method of sending base64 encoded strings (with a couple of
extra checks for mode in the send/receive path).

The check for binaryType support in WebSocket is a collosal hack right
now due to the fact that the 'binaryType' property doesn't exist on
the WebSocket prototype. So we have to create a connection to
a localhost port in order to test.

A potentionally big performance boost could probably be achieved by
re-using a larger typed array for storing the data instead of creating
a typed array every time we receive a message.
  • Loading branch information
kanaka committed Aug 14, 2012
1 parent f55362f commit 376872d
Showing 1 changed file with 86 additions and 21 deletions.
107 changes: 86 additions & 21 deletions include/websock.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ function Websock() {

var api = {}, // Public API
websocket = null, // WebSocket object
protocols, // Protocols to request in priority order
mode = 'base64',
rQ = [], // Receive queue
rQi = 0, // Receive queue index
rQmax = 10000, // Max receive queue size before compacting
Expand Down Expand Up @@ -92,7 +94,7 @@ function get_rQi() {
}
function set_rQi(val) {
rQi = val;
};
}

function rQlen() {
return rQ.length - rQi;
Expand Down Expand Up @@ -123,26 +125,46 @@ function rQshift32() {
(rQ[rQi++] << 8) +
(rQ[rQi++] );
}
function rQslice(start, end) {
if (mode === 'binary') {
if (end) {
return rQ.subarray(rQi + start, rQi + end);
} else {
return rQ.subarray(rQi + start);
}
} else {
if (end) {
return rQ.slice(rQi + start, rQi + end);
} else {
return rQ.slice(rQi + start);
}
}
}

function rQshiftStr(len) {
if (typeof(len) === 'undefined') { len = rQlen(); }
var arr = rQ.slice(rQi, rQi + len);
var arr = rQslice(0, len);
rQi += len;
return arr.map(function (num) {
return String.fromCharCode(num); } ).join('');

return String.fromCharCode.apply(null, arr);
}
function rQshiftBytes(len) {
if (typeof(len) === 'undefined') { len = rQlen(); }
rQi += len;
return rQ.slice(rQi-len, rQi);
}

function rQslice(start, end) {
if (end) {
return rQ.slice(rQi + start, rQi + end);
var a = rQslice(0, len), b = [];
if (mode === 'binary') {
// Convert to plain array
b.push.apply(b, a);
} else {
return rQ.slice(rQi + start);
// Already plain array, just return the original
b = a
}
rQi += len;
return b;
}
function rQshiftArray(len) {
if (typeof(len) === 'undefined') { len = rQlen(); }
var a = rQslice(0, len);
rQi += len;
return a;
}

// Check to see if we must wait for 'num' bytes (default to FBU.bytes)
Expand Down Expand Up @@ -170,13 +192,26 @@ function rQwait(msg, num, goback) {

function encode_message() {
/* base64 encode */
return Base64.encode(sQ);
if (mode === 'binary') {
return (new Uint8Array(sQ)).buffer;
} else {
return Base64.encode(sQ);
}
}

function decode_message(data) {
//Util.Debug(">> decode_message: " + data);
/* base64 decode */
rQ = rQ.concat(Base64.decode(data, 0));
if (mode === 'binary') {
// Create new arraybuffer and dump old and new data into it
// TODO: this could be far more efficient and re-use the array
var new_rQ = new Uint8Array(rQ.length + data.byteLength);
new_rQ.set(rQ);
new_rQ.set(new Uint8Array(data), rQ.length);
rQ = new_rQ;
} else {
/* base64 decode and concat to the end */
rQ = rQ.concat(Base64.decode(data, 0));
}
//Util.Debug(">> decode_message, rQ: " + rQ);
}

Expand Down Expand Up @@ -230,7 +265,7 @@ function recv_message(e) {
// Compact the receive queue
if (rQ.length > rQmax) {
//Util.Debug("Compacting receive queue");
rQ = rQ.slice(rQi);
rQ = rQslice(rQi);
rQi = 0;
}
} else {
Expand Down Expand Up @@ -263,7 +298,32 @@ function init() {
rQ = [];
rQi = 0;
sQ = [];
websocket = null;
websocket = null,
protocols = "base64";

var bt = false,
wsbt = false;

if (('Uint8Array' in window) &&
('set' in Uint8Array.prototype)) {
bt = true;
}
// TODO: this sucks, the property should exist on the prototype
// but it does not.
try {
if (bt && ('binaryType' in (new WebSocket("ws://localhost:17523")))) {
wsbt = true;
}
} catch (exc) {
// Just ignore failed test localhost connections
}
if (bt && wsbt) {
Util.Info("Detected binaryType support in WebSockets");
protocols = ['binary', 'base64'];
} else {
Util.Info("No binaryType support in WebSockets, using base64 encoding");
protocols = 'base64';
}
}

function open(uri) {
Expand All @@ -272,16 +332,21 @@ function open(uri) {
if (test_mode) {
websocket = {};
} else {
websocket = new WebSocket(uri, 'base64');
// TODO: future native binary support
//websocket = new WebSocket(uri, ['binary', 'base64']);
websocket = new WebSocket(uri, protocols);
}

websocket.onmessage = recv_message;
websocket.onopen = function() {
Util.Debug(">> WebSock.onopen");
if (websocket.protocol) {
mode = websocket.protocol;
Util.Info("Server chose sub-protocol: " + websocket.protocol);
} else {
mode = 'base64';
Util.Error("Server select no sub-protocol!: " + websocket.protocol);
}
if (mode === 'binary') {
websocket.binaryType = 'arraybuffer';
}
eventHandlers.open();
Util.Debug("<< WebSock.onopen");
Expand Down

0 comments on commit 376872d

Please sign in to comment.