最近需要实现一个功能,后端通过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<any>
}
@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";
}
}
}

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

 

angular + express 实现websocket通信的更多相关文章

  1. beetle.express针对websocket的高性能处理

    客户需要对websocket服务应用,其要求每秒同时给3W个在线的websocket连接进行广播消息.以下针对beetle.express扩展websocket简单的性能测试.从测试结果来看基本没什么 ...

  2. C#(SuperWebSocket)与websocket通信

    原文:C#(SuperWebSocket)与websocket通信 客户端代码 点击可以查看一些关于websocket的介绍 <!DOCTYPE html> <html> &l ...

  3. js判断是否安装某个android app,没有安装下载该应用(websocket通信,监听窗口失去焦点事件)

    现在经常有写场景需要提示用户下载app, 但是如果用户已经安装,我们希望是直接打开app. 实际上,js是没有判断app是否已经安装的方法的,我们只能曲线救国. 首先,我们需要有call起app的sc ...

  4. Express框架Fetch通信

    最近自己弄个博客站点,前台用的React,服务器用的是node实现的,node是第一次接触,所以还在摸索,这篇mark下通信时遇到的坑. fetch配置: window.fetchUtility = ...

  5. Springboot集成WebSocket通信全部代码,即扣即用。

    websocket通信主要来自两个类以及一个测试的html页面. MyHandler 和 WebSocketH5Config,下面全部代码 MyHandler类全部代码: package com.un ...

  6. 【Java Web开发学习】Spring MVC整合WebSocket通信

    Spring MVC整合WebSocket通信 目录 ========================================================================= ...

  7. websocket通信1009错误,

    问题说明: springboot继承 WebSocketConfigurer实现websocket通信服务,服务器端报错,"The decoded text message was too ...

  8. Python3+WebSockets实现WebSocket通信

    一.说明 1.1 背景说明 前段时间同事说云平台通信使用了个websocket的东西,今天抽空来看一下具体是怎么个通信过程. 从形式上看,websocket是一个应用层协议,socket是数据链路层. ...

  9. webSocket通信

    针对webSocket通信总结: 1.webSocket通信原理图: 2.webSocket通信实例 参考地址1:https://www.cnblogs.com/cjm123/p/9674506.ht ...

  10. angular组件间的通信(父子、不同组件的数据、方法的传递和调用)

    angular组件间的通信(父子.不同组件的数据.方法的传递和调用) 一.不同组件的传值(使用服务解决) 1.创建服务组件 不同组件相互传递,使用服务组件,比较方便,简单,容易.先将公共组件写在服务的 ...

随机推荐

  1. 如何从零开始构建 API ?

    假设你请承包商从零开始建造一座房子,你肯定期望他们交付最高质量的房子.他们必须通过检查.遵守安全规范并遵循项目中约定的要求.因为建房子可容不得走捷径.如果承包商经常走捷径,他们的声誉会受到影响,从而失 ...

  2. Dapr v1.11 版本已发布

    Dapr是一套开源.可移植的事件驱动型运行时,允许开发人员轻松立足云端与边缘位置运行弹性.微服务.无状态以及有状态等应用程序类型.Dapr能够确保开发人员专注于编写业务逻辑,而不必分神于解决分布式系统 ...

  3. 在线免费ChatGPT,官方api

    作为一款强大的语言模型,ChatGPT在自然语言处理领域享有较高声誉.现在,您可以在我们的在线平台上免费体验ChatGPT的功能了! 经过不断地优化和改进,我们的在线聊天机器人已经能够针对各种话题展示 ...

  4. 2023-06-28:你想要用小写字母组成一个目标字符串 target。 开始的时候,序列由 target.length 个 ‘?‘ 记号组成 而你有一个小写字母印章 stamp。 在每个回合,你可

    2023-06-28:你想要用小写字母组成一个目标字符串 target. 开始的时候,序列由 target.length 个 '?' 记号组成 而你有一个小写字母印章 stamp. 在每个回合,你可以 ...

  5. 云享·案例丨打造数智物流底座,华为云DTSE助力物联云仓解锁物流新“速度”

    摘要:华为云凭借领先的技术和快速响应的开发者支持服务,助力物联亿达实现云上资源高可用.提升系统安全性与稳定性,为物联亿达提供了扎实的数字化基础. 本文分享自华为云社区<云享·案例丨打造数智物流底 ...

  6. Java杂记————object.getClass()和object.class以及Java中的toString()方法的的区别

    不说废话,直接上干货: (注意大小写:object为对象,Object为类) 1,object.getClass()它是Object类的实例方法,返回一个对象运行时的类的Class对象,换句话说,它返 ...

  7. 创建属于自己的github、使用git提交、更新代码至github、写好readme

    1. 在github上创建一个Repository 点击github网站,你可以用你的邮箱先注册一个账号. 点击New,转到创建一个repository的界面,如下图所示,你可以填写你的Reposit ...

  8. mysql创建可以让Django链接的用户名

    输入以下命令: 1 grant all privileges on 库名.* to 账户名@'%' identified by 'password'; 2 flush privileges;

  9. rpm安装21c单实例数据库

    linux 7.6 使用rpm安装21c单实例数据库 一.基础环境配置 1.1 关闭防火墙 systemctl stop firewalld systemctl disable firewalld s ...

  10. CGLIB动态代理对象GC问题排查

    一.问题是怎么发现的 最近有个新系统开发完成后要上线,由于系统调用量很大,所以先对核心接口进行了一次压力测试,由于核心接口中基本上只有纯内存运算,所以预估核心接口的压测QPS能够达到上千. 压测容器配 ...