前言
在详解 HTTP系列之一讲到HTTP/2.0 突破了传统的“请求-问答模式”这一局限,实现了服务器主动向客户端传送数据。而本章将通过一种在单个TCP连接上进行全双工通信的协议–websocket协议
,实现一个简单的聊天室(本文转载自xing.org1^大佬的博客,详情请点击此处跳转)
核心要点
聊天室的核心要点是由服务器向每个正在连接的用户发送消息,也就是上面讲到的改变。如果不是这样,只能是客户端以一个很短的时间间隔向服务端请求数据。
要做到广播,就需要server.connections
,这个数组记录了所有连接到websocket服务器的用户(也就是进入聊天室的人),通过遍历这个数组,然后给数组中每个连接进来的用户对象发送消息即可。
实现这一功能的代码如下:
const ws = require('nodejs-websocket');
const server = ws.createServer((connect)=>{
/* ...... */
// 对连接到服务端的每个对象发送数据
function broadcast(jsonStr){
server.connections.forEach((element)=>{
//切记,send参数必须是字符串类型,所以这里将对象字符串化
element.send(JSON.stringify(jsonStr));
});
}
})
聊天室实现代码
环境:
- nodejs
- npm install nodejs-websocket
客户端 test.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>小石头的群聊</title>
</head>
<body>
<div class="container">
<input type="text" placeholder="请输入要发送的内容" id="ipt">
<button id="btn">发送</button>
<button id="closeBtn">退出群聊</button>
<p>
群聊内容如下:
</p>
<div id="rst" style="width: 800px;height: 500px;background-color: greenyellow;"></div>
</div>
<script> // 聊天室功能 const ws = new WebSocket('ws://localhost:8081'), ipt = document.getElementById('ipt'), btn = document.getElementById('btn'), closeBtn = document.getElementById('closeBtn'), content = document.getElementById('rst'); // 初次进入聊天室,给个提醒 ws.addEventListener('open', (e) => {
console.log('加入聊天室成功', e); content.appendChild(creatEle('您已成功加入小石头的群聊~')); // // 聊天区滚动到底 goBottom(); }); // 接收ws服务器发送的消息,并展示到div#rst当中 ws.addEventListener('message', (e) => {
console.log(e.data); content.appendChild(creatEle(e.data)); goBottom(); }); // 一个带滚动条的DIV元素,怎么让它的滚动条位置默认保持在最底部? function goBottom() {
content.scrollTop = content.scrollHeight; } // btn被点击时发送请求 btn.onclick = function () {
btnClickEvent(); } ipt.addEventListener('keydown', (e) => {
if (e.keyCode === 13) {
btnClickEvent(); } }); // close-btn被点击时退出群聊 closeBtn.onclick = function () {
ws.close(); } // 创建一个p标签,存储对应内容,以追加到内容展示区域 function creatEle(str) {
console.log(str.indexOf('{')); const TYPE_LEAVE = 0; //leave,离开 const TYPE_ENTRY = 1; //entry,进入 const TYPE_SPEAK = 2; //speak,发言 const eleP = document.createElement('p'); if (str.indexOf('{') == 0) {
let parseStr = JSON.parse(str); eleP.innerHTML = `<span class="timer">${
parseStr.time}</span><br/><span class="msg">${
parseStr.msg}</span>`; switch (parseStr.type) {
case TYPE_LEAVE: eleP.className = 'leave'; break; case TYPE_ENTRY: eleP.className = 'entry'; break; case TYPE_SPEAK: eleP.className = 'speek'; break; default: eleP.className = 'default'; break; } } else {
eleP.innerText = str; } return eleP; } // 添加回车发送消息事件 function btnClickEvent() {
if (ipt.value.length <= 0) {
alert('不能发送空消息'); return; } ws.send(ipt.value); ipt.value = ''; } </script>
</body>
</html>
服务端 server.js:
/* * @Author: @Guojufeng * @Date: 2019-06-02 19:42:06 * @Last Modified by: @Guojufeng * @Last Modified time: 2019-06-02 21:39:56 * 优化 - 加入消息类型和当前时间的响应 */
const ws = require('nodejs-websocket');
const POST = 8081;
let count = 0;//记录加入人数
const TYPE_LEAVE = 0;//leave,离开
const TYPE_ENTRY = 1;//entry,进入
const TYPE_SPEAK = 2;//speak,发言
const server = ws.createServer((connect)=>{
count++;// 有人加入,计数加一
connect.userName = `用户${
count}`;//connet是一个对象,新增一个属性用以标记该用户的名字(标识)
// 1、通知所有人,connect用户加入群聊
broadcast({
type: TYPE_ENTRY,
msg: `${
connect.userName}加入群聊。`
});
// 2、通知所有人,connect用户发言
connect.on('text',(rst)=>{
broadcast({
type: TYPE_SPEAK,
msg: `${
connect.userName}: ${
rst}`
});
});
// 3、通知所有人,connect用户退出群聊
connect.on('close',(code,reason)=>{
//个人认为利用code和reason这里,还可以模拟微信群聊中,用户被群主踢出群的情况
broadcast({
type: TYPE_LEAVE,
msg: `${
connect.userName}退出了群聊。`
});
count--;// 有人退出,计数减一
});
// error事件
connect.on('error',()=>{
console.log('发生异常');
})
});
// 广播功能代码
function broadcast(cont){
// 利用connections是存放所有加入群聊用户的数组,来给所有人广播内容
let sendCont = {
type: cont.type,
msg: cont.msg,
time: new Date().toLocaleTimeString()
};
server.connections.forEach((element)=>{
element.send(JSON.stringify(sendCont));//切记,send参数必须是字符串类型,所以这里将对象字符串化
});
}
server.listen(POST,()=>{
console.log(`${
POST}服务器启动成功。`)
});
启动服务:
- nodejs执行server.js文件,并成功监听到对应端口
- 浏览器打开index.html(可开启多个页面模拟多个用户),进行测试