该功能使用nodejs 写后台, vue写前端, 利用websoket作为长连接, protobuf作为数据格式传输数据实现了简单的聊天, 其中node是使用了nodejs-websocket作为三方库
直接上代码
vue代码
webSocketManager.js 自定义的工具类
// 获取protobuf 的root
let protobuRoot = require("protobufjs").Root;
// 获取定义的protobuf文件的对象json
let protoJson = require("../utils/proto");
let messageRoot = protobuRoot.fromJSON(protoJson);
// 定义websocket 地址
let socketurl = "ws://192.168.0.252:8091";
// 重连锁, 防止过多重连
let reconnectLock = false;
// 定义一个消息发送中(包含发送失败的)的字典
window.messageSendingDic = {};
// 定义一个消息websocket连接状态的字段, 并且要绑定到widow上, 方便调用
// 0 未连接, 1 连接成功 2 连接中
window.webSocketState = 0;
// 定义连接服务器方法
function connectWebsocket(){
//如果用户已登录, 进行连接websoket, 如果没有登陆, 登录后进行连接 用token判断
// 创建一个websocket连接
// let webSocket = new WebSocket(socketurl);
// 如果想要传token, 因为ws不支持通过设置header, 所以直接在地址中加参数,
// 如ws://192.168.0.252:8091?name=lulu&token=123456
let name = "lulu";
let token = "123456"
let webSocket = new WebSocket(socketurl+`?appname=${name}&token=${token}`);
// let webSocket = new WebSocket(socketurl+`?appname=${name}&token=${token}`, ["soap"]);
// 监听webSocket的各个状态
// 连接成功
webSocket.onopen = function() {
console.log("websocket连接成功")
// 连接成功后将连接状态改变
window.webSocketState = 1;
// 连接成功后, 要将消息队列里面的消息重新发送出去(底层重发, 和页面无关)
for(let session in window.messageSendingDic){
session.forEach(message => {
// 重发消息
reSendMessage(message)
});
}
}
// 连接出错
webSocket.onerror = function(error){
console.log("websocket连接出错", error);
console.log("websocket连接出错", error.data);
// 进行重连
reconnectWebsocket();
}
// 连接关闭
webSocket.onclose = function(result){
console.log("websocket连接关闭", result);
if(result == "退出登录"){
return
}
// 进行重连
reconnectWebsocket();
}
// 接受到消息
webSocket.onmessage = function(message){
// console.log("websocket接受到消息", message);
// 将受到的消息进行分类, 分发处理
formatAcceptMessage(message)
}
// 将webSocket绑定到window上面, 方便后续调用
window.webSocket = webSocket;
}
// 定义重连方法 如果连接失败, 或者关闭,
function reconnectWebsocket(){
// 如果正在重连, 则返回
if(reconnectLock){
return;
}
// 进行加锁
reconnectLock = true;
// 重连时将连接状态改变
window.webSocketState = 2;
// 为了防止过多请求, 1s后进行重连
setTimeout(function(){
// 解锁
reconnectLock = false;
// 进行连接, 如果失败接着重连
// connectWebsocket();
}, 1000)
}
/**
* 关闭websocket 退出时会用到
*
*/
function closeWebsocket(){
window.webSocket.onclose("退出登录")
}
// 定义发送消息的方法 message 格式为json
/**
*
* @param {
* message: "内容",
* id: "xxxxxxx"
* } message 消息内容
* @param "1" messageType 消息类型
* @param "QueryMsg" messageClass 附加字段吗消息类, 这里是以protobufjs的消息类为例
*/
function sendMessage(message, messageType) {
// 这里可以对message做一些格式化处理
// let formaterMessge = message;
// 如果没有传递messageType, 则默认为即时消息
if(!messageType){
messageType = 1;
}
// 如果发送的消息为即时消息, 要记录消息的发送状态
if(messageType == 1){
// 将消息添加到发送中的数组中进行记录
// 先判断该回话有没有对应的数组, 如果没有就创建, 在添加, 如果有直接添加
if(window.messageSendingDic[message.sessionId]) {
window.messageSendingDic[message.sessionId].push(message);
} else {
window.messageSendingDic[message.sessionId] = [];
window.messageSendingDic[message.sessionId].push(message);
}
}
// 如果websocket连接成功, 进行发送消息
if(window.webSocketState == 1) {
// formaterMessge = JSON.stringify(formaterMessge)
let bufferMessage = creatBufferMessage(message, messageType)
//
console.log("要发送的消息", message, messageType)
// 这里就可以直接用window调用了
window.webSocket.send(bufferMessage);
} else {
// 如果websocket没有连接成功, 直接告诉消息发送页面消息发送失败, 模拟接受到消息, 发给对应页面
let formaterMessge = {};
// 将处理后的消息进行发送通知, 通知给需要的页面进行处理, 在需要的页面进行监听
// 注意: 使用页面添加window.addEventListener("receivedNewMessage", this.testAction)
window.dispatchEvent(new CustomEvent("receivedNewMessage", message));
}
}
// 定义重发送消息的方法 message 格式为json
/**
*
* @param {
* message: "内容",
* id: "xxxxxxx"
* } message 消息内容
* @param "1" messageType 消息类型
* @param "QueryMsg" messageClass 附加字段吗消息类, 这里是以protobufjs的消息类为例
*/
function reSendMessage(message) {
// 如果websocket连接成功, 进行发送消息
if(window.webSocketState == 1) {
// 这里就可以直接用window调用了
window.webSocket.send(message);
}
}
// 定义收到消息进行消息解析的方法
function formatAcceptMessage(message) {
// 处理消息. 格式化, 获取消息的blob数据
let bufferMessage = message.data;
// 将buffer数据解析为json消息
getMessageFromBufferMessage(bufferMessage, (message, messageType) => {
console.log("接受到的消息")
console.log(message, messageType)
// 除了是服务器发送的确认消息外, 都应该向服务器发送确认消息
if(messageType == 2){
// 2是确认消息, 收到服务器发送的确认消息后, 说明消息发送成功
// 将发送成功的消息从发送中移除
if(window.messageSendingDic[message.sessionId]) {
let sendingArray = window.messageSendingDic[message.sessionId];
// 过滤发送成功的
window.messageSendingDic[message.sessionId] = sendingArray.filter(msg => {
return msg.id != message.id
});
}
} else {
// 向服务器发送确认消息
// 创建确认消息
let ackMessage = {
mid: message.mid,
uid: message.uid,
sessionId: message.sessionId
}
// 发送确认消息
sendMessage(ackMessage, "2")
// 将处理后的消息进行发送通知, 通知给需要的页面进行处理, 在需要的页面进行监听
if(messageType == 1){
// 1是即时消息, 发送给聊天页面和聊天列表页, 去刷新页面信息
// 注意: 使用页面添加window.addEventListener("receivedNewMessage", this.testAction)
window.dispatchEvent(new CustomEvent("receivedNewMessage", {detail: message}));
} else if(messageType == 3){
// 3是同步消息,
// 这里面是数组, 注意发送给聊天页面
message.msgsArray.forEach(element => {
// 注意: 使用页面添加window.addEventListener("receivedNewMessage", this.testAction)
window.dispatchEvent(new CustomEvent("receivedNewMessage", message));
});
} else if(messageType == 4){
// 4是离线推送消息
// 这里面是数组, 注意发送给聊天页面
message.msgsArray.forEach(element => {
// 注意: 使用页面添加window.addEventListener("receivedNewMessage", this.testAction)
window.dispatchEvent(new CustomEvent("receivedNewMessage", message));
});
} else if(messageType == 51){
// 好有申请
// 注意: 使用页面添加window.addEventListener("receivedApplyFriendMessage", this.testAction)
window.dispatchEvent(new CustomEvent("receivedApplyFriendMessage", message));
} else if(messageType == 52){
// 好友接受申请
// 注意: 使用页面添加window.addEventListener("receivedAcceptFriendMessage", this.testAction)
window.dispatchEvent(new CustomEvent("receivedAcceptFriendMessage", message));
} else if(messageType == 53){
// 被踢出群
// 注意: 使用页面添加window.addEventListener("receivedKickedOutMessage", this.testAction)
window.dispatchEvent(new CustomEvent("receivedKickedOutMessage", message));
} else if(messageType == 54){
// 被禁言
// 注意: 使用页面添加window.addEventListener("receivedBannedSpeakMessage", this.testAction)
window.dispatchEvent(new CustomEvent("receivedBannedSpeakMessage", message));
}else if(messageType == 58){
// 通知
// 注意: 使用页面添加window.addEventListener("receivedNotificationMessage", this.testAction)
window.dispatchEvent(new CustomEvent("receivedNotificationMessage", message));
}
// 注意: 使用页面添加window.addEventListener("acceptNewMessage", this.testAction)
window.dispatchEvent(new CustomEvent("acceptNewMessage", message));
}
});
}
// 将buffer二进制数据转换为json数据
function getMessageFromBufferMessage(bufferMessage, result){
// 创建一个文件读取器
let reader = new FileReader();
// 将消息读取为arrayBuffer类型
reader.readAsArrayBuffer(bufferMessage);
// 读取成功的回调
reader.onload = function() {
// 获取消息的buffer
let buffer = new Uint8Array(reader.result);
// 获取消息类型, 第一个字节
let messageType = buffer[0];
// 获取对应消息类的名字, 默认确认空
let messageTypeName = getMessageTypeName(messageType);
// 获取对应protobuf消息类型
let protobufTypeObject = getProtobufTypeObject(messageTypeName);
// 获取消息内容buffer
let bufferMessageContent = buffer.subarray(1);
// 将消息内容buffer进行解码, 得到具体消息
let message = protobufTypeObject.decode(bufferMessageContent);
result(message, messageType);
}
}
// 根据messageType获取(将消息类型转换为protobuf消息的毒性)对应的消息类型对象
// messageType 消息类型, 例如 "Ack", 在proto.js中可以找到
function getProtobufTypeObject(messageTypeName){
// 根据messageType获取(将消息类型转换为protobuf消息的对象)对应的消息类型对象
let protobufTypeObject = messageRoot.lookupType(messageTypeName);
return protobufTypeObject;
}
// 创建protobuf消息, 将json消息转换为对应的protobuf消息
function creatBufferMessage(message, messageType){
// 获取对应消息类的名字, 默认确认空
let messageTypeName = getMessageTypeName(messageType);
// 获取对应protobuf消息类型
let protobufTypeObject = getProtobufTypeObject(messageTypeName);
// 创建消息, 最后还需要添加一个字符表示消息类型
let protobufMessageContent = protobufTypeObject.create(message);
// 将消息进行编码
let encodeProtobufMessageContent = protobufTypeObject.encode(protobufMessageContent)
// 消息转换完成
let bufferMessageContent = encodeProtobufMessageContent.finish();
// console.log("11111111")
// console.log("2222222", encodeProtobufMessageContent)
// console.log("333333", bufferMessageContent)
// 完整的proto信息, 添加了头部消息乐行
let protobufMessage = bufferMessageAddType(messageType, bufferMessageContent);
return protobufMessage;
}
function getMessageTypeName(messageType){
let messageTypeName = "";
if(messageType == 1){
// 新消息
messageTypeName = "ChatMsg"
} else if(messageType == 2){
// 确认消息
messageTypeName = "Ack"
} else if(messageType == 3){
// 同步消息
messageTypeName = "ChatMsgList"
} else if(messageType == 4){
// 离线推送消息
messageTypeName = "ChatMsgList"
} else if(messageType == 51){
// 好友申请的命令
messageTypeName = "RefreshApply"
} else if(messageType == 52){
// 好友接受的命令
messageTypeName = "RefreshContact"
} else if(messageType == 53){
// 被踢出群
messageTypeName = "GroupRemove"
} else if(messageType == 54){
// 被禁言
messageTypeName = "GroupBanned"
} else if(messageType == 55){
// 被解禁
messageTypeName = "GroupBeLifted"
} else if(messageType == 56){
// 被踢出会议房间
messageTypeName = "GroupKick"
} else if(messageType == 57){
// 面对面建群,加入群聊前,进入房间时刷新列表用
messageTypeName = "RefreshContact"
} else if(messageType == 58){
// 通知
messageTypeName = "Push"
}
return messageTypeName;
}
// 在bufferMessage 前面加上 一个字节, 表示消息的类型, 方便客户端取用, 辨识是哪种消息类型
function bufferMessageAddType(type, buffer){
/**
* Uint8Array是JavaScript中的一种类型化数组。
* 它提供了一种用于表示8位无符号整数的固定长度的数组,
* 可以让你更轻松,更高效地操作二进制数据
*/
// 创建一个 1 + buffer.length长度的数组
let array = new Uint8Array(1 + buffer.byteLength)
// 该方法允许你通过一个子数组来填充当前数组的一部分
array.set(new Uint8Array([type]), 0)
array.set(new Uint8Array(buffer), 1)
// 注意 vue中使用 arraybuffer, 而nodejs中需要使用buffer, 因为底层不完全相同
let arrayBuffer = array.buffer;
return arrayBuffer;
}
// 如果服务器端有消息确认, 可以根据消息确认, 添加消息是否发送成功的状态,
// 需要单独创建一个数组, 用来存放发送中的数据(包含发送失败的数据)
module.exports = {
connectWebsocket,
closeWebsocket,
sendMessage
}
chat.vue 聊天页面
<template>
<div class="chat-page-box">
<div class="chat-page-header">聊天</div>
<div class="chat-page-content" id="chat-page-content" @click="clickContentPart">
<div v-for="(message, index) in messageArray" :key="index">
<div :class="index % 2 == 0 ? 'message-left-cell':'message-right-cell'">
<div class="message-cell-portrait-part">
<img class="message-cell-portrait" src="" alt="">
</div>
<div class="message-cell-content-part">
<div class="message-cell-name">
<span>name</span>
</div>
<div class="message-cell-content">
<!-- <img class="message-cell-bubble" src="@/assets/images/icon_session_bubble_right.png" alt=""> -->
<div class="me_message_content_icon"></div>
<div class="message-cell-content-text">
<div>{{message.text}}</div>
</div>
</div>
</div>
</div>
<div class="message-left-cell">
<div></div>
<!-- <div>{{message.text}}</div> -->
</div>
</div>
</div>
<div class="chat-page-bottom">
<div class="chat-bottom-part-text">
<div class="chat-bottom-part-voice">
<img class="vocice-icon" src="@/assets/images/icon_session_voice.png" alt="">
</div>
<div class="chat-bottom-part-textview">
<el-input
ref="getfocus"
class="chat-bottom-part-textfield"
v-model="message"
placeholder="请输入内容"
@blur="blurAction"
@keyup.enter.native="enterAction"
@focus="textFocusAction"
></el-input>
</div>
<div class="chat-bottom-part-add" @click="addAction">
<img class="add-icon" src="@/assets/images/icon_session_add.png" alt="">
</div>
</div>
<div class="chat-bottom-part-tool" :style="{height: toolHeight + 'px'}" v-if="toolHeight">
<div class="chat-bottom-part-tool-item">图片</div>
</div>
</div>
<!-- <div class="row">
<span class="title">姓名:</span>
<el-input v-model="name" placeholder="请输入内容"></el-input>
</div>
<div class="row">
<span class="title">消息:</span>
<el-input v-model="message" placeholder="请输入内容"></el-input>
</div> -->
<!-- <span class="button" @click="sendMessage">发送</span> -->
</div>
</template>
<script>
import {closeWebsocket, sendMessage} from "@/manager/webSocketManager"
// const WebSocket = require("websocket");
// const ws = new WebSocket("ws://192.168.0.252:8091")
// // 长连接websocket
// ws.onopen = function () {
// ws.send(JSON.stringify({
// username: '连接成功',
// mes: ''
// }))
// console.log("websocket连接成功")
// }
// ws.onmessage = function (data) {
// console.log("接收到消息", JSON.parse(data.data))
// // localChat.push(JSON.parse(data.data))
// }
// ws.onclose = function(res){
// console.log("连接关闭", res)
// }
// ws.onerror = function(res){
// console.log("连接出错", res)
// }
export default {
data() {
return {
name:"",
message:"",
toolHeight: 0,
keyBoardHeight: 0,
messageArray: [
{
sessionId: "1234567890",
sender: "小明",
mid: "100000",
type: 1,
text: "你在干嘛呢, 知道了么你在干嘛呢, 知道了么你在干嘛呢, 知道了么你在干嘛呢, 知道了么你在干嘛呢, 知道了么",
uid: "1234567890"
},
{
sessionId: "1234567890",
sender: "小明",
mid: "100000",
type: 1,
text: "你在干嘛呢, 知道了么",
uid: "1234567890"
}
]
}
},
created() {
window.addEventListener("receivedNewMessage", this.receviedMessage)
},
mounted(){
window.addEventListener("keyboardWillShow", this.onKeyBoardShow)
},
beforeDestroy(){
window.removeEventListener("keyboardWillShow", this.onKeyBoardShow)
},
methods: {
sendMessage(){
console.log("点击了发送消息")
let message = {
sessionId: "1234567890",
sender: "小明",
mid: "100000",
type: 1,
text: this.message,
uid: "1234567890"
};
this.message = "";
sendMessage(message)
this.messageArray.push(message)
this.scrollToBottom()
},
receviedMessage(event){
let message = event.detail;
console.log("xxxxx", event.detail)
this.messageArray.push(event.detail)
this.scrollToBottom()
// console.log(this.messageArray)
},
// 建立长连接
longConnection() {
console.log("点击了关闭长连接")
// connectWebsocket();
closeWebsocket()
},
// 获得焦点
textFocusAction(){
this.toolHeight = 0;
},
// 失去焦点
blurAction(event){
// console.log("dd", event)
},
// 点击了enter键
enterAction(value){
this.sendMessage();
},
onKeyBoardShow(event){
console.log(event.height);
},
// 发消息(收消息)后自动滑动到底部
scrollToBottom() {
// const container = document.getElementById('chat-page-content'); // 替换为你的容器元素ID
// container.scrollIntoView(false);
this.$nextTick(() => {
var container = this.$el.querySelector("#chat-page-content");
container.scrollTop = container.scrollHeight;
});
},
addAction(){
if(this.toolHeight){
// 自动获取输入框的焦点
// this.$nextTick(() => {
// this.$refs.getfocus.focus();
// })
this.toolHeight = 0;
} else {
this.$nextTick(() => {
if(this.keyBoardHeight){
this.toolHeight = 260;
} else {
this.toolHeight = 260;
}
})
}
},
clickContentPart(){
this.toolHeight = 0;
}
}
}
</script>
<style lang="scss">
.chat-page-box{
background: #f5f5f5;
overflow: hidden;
height: 100%;
.chat-page-header{
position: absolute;
top: 0px;
left: 0px;
right: 0px;
height: 50px;
font-size: 18px;
line-height: 50px;
color: #4a4a4a;
background: #fff;
text-align: center;
// background: rgb(98, 98, 240);
}
.chat-page-content{
position: absolute;
top: 50px;
left: 0px;
right: 0px;
bottom: 50px;
background: #f5f5f5;
padding: 0 10px;
overflow: scroll;
.message-left-cell{
margin-top: 10px;
display: flex;
padding-right: 60px;
.message-cell-portrait-part{
.message-cell-portrait{
flex-shrink: 0;
width: 40px;
height: 40px;
background: #f5f5f5;
border-radius: 20px;
}
}
.message-cell-content-part{
margin-left: 10px;
display: flex;
flex-direction: column;
.message-cell-name{
margin-left: 5px;
line-height: 20px;
}
.message-cell-content{
position: relative;
display: flex;
.message-cell-bubble{
position: absolute;
z-index: 1;
height: 100%;
width: 100%;
}
.message-cell-content-text{
margin-left: 5px;
padding: 10px 7px;
background: #ffffff;
border-radius: 4px;
color: #4a4a4a;
word-wrap: break-word;
word-break: break-all;
}
.me_message_content_icon {
width: 0;
height: 0;
border-right: 6px solid #ffffff;
border-bottom: 6px solid transparent;
border-top: 6px solid transparent;
position: absolute;
// right: -5px;
left: 0px;
top: 12px;
}
}
}
}
.message-right-cell{
margin-top: 10px;
padding-left: 60px;
display: flex;
flex-direction: row-reverse;
.message-cell-portrait-part{
.message-cell-portrait{
flex-shrink: 0;
width: 40px;
height: 40px;
background: #f5f5f5;
border-radius: 20px;
}
}
.message-cell-content-part{
margin-right: 10px;
display: flex;
flex-direction: column;
.message-cell-name{
display: none;
}
.message-cell-content{
position: relative;
display: flex;
.message-cell-content-text{
// 如果不设置次代码, z-index设置无效
position: relative;
z-index: 2;
margin-right: 5px;
padding: 10px 7px;
background: #be3468;
border-radius: 4px;
color: #ffffff;
word-wrap: break-word;
word-break: break-all;
}
.me_message_content_icon {
width: 0;
height: 0;
border-left: 6px solid #be3468;
border-bottom: 6px solid transparent;
border-top: 6px solid transparent;
position: absolute;
// right: -5px;
right: 0px;
top: 12px;
// margin-right: 10px;
}
}
}
}
}
.chat-page-bottom{
position: absolute;
left: 0px;
right: 0px;
bottom: 0px;
// height: 260px;
background: #f5f5f5;
// background: rgb(98, 98, 240);
border-top: 1px solid #d1d1d1;
.chat-bottom-part-text{
display: flex;
align-items: center;
justify-content: space-between;
height: 50px;
background: #fafafa;
.chat-bottom-part-voice{
height: 50px;
width: 50px;
.vocice-icon{
width: 28px;
height: 28px;
margin-left: 15px;
margin-top: 11px;
}
}
.chat-bottom-part-textview{
flex: 1;
height: 34px;
border: 0.5px solid #d1d1d1;
border-radius: 4px;
.chat-bottom-part-textfield{
width: 100%;
// height: 32px;
}
.el-input__inner{
height: 34px;
line-height: 34px;
}
}
.chat-bottom-part-add{
height: 50px;
width: 50px;
.add-icon{
width: 28px;
height: 28px;
margin-left: 7px;
margin-top: 11px;
}
}
}
.chat-bottom-part-tool{
display: flex;
.chat-bottom-part-tool-item{
width: 60px;
height: 60px;
margin-top: 20px;
margin-left: 20px;
text-align: center;
border: 0.5px solid #d1d1d1;
border-radius: 8px;
line-height: 60px;
}
}
}
.button{
padding: 5px 10px;
background: #00f;
cursor: pointer;
margin-top: 20px;
width: auto;
display: inline-block;
color: white;
border-radius: 4px;
}
.row {
display: flex;
margin-top: 20px;
align-items: center;
}
.title {
flex-shrink: 0;
}
}
</style>
main.js中
import {connectWebsocket} from "@/manager/webSocketManager"
connectWebsocket();
``
nodejs代码
const ws = require("nodejs-websocket");
//定义一个对象,用于存放正在连接中的socket, 字段名是以token命名
const conningObject = {};
// 获取protobuf 的root
let protobuRoot = require("protobufjs").Root;
// 获取定义的protobuf文件的对象json
let protoJson = require("./proto.js");
let messageRoot = protobuRoot.fromJSON(protoJson);
// lookupType根据传入的字符传, 获取对应的消息类型对象(用于创建对应的消息)
// 例如传入proto.js中的Ack 表明获取ack类型对象, 用于创建ack类型的消息
// let messageTypeObject = messageRoot.lookupType("Ack");
// console.log("消息类型");
// console.log(messageTypeObject);
// creatProtobufMessage("Ack", {mid: 123456, uid: "qweeer", sessionId: "fddddd"})
let webServe = ws.createServer(function (connection) {
// console.log('创建成功', connection)
//连接成功的回调
// 获取连接的token
let path = connection.path;
let pathParams = getParamsFromURL(path);
console.log(pathParams);
// 不满足服务器条件时服务器主动断开连接
if(pathParams.token) {
// 如果token正确进行继续操作
// 如果是第一次连接, 添加到对应的数组, 如果不是, 不用继续添加
if (!conningObject[pathParams.token]) {
console.log("添加connect");
//将用户发来的信息对所有用户发一遍
conningObject[pathParams.token] = connection;
// console.log(conningObject)
// console.log(conningObject.keys())
}
//监听数据,当客户端传来数据时的操作
// 监听收到的数据, 如果发送的是字符串在这个方法中响应
connection.on("text", function (data) {
console.log('接受到字符串类型消息', data);
// 解析数据
// 发送确认消息
})
// 监听收到的数据, 如果发送的是二进制数据在这个方法中响应
connection.on("binary", function (inStream) {
// console.log('接受到二进制类型消息', inStream)
// console.log(result)
// Empty buffer for collecting binary data
// 定义一块buffer内存空间用来存放接受到的二进制文件
var buffer = Buffer.alloc(0)
// Read chunks of binary data and add to the buffer
inStream.on("readable", function () {
// 因为二进制文件时分段发送的, 不是一次性发送的, 所以这里进行拼接
var newData = inStream.read()
if (newData){
// 将接受到的二进制文件拼接到buffer空间内
buffer = Buffer.concat([buffer, newData], buffer.length+newData.length)
}
})
inStream.on("end", function () {
// console.log("Received " + buffer.length + " bytes of binary buffer")
// console.log(buffer);
// 接受二进制文件完成, 将二进制数据进行解析
getMessageFromMessageBuffer(buffer, (message, messageType) => {
// 如果用户发送的不是确认消息, 则立即向客户端发送确认消息
if(messageType == 2){
// 收到的是确认消息
} else {
// 收到消息后要立即向客户端发送确认消息
let protobufMessage = creatProtobufMessage({
mid: message.mid,
uid: message.uid,
sessionId: message.sessionId
}, "2")
//
// console.log("要发送的消息", protobufMessage)
// 发送确认消息buffer的方法
connection.send(protobufMessage);
// 将处理后的消息进行发送通知, 通知给需要的页面进行处理, 在需要的页面进行监听
if(messageType == 1){
// 1是即时消息, 发送给对应的聊天对象
// 获取sessionId
let sessionId = message.sessionId;
// 将消息存到数据库
// 根据sessionId获取会话中的人员信息(这个过程需要去除本人)
let usersInfo = [{id:"1"}, {id:"2"}];
// 根据userId获取对应人员目前的token
let tokens = ["123456", "654321"];
// 根据token查询当前websocket连接中有没有对应的人员,(本人除外)
// 如果有对应连接, 将消息发送给对应人员,
// 如果没有对应连接, 发送推送, 并记录此消息为离线消息, 下次用户连接时, 直接发送过去
let connectKeys = Object.keys(conningObject);
console.log(connectKeys);
tokens.forEach(token => {
connectKeys.every(key => {
if(token == key){
//发送消息
console.log(message)
conningObject[key].send(buffer);
return false;
}
});
});
} else if(messageType == 3){
// 3是客户端向服务器发送了消息同步的指令,
} else if(messageType == 4){
// 4是离线推送消息
// 客户端不会发送此类消息, 是服务器向客户端发送的消息
} else if(messageType == 51){
// 好有申请
// 发送的对应的人员
} else if(messageType == 52){
// 好友接受申请
// 发送的对应的人员
} else if(messageType == 53){
// 踢出群
// 发送的对应的人员
} else if(messageType == 54){
// 禁言
// 发送的对应的人员
}else if(messageType == 58){
// 通知
// 客户端不会发送此类消息, 是服务器向客户端发送的消息
}
}
})
})
})
connection.on('connect', function(code) {
console.log('开启连接', code)
})
connection.on('close', function(code, reason) {
console.log('关闭连接', code)
console.log('关闭原因:', reason)
// console.log(conningObject);
// 获取连接的token
let path = connection.path;
let pathParams = getParamsFromURL(path);
console.log(pathParams.token);
// 连接关闭时要将这个连接从连接对象中移除
delete conningObject[pathParams.token]
// console.log(conningObject);
})
connection.on('error', function(code) {
console.log('异常关闭', code)
})
} else {
// 如果token 不合理就进行断开
console.log('token不正确')
connection.close(1, "token不正确");
}
});
webServe.listen(8091);
webServe.on('connection', (connection) => {
console.log("客户端进行连接");
// console.log("客户端进行连接", connection);
})
function getMessageFromMessageBuffer(messageBuffer, result){
// ArrayBuffer 对象代表储存二进制数据的一段内存
// Uint8Array 对象是 ArrayBuffer 的一个数据类型(8 位不带符号整数)
// 获取消息的buffer
let buffer = new Uint8Array(messageBuffer);
// console.log("111111111")
// console.log(messageBuffer)
// console.log(buffer)
// console.log("111111111")
// 获取消息类型, 第一个字节
let messageType = buffer[0];
// 获取对应消息类的名字, 默认确认空
let messageTypeName = getMessageTypeName(messageType);
// 获取对应protobuf消息类型
let protobufTypeObject = getProtobufTypeObject(messageTypeName);
// 获取消息内容buffer
let bufferMessageContent = buffer.subarray(1);
// 将消息内容buffer进行解码, 得到具体消息
let message = protobufTypeObject.decode(bufferMessageContent);
// 消息内容为
// console.log("消息内容为")
// console.log(message)
result(message, messageType);
// 读取成功的回调
//1-实时消息 2-确认收到消息 //4-有未读消息
// if(messageType == 1 || messageType == 4){
// } else if(messageType == 2){
// } else if(messageType == 58){
// }
}
// 根据messageType获取(将消息类型转换为protobuf消息的毒性)对应的消息类型对象
// messageType 消息类型, 例如 "Ack", 在proto.js中可以找到
function getProtobufTypeObject(messageTypeName){
// 根据messageType获取(将消息类型转换为protobuf消息的对象)对应的消息类型对象
let protobufTypeObject = messageRoot.lookupType(messageTypeName);
return protobufTypeObject;
}
// 创建protobuf消息, 将json消息转换为对应的protobuf消息
function creatProtobufMessage(message, messageType){
// 获取对应消息类的名字, 默认确认空
let messageTypeName = getMessageTypeName(messageType);
// 获取对应protobuf消息类型
let protobufTypeObject = getProtobufTypeObject(messageTypeName);
// 创建消息, 最后还需要添加一个字符表示消息类型
let protobufMessageContent = protobufTypeObject.create(message);
// 将消息进行编码
let encodeProtobufMessageContent = protobufTypeObject.encode(protobufMessageContent)
// 消息转换完成
let bufferMessageContent = encodeProtobufMessageContent.finish();
// console.log("11111111")
// console.log("2222222", encodeProtobufMessageContent)
// console.log("333333", bufferMessageContent)
// 完整的proto信息, 添加了头部消息乐行
let protobufMessage = bufferMessageAddType(messageType, bufferMessageContent);
return protobufMessage;
}
function getMessageTypeName(messageType){
let messageTypeName = "";
if(messageType == 1){
// 新消息
messageTypeName = "ChatMsg"
} else if(messageType == 2){
// 确认消息
messageTypeName = "Ack"
} else if(messageType == 3){
// 同步消息
messageTypeName = "ChatMsgList"
} else if(messageType == 4){
// 离线推送消息
messageTypeName = "ChatMsgList"
} else if(messageType == 51){
// 好友申请的命令
messageTypeName = "RefreshApply"
} else if(messageType == 52){
// 好友接受的命令
messageTypeName = "RefreshContact"
} else if(messageType == 53){
// 被踢出群
messageTypeName = "GroupRemove"
} else if(messageType == 54){
// 被禁言
messageTypeName = "GroupBanned"
} else if(messageType == 55){
// 被解禁
messageTypeName = "GroupBeLifted"
} else if(messageType == 56){
// 被踢出会议房间
messageTypeName = "GroupKick"
} else if(messageType == 57){
// 面对面建群,加入群聊前,进入房间时刷新列表用
messageTypeName = "RefreshContact"
} else if(messageType == 58){
// 通知
messageTypeName = "Push"
}
return messageTypeName;
}
// 在bufferMessage 前面加上 一个字节, 表示消息的类型, 方便客户端取用, 辨识是哪种消息类型
function bufferMessageAddType(type, buffer){
/**
- Uint8Array是JavaScript中的一种类型化数组。
- 它提供了一种用于表示8位无符号整数的固定长度的数组,
- 可以让你更轻松,更高效地操作二进制数据
*/
// 创建一个 1 + buffer.length长度的数组
let array = new Uint8Array(1 + buffer.byteLength)
// 该方法允许你通过一个子数组来填充当前数组的一部分
array.set(new Uint8Array([type]), 0)
array.set(new Uint8Array(buffer), 1)
let arrayBuffer = array.buffer;
// 将arraybuffer 转换为buffer
let messageBuffer = Buffer.from(arrayBuffer)
// 注意 vue中使用 arraybuffer, 而nodejs中需要使用buffer, 因为底层不完全相同
return messageBuffer;
}
// 获取url上的参数, 使用的是正则表达式
function getParamsFromURL(url) {
const regex = /?&=([^&#]*)/g;
const params = {};
let match;
while (match = regex.exec(url)) {
params[decodeURIComponent(match[1])] = decodeURIComponent(match[2]);
}
return params;
}