【WebSocket】使用ws搭建一个简单的在线聊天室

前言

什么是WebSockets?

WebSockets 是一种先进的技术。它可以在用户的浏览器和服务器之间打开交互式通信会话。使用此 API,你可以向服务器发送消息并接收事件驱动的响应,而无需通过轮询服务器的方式以获得响应。
webscokets 包括webscoket接口、CloseEvent接口 和MessageEvent接口。

什么是WebSocket?

WebSocket是一种基于TCP的全双工通信协议,可以更好的节省资源和带宽实现即时通讯,是一种持久化的协议。

什么是ws?

ws 是一个简单易用、速度极快、经过全面测试的 WebSocket 客户端和服务器实现库。

搭建简易WebSocket服务器

安装 ws

npm安装

npm install ws

使用ws

ws使用之前需要导入。

ws属性介绍

new WebSocketServer(options[, callback])

  • options {Object} 配置对象。
    • backlog {Number} 待处理连接队列的最大长度。
    • clientTracking {Boolean} 指定是否跟踪客户。
    • handleProtocols {Function} 用于处理 WebSocket 子协议的函数。请参见下面的说明。
    • host {String} 绑定服务器的主机名。
    • maxPayload {Number} 允许的最大报文大小(字节)。默认为 100 MiB(104857600 字节)。
    • noServer {Boolean} 启用无服务器模式。
    • path {String} 只接受与此路径匹配的连接。
    • perMessageDeflate {Boolean|Object} 启用/禁用 permessage-deflate 功能。
    • port {Number} 绑定服务器的端口。
    • server {http.Server | https.Server} 预先创建的 Node.js HTTP/S 服务器。
    • skipUTF8Validation {Boolean} 指定是否跳过文本和关闭信息的 UTF-8 验证。默认为 false。只有在客户端受信任时才设为 true。
    • verifyClient {Function} 用于验证输入连接的函数。请参阅下文说明。(不鼓励使用)。
    • WebSocket {Function} 指定要使用的 WebSocket 类。它必须是从原始 WebSocket 扩展而来。默认为 WebSocket。
  • callback {Function} 回调函数,将添加到 http 服务’listening’事件上。

创建一个新的服务器实例。必须提供端口、服务器或 noServer 中的一个,否则会出错。如果设置了端口,HTTP 服务器将自动创建、启动和使用。若要使用外部 HTTP/S 服务器,请仅指定服务器或 noServer。在这种情况下,必须手动启动 HTTP/S 服务器。无服务器 "模式允许 WebSocket 服务器与 HTTP/S 服务器完全分离。例如,这样就可以在多个 WebSocket 服务器之间共享一个 HTTP/S 服务器。

事件模型

  • Event:‘close’ : 服务器关闭时发出。只有在内部创建 HTTP 服务器时,该事件才会依赖于 HTTP 服务器的 "关闭 "事件。在其他所有情况下,该事件都是独立发生的。
  • Event: ‘connection’ : 握手完成时发出。请求是客户端发送的 http GET 请求。用于解析权限头、cookie 头和其他信息。
    • 参数 1 websocket {WebSocket}
    • 参数 2 request {http.IncomingMessage}
  • Event: ‘error’ : 当底层服务器出现错误时发出。
    • 参数 1 error {Error}
  • Event: ‘headers’ : 在作为握手的一部分将响应标头写入套接字之前发出。这样,您就可以在发送前检查/修改标头。
    • 参数 1 headers {Array}
    • 参数 2 request {http.IncomingMessage}
  • Event: ‘listening’ : 当底层服务器已绑定时发出。
  • Event: ‘wsClientError’:在建立 WebSocket 连接前发生错误时发出。socket 和 request 分别是发生错误的套接字和 HTTP 请求。该事件的监听器负责关闭套接字。当 "wsClientError "事件发生时,没有 http.ServerResponse 对象,因此任何 HTTP 响应(包括响应标头和正文)都必须直接写入套接字。如果没有该事件的监听器,套接字将以包含描述性错误信息的默认 4xx 响应关闭。
    • error {Error}
    • socket {net.Socket|tls.Socket}
    • request {http.IncomingMessage}

