const Koa = require('koa'); const http = require('http'); const WebSocket = require('ws'); const WebSocketApi = require('../utils/websokect'); const port = normalizePort(process.env.PORT || '3000'); const app = new Koa(); const server = http.createServer(app.callback()); const wss = new WebSocket.Server({ server }); WebSocketApi(wss, app); server.listen(port);
此时运行koa2工程后,在浏览器控制台输入
👇
:
var ws = new WebSocket('ws://localhost:3000')
可以看到已经创建了一个websocket连接,并且其中readyState=1表示连接成功。
连接对象维护
前面我们已经搭建好了websocket服务,客户端能正常的创建一个websocket连接,但是我们知道消息中心是存在多个客户端之间的数据传递的,那么我们搭建的websocket服务器如何作为信令服务器,在大量客户端连接到服务器时正确找到需要通信的两方并传递数据呢?
首先我们需要将所有的客户端websocket连接进行分类,所有的连接分为发起方和接收方两种,另外,维护发起方与接收方之间的联系需要Koa2应用创建两个全局变量,cusSender和cusReader两个数组,分别保存所有的发起方连接和接收方连接。
app.context下添加cusSender和cusReader数组
👇
:
var server = http.createServer(app.callback()); app.context.cusSender = []; app.context.cusReader = []; const wss = new WebSocket.Server({ server });
cusSender和cusReader数组保存所有的ws实例,后续所有的连接查找和状态维护都是在这两个数组下面操作。
我们将ws连接进行分类后,还需要对每个类别进行标识,这样每一个ws连接就具有了唯一标识,根据这个唯一ID标识,就可以进行一对一或一对多的通信。
发起方连接我们传入两个参数,分别表示角色role以及唯一标识roomId,接收方连接我们传入两个参数,分别表示角色role和唯一标识userId。
发起方与接收方连接如下
👇
:
socketA = new WebSocket('ws://localhost:3000/sender/1'); socketB = new WebSocket('ws://localhost:3000/reader/2');
针对客户端传入的连接参数,服务端接收消息后根据角色将连接实例分别保存到全局变量cusSender和cusReader数组中
👇
:
const WebSocketApi = (wss, app) => { wss.on('connection', (ws, req) => { let { url } = req // url 的值是 /$role/$uniId let { cusSender, cusReader } = app.context; if (!url.includes('sender') || !url.includes('reader')) { return ws.clode() // 关闭 url 前缀不是 /sender或/reader 的连接 } let [ role, uniId] = url.slice(1).split('/') if(!uniId) { console.log('缺少参数') return ws.clode() } console.log('已连接客户端数量:', wss.clients.size) // 判断如果是发起端连接 if (role == 'sender') { // 此时 uniId 就是 roomid ws.roomid = uniId let index = (cusSender = cusSender || []).findIndex( row => row.roomid == ws.roomid ) // 判断是否已有该发送端,如果有则更新,没有则添加 if (index >= 0) { cusSender[index] = ws } else { cusSender.push(ws) } app.context.cusSender = [...cusSender] } if (role == 'reader') { // 接收端连接 ws.userid = uniId let index = (cusReader = cusReader || []).findIndex( row => row.userid == ws.userid ) if (index >= 0) { cusReader[index] = ws } else { cusReader.push(ws) } app.context.cusReader = [...cusReader] } ws.on('close', () => { if (role == 'sender') { // 清除发起端 let index = app.context.cusSender.findIndex(row => row == ws) app.context.cusSender.splice(index, 1) // 解绑接收端 if (app.context.cusReader && app.context.cusReader.length > 0) { app.context.cusReader .filter(row => row.roomid == ws.roomid) .forEach((row, ind) => { app.context.cusReader[ind].roomid = null row.send('leaveline') }) } } if (role == 'reader') { // 接收端关闭逻辑 let index = app.context.cusReader.findIndex(row => row == ws) if (index >= 0) { app.context.cusReader.splice(index, 1) } } }); }) } module.exports = WebSocketApi
至此,客户端ws连接对象的维护已经完成,接下来就是发起方与接收方的消息通信的实现了。
消息发送与接收
上一步我们在koa2后端创建了两个数组,分别保存了发起方与接收方的所有ws连接实例,那么我们客户端的消息发送方在发送数据时,指定消息接收方唯一标识,这样就可以保证发送与接收双方的消息传递。
发送方消息发送时添加接收方的userId参数
👇
:
const message = 'hello!' socketA.send(`message|2|${message}`); // 发送信息
服务端添加处理逻辑,进行消息转发
👇
:
const WebSocketApi = (wss, app) => { wss.on('connection', (ws, req) => { ... ws.on('message', msg => { if (typeof msg != 'string') { msg = msg.toString() // return console.log('类型异常:', typeof msg) } let { cusSender, cusReader } = app.context; eventHandel(msg, ws, role, cusSender, cusReader); }) }) } const eventHandel = (message, ws, role, cusSender, cusReader) => { if (role == 'reader') { let arrval = message.split('|') let [type, roomid, val] = arrval; console.log(type, roomid, val); if (type == 'message') { let seader = cusSender.find(row => row.roomid == roomid) if (seader) { console.log(message, type, val); seader.send(`${type}|${val}`) } } } if (role == 'sender') { let arrval = message.split('|') let [type, userid, val] = arrval // 注意:这里的 type, userid, val 都是通用值,不管传啥,都会原样传给 reader if (type == 'message') { console.log(cusReader,userid); let reader = cusReader.find(row => row.userid == userid) console.log(reader); if (reader) { console.log(message, type, val) reader.send(`${type}|${val}`) } } } } module.exports = WebSocketApi
接收方订阅消息
👇
:
socketB.onmessage = evt => { let string = evt.data let value = string.split('|') if (value[0] == 'message') { let message = value[1]; console.log(message); } }
发送方与接收方的发送消息与订阅消息的逻辑一致,至此客户端之间消息通信已全部完成。
消息中心组件
消息中心组件MessageCenter.vue主要包括消息同步显示功能和消息发送功能,消息显示组件为MessageItem.vue,组件保存的消息列表包括发送的消息与接收到的消息,类似微信的聊天,发送的消息展示在左侧,接收的消息展示在右侧,MessageItem.vue组件代码如下
👇
:
<template> <div class="message-wrapper"> <div class="sender" v-if="props.role === 'sender'"> <span class="avatar"> <img :src="url"/> </span> <div class="content"> <div class="triangle"></div> <p>{{ props.content }}</p> </div> </div> <div class="reciever" v-if="props.role === 'reciever'"> <div class="content"> <div class="triangle"></div> <p>{{ props.content }}</p> </div> <span class="avatar"> <img :src="url"/> </span> </div> </div> </template> <script lang="ts" setup> import { ref, defineProps} from 'vue'; const props = defineProps<{ userid: 1, role: '', content: '' }>(); // 暂时没有获取头像的功能,静态图片显示 const url = props.userid === 1 ? 'assets/images/avatar.jpg' : 'assets/images/bg.jpg'; </script> <style lang="less" scoped> .message-wrapper { display: flow-root; margin-top: 10px; width: 100%; .sender { // float: left; display: flex; .avatar { width: 40px; height: 40px; margin-left: 10px; img { height: 40px; width: 40px; border-radius: 4px; } } .content { margin-left: 20px; padding-right: 70px; position: relative; .triangle { width: 0; height: 0; border: 8px solid; border-left-color: transparent; border-top-color: transparent; border-right-color: white; border-bottom-color: transparent; position: absolute; top: 5px; left: -16px; } p { background: white; font-size: 18px; padding: 5px 10px; border-radius: 5px; word-wrap: break-word; word-break: break-all; } } } .reciever { float: right; display: flex; .avatar { margin-left: 20px; margin-right: 10px; width: 40px; height: 40px; img { height: 40px; width: 40px; border-radius: 4px; } } .content { position: relative; padding-left: 70px; .triangle { width: 0; height: 0; border: 8px solid; border-left-color: yellowgreen; border-top-color: transparent; border-right-color: transparent; border-bottom-color: transparent; position: absolute; top: 5px; right: -16px; } p { background: yellowgreen; font-size: 18px; padding: 5px 10px; border-radius: 5px; word-wrap: break-word; word-break: break-all; } } } } </style>
MessageCenter.vue组件的消息发送与功能
👇
:
let value1 = ref(''); let value2 = ref(''); const messageList1 = ref(new Array<MessageItem>()); const messageList2 = ref(new Array<MessageItem>()); const socketA = new WebSocket('ws://localhost:3000/sender/1'); const socketB = new WebSocket('ws://localhost:3000/reader/2');// 订阅消息 const onMessage = () => { socketA.onmessage = evt => { let string = evt.data let value = string.split('|') if (value[0] == 'message') { let message = value[1]; console.log(message, 'value1'); messageList1.value.push({ userid: 2, role: 'reciever', constent: message }); } }; socketB.onmessage = evt => { let string = evt.data let value = string.split('|') if (value[0] == 'message') { let message = value[1]; console.log(message, 'value2'); messageList2.value.push({ userid: 1, role: 'reciever', constent: message }); } } }; const onMessageSend1 = () => { messageList1.value.push({ userid: 1, role: 'sender', constent: value1.value }); socketA.send(`message|2|${value1.value}`); // 发送信息 value1.value = ''; // 发送后清空输入框 } const onMessageSend2 = () => { messageList2.value.push({ userid: 2, role: 'sender', constent: value2.value }); socketB.send(`message|1|${value2.value}`); // 发送信息 value2.value = ''; // 发送后清空输入框 } ...
消息中心的界面实现如下
👇
:
作者:jayray 来源:公众号—— 玩心大胆子小
版权声明:本文内容转自互联网,本文观点仅代表作者本人。本站仅提供信息存储空间服务,所有权归原作者所有。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至[email protected] 举报,一经查实,本站将立刻删除。
赞
(0)
让VR参与传统体育训练,运动员打破的记录会更多吗?
Comcast 和 Broadcom 利用创新芯片组打造基于人工智能的接入网络
Moonshine:一种快速、准确、轻量级的语音转文本模型,用于边缘设备转录和语音命令处理
如何实现全渠道联络中心?
安全文件共享的艺术:解密 SFTP、HTTPS 和 WebDAV
WebRTC加密和安全(上)
2024 年投资智能对话系统的 6 个理由