メモ置き場

メモ置き場です.開発したものや調べたことについて書きます.

[tex: ]

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になることに注意する.