起因

为什么做这个东西,是突然间听一后端同事说起Annie这个东西,发现这个东西下载视频挺方便的,会自动爬取网页中的视频,然后整理成列表。发现用命令执行之后是下面的样子:

心里琢磨了下,整一个界面玩一下吧。然后就做成下面这个样子了。

列表

下载列表

本文地址仓库:https://github.com/Rynxiao/yh-tools,如果喜欢,欢迎star.

涉及技术

  • Express 后端服务
  • Webpack 模块化编译工具
  • Nginx 主要做文件gzip压缩(发现Express添加gzip有点问题,才弃坑nginx)
  • Ant-design 前端UI库
  • React + React Router
  • WebSocket 进度回传服务

其中还有点小插曲,最开始是使用docker起了一个nginx服务,但是发现内部转发一直有问题,同时获取宿主主机IP也出现了点问题,然后折磨了好久放弃了。(docker研究不深,敬请谅解_

下载部分细节

首先浏览器会连接WebSocket服务器,同时在WebSocket服务器上存在一个所有客户端的Map,浏览器端生成一个uuid作为浏览器客户端id,然后将这个链接作为值存进Map中。

客户端:

// list.jsx
await WebSocketClient.connect((event) => {
const data = JSON.parse(event.data);
if (data.event === 'close') {
this.updateCloseStatusOfProgressBar(list, data);
} else {
this.generateProgressBarList(list, data);
}
}); // src/utils/websocket.client.js
async connect(onmessage, onerror) {
const socket = this.getSocket();
return new Promise((resolve) => {
// ...
});
} getSocket() {
if (!this.socket) {
this.socket = new WebSocket(
`ws://localhost:${CONFIG.PORT}?from=client&id=${clientId}`,
'echo-protocol',
);
}
return this.socket;
}

服务端:

// public/javascript/websocket/websocket.server.js
connectToServer(httpServer) {
initWsServer(httpServer);
wsServer.on('request', (request) => {
// uri: ws://localhost:8888?from=client&id=xxxx-xxxx-xxxx-xxxx
logger.info('[ws server] request');
const connection = request.accept('echo-protocol', request.origin);
const queryStrings = querystring.parse(request.resource.replace(/(^\/|\?)/g, '')); // 每有连接连到websocket服务器,就将当前连接保存到map中
setConnectionToMap(connection, queryStrings);
connection.on('message', onMessage);
connection.on('close', (reasonCode, description) => {
logger.info(`[ws server] connection closed ${reasonCode} ${description}`);
});
}); wsServer.on('close', (connection, reason, description) => {
logger.info('[ws server] some connection disconnect.');
logger.info(reason, description);
});
}

然后在浏览器端点击下载的时候,会传递两个主要的字段resourceId(在代码中由parentIdchildId组成)和客户端生成的bClientId。这两个id有什么用呢?

  • 每次点击下载,都会在Web服务器中生成一个WebSocket的客户端,那么这个resouceId就是作为在服务器中生成的WebSocket服务器的key值。
  • bClientId主要是为了区分浏览器的客户端,因为考虑到同时可能会有多个浏览器接入,这样在WebSocket服务器中产生消息的时候,就可以用这个id来区分应该发送给哪个浏览器客户端

客户端:

// list.jsx
http.get(
'download',
{
code,
filename,
parent_id: row.id,
child_id: childId,
download_url: url,
client_id: clientId,
},
); // routes/api.js
router.get('/download', async (req, res) => {
const { code, filename } = req.query;
const url = req.query.download_url;
const clientId = req.query.client_id;
const parentId = req.query.parent_id;
const childId = req.query.child_id;
const connectionId = `${parentId}-${childId}`; const params = {
code,
url,
filename,
parent_id: parentId,
child_id: childId,
client_id: clientId,
}; const flag = await AnnieDownloader.download(connectionId, params);
if (flag) {
await res.json({ code: 200 });
} else {
await res.json({ code: 500, msg: 'download error' });
}
}); // public/javascript/annie.js
async download(connectionId, params) {
//...
// 当annie下载时,会进行数据监听,这里会用到节流,防止进度回传太快,websocket服务器无法反应
downloadProcess.stdout.on('data', throttle((chunk) => {
try {
if (!chunk) {
isDownloading = false;
}
// 这里主要做的是解析数据,然后发送进度和速度等信息给websocket服务器
getDownloadInfo(chunk, ws, params);
} catch (e) {
downloadSuccess = false;
WsClient.close(params.client_id, connectionId, 'download error');
this.stop(connectionId);
logger.error(`[server annie download] error: ${e}`);
}
}, 500, 300));
}

服务端收到进度以及速度的消息后,回传给客户端,如果进度达到了100%,那么就删除掉存在server中的服务器中起的websocket的客户端,并且发送一个客户端被关闭的通知,通知浏览器已经下载完成。

// public/javascript/websocket/websocket.server.js
function onMessage(message) {
const data = JSON.parse(message.utf8Data);
const id = data.client_id; if (data.event === 'close') {
logger.info('[ws server] close event');
closeConnection(id, data);
} else {
getConnectionAndSendProgressToClient(data, id);
}
} function getConnectionAndSendProgressToClient(data, clientId) {
const browserClient = clientsMap.get(clientId);
// logger.info(`[ws server] send ${JSON.stringify(data)} to client ${clientId}`); if (browserClient) {
const serverClientId = `${data.parent_id}-${data.child_id}`;
const serverClient = clientsMap.get(serverClientId); // 发送从web服务器中传过来的进度、速度给浏览器
browserClient.send(JSON.stringify(data));
// 如果进度已经达到了100%
if (data.progress >= 100) {
logger.info(`[ws server] file has been download successfully, progress is ${data.progress}`);
logger.info(`[ws server] server client ${serverClientId} ready to disconnect`);
// 从clientsMap将当前的这个由web服务器创建的websocket客户端移除
// 然后关闭当前连接
// 同时发送下载完成的消息给浏览器
clientsMap.delete(serverClientId);
serverClient.send(JSON.stringify({ connectionId: serverClientId, event: 'complete' }));
serverClient.close('download completed');
}
}
}