WebSocket

它扩展了 EventEmitter.WebSocket 类。客户端的可以用这个连接 websocket 服务,在 websocket 服务中也需要用它来发送消息。

就绪状态常量
常量描述
CONNECTING0连接尚未打开。
OPEN1连接已打开,随时可以进行通信。
CLOSING2连接正在关闭。
CLOSED3连接已关闭。
相关事件模型
  • Event: ‘close’ : 连接关闭时发出。code 是一个数值,表示状态代码,说明连接被关闭的原因。reason 是一个缓冲区,包含一个可由人工读取的字符串,用于解释连接被关闭的原因。
    • 参数 1 code {Number}
    • 参数 2 reason {Buffer}
  • Event: ‘error’ : 发生错误时发出。
    • 参数 1 error {Error}
  • Event: ‘message’ : 收到信息时发出。data 是信息内容,isBinary 指定信息是否为二进制。
    • 参数 1 data {Buffer|ArrayBuffer|Buffer[]}
    • 参数 2 isBinary {Boolean}
  • Event: ‘open’: 连接建立时发出。
  • Event: ‘ping’ : 收到 ping 时发出。
    • data {Buffer}
  • Event: ‘pong’ : 收到 pong 时发出。
    • data {Buffer}

websocket服务器实现

这是一个简单的ws服务器,他主要功能是,记录连接在线的用户,以及向所以在线状态的客户端广播转发收到的消息。
运行命令:
假如你服务文件名是server.js,则:

	node  server.js
