websocket項目 搭建node專題及常見問題 - CSDN

2021-01-09 CSDN技術社區

項目中使用的場景:

帳戶掃碼登錄,微信掃碼授權,消息實時提醒,配置結果響應,客戶端同步數據。。。
之前項目裡做即時通信都是用的輪循,輪詢的效率低,非常浪費資源,後面好幾個項目都開始用的websocket配合koa和redis來實現,現在整理整理深入了解下整個即時通信實現的過程。

前提:需要安裝的包koa 由 Express 幕後的原班人馬打造,使用起來比Node更優雅簡潔 https://koa.bootcss.com/koa-router 是路由導航,實際應用中可以認為node接口https://www.npmjs.com/package/koa-routerkoa-websocket koa的中間件對websocket的封裝 https://www.npmjs.com/package/koa-websocketredis 我們這裡用到的是redis的訂閱發布功能https://www.npmjs.com/package/redisdotenv 獲取項目文件的環境變量也可以前端在config.js裡寫死 https://www.npmjs.com/package/dotenv一、搭建基礎的socket服務

搭建方法跟node服務差不多,多了koa-websocket中間件

const Koa = require('koa')const router = require('koa-router')()const websockify = require('koa-websocket')const app = websockify(new Koa())router.all('/kapi/socket/init', async ctx => { const { channel } = ctx.query console.log(channel) ctx.websocket.send('message')})app.ws.use(router.routes()).use(router.allowedMethods())app.listen(3131, () => console.log(`socket listening on port ${Config.socket.port}`))

二、連結訂閱redis頻道

在socket的接口基礎上,添加的了redis連結,頻道的訂閱,這裡只展示關鍵的代碼,後面會再做拆分整理

const Koa = require('koa')const router = require('koa-router')()const websockify = require('koa-websocket')const app = websockify(new Koa())const redis = require('redis')const redisConfig = { host: '18.8.1.3', port: '32', password: '1232343', db: 4}router.all('/kapi/socket/init', async ctx => { const { channel } = ctx.query console.log(channel) let client = redis.createClient(redisConfig) client.subscribe(channel) client.on('message', async (channel, message) => { console.log( 'Received subscribe message, channel [%s] message [%s]', channel, message ) await ctx.websocket.send(message) })}) app.ws.use(router.routes()).use(router.allowedMethods())app.listen(3131, () => console.log(`socket listening on port ${Config.socket.port}`))

三、客戶端調用

客戶端調用之前一定別忘了先啟動socket,可以用pm2或者npm運行koa文件,展示的都是關鍵的代碼,至於監聽error 、open、close

import React, { useState, useEffect, useCallback } from 'react'import createSocket from 'src/api/socket' export default ()=>{ const [mess,setmess] = useState('暫無消息') const [channel] = useState(2) const sendSocket = useCallback(() => { wsServer = new WebSocket( `localhost:3131/kapi/socket/init?channel=${channel}` ) wsServer.addEventListener('message', socketMessage) const socketMessage = (event) => { const data = JSON.parse(event.data) if(data.code===200){ console.log(data) setmess(data) wsServer.removeEventListener('message', socketMessage) } } }, [channel]) useEffect(() => { sendSocket() }, [ sendSocket]) return ( <div>{mess}</div> )}

四、拆分整理細化一、將原來的socket服務拆分再細化


1.index.js

const Koa = require('koa')const router = require('koa-router')()const websockify = require('koa-websocket')const app = websockify(new Koa())const Config = require('../../config/const')const socketApiRoutes = require('./route')socketApiRoutes(router)app.ws.use(router.routes()).use(router.allowedMethods())app.listen(Config.socket.port, () => console.log(`socket listening on port ${Config.socket.port}`))

2.redis.js

