WebSockets 是一种先进的技术。它可以在用户的浏览器和服务器之间打开交互式通信会话。使用此 API,你可以向服务器发送消息并接收事件驱动的响应,而无需通过轮询服务器的方式以获得响应。
webscokets 包括webscoket接口、CloseEvent接口 和MessageEvent接口。
WebSocket是一种基于TCP的全双工通信协议,可以更好的节省资源和带宽实现即时通讯,是一种持久化的协议。
ws 是一个简单易用、速度极快、经过全面测试的 WebSocket 客户端和服务器实现库。
npm安装
npm install ws
ws使用之前需要导入。
创建一个新的服务器实例。必须提供端口、服务器或 noServer 中的一个,否则会出错。如果设置了端口,HTTP 服务器将自动创建、启动和使用。若要使用外部 HTTP/S 服务器,请仅指定服务器或 noServer。在这种情况下,必须手动启动 HTTP/S 服务器。无服务器 "模式允许 WebSocket 服务器与 HTTP/S 服务器完全分离。例如,这样就可以在多个 WebSocket 服务器之间共享一个 HTTP/S 服务器。
它扩展了 EventEmitter.WebSocket 类。客户端的可以用这个连接 websocket 服务,在 websocket 服务中也需要用它来发送消息。
常量 | 值 | 描述 |
---|---|---|
CONNECTING | 0 | 连接尚未打开。 |
OPEN | 1 | 连接已打开,随时可以进行通信。 |
CLOSING | 2 | 连接正在关闭。 |
CLOSED | 3 | 连接已关闭。 |
这是一个简单的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>Documenttitle>
<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 === '') return
nickName = nameInput.value
nameShowDiv.innerHTML = nickName
nameShowDiv.parentElement.parentElement.classList.remove('hide')
nameInput.parentElement.classList.add('hide')
}
editNameBtn.onclick = function () {
nameInput.value = nickName
nameInput.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.value
if (!content || content === '' || !ws) return
sendMsg({
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 += ` ${item} `
})
userDom.innerHTML = domStr
break;
case 'tips':
// 提示信息
msgDom.innerHTML += `${msg.data} `
break;
case 'info':
// 信息列表
const { content, author, date } = msg.data
msgDom.innerHTML += `${author === nickName ? 'msg-box-right' : 'msg-box'}">
${author} ${transTime(date)}
${content}
`
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() + 1
let 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的侧重点在数据处理上,怎么转发和处理客户端发的数据更为重要。