2020-06-29に投稿

WebSokcetでテキストデータとバイナリデータを同時に送信する方法

やりたいこと

WebsocketではJSONなどのテキストデータ,またはバイナリデータをサーバーとクライアント間で送受信できる.しかしテキストデータとバイナリデータを同時に送ることができず,バイナリデータをクライアントからサーバーに送信し,クライアント側が動的に設定したファイル名でサーバー側にそのバイナリデータを保存することができなかった.

解決法

参考にした記事: ArrayBufferの分割/結合方法 - Qiita, String と ArrayBuffer の相互変換 JavaScript

クライアント側のバイナリデータをArrayBufferとし,そのArrayBufferの先頭にテキスト(ファイル名)をArrayBuffer化したものを結合し,サーバー側でそのArrayBufferをテキストとバイナリデータに分離した.

今回の実装ではクライアント側からmediaRecorder.ondataavailable で発生したBlob型のバイナリをArrayBufferに変換しArrayBuffer化したファイル名と結合してサーバーに送信した.

クライアント側:

function string_to_buffer(src) { // String => ArrayBuffer
  return (new Uint16Array([].map.call(src, function(c) {
    return c.charCodeAt(0)
  }))).buffer;
}
function concatenation(segments) { // ArrayBufferの結合
    var sumLength = 0;
    for(var i = 0; i < segments.length; ++i){
        sumLength += segments[i].byteLength;
    }
    var whole = new Uint8Array(sumLength);
    var pos = 0;
    for(var i = 0; i < segments.length; ++i){
        whole.set(new Uint8Array(segments[i]),pos);
        pos += segments[i].byteLength;
    }
    return whole.buffer;
}

const options = { mimeType: "audio/webm" };
const mediaRecorder = new MediaRecorder(mediaStream, options);
const ws = new WebSocket("ws://localhost:8081");
ws.binaryType = "arraybuffer";
mediaRecorder.ondataavailable = function(e) {
  if (e.data.size > 0) {
    e.data.arrayBuffer().then(audioBuffer => { // Blob => ArrayBuffer 非同期処理
      if (ws.readyState == WebSocket.OPEN) {
        const fileNameBuffer = string_to_buffer("filename");
        const concatArrayBuffer = concatenation([fileNameBuffer, audioBuffer]);
        ws.send(concatArrayBuffer); // サーバーに送信
      }
    });
  }
};
mediaRecorder.start(1000);

サーバー側(Node.js):

function buffer_to_string(buf) { // ArrayBuffer => String
  return String.fromCharCode.apply("", new Uint16Array(buf))
}

const websocketServer = require("ws").Server;
const serverInstance = new websocketServer({ port: 8081 });
const fileNameBufferLength = 16 // ファイル名のArrayBufferの長さは文字数*2
serverInstance.on("connection", function(ws) {
    ws.on("message", function(message) {
        const fileNameBuffer = message.slice(0, fileNameBufferLength);
        const fileName = buffer_to_string(fileNameBuffer).replace(/\0/g, ""); // \u0000 のヌル文字が入ることがあるので取り除く
        const binaryArrayBuffer = message.slice(fileNameBufferLength);
        fs.appendFile(fileName, binaryArrayBuffer, function(err) { // バイナリファイル保存
            if (err) throw err;
        });
    }); 
});

問題点としてはファイル名の長さは一定である必要がある.もしファイル名の長さが一定にできないのであればファイル名ではなく一定の長さのハッシュを今回の方法で送り,別にハッシュとファイル名をセットにしたJSONをクライアントからサーバーに送る必要がある.

また通常の配列とは異なるArrayBufferに対して無理やりスライスと結合を行っているので動作保証ができているかは不安が残る.

Originally published at banatech.net
ツイッターでシェア
みんなに共有、忘れないようにメモ

bana118

https://banatech.net でもやっているブログをうつしてみました

Crieitは個人で開発中です。 興味がある方は是非記事の投稿をお願いします! どんな軽い内容でも嬉しいです。
なぜCrieitを作ろうと思ったか

また、「こんな記事が読みたいけど見つからない!」という方は是非記事投稿リクエストボードへ!

有料記事を販売できるようになりました!

こじんまりと作業ログやメモ、進捗を書き残しておきたい方はボード機能をご利用ください!
ボードとは?

関連記事

コメント