美文网首页
vue, node使用websocket和protobuf结合实

vue, node使用websocket和protobuf结合实

作者: FM_0138 | 来源:发表于2023-12-14 16:23 被阅读0次

    该功能使用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;
    }

    相关文章

      网友评论

          本文标题:vue, node使用websocket和protobuf结合实

          本文链接:https://www.haomeiwen.com/subject/fqcwgdtx.html