Node.js: неправильно написано лишних байтов при обратном давлении в доступном для записи потоке
Я сделал простую двоичную передачу с Socket.io для передачи файла на сервер от клиента. Я думал, что это работает, но я понял, что размер файла был другим. При сбое writableStream.write я прикрепил обработчик события стока, чтобы подождать, пока он не сможет переписать и продолжить запись, но каждый раз, когда происходит событие стока, размер файла увеличивается во время запуска события стока, каждый размер которого составляет 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 обратное давление). После загрузки я проверил размер созданного файла, и он составил 4 172 851 байт, что на 10240 байт больше исходного, и это размер фрагмента (10240).
Иногда запись завершается неудачно в 2 раза, тогда как размер на 20480 байт превышает исходный, что вдвое больше размера отправленного мной фрагмента.
Я дважды проверил свой код противодавления, но мне кажется, что это не так. Я использую Node v6.2.2 и Socket.io v1.6.0, протестировано в браузере Chrome. Есть что-то, что я пропустил? Или я неправильно понял обратное давление? Любой совет будет очень признателен.
ОБНОВИТЬ
Похоже, когда происходит обратное давление, он записывает одни и те же данные дважды (как я сказал в комментарии). Поэтому я изменил код следующим образом:
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();
});
Это сработало. Я очень запутался, потому что, когда записываемый поток не может записать, он не записывает данные в поток, но на самом деле это не так. Кто-нибудь знает об этом?
1 ответ
Я думаю, проблема в том, как вы делаете объект бен в var bin = ...;
, Не могли бы вы так код здесь?
Обновить
Вот код бэкэнда:
var express = require('express');
var app = express();
var server = app.listen(80);
var io = require('socket.io');
var fs = require('fs');
io = io.listen(server);
app.use(express.static(__dirname + '/public'));
app.use(function(req, res, next) {
//res.send({a:1})
res.sendFile(__dirname + '/socket_test_index.html');
});
io.on('connection', function(client) {
console.log('Client connected...', client);
client.on('join', function(data) {
//console.log(data);
});
setInterval(()=>{
client.emit('news', 'news from server');
}, 10000)
});
io.of('/upload', function(client){
console.log('upload')
logic(client);
})
function logic(socket) {
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() {
var 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!');
});
socket.emit('serverGrantFileTransfer', {});
}
Вот HTML-код:
<script src="/socket.io/socket.io.js"></script>
<script>
var socket = io('http://localhost');
socket.on('news', function (data) {
console.log(data);
});
socket.on('connect', function(data) {
socket.emit('join', 'Hello World from client');
});
</script>
<input type="file" id="file" />
<script>
var fileEl = document.getElementById('file');
fileEl.onchange = function() {
var file = fileEl.files[0];
if(!file) return;
var socket = io('http://localhost/upload');
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);
});
};
</script>
Скопируйте их в корневой каталог в экспресс-проекте. Я протестировал с двумя картинками и одним файлом.pdf. Все они передаются одинаковыми байтами