整体来说就这么多,有一点需要指出,annie在解析的时候有时候可能消息处理不是很稳定,导致我数据解析的时候出现了一些问题,但是我用mock的数据以及mock的进度条回传是不会出现问题的。

最后总结

多读书,多看报,少吃零食,多睡觉

Node配合WebSocket做多文件下载以及进度回传的更多相关文章

  1. Android中使用AsyncTask实现文件下载以及进度更新提示

    Android提供了一个工具类:AsyncTask,它使创建需要与用户界面交互的长时间运行的任务变得更简单.相对Handler来说AsyncTask更轻量级一些,适用于简单的异步处理,不需要借助线程和 ...

  2. node.js Websocket实现扫码二维码登录---GoEasy

    最近在做一个扫码登录功能,为此我还在网上搜了一下关于微信的扫描登录的实现方式.当这个功能完成了后,我决定将整个实现思路整理出来,方便自己以后查看也方便其他有类似需求的程序猿些. 要实现扫码登录我们需要 ...

  3. node.js Websocket消息推送---GoEasy

    Goeasy, 它是一款第三方推送服务平台,使用它的API可以轻松搞定实时推送!个人感觉goeasy推送更稳定,推送 速度快,代码简单易懂上手快 浏览器兼容性:GoEasy推送 支持websocket ...

  4. python 全栈开发,Day36(作业讲解(大文件下载以及进度条展示),socket的更多方法介绍,验证客户端链接的合法性hmac,socketserver)

     先来回顾一下昨天的内容 黏包现象粘包现象的成因 : tcp协议的特点 面向流的 为了保证可靠传输 所以有很多优化的机制 无边界 所有在连接建立的基础上传递的数据之间没有界限 收发消息很有可能不完全相 ...

  5. Node.js+websocket+mongodb实现即时聊天室

    ChatRoom Node.js+websocket+mongodb实现即时聊天室 A,nodejs简介:Node.js是一个可以让javascript运行在服务器端的平台,它可以让javascrip ...

  6. 基于Node.js + WebSocket 的简易聊天室

    代码地址如下:http://www.demodashi.com/demo/13282.html Node.js聊天室运行说明 Node.js的本质就是运行在服务端的JavaScript.Node.js ...

  7. 036_python的大文件下载以及进度条展示

    复习 1.黏包现象 粘包现象的成因: tcp协议的特点,面向流的,为了保证可靠传输,所以有很多优化的机制. 无边界 所有在连接建立的基础上传递的数据之间没有界限. 收发消息很有可能不完全相等. 缓存机 ...

  8. Android文件下载之进度检测

    近期因为项目的需要,研究了一下Android文件下载进度显示的功能实现,接下来就和大家一起分享学习一下,希望对广大初学者有帮助. 先上效果图: 上方的蓝色进度条,会根据文件下载量的百分比进行加载,中部 ...

  9. 借助node实战WebSocket

    一.WebSocket概述 WebSocket协议,是建立在TCP协议上的,而非HTTP协议. 如下: ws://127.0.0.1或wss://127.0.0.1就是WebSocket请求. 注:w ...

