• angular + express 实现websocket通信


    最近需要实现一个功能,后端通过TCP协议连接雷达硬件的控制器,前端通过websocket连接后端,当控制器触发消息的时候,把信息通知给所以前端;

    第一个思路是单独写一个后端服务用来实现websocket,调试成功了,后来又发现一个插件express-ws,于是决定改变思路,研究了下,最终代码如下,希望帮助更多的朋友,不再害怕websocket

     首先写一个前端websocket服务。这里我选择放弃单例模式,采用谁调用谁负责销毁的思路

    复制代码
    import { Injectable } from '@angular/core';
    import { Observable } from 'rxjs';
    import { LoginService } from '../login/login.service';
    import { environment } from 'src/environments/environment';
    
    export class WsConnect {
      ws!:WebSocket;
      sendWs!:(msg:string)=>void;
      closeWs!:()=>void;
      result!:Observable
    }
    @Injectable({providedIn:"root"})
    export class WebsocketService {
    
      origin = window.location.origin.replace('http', 'ws');
    
      constructor(
        private loginService: LoginService
      ) { }
    
      getUrl(path:string){
        return `${this.origin}${path}`;
      }
      connect(path:string):WsConnect{
        let url = this.getUrl(path);
        let ws = new WebSocket(url, this.loginService.userInfo.jwt);  // 在这里放入jwt信息,目前没有找到其它地方可以放。有些网友建议先放入地址,然后在nginx里重新放入header,我觉得不够接地气
        return {
          ws,
          sendWs:function(message:string){
            ws.send(message);
          },
          closeWs:function(){
            ws.close();
          },
          result:new Observable(
            observer => {
                ws.onmessage = (event) => { observer.next(event.data)};//接收数据
                ws.onerror =  (event) => {console.log("ws连接错误:",event);observer.error(event)};//发生错误
                ws.onclose =  (event) => {console.log("ws连接断开:",event); observer.complete() };//结束事件
                ws.onopen =  (event) => { console.log("ws连接成功:",event);};//结束事件
            }
          )
        }
      }
    }
    复制代码

    然后在组件里调用

    复制代码
    import { Component, OnDestroy, OnInit } from '@angular/core';

    import { WebsocketService, WsConnect } from '../utils/websocket-client.service'; @Component({ selector:
    'app-car-measure', templateUrl: './car-measure.component.html', styleUrls: ['./car-measure.component.scss'] }) export class CarMeasureComponent implements OnInit , OnDestroy{ connect!:WsConnect; constructor(public wsService:WebsocketService) { } ngOnInit() { this.connectServer(); } connectServer(){ this.connect = this.wsService.connect('/websocket/carMeasure') this.connect.result.subscribe( (data:any) => { //接收到服务端发来的消息 console.log("服务器消息:",data); setTimeout(() => { this.connect.sendWs("这是从客户端发出的消息"); }, 5000); } ) } ngOnDestroy() { this.connect.closeWs(); // 这个方法时把整个ws销毁,而不是取消订阅哦,所以有需要的同学可以考虑取消订阅的方案 } }
    复制代码

    后端引入express-ws,封装一个可调用的文件,部分代码借鉴了网上的代码,做了一些改善

    复制代码
    //websocket.js
    const express = require('express');
    const router = express.Router();
    const expressWs = require('express-ws')
    // 初始化
    let WS = null;
    // 声明一个通道类
    let channels = null;
    let pathList = [
        '/websocket/carMeasure',
        '/path2'
    ]
    function initWebSocket(app) {
        WS = expressWs(app) //混入app, wsServer 存储所有已连接实例
        // 创建通道
        channels = new channel(router)
        pathList.forEach(path=>{
            channels.createChannel(path)
            // channels.createChannel('/carMeasure/websocket/carSize')
        })
        app.use(router)
    }
    // 通道类
    class channel {
        router;
        constructor(props) {
            this.router = props;
        }
        createChannel(path) {
            // 建立通道
            this.router.ws( path, (ws, req) => {
                //把自定义信息加入到socket里面取,expressws会自动放入到从WS.getWss().clients,
                // 并且会自动根据活动用户删除或者增加客户端
                ws['wsPath'] = path;
                ws['userId'] = req.userInfo._id;
                ws['roleId'] = req.userInfo.role;
                ws.on('message', (msg) => getMsg(msg, path))
                ws.on('close', (code) => close(code, path))
                ws.on('error', (e) => error(e, path))
            })
        }
    }
    /**
     * 
     * @param {*} msg 消息内容
     * @param {String} from 消息来源
     */
    // 监听消息
    let getMsg = (msg, from) => {
        console.log(msg, from);
        // SendMsgAll({path:'/path2', data: msg })
    }
    // 发送消息
    let sendMsg = (client, data) => {
        if (!client) return
        client.send(JSON.stringify(data))
    }
    let close = (code) => {
        console.log('关闭连接', code);
    }
    let error = (e) => {
        console.log('error: ', e);
    }
    // 群发
    /**
     * 
     * @param {String} path 需要发送的用户来源 路由,默认全部
     * @param {*} data 发送的数据
     */
    function sendMsgToClients(clients,data){
        clients.forEach((client)=> {
            if (client._readyState == 1) {
                sendMsg(client, data)
            }
        })
    }
    
    function sendMsgToAll(data = "") {
        let allClientsList = Array.from(WS.getWss().clients)
        sendMsgToClients(allClientsList,data)
    }
    
    function sendMsgToPath(data = "", path = '') {
        let allClientsList = Array.from(WS.getWss().clients).filter((ws)=>ws['wsPath'] == path)
        sendMsgToClients(allClientsList,data)
    }
    function sendMsgToId(data = "", userId = '') {
        let allClientsList = Array.from(WS.getWss().clients).filter((ws)=>ws['userId'] == userId)
        sendMsgToClients(allClientsList,data)
    }
    function sendMsgToRole(data = "", roleId = '') {
        let allClientsList = Array.from(WS.getWss().clients).filter((ws)=>ws['roleId'] == roleId)
        sendMsgToClients(allClientsList,data)
    }
    module.exports = {
        initWebSocket,
        sendMsgToAll,
        sendMsgToPath,
        sendMsgToId,
        sendMsgToRole,
    }
    复制代码

    然后再app.js里面调用就可以了

    const {initWebSocket} = require('./public/utils/websocket')
    initWebSocket(app)

    其中涉及到了权限验证的问题,也可以直接验证jwt

    复制代码
    app.use((req,res,next) => {
      if(!whiteList.some(item =>  req.url.startsWith(item))) {
          let httpJwt= req.headers['jwt'];
          let wsJwt= req.headers['sec-websocket-protocol'];  // 这里验证websocket的身份信息,其它代码
          utils.verifyToken(httpJwt || wsJwt).then(res => {   //utils.verifyToken封装了jwt的验证
              req["userInfo"] = res;                        //放入一些信息,方便后续操作
              next()
          }).catch(e => {
              console.error(e);
              res.status(401).send('invalid token')
          })
      } else {
          next()
      }
    })
    复制代码

    万事具备,最后一步就是等待硬件设备的触发了,其它tcp客户端的代码就不放出来干扰大家了,就是粗暴的调用即可

    var {sendMsgToPath} = require('../public/utils/websocket');
    sendMsgToPath(JSON.stringify(result), this.carMeasurePath);   // 注意websocket或者tcp的传输都只能用字符串或者blob

     

    另外注意要配置nginx代理,nginx的配置各位应该都清楚吧,这里就不多说了,注意的是这里有几个可选择的地方,一个是前端,可以把ws服务做成单例,另一个是后端路由其实可以写在http的路由文件里,还有一个是对后端ws client的使用,利用了express-ws自身的方法,当然也可以自己写对象来搜集clients (不太建议)

    想了以下还是放出来给小白,这里是proxy.config.json

    复制代码
    {
        "/api": {
            "target": "http://localhost:3000",
            "secure": false,
            "logLevel": "debug",
            "changeOrigin": true,
            "pathRewrite": {
                "^/api": "/"
            }
        },
        "/websocket":{
          "target": "http://localhost:3000",
          "secure": false,
          "ws": true
        }
    }
    复制代码

    毕竟讲究的是手把手把你教会,不会也得会,这里是放入服务器的nginx.cong

    复制代码
    worker_processes  1;
    
    events {
        worker_connections  1024;
    }
    
    http {
        server {
            listen 80;
            server_name  localhost;
            client_max_body_size 20M;
            underscores_in_headers on;
    
            include /etc/nginx/mime.types;
    
    
            gzip on;
            gzip_static on;
            gzip_min_length 1000;
            gzip_types text/plain text/css application/json application/javascript application/x-javascript text/xml application/xml application/xml+rss text/javascript;
    
            location  /{
                root  /usr/share/nginx/html;
                index  index.html index.htm;
                try_files $uri $uri/ /index.html;
            }
    
            location /api {
                rewrite  ^/api/(.*)$ /$1 break;
                proxy_pass http://localhost:3000;
    
            }
             location /websocket {
                proxy_set_header   X-Real-IP $remote_addr;
                proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
                proxy_set_header   Host      $http_host;
                proxy_set_header X-NginX-Proxy true;
                proxy_pass http://localhost:3000;
                proxy_http_version 1.1;
                proxy_set_header Upgrade $http_upgrade;
                proxy_set_header Connection "upgrade";
            }
        }
    }
    复制代码

     

    最后,祝大家工作顺利,请记住 ‘耳机大神’ 永远陪着你

     
  • 相关阅读:
    部署wordpress项目
    给Windows文件夹添加备注信息
    TNZ5TSC OSN1800V全新板卡100G支路业务处理板
    学生花卉网网页设计作品 学生鲜花网页模板 简单在线花店主页成品 鲜花网页制作 HTML学生花店商城网站作业设计
    学习Maven Web 应用
    [YsOI2020]植树
    VSCode配置用户代码段以及常用快捷键汇总
    普及篇|云备份和云容灾,你用对了吗?
    计算机组成原理 期末复习笔记整理(上)(个人复习笔记/侵删/有不足之处欢迎斧正)
    QT中的QPropertyAnimation使用和toast案列
  • 原文地址:https://www.cnblogs.com/haoyuerji/p/17720382.html