この記事は
HTML5 Advent Calendarの8日目です。
いつの間にか手元の環境(Chrome17 dev + Node 0.6.3)においてWebSocketでバイナリデータが扱える様になっていたので何か作ってみようかと。
まず、NodeでWebSocketを使おうとしたらそれ用のライブラリを使う事になるのだが現時点でバイナリデータが扱えるのは
WebSocket-Nodeのみだった。
当初はサーバーから画像のRAWデータをガンガンクライアントに送りまくるというのを作ろうとしたのだが
node-pngがNodeの0.6系に対応していなかったので断念。CanvasのgetImageDataで得られるデータをそのままサーバーに投げて、サーバー側で加工した物を受け取るサンプルを作った。
バイナリデータの送信
クライアントのコード(client.js)
var socket = null;
function bootstrap() {
var c = document.getElementById('mycanvas');
var ctx = c.getContext('2d');
socket = new WebSocket('ws://localhost:8082');
socket.binaryType = 'arraybuffer';
socket.onopen = function() {
send(ctx);
}
socket.onmessage = handleReceive;
};
function send(ctx) {
var data = ctx.getImageData(0, 0, 200, 200).data;
var byteArray = new Uint8Array(data);
socket.send(byteArray.buffer);
}
CanvasのgetImageDataで得られるデータは8bit4チャンネルのRGBAの数値配列。1ピクセルあたり要素は4つ、それぞれが0-255の値なので、これをそのままUint8Arrayのコンストラクタに渡した。見なれないのは次の行ですね。
socket.binaryType = 'arraybuffer';
サーバーでRAWデータを加工
サーバーのコード(server.js)
#!/usr/bin/env node
var WebSocketServer = require('websocket').server,
http = require('http'),
fs = require('fs');
wsServer = new WebSocketServer({
httpServer: server,
maxReceivedFrameSize: 0x1000000,
autoAcceptConnections: false
});
wsServer.on('request', function(request) {
var connection = request.accept(null, request.origin);
connection.on('message', function(message) {
if (message.type === 'utf8') {
}
else if (message.type === 'binary') {
var data = message.binaryData;
var len = data.length;
var buf = new Buffer(len);
for (var i = 0; i < len; i+=4 ) {
var r = data.readUInt8(i);
var g = data.readUInt8(i+1);
var b = data.readUInt8(i+2);
var y = Math.floor((77*r + 28*g + 151*b)/255);
var v = y + (y << 8) + (y << 16) + (0xFF << 24);
buf.writeInt32LE(v, i);
}
connection.sendBytes(buf);
}
});
connection.on('close', function(reasonCode, description) {
});
});
受信したデータを8bitずつ読んで、それぞれRGBAとして処理する。加工したデータはNodeのBufferオブジェクトに値を書き込んで connection.sendBytesメソッドに渡す。writeInt32LEを使ってバッファに書き込んでいるのはビット演算子が使いたかっただけ。writeUInt8を4回やってもいい。
バイナリデータの受信
再びクライアントのコード(client.js)
WebSocketのonmessageイベントハンドラにセットした関数。
function handleReceive(message) {
var c = resultCanvas = document.getElementById('result');
var ctx = c.getContext('2d');
var imageData = ctx.createImageData(200, 200);
var pixels = imageData.data;
var buffer = new Uint8Array(message.data);
for (var i=0; i < pixels.length; i++) {
pixels[i] = buffer[i];
}
ctx.putImageData(imageData, 0, 0);
}
サーバーでやったのと同様に8bitずつ値を取得したかったので、Uint8Arrayを使った。
まとめ
結果は上のスクリーンショットの通り。全てのコードはgistにアップしました。
WebSocket with binary data ― Gist
https://gist.github.com/1447986
とりあえずNodeのBufferオブジェクトとTyped Arrayの使い方を覚えればなんとかなりそう。これからは送信する前にbase64する手間が省けて嬉しいですね。