const redis = require('redis')const dotenv = require('dotenv').config({ path: process.env.NODE_ENV == 'production' ? '.env' : '.env.local'})const ENV = dotenv.parsedconst redisConfig = { host: ENV.REDIS_HOST, port: ENV.REDIS_PORT, password: ENV.REDIS_PASSWORD, db: ENV.REDIS_DB}const createRedisClient = (channel, callback) => { let client = redis.createClient(redisConfig) client.subscribe(channel) client.on('ready', () => { console.log( 'Redis [%s:%s/%s] is connected and ready for subscribe channel [%s] use.', redisConfig.host, redisConfig.port, redisConfig.database, channel ) }) client.on('connect', () => { console.log('Redis connect') }) client.on('message', async (channel, message) => { console.log( 'Received subscribe message, channel [%s] message [%s]', channel, message ) await callback(channel, message) }) client.on('reconnecting', err => { console.log('Redis reconnecting:' + err) }) client.on('error', err => { console.log('Redis Error:' + err) }) client.on('subscribe', (channel, count) => { console.log( 'client subscribed to ' + channel + ',' + count + ' total subscriptions' ) }) client.on('unsubscribe', (channel, count) => { console.log( 'client unsubscribed from' + channel + ', ' + count + ' total subscriptions' ) }) return client}module.exports = createRedisClient

3.route.js

const createRedisClient = require('./redis')const socketApiRoutes = router => { router.all('/kapi/socket/init', async ctx => { const { channel } = ctx.query console.log(channel) createRedisClient(channel, (channel, message) => { console.log(`on message channel: ${channel}`) waitForSocketConnection(ctx.websocket, () => { ctx.websocket.send(message) }) }) function waitForSocketConnection(socket, callback) { setTimeout(() => { if (socket.readyState == 1) { if (callback != null) { callback() } } else { waitForSocketConnection(socket, callback) } }, 1000) } ctx.websocket.on('message', function(res) { }) ctx.websocket.on('close', function() { console.log('ctx websocket close') }) ctx.websocket.body = { status: true } })}module.exports = socketApiRoutes

二、將客戶端socket的調用封裝一下

import { isSupportSocket } from 'src/utils/util'interface ScoketEvent { open?: () => void message?: (event: MessageEvent) => void error?: () => void close?: () => void}const createSocket = (pathname: string, eventOption?: ScoketEvent) => { let wsServer: WebSocket | null = null if (isSupportSocket()) { wsServer = new WebSocket( `${window.location.protocol === 'https:' ? 'wss' : 'ws'}://${ window.location.host }${pathname}` ) let _interval: NodeJS.Timer | null = null let _setTimeout: NodeJS.Timer | null = null if (wsServer) { const socketOpen = () => { if (eventOption && eventOption.open) { eventOption.open() } wsServer && wsServer.send('socket heart check') _interval = setInterval(() => { wsServer && wsServer.send('socket heart check') }, 30 * 1000) } const socketMessage = (event: MessageEvent) => { if (eventOption && eventOption.message) { eventOption.message(event) console.log(event, 'socketMessage') } } const socketError = () => { if (eventOption && eventOption.error) { eventOption.error() } if (_interval) clearInterval(_interval) wsServer = null console.log('websocket error') } const socketClose = () => { if (eventOption && eventOption.close) { eventOption.close() } if (wsServer) { wsServer.removeEventListener('open', socketOpen) wsServer.removeEventListener('message', socketMessage) wsServer.removeEventListener('error', socketError) } if (_interval) clearInterval(_interval) if (_setTimeout) clearTimeout(_setTimeout) } wsServer.addEventListener('open', socketOpen) wsServer.addEventListener('message', socketMessage) wsServer.addEventListener('error', socketError) wsServer.addEventListener('close', socketClose) } } else { console.error('該瀏覽器不支持socket, 請安裝新版本') } return wsServer}export default createSocket

三、客戶端socket調用

在頁面裡調用上面封裝好的socket

import React, { useState, useEffect, useCallback } from 'react'import { RouteConfig } from 'react-router-config'import createSocket from 'src/api/socket'interface IProps extends RouteConfig {}export default (props: IProps) => { const [channel] = useState<number>(2) const [mess,setmess] = useState<string>('暫無消息') const sendSocket = useCallback(() => { const wsServer: WebSocket | null = createSocket( `/kapi/socket/init?channel=${channel}`, { message: socketMessage } ) function socketMessage(event: MessageEvent) { const data = JSON.parse(event.data) console.log(data) setmess(data) wsServer && wsServer.close() } return () => { if (wsServer) { wsServer.removeEventListener('message', socketMessage) wsServer.close() } } }, [channel]) useEffect(() => { sendSocket() }, [sendSocket]) return ( <div>{mess}</div> )}

