node.jswebsocket使用socket.io库实现实时聊天室

小辣抓      2022-02-09     756

关键词:

认识websocket

WebSocket protocol 是HTML5一种新的协议。它实现了浏览器与服务器全双工通信(full-duple)。一开始的握手需要借助HTTP请求完成。

其实websocket 并不是很依赖Http协议,它也拥有自己的一套协议机制,但在这里我们需要利用的socket.io 需要依赖到http 。
之前用java jsp写过一个聊天,其实实现逻辑并不难,只是大部分时间都用在UI的设计上,其实现原理就是一个基于websocket的通信,要想做一个好的聊天室,我觉得大部分精力可能更应该花在与用户的视觉层交互上。

废话不闲扯,我们先来看一下websocket 与传统的ajax 有什么不同之处。
在之前,如果我们想要获取到服务器更新的信息,我们可以使用ajax 轮询来完成,然而,这样做的弊端是增大了我们与服务器的交互次数,然而极大部分的交互都是无意义的,因为我们只是做一个询问,如果没有任何新的信息,我们几乎什么都不用做,因此这样会极大的浪费服务器资源和带宽。
然而使用websocket 会使客户端与服务器建立一个长连接,并且,当服务器有新消息时可以主动推送到客户端,所以我们可以不用一次次的去询问服务器是否有新消息,而是直接由服务器主动推送到客户端,这样在无消息的状态下,客户端不会频繁的去请求服务器。
使用websocket 的特点在于服务器可以主动推送消息到客户端。

使用socket.io 库实现实时聊天

这也是这篇博文的主题之处。socket.io发布到npm 平台上,我们可以直接用npm 来安装到**当前**node_modules目录下。

npm install socket.io --save 

下面我们就可以直接使用require 方法来将这个模块引入

const socket = require("socket.io");

在创建此websocket 服务器之前,它需要依赖于一个已经创建好的http服务器。

let socketServer = socket.listen(require("http").createServer((req,resp) => {
    //返回页面
    resp.end(require("fs").readFileSync("./socketIOTest1.html"));
}).listen(9999,"localhost",() => {console.log("listening");}));

在上述代码中socketIOTest1.html 是在当前目录下的一个html文件,在下面我会贴上详细的代码,这里先稍稍带过。

在websocket 服务器对象中有一个connection事件,这个事件在有客户端连接到socket服务器时被触发。下面我们监听这个事件,打印一句话来表示有用户连接。

