這篇文章將為大家詳細(xì)講解有關(guān)Node中怎么利用WebSocket實(shí)現(xiàn)多文件下載功能,文章內(nèi)容質(zhì)量較高,因此小編分享給大家做個(gè)參考,希望大家閱讀完這篇文章后對(duì)相關(guān)知識(shí)有一定的了解。
網(wǎng)站的建設(shè)創(chuàng)新互聯(lián)建站專(zhuān)注網(wǎng)站定制,經(jīng)驗(yàn)豐富,不做模板,主營(yíng)網(wǎng)站定制開(kāi)發(fā).小程序定制開(kāi)發(fā),H5頁(yè)面制作!給你煥然一新的設(shè)計(jì)體驗(yàn)!已為公路鉆孔機(jī)等企業(yè)提供專(zhuān)業(yè)服務(wù)。
列表
下載列表
本文地址倉(cāng)庫(kù):https://github.com/Rynxiao/yh-tools,如果喜歡,歡迎star.
涉及技術(shù)
Express 后端服務(wù)
Webpack 模塊化編譯工具
Nginx 主要做文件gzip壓縮(發(fā)現(xiàn)Express添加gzip有點(diǎn)問(wèn)題,才棄坑nginx)
Ant-design 前端UI庫(kù)
React + React Router
WebSocket 進(jìn)度回傳服務(wù)
其中還有點(diǎn)小插曲,最開(kāi)始是使用docker起了一個(gè)nginx服務(wù),但是發(fā)現(xiàn)內(nèi)部轉(zhuǎn)發(fā)一直有問(wèn)題,同時(shí)獲取宿主主機(jī)IP也出現(xiàn)了點(diǎn)問(wèn)題,然后折磨了好久放棄了。(docker研究不深,敬請(qǐng)諒解^_^)
下載部分細(xì)節(jié)
首先瀏覽器會(huì)連接WebSocket服務(wù)器,同時(shí)在WebSocket服務(wù)器上存在一個(gè)所有客戶(hù)端的Map,瀏覽器端生成一個(gè)uuid作為瀏覽器客戶(hù)端id,然后將這個(gè)鏈接作為值存進(jìn)Map中。
客戶(hù)端:
// 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; }
服務(wù)端:
// 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服務(wù)器,就將當(dāng)前連接保存到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); }); }
然后在瀏覽器端點(diǎn)擊下載的時(shí)候,會(huì)傳遞兩個(gè)主要的字段resourceId(在代碼中由parentId和childId組成)和客戶(hù)端生成的bClientId。這兩個(gè)id有什么用呢?
每次點(diǎn)擊下載,都會(huì)在Web服務(wù)器中生成一個(gè)WebSocket的客戶(hù)端,那么這個(gè)resouceId就是作為在服務(wù)器中生成的WebSocket服務(wù)器的key值。
bClientId主要是為了區(qū)分瀏覽器的客戶(hù)端,因?yàn)榭紤]到同時(shí)可能會(huì)有多個(gè)瀏覽器接入,這樣在WebSocket服務(wù)器中產(chǎn)生消息的時(shí)候,就可以用這個(gè)id來(lái)區(qū)分應(yīng)該發(fā)送給哪個(gè)瀏覽器客戶(hù)端
客戶(hù)端:
// 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) { //... // 當(dāng)annie下載時(shí),會(huì)進(jìn)行數(shù)據(jù)監(jiān)聽(tīng),這里會(huì)用到節(jié)流,防止進(jìn)度回傳太快,websocket服務(wù)器無(wú)法反應(yīng) downloadProcess.stdout.on('data', throttle((chunk) => { try { if (!chunk) { isDownloading = false; } // 這里主要做的是解析數(shù)據(jù),然后發(fā)送進(jìn)度和速度等信息給websocket服務(wù)器 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)); }
服務(wù)端收到進(jìn)度以及速度的消息后,回傳給客戶(hù)端,如果進(jìn)度達(dá)到了100%,那么就刪除掉存在server中的服務(wù)器中起的websocket的客戶(hù)端,并且發(fā)送一個(gè)客戶(hù)端被關(guān)閉的通知,通知瀏覽器已經(jīng)下載完成。
// 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); // 發(fā)送從web服務(wù)器中傳過(guò)來(lái)的進(jìn)度、速度給瀏覽器 browserClient.send(JSON.stringify(data)); // 如果進(jìn)度已經(jīng)達(dá)到了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將當(dāng)前的這個(gè)由web服務(wù)器創(chuàng)建的websocket客戶(hù)端移除 // 然后關(guān)閉當(dāng)前連接 // 同時(shí)發(fā)送下載完成的消息給瀏覽器 clientsMap.delete(serverClientId); serverClient.send(JSON.stringify({ connectionId: serverClientId, event: 'complete' })); serverClient.close('download completed'); } } }
關(guān)于Node中怎么利用WebSocket實(shí)現(xiàn)多文件下載功能就分享到這里了,希望以上內(nèi)容可以對(duì)大家有一定的幫助,可以學(xué)到更多知識(shí)。如果覺(jué)得文章不錯(cuò),可以把它分享出去讓更多的人看到。