Node配合WebSocket做多文件下载以及进度回传

起因

为什么做这个东西,是突然间听一后端同事说起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的进度条回传是不会出现问题的。

最后总结

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

原文地址:https://www.cnblogs.com/rynxiao/p/11811353.html

时间: 2024-11-01 13:25:42

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

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

Android提供了一个工具类:AsyncTask,它使创建需要与用户界面交互的长时间运行的任务变得更简单.相对Handler来说AsyncTask更轻量级一些,适用于简单的异步处理,不需要借助线程和Handter即可实现.AsyncTask是抽象类.AsyncTask定义了三种泛型类型Params,Progress和Result: Params启动任务执行的输入参数,比如,HTTP请求的URL. Progress后台任务执行的百分比. Result后台执行任务最终返回的结果,比如String.

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

Goeasy, 它是一款第三方推送服务平台,使用它的API可以轻松搞定实时推送!个人感觉goeasy推送更稳定,推送速度快,代码简单易懂上手快浏览器兼容性:GoEasy推送支持websocket 和polling两种连接方式,从而可以支持IE6及其以上的所有版本,同时还支持其它浏览器诸如Firefox, Chrome, Safari 等等.支 持不同的开发语言:   GoEasy推送提供了Restful API接口,无论你的后台程序用的是哪种语言都可以通过RestfulAPI来实现后台实时推送.

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

最近在做一个扫码登录功能,为此我还在网上搜了一下关于微信的扫描登录的实现方式.当这个功能完成了后,我决定将整个实现思路整理出来,方便自己以后查看也方便其他有类似需求的程序猿些. 要实现扫码登录我们需要解决两个问题: 1.  在没有输入用户名及密码的情况下,如何解决权限安全问题?换句话讲,如何让服务器知道扫码二维码的客户端是一个合法的用户? 2.  服务器根据用户在客户端的选择如何实时在网页上作出相应的响应? 首先我们先理一下微信的实现思路,来方便我们理解解决这一难题的思路方向.微信登录的二维码实

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

ChatRoom Node.js+websocket+mongodb实现即时聊天室 A,nodejs简介:Node.js是一个可以让javascript运行在服务器端的平台,它可以让javascript脱离浏览器的束缚运行在一般的服务器下面,你可以用Node.js轻松地进行服务器端应用的开发.Node.js是一个为实时Web应用开发而诞生的平台,它充分考虑了在实时响应和超大规模数据下架构的可扩展性,这使得它摒弃了传统的平台依靠多线程来实现高并发的的设计思路,而采用了单线程,异步式I/O和事件驱动

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

复习 1.黏包现象 粘包现象的成因: tcp协议的特点,面向流的,为了保证可靠传输,所以有很多优化的机制. 无边界 所有在连接建立的基础上传递的数据之间没有界限. 收发消息很有可能不完全相等. 缓存机制,导致没发过去的消息会在发送端缓存,没接收完的消息会在接收端缓存. 解决: 给应用层定制协议 解决方案一:只发送一条信息 先发送一个定长表示待发送数据长度的bytes,先接收一个固定长度 再发送要发送的数据,再按照长度接收数据. 解决方案二 :发送的多条信息 先发送一个定长表示待发送字典长度的by

Node.js配合WebSocket实现网页斗地主(一、环境配置)

最近闲着没事,研究了一下Node.js技术.发现Node.js真的很强大,它配合socket.io竟然可以实现Web的socket通讯技术.所以我决定来做个Web版的斗地主来好好玩玩这个WebSocket技术,呵呵!!! 先来部署环境 1.安装Node.js 根据自己的操作系统,去Node.js官网下载安装即可.如果成功安装.在命令行输入node -v和npm -v应该能看到相应的版本号.下面是我的版本信息 1 node -v 2 v4.0.0 3 npm -v 4 2.14.2 2.搭建Web

Node.js Websocket 区分不同的用户

实现ws://serverIP:port/:param1/:param2 .通过param1,param2来管理不同的ws回话,以便实现群发和指定用户的消息推送 npm install ws --save node server.js server.js var webSocketServer = new (require('ws')).Server({port: (process.env.PORT || 5000)}), webSockets = {} // userID: webSocket

借助node实战WebSocket

一.WebSocket概述 WebSocket协议,是建立在TCP协议上的,而非HTTP协议. 如下: ws://127.0.0.1或wss://127.0.0.1就是WebSocket请求. 注:ws表示WebSocket协议,wss表示加密的WebSocket协议. WebSocket的好处就是允许服务器和客服端进行实时地互相通信,而不像Ajax那样,只能由客服端发起请求,并且WebSocket不受同源策略限制,这恰恰是Ajax的软肋. 好了,初步了解WebSocket后,我们就一起一步步探

Android文件下载之进度检测

近期因为项目的需要,研究了一下Android文件下载进度显示的功能实现,接下来就和大家一起分享学习一下,希望对广大初学者有帮助. 先上效果图: 上方的蓝色进度条,会根据文件下载量的百分比进行加载,中部的文本控件用来现在文件下载的百分比,最下方的ImageView用来展示下载好的文件,项目的目的就是动态向用户展示文件的下载量. 下面看代码实现:首先是布局文件: <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/