SocketIO の 聊天练习

举报
空城机 发表于 2022/05/20 09:01:36 2022/05/20
【摘要】 基于socketIO的双向通信,准备制作一个聊天界面。聊天界面的大体样式参考于微信界面,后端用了node和socketIO

socketIO

socketIO概念

一个库,基于 Node.js 的实时应用程序框架。可以在浏览器和服务器之间实现实时,双向和基于事件的通信。它适用于每个平台、浏览器或设备,同样注重可靠性和速度。

与websocket关系

websocket出现之前,客户端和服务器之间的即时通信往往依赖于客户端进行轮询操作,websocket使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据,同时也降低了服务器的性能消耗。但是,websocket并不能兼容所有的浏览器,所以socketIO是不仅包含了websocket,还对轮询(Polling)机制以及其它的实时通信方式封装成了通用的接口


聊天练习结构

基于socketIO的双向通信,准备制作一个聊天界面。

前端:聊天界面的大体样式参考于微信界面
后台:使用node.js + socketIO

在动手之前,首先要规划一下需要有的功能(虽然很多是我自己后面想到再加的 🐶 )


项目步骤

  1. 首先使用yarn init创建一个项目
  2. 下载需要的express和socket.io, 命令:yarn add socket.io express

  1. 在main.js中定义好需要io, 并且为了防止socketIO连接时产生跨域问题,可以使用cors进行设置
const express = require('express');
const app = express();
const { Server } = require('socket.io');
const http = require('http');
const server = http.createServer(app);
const io = new Server(server, {
    cors: {
        allowedHeaders: ["chat-room"],  // 被允许的请求头
    },
});
  1. 在main.js中编写io的连接监听,测试客户端是否连接到服务器,这里使用of建立一个房间

这里可以参照官方的实例(不过是非跨域的):https://socket.io/get-started/chat

如果想要处理跨域可参考:https://socket.io/docs/v4/handling-cors/#cors-header-access-control-allow-origin-missing

io.of('my-chatroom').on('connection', (socket)=>{
    console.log('有新用户连接了');
})
//服务器本地主机的数字   注意这里不是app
server.listen(3007, function(){
    console.log("http:127.0.0.1:3007 启动了。。。");
})

前端测试页面:(这里的socket.io.js是从node_module中拿出来的)

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <script src="./js/socket.io.js"></script>
    <title>Document</title>
</head>
<body>
    <script>
        socket = io('http://127.0.0.1:3007/my-chatroom', {
            // 用于通知服务器在真正的请求中会采用chat-room请求头
            extraHeaders: {
                "chat-room": "123456789"
            },
        });
    </script>
</body>
</html>

然后使用nodemon运行main.js, 并且使用live server运行前端测试的html页面,可以看到终端中服务器和客户端已经通过socketIO连接了。

  1. 本次使用的数据没有写入在数据库或其他文件内,而是直接定义的。
    数据格式基本如下所示:
// 数据格式
"userList": [
    // 用户
    {
        "name": "张三",   // 姓名
        "password": "123",   // 密码
        "online": false,   // 是否在线
        "nowSocketId": null,   // 当前的socketid
        "headPortrait": "http://127.0.0.1:3007/img/cherry.png",  // 头像
        // 聊天信息
        "infos": [
            // 聊天对象
            {
                "name": "李四",   // 对象名
                // 最后一次信息
                "lastMsg": {
                    "msg": "这是最后一次了",
                    "time": 78495
                },
                // 所有信息
                "allMsg": [
                    {
                        "msg": "这是最后一次了",
                        "time": 78495
                    }
                ]
            },
        ]
    },
]
  1. 同时为了避免代码过多,新建一个routerGet.js来应对http访问的请求,前端访问的get请求方法也是使用promise自定义的

image.png

// 利用promise封装一个ajax,可以使用then返回数据
        function getAjaxNew(config) {
            let promise = new Promise((resolve, reject) => {
                const req = new XMLHttpRequest();
                let readystatechange = ()=>{
                    if(req.readyState === 4) {  //判断响应状态是否成功
                        let responseHeaders = req.getAllResponseHeaders(); //获取响应头信息
                        let data = req.responseText; //获取响应数据
                        // 数据处理
                        resolve(req.response);
                    }
                }
                req.onreadystatechange = readystatechange;
                if (config.dataType)
                    req.responseType = config.dataType;

                if (config.method == 'GET') {
                    if (config.options) {
                        req.open(config.method, config.url + '?' + config.options.join('&'), true); // true代表异步
                    }
                    else 
                        req.open(config.method, config.url , true); // true代表异步
                    req.send();//发送请求
                } else {
                    req.open(config.method, config.url, true); // true代表异步
                    if (config.options) {
                        let data = JSON.stringify(config.options);
                        req.send(data);//发送请求
                    } else{
                        req.send();//发送请求
                    }
                }
            })
            return promise;
        }

如果登录成功,再获取用户列表,并且返回用户列表信息时为了安全性,需要将列表中一些关键数据比如password等进行隐藏,置空。

// 隐藏关键信息
function dataDeal(arr, myName) {
    arr = JSON.parse(JSON.stringify(arr));
    for(let i = 0; i <arr.length; i++) {
        let item = arr[i];
        item.password = null;
        for(let p of item.infos) {
            p.allMsg = null;
        }
        if (myName == item.name) {
            arr.splice(i, 1)
        }
    }
    return arr;
}

  1. 在socketIO的后台方法中,介绍一下消息的收发
  • 后台通过socket.on接收前端传来的请求,并且通过socket.emit来发送数据给请求者
  • 登录或者离线,那么就要发送给除自己之外的用户,可以使用.broadcast()来进行广播消息
  • 发送给指定的用户,可以使用.to()方法,传入的参数是指定用户的socketid

更多方法可以查看官方API:https://socket.io/docs/v4/server-api/
或者书栈上的中文文档: https://www.bookstack.cn/read/veaba-socket.io-docs/README.md

io.of('my-chatroom').on('connection', (socket)=>{
    // 当有用户登录时
    socket.on('login', (info)=>{
        // uName:登录者的名称  needToEmit:是否需要通知其他用户
        let uName = info.auth.name, needToEmit = false;
        ......
        // 通知其他用户谁登录了  broadcast:除自己以外广播消息
        socket.broadcast.emit('newUserLogin', {
            newUser: uName
        })
    })
    // 接收新消息,存入数据,发送给需要提醒的某人
    socket.on('chatSend', (data)=>{
        ......
        // 如果键存在,则发送消息
        if (anotherid) {
            // to 发送给指定socketid用户
            socket.to(anotherid).emit('hasNewMsg', {
                originName: data.myName,
                time: data.time,
                con: data.con
            })
        }
    })
})

效果:

登录和离线左侧的头像栏会改变颜色
聊天的话也会时时进行

总结

其实这个练习不足之处还是挺多的,没有把数据保存到下来,并且一些安全性问题和一些交互问题上其实也有待考虑。但是大致上使用socketIO做好了,因为本次目的还是为了学习socketIO

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。