我的需求:
我的需求可以简单描述为,对一个大文件进行分片切割上传。我实现的思路为,
对一个大文件,按照设定的chunksSize切分为N = file.size/chunkSize块,
然后循环创建N个读流读取每个分片的内容,然后发起N个http.request的Post请求去上传文件。
代码如下
(说明: upload函数用来根据分块的个数n,计算每块起始标志位和终止标识位,并调用senddataPromise函数对每片进行操作)
function upload(username,filepath,file_id,filelength,n,alreadychunks,chunkSize) {
return new Promise(function (resolve,reject) {
var start = 0,end = 0;
var promiseall = [];
for (let curindex = 0;curindex < n;curindex++) {
if(filelength - start <= chunkSize) {
end = filelength - 1;
}else {
end = start+chunkSize - 1; // 因读取时包含start和end位
}
if(alreadychunks.indexOf(curindex) == -1) {
let options = {
flags: 'r',
highWaterMark: chunkSize,
start: start,
end: end
};
promiseall.push(senddataPromise(filepath,options,username,file_id,curindex,end-start+1));
}
start = end + 1;
}
let timer = setInterval(() => {
if(promiseall.length == n) {
clearInterval(timer);
Promise.all(promiseall).then(values=>{
console.log(values);
console.log("all done");
resolve(true)
}).catch(err => {
console.log(err);
reject(err);
})
}
},500)
})
}
senddataPromise函数是对第i块分片创建读流读取内容,并调用doapost函数发送到后端
function senddataPromise(path,options,username,summary,curindex,length) {
return new Promise(function (resolve,reject) {
let readSteam = fs.createReadStream(path,options);
readSteam.on("data",(chunk) => {
console.log("第"+curindex+"块 JSON开始")
let chunkjson = JSON.stringify(chunk);
console.log("第"+curindex+"块 JSON结束")
let tempcell = {
data: chunkjson,
n: curindex,
file_id: summary,
username: username,
length: length
};
chunk = null;
chunkjson = null;
doapost(tempcell).then(values=>{
resolve(values)
}).catch(err=>{
reject(err);
});
})
})
}
doapost函数发起post请求发送分片数据
function doapost(data) {
return new Promise(function (resolve,reject) {
let i = data.n;
console.log("第"+i+"份请求准备发出")
let contents = queryString.stringify(data);
data = null;
let options = {
host: "localhost",
path: "/nodepost/",
port: 8000,
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'Content-Length': contents.length
}
};
let req = http.request(options, function (res) {
console.log("第"+i+"份请求返回数据")
res.on("data", function (chunk) {
console.log(chunk.toString());
});
res.on("end", function (d) {
resolve("end");
});
res.on("error", function (e) {
reject(e);
})
});
req.write(contents);
req.end();
contents = null;
console.log("第"+i+"份请求已发出")
})
}
我的问题:
按照正常的思路,因为读取文件内容为异步操作,后面发送请求也为异步操作,所以
也就是说会出现对于n个分片,读取数据已经读取了p片,并且已经有q(**q < p**)片
已经完成上传完成返回数据的情况,但是现在问题是,***发现并没有分片上传完返回数据的
情况出现,都是在n个分片读取完成后,才开始统一执行分片内容上传操作***
图片如下:(由于图片无法上传,我把程序运输出拷贝一下)
{
kind: 'upload',
username: 'moran999',
filepath: 'F:/my_upload_test/NowTest.pdf',
file_id: '-196987878-472217752177633040957425519',
alreadychunks: [],
chunkSize: 1048576,
n: 9 }
第0块 JSON开始
第0块 JSON结束
第0份请求准备发出
第0份请求已发出
第1块 JSON开始
第1块 JSON结束
第1份请求准备发出
第1份请求已发出
第2块 JSON开始
第2块 JSON结束
第2份请求准备发出
第2份请求已发出
第3块 JSON开始
第3块 JSON结束
第3份请求准备发出
第3份请求已发出
第5块 JSON开始
第5块 JSON结束
第5份请求准备发出
第5份请求已发出
第4块 JSON开始
第4块 JSON结束
第4份请求准备发出
第4份请求已发出
第6块 JSON开始
第6块 JSON结束
第6份请求准备发出
第6份请求已发出
第8块 JSON开始
第8块 JSON结束
第8份请求准备发出
第8份请求已发出
第7块 JSON开始
第7块 JSON结束
第7份请求准备发出
第7份请求已发出
第8份请求返回数据
moran999
第4份请求返回数据
moran999
第6份请求返回数据
moran999
第1份请求返回数据
moran999
第2份请求返回数据
moran999
第0份请求返回数据
moran999
第3份请求返回数据
moran999
第7份请求返回数据
moran999
第5份请求返回数据
moran999
[ 'end', 'end', 'end', 'end', 'end', 'end', 'end', 'end', 'end' ]
all done
可以看到其POST数据的发出并不是和读流无关的,即任何一个POST都不会发出,
直到到所有的读流读取完数据,想问一下各位码友是什么原因尼??因为正常
理解下当第i个读流读的时候,前面已经读取完内容的读流完全可以进行post操作
了啊,但实际上并没有。
之所以会问这个问题是因为当我输入的文件比较大时,他执行到《第12块 JSON开始时,
就内存溢出了》,而如果程序是post不用等待所有的读流读完时,当有一部分post执行完之后,其对应的数据就被回收了,释放相应的内存,就不会出现内存溢出了。
如 @zonxin 所说,代码和你设想的大致相同。
补充为什么第12块就已经溢出
let chunkjson = JSON.stringify(chunk);
把本来1M
的Buffer
转成和数组样式的字符串[104,101,...]
,内存涨了x
倍(就不用说后面还有个queryString.stringify
)。Buffer
除外),而楼主刚好把Buffer
转成string
。pipe
(转成string
也没法用),以至于发送过的字节还保留在内存,直到完整是字符串发送完,而完整的一块缺有却有x*1M
。