Websocketでバイナリデータを送る
この記事とこの記事でWebsocketを実現する方法を書いた.
参考にしたコードはこちら.
このサンプルコードでは,lws_write
関数の第3引数にLWS_WRITE_TEXTが指定されていることからわかるように,Websocketから送られてくるデータはテキストデータとなっている.
Zynqでリアルタイムなモニタを構築する場合,データはバイナリデータとして送ったほうが効率が良い.libwebsocketsでバイナリデータを送るための方法についてまとめる.また記事の後半で,JavaScriptからバイナリデータを受信する方法について述べる.
libwebsocketsでバイナリデータを送る
libwebsocketsでバイナリデータを送るには,lws_write
の第3引数にLWS_WRITE_BINARYを指定すればよい.しかし,libwebsocketでは送信するデータは全てunsigned charの配列に格納されている必要がある.したがって,送りたいデータ全てをunsigned char型にキャストする必要がある.
ここでは32bit整数の列とテキストデータをバイナリデータに変換して送る.サンプルコードではclient.cで生成したデータをserver.cで受信してWebブラウザに送信する処理を行なっている.データ生成の大元となっているclient.cを編集する.
example-protocos
関数内のLWS_CALLBACK_CLIENT_WRITEABLEスコープでデータ送信の処理を行なっている.送信するデータを長さ16の32bit配列に変更する.送信するデータ長は32bit×16 = 512bit = 64Byteなので,送信バッファー長を変更する.
送信データをunsinged char型の配列に埋め込む作業を行う.32bit整数配列から直接unsigned charにキャストするとうまく行かない.まずuint8_t型の配列にコピーしてからunsigned char型へ詰め込む必要があった.もし直接unsigned charに書き込んでうまく行った人がいれば教えてください.
配列の値をコピーするときにはmemcpy
を使う.以下にコードの例を示す.
const int DATA_LENGTH = 16; const int INT_SIZE = DATA_LENGTH * 4; const int STR_LENGTH = 16; const int RX_SIZE = INT_SIZE + STR_LENGTH; // 32bit配列 int data[DATA_LENGTH]; // 文字列 char text[STR_LENGTH] = "sample text"; // 8bit 整数配列 uint8_t buf8[RX_SIZE]; // 送信用unsigned char配列. unsigned char buf[LWS_SEND_BUFFER_PRE_PADDING + RX_SIZE + LWS_SEND_BUFFER_POST_PADDING]; memset(but8, 0, RX_SIZE); memset(&buf[LWS_SEND_BUFFER_PRE_PADDING], 0, RX_SIZE); for(int i=0;i<16;i++){ data[i] = i; } // まずデータを8bit整数列にコピー memcpy(&buf8[0], (uint8_t*)&data[0], INT_SIZE); memcpy(&buf8[INT_SIZE], (uint8_t*)text, STR_SIZE); // 送信用バッファーにコピー memcpy(&buf[LWS_SEND_BUFFER_PRE_PADDING], buf8, RX_SIZE); // Websocketで送信 // LWS_WRITE_BINARYを指定する lws_write(wsi, &buf[LWS_SEND_BUFFER_PRE_PADDING], RX_SIZE, LWS_WRITE_BINARY);
今回送信するデータ長は16で固定だが,もし可変長配列を扱う場合は配列の長さをByte単位でカウントするようにしておけば良い.
JavaScriptでバイナリデータを受信する
Websocketでバイナリファイルを受信することを示すために,Websocketインスタンスに対して次の文を追加する.
ws.binaryType = "arraybuffer";
これを書かないとデータが受信できないので注意すること.
Websocket通信でデータが送られてきたとき,onmessageイベントハンドラが実行される.イベントハンドラに設定した無名関数の引数に受信メッセージを取ることができた.
var ws = new Websocket( "ws://" + document.domain + ':' + location.port, "example-protocol" ); ws.onmessage = function(e) { // なんか処理を書く // メッセージのデータにアクセスするにはe.dataとする }
e.data
がバイナリデータになる.
JavaScriptでバイナリを扱うためにUint8ArrayやInt32Arrayというクラスを使う.メッセージで受け取ったデータをこれらのインスタンスに変換し,そこから数値データや文字列を取り出していく.
// バイナリデータの配列から文字列に変換する関数 function stringFromUint8Array(u8){ var ret = ""; for(i=0;i<u8.length;++i){ var x = u8[i]; if(x === 0){ break; } ret += String.fromCharCode(x); } return ret; } ws.onmessage = function(e){ e_data = e.data; // e_dataにはWebsocketからのバイナリデータが入っている. // 最初の64バイトは32bitの整数配列,16バイト分は文字列となる. var h32 = new Int32Array(e_data, 0, 16); var u8 = new Uint8Array(e_data, 16*4, 16); console.log(stringFromUint8Array(h8); console.log(h32); }
Int32ArrayやUint8Arrayコンストラクタの引数はデータバッファ,バイトオフセット,コピーする長さを表す.
長さは要素の個数という意味である.つまりInt32Array(e_data, 0, 16)
では32bit整数を16個分コピーしている.つまり先頭から64Byte分のデータをコピーする.
よって,Uint8Arrayでコピーする際はオフセットが64 Byteになることに注意する.