const { WebSocketServer } = require("ws");const userSet = new Set(); //  用户列表
//  ws服务器
const server = new WebSocketServer({port: 8080,perMessageDeflate: {zlibDeflateOptions: {// See zlib defaults.chunkSize: 1024,memLevel: 7,level: 3,},zlibInflateOptions: {chunkSize: 10 * 1024,},// Other options settable:clientNoContextTakeover: true, // Defaults to negotiated value.serverNoContextTakeover: true, // Defaults to negotiated value.serverMaxWindowBits: 10, // Defaults to negotiated value.// Below options specified as default values.concurrencyLimit: 10, // Limits zlib concurrency for perf.threshold: 1024, // Size (in bytes) below which messages// should not be compressed if context takeover is disabled.},},() => {console.log("创建成功");}
);server.on("open", function open() {console.log("打开 connected");
});server.on("close", function close(e) {console.log("关闭连接 disconnected", e);
});server.on("connection", (ws, req) => {const query = getQuery(req.url);console.log(query, "req::");if (userSet.has(query.username)) {sendMsg(ws, {type: "error",data: "用户名重复",});ws.close();} else {userSet.add(query.username);sendMsg(ws, {type: "success",data: "连接成功",});sendMsg(ws, {type: "user",data: Array.from(userSet),});}const ip = req.socket.remoteAddress;const port = req.socket.remotePort;const clientName = ip + port;console.log("%s 连接 ", clientName);ws.username = query.username;ws.on("message", function incoming(message) {let msg = handleMessage(message);server.clients.forEach((client) => {console.log("username::", client.username);//  广播消息if (client.readyState === ws.OPEN) {//  发送用户列表if (msg.type === "tips" || msg.type === "close") {sendMsg(client, {type: "user",data: Array.from(userSet),});}sendMsg(client, msg);}});});ws.on("close", function close(e) {console.log("关闭", e);});
});function handleMessage(data) {let message;let msg;if (typeof data === "string") {message = data;} else {message = JSON.parse(data.toString());}if (typeof message === "string") {msg = message;} else if (typeof message === "object" && message.type) {console.log(message, "data::");switch (message.type) {case "name":msg = {type: "tips",data: `用户${message.data}连接了`,};break;case "info":msg = {type: "info",data: message.data,};break;case "close":msg = {type: "tips",data: `用户${message.data}退出`,};userSet.delete(message.data);break;}} else {msg = "暂不识别的内容";}return msg;
}function getQuery(url) {let paramsStr = decodeURIComponent(url.split("?")[1]);let paramsArr = paramsStr.split("&");let params = {};paramsArr.forEach((str) => {let attr = str.split("=");params[attr[0]] = attr[1];});return params;
}function sendMsg(ws, msg) {if (!ws) {console.error("没有连接");return;}ws.send(JSON.stringify(msg));
}

前端实现

我页面是使用的是html实现的websocket。没有依赖ws,所以直接浏览器打开即可。
使用前需运行ws服务器。

<!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"><title>Document</title><style>* {margin: 0;padding: 0;box-sizing: border-box;}.box {margin: 32px 10%;border-radius: 16px;border: 1px solid #000;overflow: hidden;}.box h1 {margin: 32px;text-align: center;}.info-box {padding: 32px;height: 88px;background-color: #eee;}.info-box .name-box {display: flex;align-items: center;}.info-box .name-box .edit-name {border: 0;color: skyblue;outline: none;}.chat-box {display: flex;margin-top: 32px;height: 50vh;border-top: 1px solid #000;}.user-box {flex-shrink: 0;min-width: 44px;max-width: 200px;border-right: 1px solid #000;}.chat-frame {flex: 1;}.message-box {display: flex;flex-direction: column;width: 100%;height: 80%;border-bottom: 1px solid #000;overflow-y: auto;}.message-box .tips-box {text-align: center;color: #999;font-size: 14px;}.msg-box {align-self: flex-start;}.msg-box-top {font-size: 16px;color: skyblue;}.msg-box-top span {font-size: 12px;color: #006eff;margin-right: 16px;}.msg-box-content {display: inline-block;width: 100%;padding: 5px 10px;line-height: 20px;background-color: skyblue;font-size: 18px;color: #fff;border-radius: 7px 7px 12px 7px;}.msg-box-right {align-self: flex-end;}.msg-box-right .msg-box-content {display: inline-block;width: 100%;padding: 5px 10px;line-height: 20px;background-color: rgb(35, 185, 35);font-size: 18px;color: #fff;border-radius: 7px 7px 7px 12px;}.send-box {display: flex;align-items: center;justify-content: center;gap: 16px;height: 20%;}.send-box textarea {width: 80%;resize: none;}.send-box .send-btn {flex-shrink: 0;width: 10%;min-width: 32px;height: 66px;}.hide {display: none !important;}.show {display: block !important;}</style>
</head><body><div class="box"><h1>在线聊天室</h1><!-- 个人信息模态框 --><div class="info-box"><div><label for="name1">请输入昵称:</label><input name="name1" class="name-input" type="text" placeholder="请输入聊天昵称"> <button class="ok-name">确定</button></div><div class="hide"><div class=" name-box"><div class="name-show"></div><button class="edit-name">#修改</button></div><button class="link-btn">连接聊天室</button><button class="out-btn hide">退出聊天室</button></div></div><!-- 在线聊天模态框 --><div class="chat-box hide"><div class="user-box"><h2>用户列表</h2><div class="user-list"></div></div><div class="chat-frame"><div class="message-box"></div><div class="send-box"><textarea class="send-content" name="message" id="" rows="5" maxlength="150"></textarea><button class="send-btn">发送</button></div></div></div></div><script>let ws;let nickName; //  昵称const okNameBtn = document.querySelector('.ok-name')const nameInput = document.querySelector('.name-input')const editNameBtn = document.querySelector('.edit-name')let userDom = document.querySelector('.user-list')let msgDom = document.querySelector('.message-box')const linkBtn = document.querySelector('.link-btn')const outBtn = document.querySelector('.out-btn')const chatBox = document.querySelector('.chat-box')const nameShowDiv = document.querySelector('.name-show')const sendBtn = document.querySelector('.send-btn')const sendContentDom = document.querySelector('.send-content')okNameBtn.onclick = function () {if (!nameInput.value || nameInput.value === '') returnnickName = nameInput.valuenameShowDiv.innerHTML = nickNamenameShowDiv.parentElement.parentElement.classList.remove('hide')nameInput.parentElement.classList.add('hide')}editNameBtn.onclick = function () {nameInput.value = nickNamenameInput.parentElement.classList.remove('hide')nameShowDiv.parentElement.parentElement.classList.add('hide')}linkBtn.onclick = function () {linkWs()linkBtn.classList.add('hide')editNameBtn.classList.add('hide')outBtn.classList.remove('hide')chatBox.classList.remove('hide')}outBtn.onclick = function () {outBtn.classList.add('hide')chatBox.classList.add('hide')linkBtn.classList.remove('hide')editNameBtn.classList.remove('hide')sendMsg({type: 'close',data: nickName})ws.close()}sendBtn.onclick = function () {let content = sendContentDom.valueif (!content || content === '' || !ws) returnsendMsg({type: 'info',data: {date: Date.now(),content: content,author: nickName}})}/***  连接websocket服务器* */function linkWs() {ws = new WebSocket(`ws://localhost:8080?username=${nickName}`)ws.addEventListener("open", (e) => {})ws.addEventListener("message", function (e) {let msg = JSON.parse(e.data)//  处理消息handleMessage(msg)})ws.addEventListener("close", function (e) {console.log('关闭连接后');outBtn.classList.add('hide')chatBox.classList.add('hide')linkBtn.classList.remove('hide')editNameBtn.classList.remove('hide')//  隐藏聊天框alert('聊天室断开连接')})}function handleMessage(msg) {switch (msg.type) {case 'user'://  用户列表填充数据let domStr = ''msg.data.forEach(item => {console.log(item, 'i');domStr += `<div> ${item} </div>`})userDom.innerHTML = domStrbreak;case 'tips'://  提示信息msgDom.innerHTML += `<div class="tips-box">${msg.data} </div>`break;case 'info'://  信息列表const { content, author, date } = msg.datamsgDom.innerHTML += `<div class="${author === nickName ? 'msg-box-right' : 'msg-box'}"><div class="msg-box-top">${author} <span>${transTime(date)}</span></div><div class="msg-box-content"> ${content}</div></div>`break;case 'close'://  关闭连接if (msg.data) {sendMsg({type: 'close',data: nickName})ws.close()}break;case 'error'://  关闭连接if (msg.data) {alert(msg.data)ws.close()}break;case 'success'://  关闭连接if (msg.data) {alert(msg.data)sendMsg({type: 'name',data: nickName})}break;default:break;}}function sendMsg(msg) {if (!ws) {console.error('没有连接');return}ws.send(JSON.stringify(msg))}window.addEventListener("beforeunload", (e) => {if (ws) {sendMsg({type: 'close',data: nickName})ws.close()}})function transTime(time) {let date = new Date(time)let year = date.getFullYear()let month = date.getMonth() + 1let day = date.getDate()let hour = date.getHours()let minute = date.getMinutes()let second = date.getSeconds()return `${year}/${month}/${day} ${hour}:${minute}:${second}`}</script>
</body></html>

效果

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

结语

这只是一个简单的在线聊天室,作学习使用,并没有涉及到用户验证,数据存储和复杂数据处理等逻辑。我认为websocket的侧重点在数据处理上,怎么转发和处理客户端发的数据更为重要。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/211490.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

中科院分区和JCR分区有什么区别

文章目录 名词解释学科划分不同参考的影响因子不同期刊分区不同期刊分区阈值不同 名词解释 中科院分区&#xff1a;又称“中科院JCR分区”&#xff0c;是中国科学院文献情报中心世界科学前沿分析中心的科学研究成果&#xff0c;期刊分区表数据每年底&#xff08;每年12月中下旬…

汽车继电器

汽车继电器 电子元器件百科 文章目录 汽车继电器前言一、汽车继电器是什么二、汽车继电器的类别三、汽车继电器的应用实例四、汽车继电器的作用原理总结前言 汽车继电器作为一种电子设备,广泛应用于汽车电路中的各种控制和保护任务,能够可靠地控制和传送电能,确保汽车系统的…

Python爬虫-实现批量抓取王者荣耀皮肤图片并保存到本地

前言 本文是该专栏的第12篇,后面会持续分享python爬虫案例干货,记得关注。 本文以王者荣耀的英雄皮肤为例,用python实现批量抓取“全部英雄”的皮肤图片,并将图片“批量保存”到本地。具体实现思路和详细逻辑,笔者将在正文结合完整代码进行详细介绍。注意,这里抓取的图片…

低代码:美味膳食或垃圾食品

低代码开发是近年来迅速崛起的软件开发方法&#xff0c;让编写应用程序变得更快、更简单。有人说它是美味的膳食&#xff0c;让开发过程高效而满足&#xff0c;但也有人质疑它是垃圾食品&#xff0c;缺乏定制性与深度。你认为低代码到底是美味的膳食还是垃圾食品呢&#xff0c;…

ubuntu串口永久权限

ubuntu串口永久权限 临时打开串口权限 sudo chmod 666 /dev/ttyUSB0该方法只能临时添加访问权限&#xff0c;一次性的&#xff0c;下次拔插串口线或者开关机还需要再次赋予串口权限。 永久打开串口权限 首先查看用户组 ls -l /dev/ttyUSB0终端输出&#xff1a; crw-rw-rw…

从零开始搭建链上dex自动化价差套利程序(11)

风险控制 需要将仓位杠杆控制到3倍以内&#xff0c;由于dydx与apex没有获取仓位杠杆的接口&#xff0c;但是每次发送交易的数额可以决定&#xff0c;故而可以设置每次发送总仓位1.5倍杠杆的数额&#xff0c;然后设置一个变量保证每个方向上的交易不超过2次&#xff0c;即可保证…

数据结构和算法-单链表

数据结构和算法-单链表 1. 链表介绍 链表是有序的列表&#xff0c;但是它在内存中是存储如下 图1 单链表示意图 小结: 链表是以节点的方式存储每个节点包含data域&#xff0c;next域&#xff0c;指向下一个节点。如图&#xff1a;发现链表的各个节点不一定是连续存储。比如地…

滑动窗口练习(三)— 加油站问题

题目 测试链接 在一条环路上有 n 个加油站&#xff0c;其中第 i 个加油站有汽油 gas[i] 升。 你有一辆油箱容量无限的的汽车&#xff0c;从第 i 个加油站开往第 i1 个加油站需要消耗汽油 cost[i] 升。你从其中的一个加油站出发&#xff0c;开始时油箱为空。 给定两个整数数组…

如何教会小白使用淘宝API接口获取商品数据

随着互联网的普及&#xff0c;越来越多的人开始接触网络购物&#xff0c;而淘宝作为中国最大的电商平台之一&#xff0c;成为了众多消费者首选的购物平台。然而&#xff0c;对于一些小白用户来说&#xff0c;如何通过淘宝API接口获取商品数据可能是一个难题。本文将详细介绍如何…

Python学习之——时间和日期

Python学习之——时间模块 参考time 模块常见接口 datetime 模块常见接口 calendar 模块常见接口 示例 参考 Python datetime模块详解、示例 搞定Python时区的N种姿势 calendar – 日历相关 time 模块 在Python中&#xff0c;通常有这几种方式来表示时间&#xff1a; 1&…

浮点数在计算机中如何存储

举例&#xff1a; 结果&#xff1a; 文字描述&#xff1a; 先将浮点数转化为二进制的表示形式&#xff0c; 接着将其二进制的形式按照科学计数法来表示&#xff0c; 符号位的确定&#xff1a;正数0&#xff0c; 负数1 指数的确定&#xff1a;将其二进制表示成为科学计数法…

Fall in love with English

Fall in love with English 爱上英语 Hiding behind the loose dusty curtain, a teenager packed up his overcoat into the suitcase. 躲藏在布满尘土的松软的窗帘后边&#xff0c;一个年轻人打包他的外套到行李箱中。 He planned to leave home at dusk though there was th…

超完整的mysql安装配置方法(包含idea和navicat连接mysql,并实现建表)

mysql安装配置方法 1、下载mysql2、解压到指定的安装目录3、配置初始化文件my.ini4、配置用户变量和系统变量5、初始化mysql6、安装mysql服务并启动修改密码7、使用idea连接mysql8、使用Navicat可视化工具连接mysql&#xff0c;并实现新建数据库&#xff0c;新建表 1、下载mysq…

计算机考研408-计算机网络、操作系统整书知识点脑图

计算机网络、操作系统整书知识点脑图 今天突然想起来考研期间为了方便记忆&#xff0c;费了很大力气整理了计算机网络、操作系统两本书知识点的脑图&#xff0c;想着放着也没啥用&#xff0c;分享出来给大家看看 但是思维导图格式的东西好像没法直接发成文章&#xff0c;上传…

【NodeJs】UniSMS 实现短信验证码

承接上文 &#xff0c;上次用的是 短信宝平台 认证已经通过 后续又新增要求 平台相当麻烦&#xff01; 短信宝实现短信发送要求&#xff1a; 1.平台绑定手机号 必须和 营业执照法人一致 2.平台个人实名认证 必须和 营业执照法人一致 3.平台需要上传营业执照 4.平台需要上…

拒接服务攻击(DOS)的初步介绍

文章目录 什么是拒绝服务攻击拒绝服务攻击的过程拒绝服务攻击的类型常见的拒绝服务攻击如何防范拒绝服务攻击分布式拒绝服务攻击&#xff08;DDoS&#xff09; 什么是拒绝服务攻击 拒绝服务攻击是一种网络攻击方式&#xff0c;攻击者通过向目标计算机系统或网络发送大量的请求…

免费分享一套Springboot+Vue前后端分离的在线商城系统,挺实用的

大家好&#xff0c;我是java1234_小锋老师&#xff0c;看到一个不错的SpringbootVue前后端分离的在线商城系统&#xff0c;分享下哈。 项目视频演示 【免费】SpringbootVue在线商城系统 毕业设计 Java毕业设计_哔哩哔哩_bilibili【免费】springbootvue在线商城系统 毕业设计 …

97基于matlab的改进的带记忆的模拟退火算法求解TSP问题

基于matlab的改进的带记忆的模拟退火算法求解TSP问题&#xff0c;采用多普勒型降温曲线描述迭代过程&#xff0c;在传统算法的基础上增加记忆功能&#xff0c;可测试中国31/64/144以及att48城市的数据&#xff0c;也可自行输入数据进行测试&#xff0c;测试结果基本达到当前最优…

Swagger2的使用

手写Api文档的几个痛点&#xff1a; 文档需要更新的时候&#xff0c;需要再次发送一份给前端&#xff0c;也就是文档更新交流不及时。 接口返回结果不明确 不能直接在线测试接口&#xff0c;通常需要使用工具&#xff0c;比如postman 接口文档太多&#xff0c;不好管理 Sw…

gin投票项目4

对应视频v2版本 gin项目投票系统4 1.增加一个注册账号的功能 增加接口 参数校验&#xff1a;&#xff08;需求&#xff09; 确认密码需要一致&#xff0c;不为空用户名必须唯一, 不为空用户名大于8小于16位密码大于8小于16位,并且不能为纯数字 正则表达式 必须知道这东西…