BETA

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

投稿日:2020-06-29
最終更新:2020-06-29

やりたいこと

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に対して無理やりスライスと結合を行っているので動作保証ができているかは不安が残る.

技術ブログをはじめよう Qrunch(クランチ)は、プログラマの技術アプトプットに特化したブログサービスです
駆け出しエンジニアからエキスパートまで全ての方々のアウトプットを歓迎しております!
or 外部アカウントで 登録 / ログイン する
クランチについてもっと詳しく

この記事が掲載されているブログ

https://banatech.net のクロスサイト

よく一緒に読まれる記事

0件のコメント

ブログ開設 or ログイン してコメントを送ってみよう