【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.pswp.cn/news/211490.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/211490.shtml
英文地址,請注明出處:http://en.pswp.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位,并且不能為純數字 正則表達式 必須知道這東西…