最近需要实现一个功能,后端通过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通信的文章就介绍到这了,更多相关angular websocket通信内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
发表评论