クライアントからサーバーにファイルを転送するためにSocket.ioで簡単なバイナリ転送を行いました。うまくいったと思ったのですが、ファイルのサイズが違うことに気付きました。writableStream.write が失敗した場合、drain イベント ハンドラを追加して、書き換え可能になるまで待機し、書き込みを続行しましたが、drain イベントが発生するたびに、drain イベントが発生した回数だけファイルのサイズが増加し、10240 バイトごとにサイズが増加します。チャンク送信ごとに設定します。
ここでコードを記述する前に、コード フローを説明する必要があります。
- クライアント要求アップロード ファイル
- サーバーは空のファイルを作成し(書き込み可能なストリームを作成)、送信を許可します
- クライアント転送データ(チャンク) 終了まで
- サーバーは書き込み可能なストリームでチャンクを書き込みます
- クライアントはすべて送信された時点で送信を終了します
- サーバーは書き込み可能なストリームを閉じます。
- 終わり!
これはサーバー側のコードです:
var writeStream = null;
var fileSize = 0;
var wrote = 0;
socket.on('clientRequestFileTransfer', (fileInfo) => {
console.log(`Client request file transfer: ${fileInfo.name}(${fileInfo.size})`);
fileSize = fileInfo.size;
wrote = 0;
writeStream = fs.createWriteStream(__dirname + '/' + fileInfo.name);
writeStream.on('close', () => {
console.log('Write stream ended.');
});
console.log('File created.');
socket.emit('serverGrantFileTransfer');
});
socket.on('clientSentChunk', (chunk) => {
function write() {
let writeDone = writeStream.write(chunk);
if(!writeDone) {
console.log('Back pressure!');
return writeStream.once('drain', write);
}
else {
wrote += chunk.length;
console.log(`Wrote chunks: ${chunk.length} / ${wrote} / ${fileSize}`);
socket.emit('serverRequestContinue');
}
}
write();
});
socket.on('clientFinishTransmission', () => {
writeStream.end();
console.log('Transmission complete!');
});
そしてそれはクライアントです(バイナリファイルを読み取るためのコードを追加):
var fileEl = document.getElementById('file');
fileEl.onchange = function() {
var file = fileEl.files[0];
if(!file) return;
var socket = io('http://localhost:3000');
socket.on('connect', function() {
var fileReader = new FileReader();
fileReader.onloadend = function() {
var bin = fileReader.result;
var chunkSize = 10240;
var sent = 0;
// make server knows the name and size of the file
socket.once('serverGrantFileTransfer', () => {
function beginTransfer() {
if(sent >= bin.byteLength) {
console.log('Transmission complete!');
socket.emit('clientFinishTransmission');
return;
}
var chunk = bin.slice(sent, sent + chunkSize);
socket.once('serverRequestContinue', beginTransfer);
socket.emit('clientSentChunk', chunk);
sent += chunk.byteLength;
console.log('Sent: ' + sent);
}
beginTransfer();
});
socket.emit('clientRequestFileTransfer', {
name: file.name,
size: file.size
});
};
fileReader.readAsArrayBuffer(file);
});
};
このコードを 4,162,611 バイト サイズのファイルでテストしたところ、1 回の書き込み失敗 (1 回のバック プレッシャ) がありました。アップロード後、作成したファイルのサイズを確認したところ、元のファイルより10240バイト大きい4,172,851バイトで、chunk(10240)のサイズでした。
サイズが元のサイズよりも 20480 バイト大きく、送信したチャンクのサイズが 2 倍になるため、書き込みが 2 回失敗することがあります。
Backpressure コードを再確認しましたが、問題はないようです。Node v6.2.2 と Socket.io v1.6.0 を使用しており、Chrome ブラウザーからテストされています。私が見逃したものはありますか?それとも背圧について誤解していましたか?どんなアドバイスでも大歓迎です。
アップデート
バックプレッシャーが発生すると、同じデータが2回書き込まれたようです(コメントで述べたように)。そこで、次のようにコードを修正しました。
socket.on('clientSentChunk', (chunk) => {
function write() {
var writeDone = writeStream.write(chunk);
wrote += chunk.length;
if(!writeDone) {
console.log('**************** Back pressure ****************');
// writeStream.once('drain', write);
// no rewrite, just continue transmission
writeStream.once('drain', () => socket.emit('serverRequestContinue'));
}
else {
console.log(`Wrote chunks: ${chunk.length} / ${wrote} / ${fileSize}`);
socket.emit('serverRequestContinue');
}
}
write();
});
出来た。書き込み可能なストリームが書き込みに失敗すると、ストリームにデータが書き込まれませんが、実際には書き込まれないため、かなり混乱しています。誰もこれについて知っていますか?