websocket实现聊天数据主动发送与主动接收

发布于 2021-08-15  103 次阅读


##

目标:

实现服务器主动接收和发送前端的消息.

平常我们使用的ajax都是基于http的,但http的缺陷也很明显,就是必须由客户端发起,否则服务器无法主动向客户发送任何消息和数据。

如果我们非要实现服务器主动发送消息到前端,就得被迫使用“轮询”,简单的说就是隔一段时间就向服务器发送一次请求,看一下服务器有没有发送数据到前端.但这样做效率低,非常消耗资源。

解决方案,使用websocket

以下方案将借助express和express-ws来实现对话聊天功能

步骤

1、新建项目文件夹

新建一个空白项目 ws-study ,然后执行 npm init -y 进行初始化。

2、安装项目依赖

npm install express express-ws nodemon//保持js的持续运行

3、配置启动项

{
  "scripts": {
    "start": "nodemon server/server.js",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
}

配置后执行npm run start就可以打开服务器并热更新代码。

//注意:必须安装必须的依赖文件

4、新建public文件夹

index.html、userA.html、userB.html

5、express资源访问

项目根目录下创建service文件夹,其中创建server.js

const express = require("express")
const expressWs = require("express-ws")
//调用函数创建一个实例
const app = express();
const websocket = require("../websoket.js")
​
//端口号
const port = 3000;
//使用这个端口
expressWs(app)
​
//中间件
app.use(express.static('public'))//主动访问静态资源里面的文件
​
app.use('/index',express.static('public/index.html'))
app.use('/userA',express.static('public/userA.html'))
app.use('/userB',express.static('public/userB.html'))
​
// app.use
​
app.use('/ws', websocket)//要提前引用websocket框架
​
app.get("/*", (req,res)=>{})
​
​
// 监听端口
app.listen(port,()=>{
    console.log(`监听到Server is running at http://localhost:${port}`);
​
})

6、比特虫生产icon图片

7、核心代码总结

userA.js中

    //建立的实例
    //userA是从服务器接到自己的数据,userA2从服务器接收到userB发过来的数据
    const ws = new WebSocket("ws://localhost:3000/ws/userA")
    const ws2 = new WebSocket("ws://localhost:3000/ws/userA2")
    
    
    //ws是接收a自己发到服务器的数据,
    //ws2是接收从服务器b发过来的数据
    //从服务端收取数据的时候
    ws.onmessage = function (res) {
​
        //这个是自己的数据,将自己加到右侧
        console.log("得到了自己发送到服务器的数据" + res);
        msgbox.innerHTML += rightMsgFn(res.data)
​
        // 添加盒子的滚动
        msgbox.scrollTo({
            top: msgbox.scrollHeight,
            behavior: "smooth"
        })
​
    }
​
    //从服务器拿到数据的时候
    ws2.onmessage = function (res) {
        //这个是对方的数据,将加到左侧
        console.log("得到了另一个发过来的数据" + res)
        msgbox.innerHTML += leftMsgFn(res.data)
​
        // 添加盒子的滚动
        msgbox.scrollTo({
            top: msgbox.scrollHeight,
            behavior: "smooth"
        })
​
    }

对应weboket.js中的代码

//userA发送过来的数据,将数据存到userAarr中,并将数据返回回去
router.ws("/userA",ws=>{
    ws.on("message",(msg)=>{
        //将userA中发过来的数据储存到userAarr这个数组中
        userAarr.push(msg)
        
        // 存了之后重新把拿到的数据返回给客户端,起验证作用
        ws.send(msg)
    })
​
​
})
​
//userA接收userB的数据,,/userA是接收自己的数据,userA2是接收从a发过来的数据
router.ws('/userA2',ws=>{
    //清空定时器
    let timer = null
    //当断开连接的时候需要清空服务器
    ws.on('close',()=>{
        if(timer){
            clearInterval(timer)
        }
    })
​
    //定时去触发send,如果userBarr中的数据更新了的话,可以及时的发送到userA里面去
​
    timer = setInterval(() => {
        //如果userBarr有数据的话
        if(userBarr.length>0){
            //获取到userAarr的第一条数据
            let msg = userBarr[0];
            //获取到了就立即删除第一条数据,或者清空arr
            userBarr.shift();
            //将信息发送给userB
            ws.send(msg)
​
        }
​
​
    }, 1000);
​
})

8、所有代码

userA.html

 <div class="main">
        <div class="msgbox" id="msgbox">
            <div class="msg left" id="leftmsg">
                <img src="./static/B.jpg" width="40" height="40" alt="">
                <section>hello.你好</section>
​
            </div>
​
            <div class="msg right">
                <section>HRLLOW.你好dadsajfjsfjs你好dadsajfjsfjs你好dadsajfjsfjs你好dadsajfjsfjs </section>
                <img src="./static/A.jpg" width="40" height="40" alt="">
​
            </div>
        </div>
​
        <textarea name="" id="txt" placeholder="请输入内容"></textarea>
​
    </div>
    <div class="tips" id="tips">内容不能为空</div>

UserA<script>js

 //建立的实例
    //userA是从服务器接到自己的数据,userA2从服务器接收到userB发过来的数据
    const ws = new WebSocket("ws://localhost:3000/ws/userA")
    const ws2 = new WebSocket("ws://localhost:3000/ws/userA2")
​
    //回调的时候
    ws.onopen = function () {
        //如果得到连接状态是1的话代表链接成功
        console.log("如果状态码是1的话就代表链接成功");
        console.log(ws.readyState);
​
    }
​
    //从服务端收取数据的时候
    ws.onmessage = function (res) {
​
        //这个是自己的数据,将自己加到右侧
        console.log("得到了自己发送到服务器的数据" + res);
        msgbox.innerHTML += rightMsgFn(res.data)
​
        // 添加盒子的滚动
        msgbox.scrollTo({
            top: msgbox.scrollHeight,
            behavior: "smooth"
        })
​
    }
    //从服务器拿到数据的时候
    ws2.onmessage = function (res) {
        //这个是对方的数据,将加到左侧
        console.log("得到了另一个发过来的数据" + res)
        msgbox.innerHTML += leftMsgFn(res.data)
​
        // 添加盒子的滚动
        msgbox.scrollTo({
            top: msgbox.scrollHeight,
            behavior: "smooth"
        })
​
    }
​
    //断开连接的时候
    ws.onclose = function () {
​
        console.log("已经断开了链接");
​
​
    }
​
​
    ws.onerror = function () {
        console.log("链接错误");
​
    }
​
    //左边默认的数据
    let leftinfo = "我是左边默认的一条消息"
    leftmsg.innerHTML = (` <img src="./static/B.jpg" width="40" height="40" alt=""><section>${leftinfo}</section>`)
​
    //生成右侧的数据,自己发送的
    function rightMsgFn(value) {
        var rightinfo = `<div class="msg right">
                <section>${value}</section>
                <img src="./static/A.jpg" width="40" height="40" alt="">
            </div>`
​
        return rightinfo
    }
​
    //生成左侧的数据,其他用户发过来的
    function leftMsgFn(value) {
        var rightinfo = `<div class="msg left">
            <img src="./static/B.jpg" width="40" height="40" alt="">
​
                <section>${value}</section>
            </div>`
​
        return rightinfo
    }
​
    //回车实践
    txt.onkeyup = function (e) {
        console.log(e.keyCode);
        if (e.keyCode === 13) {
            //非空判断
            if (txt.value.trim() != '') {
                let val = txt.value
                //把val发送到服务器
                ws.send(val)
​
                //这是用于写静态验证用的,实际上是需要把从服务器发过来的信息inner到html里,实际上是加在onmessge里面的
                // msgbox.innerHTML += rightMsgFn(val)
​
                txt.value = ''
​
                //到底部判断
​
                var box = document.getElementById("msgbox")
​
                //到底滑动
                box.scroll({
                    top: box.scrollHeight,
                    behavior: "smooth"
                })
​
            } else {
                //显示不能为空
                tips.style.display = "block"
                setTimeout(() => {
                    tips.style.display = ""
​
                }, 1500);
​
            }
​
​
​
        }
​
​
    }

base.css//公用css

body{
    background: #efefef;
    width: 100%;
    height: 100vh;
    box-sizing: border-box;
    padding: 20px;
}
​
.main{
    display: flex;
​
    flex-direction: column;
​
    justify-content: space-between;
    
    height: 100%;
​
}
​
.msgbox{
    height: 70%;
    width: 100%;
    box-sizing: border-box;
    border: 1px solid orange;
    padding: 10px;
    overflow: hidden;
​
    
}
​
textarea{
    padding: 10px;
    height: 20%;
    width: 100%;
    box-sizing: border-box;
    border: 2px solid pink;
    resize: none;
    outline: none;
}
​
.msg{
    display: flex;
    justify-content: space-between;
    margin-bottom: 10px;
}
​
.msg section{
    flex: 1;
    background: yellowgreen;
    border-radius: 6px;
    position: relative;
    padding: 10px;
    box-sizing: border-box;
    word-break: break-all ;
    margin-bottom: 10px;
}
​
​
​
.left section{
​
    margin-left: 10px;
}
​
.left .content{
    display: flex;
    justify-content: space-between;
    margin-bottom: 10px;
​
}
​
​
.right section{
    background: wheat;
​
​
    margin-right: 10px;
}
​
.msg section::after{
    content: "";
    width: 0px;height: 0px;
    position: absolute;
    left: -17px;top: 11px;
    border: 9px solid transparent;
    border-right-color: #9acd32;
}
​
.right section::after{
    border-right-color: transparent;
    border-left-color: wheat;
    
    left: auto;
    right: -17px;
}
​
.tips{
​
    position: fixed;
​
    top: 38%;
    left: 38%;
    width: 120px;
    line-height: 35px;
    text-align: center;
    height: 35px;
    background-color: rgba(0, 0, 0, 0.2);
    display: none;
    transition: all 1s;
}

userB

<body>
    <div class="main">
        <div class="msgbox" id="msgbox">
            <div class="msg left" id="leftmsg">
                <img src="./static/A.jpg" width="40" height="40" alt="">
                <section>hello.你好</section>
​
            </div>
​
            <div class="msg right">
                <section>HRLLOW.你好dadsajfjsfjs你好dadsajfjsfjs你好dadsajfjsfjs你好dadsajfjsfjs </section>
                <img src="./static/B.jpg" width="40" height="40" alt="">
​
            </div>
        </div>
​
        <textarea name="" id="txt" placeholder="请输入内容"></textarea>
​
    </div>
    <div class="tips" id="tips">内容不能为空</div>
</body>

userB.js<script>

 //建立的实例
    //ws是从服务器接到自己的数据,ws2从服务器接收到userA发过来的数据
​
    const ws = new WebSocket("ws://localhost:3000/ws/userB")
    const ws2 = new WebSocket("ws://localhost:3000/ws/userB2")
​
    //回调的时候
    ws.onopen = function () {
        //如果得到连接状态是1的话代表链接成功
        console.log("如果状态码是1的话就代表链接成功");
        console.log(ws.readyState);
​
    }
​
    //从服务端收取数据的时候
    ws.onmessage = function (res) {
​
        //这个是自己的数据,将自己加到右侧
        console.log("得到了自己发送到服务器的数据" + res);
        msgbox.innerHTML += rightMsgFn(res.data)
​
​
        // 添加盒子的滚动
        msgbox.scrollTo({
            top: msgbox.scrollHeight,
            behavior: "smooth"
        })
    }
​
    //从服务器拿到数据的时候
    ws2.onmessage = function (res) {
        //这个是对方的数据,将加到左侧
        console.log("得到了另一个发过来的数据" + res)
        msgbox.innerHTML += leftMsgFn(res.data)
​
​
        // 添加盒子的滚动
        msgbox.scrollTo({
            top: msgbox.scrollHeight,
            behavior: "smooth"
        })
    }
​
    //断开连接的时候
    ws.onclose = function () {
​
        console.log("已经断开了链接");
​
​
​
    }
​
​
    ws.onerror = function () {
        console.log("链接错误");
​
    }
​
    //左边默认的数据
    let leftinfo = "我是左边默认的一条消息"
    leftmsg.innerHTML = (` <img src="./static/A.jpg" width="40" height="40" alt=""><section>${leftinfo}</section>`)
​
    //生成右侧的数据,自己发送的
    function rightMsgFn(value) {
        var rightinfo = `<div class="msg right">
                <section>${value}</section>
                <img src="./static/B.jpg" width="40" height="40" alt="">
            </div>`
​
        return rightinfo
    }
​
​
    //生成左侧的数据,其他用户发过来的
    function leftMsgFn(value) {
        var rightinfo = `<div class="msg left">
            <img src="./static/A.jpg" width="40" height="40" alt="">
                <section>${value}</section>
            </div>`
​
        return rightinfo
    }
​
    //回车实践
    txt.onkeyup = function (e) {
        console.log(e.keyCode);
        if (e.keyCode === 13) {
            //非空判断
            if (txt.value.trim() != '') {
                let val = txt.value
                //把val发送到服务器
                ws.send(val)
​
                //这是用于写静态验证用的,实际上是需要把从服务器发过来的信息inner到html里,实际上是加在onmessge里面的
                // msgbox.innerHTML += rightMsgFn(val)
​
                txt.value = ''
​
                //到底部判断
​
                var box = document.getElementById("msgbox")
​
                //到底滑动
                box.scroll({
                    top: box.scrollHeight,
                    behavior: "smooth"
                })
​
            } else {
                //显示不能为空
                tips.style.display = "block"
                setTimeout(() => {
                    tips.style.display = ""
​
                }, 1500);
​
            }
​
​
​
        }
​
​
    }

websoket.js

const express = require("express")
const expressWs = require("express-ws")
const router = express.Router();
expressWs(router);
​
​
//存放user1的数据
let userAarr = [];
​
//存放user2的数据let userBarr = [];
​
​
//userA发送过来的数据,将数据存到userAarr中,并将数据返回回去
router.ws("/userA",ws=>{
    ws.on("message",(msg)=>{
        //将userA中发过来的数据储存到userAarr这个数组中
        userAarr.push(msg)
        
        // 存了之后重新把拿到的数据返回给客户端,起验证作用
        ws.send(msg)
    })
​
​
})
​
//userB接收userA的数据,,/userB是接收自己的数据,userB2是接收从a发过来的数据
router.ws('/userB2',ws=>{
    //清空定时器
    let timer = null
    //当断开连接的时候需要清空服务器
    ws.on('close',()=>{
        if(timer){
            clearInterval(timer)
        }
    })
​
    //定时去触发send,如果userAarr中的数据更新了的话,可以及时的发送到userB里面去
​
    timer = setInterval(() => {
        //如果userAarr有数据的话
        if(userAarr.length>0){
            //获取到userAarr的第一条数据
            let msg = userAarr[0];
            //获取到了就立即删除第一条数据,或者清空arr
            userAarr.shift();
            //将信息发送给userB
            ws.send(msg)
​
        }
​
​
    }, 1000);
​
})
​
​
​
​
​
//userB发送过来的数据,将数据存到userBarr中,并将数据返回回去
router.ws("/userB",ws=>{
    ws.on("message",(msg)=>{
        //将userB中发过来的数据储存到userBarr这个数组中
        userBarr.push(msg)
        
        // 存了之后重新把拿到的数据返回给客户端,起验证作用
        ws.send(msg)
    })
​
​
})
​
​
​
//userA接收userB的数据,,/userA是接收自己的数据,userA2是接收从a发过来的数据
router.ws('/userA2',ws=>{
    //清空定时器
    let timer = null
    //当断开连接的时候需要清空服务器
    ws.on('close',()=>{
        if(timer){
            clearInterval(timer)
        }
    })
​
    //定时去触发send,如果userBarr中的数据更新了的话,可以及时的发送到userA里面去
​
    timer = setInterval(() => {
        //如果userBarr有数据的话
        if(userBarr.length>0){
            //获取到userAarr的第一条数据
            let msg = userBarr[0];
            //获取到了就立即删除第一条数据,或者清空arr
            userBarr.shift();
            //将信息发送给userB
            ws.send(msg)
​
        }
​
​
    }, 1000);
​
})
//导出
module.exports = router;

效果展示

博客园https://www.cnblogs.com/jiali1010/articles


一沙一世界,一花一天堂。君掌盛无边,刹那成永恒。