随机推荐

  1. 基 B/S 平台的机房监控云平台-U位篇

    前言 机柜 U 位管理是一项突破性创新技术--继承了 RFID 标签(电子标签)的优点的同时,完全解决了 RFID 技术(非接触式的自动识别技术)在机房 U 位资产监控场应用景中的四大缺陷,采用工业互 ...

  2. windows下python和pycharm安装及其使用

    1.python安装及环境变量配置 1.1 python安装 1.1.1 python下载 官网下载:https://www.python.org/ Downloads-Windows(Mac os ...

  3. 移动端适配 rem 设置

    refresh();    window.onresize = function(){      setTimeout(function(){        refresh();      },10) ...

  4. docker 更新后出现 error during connect

    docker更新后出现 error during connect: Get http://%2F%2F.%2Fpipe%2Fdocker_engine/v1.39/containers/json: o ...

  5. (7)Cmake的使用简介

        CMake是一个跨平台的安装(编译)工具,是一个比Make更高级的的编译配置工具,可以根据不同平台.不同编译器,通过编写CmakeLists,可以控制生成的Makefile,从而控制编译过程. ...

  6. redis安装及简单使用

    前言 一般企业级开发,数据库用的都是关系型数据库Mysql.Oracle及SqlServer.无一例外,在开发过程中,我们都必须通过数据库驱动来连接到数据库,之后才可以完成对数据库的增删改查等业务.而 ...

  7. 2019头条java面试总结 (包含面试题解析)

    2019滴滴java面试总结  (包含面试题) 本人8年开发经验.今年年初找工作,在互联网寒冬下成功拿到阿里巴巴.今日头条.滴滴等公司offer,岗位是Java后端开发. 面试了很多家公司,感觉大部分 ...

  8. 使用Xming显示Oracle Linux图形界面

    如果你在尝试各种官方说明文档中的方法之后,xclock仍然无法远程显示. 系统 Win10 - Oracle Linux 7.5 Xming的文档以及网上教程都说的是Xming相关的配置 但是,要显示 ...

  9. 爬虫4:pdf页面+pdfminer模块+demo

    本文介绍下pdf页面的爬取,需要借助pdfminer模块 demo一般流程: 1)设置url url = 'http://www.------' + '.PDF' 2)requests模块获取url ...

  10. 机器学习:LibSVM与weka在eclipse中的使用

    LibSVM是weka3.5以后的版本新加的功能,使用这个算法必须自己下载jar包,配置进项目: LibSVM在weka可视化界面的使用,很多人写过,但在clipse下的调用资料却不多,试了很多都不能 ...