//监听connection 事件
socketServer.on("connection",socket => {
    console.log("有一用户连接");
}

上述代码中,callback有一个参数socket为连接到客户端的一个socket端口对象,这个对象有一个message 事件,当客户端有消息推送到服务器时,事件循环会取出这个事件与之对应的回调函数并执行。

socket.on("message",msg => {
    console.log(msg);
});

同时,socket对象还可以监听disconnect 事件,来监听用户断开连接的情况

socket.on("disconnect",() => {
    console.log("有一用户退出连接");
});

因为我们这次的主题是要创建一个能够实时聊天的聊天室,因此光有这些是不够的,我们还需要一个能够与用户交互的客户端。
下面是我的socketIOTest代码:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
</head>
<body>
    <textarea name="" id="content" cols="30" rows="10" ></textarea>
    <input id="write" type="text" placeholder="please write content here">
    <input id="send" type="button" value="send" />
    <script src="./socket.io/socket.io.js"></script>
    <script>
        let send = document.getElementById("send");
        let write = document.getElementById("write");
        let content = document.getElementById("content");
        let socket = io.connect();
        //发送消息
        send.onclick = () => {
            let msg = write.value;
            // content.innerHTML = content.value + msg + "\n";
            socket.send(msg);
        };
        //接收到消息
        socket.on("message",msg => {
            console.log("从服务器接收到的消息 : " + msg);
            //更新内容
            content.innerHTML = content.value + msg + "\n";
        });
        socket.on("disconnect",() => {
            console.log("与服务器断开连接");
        });
    </script>
</body>
</html>

在上述代码中,我用script标签引入了一个socket.io.js文件,这个文件不需要另外去下载,而直接引入即可,因为socket.io.js是被包含于socket.io模块中,在上面node的程序代码中,我们通过require方法引入了socket.io模块,因此我们可以直接通过相对路径访问到它。

<script src="./socket.io/socket.io.js"></script>

接下来我们就可以在script标签中使用如同服务端的代码。

    let socket = io.connect();

使用io.connect()方法连接到websocket服务器,该方法返回一个与连接的服务器与之对应的一个socket端口对象。

下面我们同样监听message 和 disconnect事件。

//接收到消息
socket.on("message",msg => {
    console.log("从服务器接收到的消息 : " + msg);
    //更新内容
    content.innerHTML = content.value + msg + "\n";
});
socket.on("disconnect",() => {
    console.log("与服务器断开连接");
});

为了更能突出websocket的作用,在html代码中,我只使用了一个textarea标签来显示内容,两个input标签用于发送。
使用socket对象的send方法就能使消息在服务器与客户端进行消息传递。

websocket群聊实现

现在我们假设一个场景,有u1和u2两个用户,同时连接到服务器,那么我们怎么使他们互相通信呢,实现的方法及其简单。当u1连接到服务器,在服务器中,使用一个map键值对把与u1对应的socket对象进行保存。

//创建一个用于放置用户对象的map
let map = new Map();
//用于记录用户数量的变量,并初始化为0
let userCount = 0;
//监听connection 事件
socketServer.on("connection",socket => {
    console.log("有一用户连接");
    map.set(++userCount,socket);
    //...
}); 

与此同时,u2也连接上服务器,也由该map把与u2与之对应的socket对象进行储存。
现在,u1点击了send按钮发送一条消息至服务器,服务器收到消息后遍历map,转发给所有socket对象,实现群聊的实时通信。

socketServer.on("connection",socket => {
    console.log("有一用户连接");
    map.set(++userCount,socket);
    //监听客户端来的信息
    socket.on("message",msg => {
        //从客户端接收的消息
        //遍历所有用户
        map.forEach((value,index,arr) => {
            value.send(msg);
        });
    });
}); 

下面我贴上服务端的完整代码,仅供参考

const socket = require("socket.io");
//创建一个websocket服务器
let socketServer = socket.listen(require("http").createServer((req,resp) => {
    //返回页面
    resp.end(require("fs").readFileSync("./socketIOTest1.html"));
}).listen(9999,"localhost",() => {console.log("listening");}));

//创建一个用于放置用户对象的map
let map = new Map();
//用于记录用户数量的变量,并初始化为0
let userCount = 0;

//监听connection 事件
socketServer.on("connection",socket => {
    console.log("有一用户连接");
    map.set(++userCount,socket);
    //监听客户端来的信息
    socket.on("message",msg => {
        //从客户端接收的消息
        //遍历所有用户
        map.forEach((value,index,arr) => {
            value.send(msg);
        });
    });
    //监听客户端退出情况
    socket.on("disconnect",() => {
        console.log("有一用户退出连接");
    });
}); 

这里写图片描述
这里写图片描述

websocket私聊实现

在说私聊的实现之前,我们首先要找到对于每一个用户的唯一标识,在通常的项目开发中,我们都使用用户的用户名进行标识,每个用户通过注册获得与之对应的用户名。将用户名保存在数据库中利用主键防止重复。

实现私聊的方法有很多种,这里我的实现方法是这样的:
① 当用户连接时,把用户的socket端口对象使用map进行储存,储存的key 为用户的socket对象,value为用户的用户名,写一个方法用于更新客户端列表
② 用户默认用户名为 <未命名>,指定自定义用户名时,使用socket.emit方法触发服务端的某个事件,遍历map找到与之对应的key,进行value修改
③ 发送消息时,根据选择列表来指定要发送的人,在服务端,遍历map,找到要发送到的用户名,进行发送,同时更新到自己的聊天框

以上就是私聊的简单实现。

下面看一下具体代码:
//Node.js

const socket = require("socket.io");
//创建一个websocket服务器
let socketServer = socket.listen(require("http").createServer((req,resp) => {
    //返回页面
    resp.end(require("fs").readFileSync("./socketIOTest1.html"));
}).listen(9999,"localhost",() => {console.log("listening");}));

//创建一个用于放置用户对象的map
let map = new Map();
//用于记录用户数量的变量,并初始化为0
let userCount = 0;
//遍历map 
let scanMap = func => {
    try{
        map.forEach((value,index,arr) => {
            func(value,index,arr);
        });
    }
    catch(e){
        if(e.message == "break"){
            return;
        }
        else{
            throw e;
        }
    }
}

//通知客户端弹出对话框
let showDialog = (socket,msg) => {
    socket.emit("showDialog",msg);
}

//更新用户列表
let updateList = socket => {
    let userArr = [];
    scanMap((value,index) => {
        if(value != undefined){
            userArr.push(value);
        }
    });
    socket.emit("newUser",userArr);
}

//监听connection 事件
socketServer.on("connection",socket => {
    console.log("有一用户连接");
    //初始化存储当前socket对象
    map.set(socket,"<未命名>");
    //将用户信息写入map
    socket.on("getUser",user => {
        //修改名称
        map.set(socket,user);
        scanMap((value,index) => {
            updateList(index);
        });
    });
    //通知所有客户端更新列表
    scanMap((value,index) => {
        updateList(index);
    });
    //监听客户端来的信息
    socket.on("message",msg => {
        //从客户端接收的消息
        let sender;
        //遍历所有用户
        scanMap((value,index) => {
            if(index == socket){
                sender = value;
            }
        });
        scanMap((value,index) => {
            if(msg.person == "all"){
                index.send(sender + " : " + msg.msg);
            }
            else if(msg.person == value){
                socket.send(sender + " : " +msg.msg);
                index.send(sender + " : " +msg.msg);
                throw new Error("break");
            }
        });
    });
    //监听客户端退出情况
    socket.on("disconnect",() => {
        //用户退出,从map里删除该用户
        map.set(socket,undefined);
        //通知所有用户更新列表
        scanMap((value,index) => {
            updateList(index);
        });
        console.log("有一用户退出连接");
    });
}); 

客户端:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
</head>
<body>
    <textarea name="" id="content" cols="30" rows="10" ></textarea>
    <input id="write" type="text" placeholder="please write content here">
    <input id="send" type="button" value="send" />
    <input type="text" id="user" placeholder="user">
    <select style="width: 100px;" size="2" name="" id="userList">
        <option value="all">群聊</option>
    </select>
    <script src="./socket.io/socket.io.js"></script>
    <script>
        let send = document.getElementById("send");
        let write = document.getElementById("write");
        let content = document.getElementById("content");
        let user = document.getElementById("user");
        //用户列表
        let userList = document.getElementById("userList");
        let socket = io.connect();
        //判断用户名是否为空
        let isUserEmpty = () => {
            if(user.value == ""){
                alert("请填写用户名");
                return false;
            }
            else {
                return true;
            }
        }
        //监听用户名变化
        let oldUser;
        user.onblur = () => {
            if(isUserEmpty()){
                //防止重复发射
                if(oldUser == user.value){return;}
                oldUser = user.value;
                socket.emit("getUser",user.value);
            }
        }       
        //发送消息
        send.onclick = () => {
            if(isUserEmpty()){
                let msg = write.value;
                // content.innerHTML = content.value + msg + "\n";
                socket.send({msg:msg,person:userList.value});
            }
            if(select.value == ""){
                alert("请选择一个聊天对象");
            }
        };
        //接收到消息
        socket.on("message",msg => {
            console.log("从服务器接收到的消息 : " + msg);
            //更新内容
            content.innerHTML = content.value + msg + "\n";
        });
        socket.on("disconnect",() => {
            console.log("与服务器断开连接");
        });
        //新用户加入聊天室
        socket.on("newUser",arr => {
            userList.innerHTML = "";
            let all = document.createElement("option");
            all.innerHTML = "群聊";
            all.setAttribute("value","all");
            userList.appendChild(all);
            //添加新用户
            arr.forEach((value,index) => {
                console.log("value :" + value + "index :" + index);
                let option = document.createElement("option");
                option.innerHTML = value;
                option.setAttribute("value",value);
                userList.appendChild(option);
                userList.setAttribute("size",userList.children.length);
            });
            //默认选中群聊
            userList.value = "all";
        });
        //接收服务器需要弹出对话框的需求
        socket.on("showDialog",msg => {
            alert(msg);
        });
    </script>
</body>
</html>

这里写图片描述

代码的具体我就不在详细讲解,都标有注释,由于只是用于博文,整体代码没有重构优化,大家看不懂的可以回复我,或者有什么地方错误请指出,我会及时改正。

另外在这个聊天室中,当用户刷新频率较快时,websocket会出现伪连接现象。

下面附上我的github地址,大家可以下载我的源码进行修改学习,共勉。
https://github.com/HaoDaWang/chat

Laravel Forge - Node.js Websocket 超时

】LaravelForge-Node.jsWebsocket超时【英文标题】:LaravelForge-Node.jsWebsocketTimeout【发布时间】:2016-06-0816:36:35【问题描述】:我有一个简单的socket.io节点服务器托管在我的laravelforge/数字海洋服务器上。使用宅基地在我的本地机器上找到... 查看详情

SocketRocket 使 Node.js WebSocket 崩溃

】SocketRocket使Node.jsWebSocket崩溃【英文标题】:SocketRocketcrashesNode.jsWebSocket【发布时间】:2013-11-0122:06:16【问题描述】:我写了一个简单的Node.jsWebSocketchatserver。为了运行服务器,我使用了foremanstart和仅包含以下行的Procfile:chat:npm... 查看详情

如何找到 node.js websocket 消息的连接?

】如何找到node.jswebsocket消息的连接?【英文标题】:HowdoIfindanode.jswebsocketmessage\'sconnection?【发布时间】:2011-05-2817:45:33【问题描述】:我正在为node.jswebsocket聊天服务器编写以下代码,并且想知道conn周围的变量生命周期问题是什... 查看详情

GraphQL 订阅和 WebSocket 协议有啥区别?

...:我有两个方面。一方面,我通过使用库/包(如ws(Node.jsWebSocket库)或Socket.io)直接使用WebSo 查看详情

如何将 Node.js WebSocket 服务器部署到 Amazon Elastic Beanstalk?

】如何将Node.jsWebSocket服务器部署到AmazonElasticBeanstalk?【英文标题】:HowtodeployaNode.jsWebSocketservertoAmazonElasticBeanstalk?【发布时间】:2015-01-0604:19:31【问题描述】:使用ElasticBeanstalkWeb控制台,我启动了一个新的WebServer1.0环境:预定... 查看详情

Websockets - 自定义排队系统有意义吗?

...00:21【问题描述】:我正在为基于回合的Unity游戏编写node.jswebsocket后端。这是我第一次使用node.js和websockets,所以我想我会这样做,以便玩家可以重新连接,并会收到他们错过的任何消息。然后我开始在游戏中实现它 查看详情

javascript: 打印 websocket 客户端 IP

...etclientIP【发布时间】:2014-02-2213:04:42【问题描述】:node.jsWebSocket示例代码sn-p我有一个使用express的简单node.js应用程序。现在每次客户端连接到节点服务器时,我都会看到字符串“新客户端已连接”,但我想知道新客户端拥有哪... 查看详情

扩展执行繁重计算的 node.js websocket 服务器

】扩展执行繁重计算的node.jswebsocket服务器【英文标题】:Scalinganode.jswebsocketserverthatdoesheavycomputations【发布时间】:2020-08-1507:32:36【问题描述】:我有一个node.js服务器,它有一个接收大量消息的websocket连接。该服务器还执行繁重... 查看详情

无法从 React 客户端向 Node.js websocket 服务器发送消息

】无法从React客户端向Node.jswebsocket服务器发送消息【英文标题】:Can\'tsendamessagetoNode.jswebsocketserverfromReactclient【发布时间】:2017-07-1722:20:29【问题描述】:我有以下代码作为服务器:varhttp=require(\'http\'),cors=require(\'cors\'),connect=req... 查看详情

JSON 编码的缓冲区数据未正确编码

...述】:我目前正在尝试json_encode一些缓冲区数据并在node.jswebsocket服务器上对其进行解码,但未成功。我正在使用一个docker容器,它提供我格式化为HTML的ANSI数据。我使用SymfonyProcesscomponent从d 查看详情

如何断开与本地主机的连接?

...:是否可以断开与本地主机的连接?我正在编写一个Node.jsWebSocket服务器,我想在本地测试如果连接错误关闭会发生什么。如果我在远程测试,我只会关闭Wi-Fi,但这不会断开我与localhost的连接。想法?【问题讨论】:【参考方案... 查看详情

使用socket进行通信

客户端通常可使用Socket的构造器来连接到指定服务器,Socket通常可使用如下两个构造器。Socket(lnetAddress/String remoteAddress,int port):创建连接到指定远程主机、远程端口的Socket,该构造器没有指定本地地址、本地端口,默认... 查看详情

python websocket客户端连接状态 - 如何更新以获得正确的状态?

...0103:16:08【问题描述】:我可以使用pythonwebsocket连接到node.jswebsocket服务器。当我询问pythonwebsocket客户端是否连接到服务器时,不 查看详情

在 IO::Socket::SOCKS 代理上使用 IO::Socket::SSL

】在IO::Socket::SOCKS代理上使用IO::Socket::SSL【英文标题】:UsingIO::Socket::SSLoveranIO::Socket::SOCKSproxy【发布时间】:2019-05-0302:30:36【问题描述】:我想混合使用SSL和SOCKS套接字。换句话说,我想通过SOCKS代理发送TLS加密数据,我想使用IO:... 查看详情

使用 socket.io 更新使用通知

】使用socket.io更新使用通知【英文标题】:updateusenotificationslivewithsocket.io【发布时间】:2018-04-2921:00:57【问题描述】:您好,我正在尝试使用套接字io从数据库获取用户通知。这是我尝试获取数据并将其发送回用户的方式if(req.use... 查看详情

何时使用 socket.io 何时使用 ajax

】何时使用socket.io何时使用ajax【英文标题】:Whentousesocket.ioandwhentouseajax【发布时间】:2013-06-2010:22:42【问题描述】:我一直在用nodejs编程,研究了如何使用socket.io和ajax调用节点服务器。socket.io是否旨在取代ajax?我很想知道在... 查看详情

socket基本使用

 导入头文件#import<sys/socket.h>#import<netinet/in.h>#import<arpa/inet.h>定义属性@interfaceViewController()///客户端socket@property(nonatomic,assign)intclientSocket;@end建立socket/**参数domain: 查看详情

在nodejs中使用socket.io和net socket

】在nodejs中使用socket.io和netsocket【英文标题】:Usingsocket.iowithnetsocketinnodejs【发布时间】:2016-01-2119:24:36【问题描述】:有没有办法可以在nodejs中使用socket.io和netsocket?所以最后我有监听端口的主服务,等待连接(网络套接字)... 查看详情