整理完畢,希望對小夥伴有所幫助。

相關焦點

  • 搭建elk常見問題
    常見問題一無法通過外部ip訪問elasticsearch解決辦法#修改config/elasticsearch.yml下約第55行,修改為當前es地址或0.0.0.0network.host: 0.0.0.0常見問題二
  • pc端的rem布局專題及常見問題 - CSDN
    用來為盒狀模型提供最大的靈活性,任何一個容器都可以指定為flex布局當我們為父盒子設為flex布局以後,子元素的float、clear和vertical-align屬性將失效flex布局又叫伸縮布局、彈性布局、伸縮布局、彈性盒子布局採用flex布局的元素稱為flex容器(flex container)簡稱容器,它的所有子元素自動成為容器成員,稱為(flex item)「項目
  • 如何寫一個vue組件專題及常見問題 - CSDN
    如何使用vue寫一個組件庫新建vue項目使用vue-cli初始化一個項目:vue init webpack VueComponentcd VueComponentnpm installnpm run dev以上就新建好了一個vue項目項目目錄首先,定義好目錄,經過觀察大多數的組件庫,基本是這樣的目錄
  • 卡方檢驗結果分析專題及常見問題 - CSDN
    簡單統計之後,得到總數和死亡率:此處需要使用到ggbarstats函數,其參數可謂是非常的多,詳細的參數表我放在另一個博客中了,有興趣的朋友可以去看看(https://blog.csdn.net/m0_45248682/article/details/104086729)。
  • f檢驗 matlab專題及常見問題 - CSDN
    15.71985 15.91986 15.71987 16.71988 15.31989 16.11990 16.2MATLAB實現參考網上多個代碼可得https://www.ilovematlab.cn/thread-246993-1-1.htmlhttps://blog.csdn.net
  • android啟動頁設計專題及常見問題 - CSDN
    轉載請註明出處:http://blog.csdn.net/wangjihuanghun/article/details/63255144啟動頁幾乎成為了每個app的標配,有些商家在啟動頁中增加了開屏廣告以此帶來更多的收入。
  • 卡方檢驗相關性專題及常見問題 - CSDN
    那麼有一個問題:協方差數值大小是否代表了相關程度呢?也就是說如果協方差為 100 是否一定比協方差為 10 的正相關性強呢?假如X1、Y1 和 X2、Y2 分別聯合分布圖,如下所示:考察兩個變量的相關關係,首先得看清楚兩個變量都是什麼類型的,統計分析中常見的變量類型有連續型數值變量,無序分類變量、有序分類變量:連續型數值變量:如銷售額、氣溫、工資收入、考試成績;無序分類變量:如性別男和女,血型種類;有序分類變量:如學歷水平小學、初中、高中
  • element 關鍵字專題及常見問題 - CSDN
    運行結果:運行結果:
  • a標籤 href vue專題及常見問題 - CSDN
    時間: 2019-11-10公司要做一個類似今日頭條的項目,用前後端分離的方式做成HTML5頁面,先在微信中運行,領導說想看看效果怎麼樣.今日頭條頭部的導航是可以滾動和添加類別的,我們這個項目也是一樣.所以在導航這個地方就需要在點擊不同分類的同時,樣式也是跟著變的,我在網上搜索了一下,發現了下面的代碼,簡潔清淅,不過具體網址忘記了
  • 垂直行業雲計算專題及常見問題 - CSDN
    黃: 2015年年底啟動醫療雲項目,在之前也做了一些探索,但真正啟動是在2015年年底。問:那個時候金山醫療雲已經成型、可以接入商用了嗎?黃: IaaS這一層雲平臺的成熟是在2015年,醫療雲用的是一樣的基礎架構,所以應該說在2015年金山醫療雲事業部成立之前就具備做包含醫院備份、科研、實時容災等項目的能力,2015年下半年成立醫療事業部之後,主要發力於PaaS產品與SaaS層面。問:雖然現在上雲的話題有很多落地案例,但大多處在觀望狀態。
  • ELK5.X搭建並收集Nginx日誌
    ELK5.X搭建並收集Nginx日誌一、基礎環境配置及軟體包下載二、安裝Elasticsearch三、安裝logstash四、安裝Kibana五、安裝並配置Nginx收集日誌六、Elasticsearch Head插件安裝ELK Stack 是Elasticsearch、Logstash、Kibana三個開源軟體的組合。
  • 線性回歸假設檢驗專題及常見問題 - CSDN
    第四步 確定模型形式和參數估計第五步 評估模型效果對於回歸問題,常用的模型評估指標有兩個:均方差(MSE):預測值與真實值的平均差距。L指的是上面定義的損失函數。模型陷阱(過擬合與模型幻覺)3.1 模型陷阱概述在現實生活中,搭建模型主要有兩種用途。1. 正向運用:對未知情況做預測。(也就是知道 x1, x2, x3.....對 y 值進行預測)要求準確度很高,得到的 y 越接近真實值越好。易受到過度擬合幹擾。2. 反向運用:解釋數據之間的聯動效應。
  • Linked List 常見問題總結
    項目地址:https://github.com/Dragon1573/Daily-Problem閒扯結束。今天不推題,給大家整理下 Linked List 相關基礎的問題,保證大家看過癮。== 0) { return null; } return current; } get(index) { const node = this.getNode(index); return node ?
  • 方差檢驗專題及常見問題 - CSDN
    5 問題總結無
  • bootstrap 有統計圖專題及常見問題 - CSDN
    這種模式還常見於評分系統的分數分布。
  • 擬合優度專題及常見問題 - CSDN
    即:回到擬合優度的問題上,我們就容易理解,當所有樣本點都落在回歸線上時,回歸方程的擬合優度一定是最高的。此時,y的SST只包含SSR部分,沒有SSE。由此可知,在y的SST中,如果SSR所佔比例遠大於SSE所佔比例,換句話說,就是回歸方程如果能夠解釋的變差所佔比例較大,那麼,這個回歸方程的擬合優度就高。
  • f p 線性回歸專題及常見問題 - CSDN
    對於一個樣本\(x_i\),它的輸出值是其特徵的線性組合:\[\begin{equation}f(x_i) = \sum_{m=1}^{p}w_m x_{im}+w_0={w}^T{x_i}\end{equation}\]線性回歸的目標是用預測結果儘可能地擬合目標label,用最常見的Least square作為loss function:\[\begin{equation}
  • python 顯著性水平專題及常見問題 - CSDN
    R2 = RSS/TSSprint(R2)'''0.987979715684'''T-Distribution統計測驗表明塔的傾斜程度與年份有關係,一個常見的統計顯著性測試是student t-test。這個測試的基礎是T分布,和正態分布很相似,都是鍾型但是峰值較低。
  • 【CTO講堂】淺析工業級物聯網項目的快速開發
    博客:http://blog.csdn.net/yfiot、 微博:http://weibo.com/yfsoft公司簡介:北京葉帆易通科技有限公司(簡稱:葉帆科技)是一家軟硬結合的高科技公司。三、支持組態化開發,少編程甚至免編程就可以快速完成物聯網項目的搭建。主持人:請您談談物聯網和網際網路+的異同?劉洪峰:簡單的說:網際網路+,是通過網際網路把人和設備建立了直接的關聯,人可以方便的遠程操控各種智能設備。物聯網,我一直認為是工控網的外延,是有數據採集,具備自動控制能力系統。
  • api 微博數據專題及常見問題 - CSDN
    2018.4.16 說明注意:今天有人言語惡劣地評論我的博客是垃圾,說我的代碼有問題,這篇博客歷史久遠,是我初玩爬蟲寫的博客。我非常感謝能對我的代碼提出意見的人,但是出言不遜,態度惡劣的人我是忍受不了的,有話好好說,是一個現代社會高學歷高知識分子的最低覺悟。代碼我已經改過了,如果還有問題非常歡迎大家來溫和地指出!!!!