实现webSocket客服端服务端

这篇文档是基于webSocket的学习笔记,在之前想学习相关知识,都是散乱了,有的就只有服务端,有的只有客户端,所以自己写一篇记录下,我写的这个demo台使用nodejs前端简单使用html单页面。

最终效果

首先先明白为啥要学习websocket,它可以解决什么问题,他的运行原理是什么?

WebSocket解决了传统的HTTP协议在实时性双向通信方面的限制问题。

首先,传统的HTTP协议是一种无状态的协议,即服务器不能主动推送数据给客户端,而是需要客户端发送请求后服务器才能响应。这导致了在实时性要求较高的场景下,如聊天应用在线游戏 等,传统的HTTP协议无法满足 实时更新的需求。而WebSocket通过建立一条持久化的连接,允许服务器主动向客户端推送数据,从而实现了实时更新。

解决了传统HTTP协议在实时性和双向通信方面的限制问题,使得客户端和服务器之间可以实现实时更新和双向通信,适用于实时性要求较高的应用场景

服务端

首先先创建一个node应用,创建一个webSocketdemo文件,运行以下代码初始化项目,并安装ws库(node支持的一个WebSocket的库)

js 复制代码
 //初始化一个 node 项目 
 npm init -y
 //安装依赖 
 npm i -save ws
 //初始化项目安装依赖(虽然这时没有啥依赖,单要保持良好习惯)
 npm i 

运行结果如下

index.js文件中写下列代码,require引入ws库并设置端口port: 8080,这时创建名叫wss的websocket连接,我们可以通过wss.on添加监听事件

js 复制代码
var webSocketsServer = require('ws').Server;
var wss = new webSocketsServer({ port: 8080 });
var clients = [];

// 为服务器添加 connection 事件监听,当有客户端连接到服务端时,立刻将客户端对象保存进数组中
wss.on('connection', function (ws) {

});

修改node项目的配置文件package.json,并在scripts添加一条指令node index.js(注:其实也可以在控制台执行运行node index.js并不会影响websocket运行),node项目为了统一管理所以写在这里,我们可以通过npm run dev这条命令来启动项目。 启动效果npm run dev

启动效果node index.js

这时我们可以通过访问页面判断是否已经启动websocket, localhost:8080

运行原理

如上图,提示Upgrade Required说明websocket启动成功,为啥会提示Upgrade Required(需要升级),这里需要解释websocket运行原理:

websocket是h5带来的新通信协议,如上图,它和传统http一样进行连接需要进行三次握手、四次挥手,连接完成后又进行一个websocket握手,当websocket握手的具体意义是浏览器告诉服务器,我现在发起的这个请求不是传统的http请求而是websocket,需要双方将http协议升级为websocket服务器接收到后马上升级协议,这时双发握手成功,实现协议转换。因此我们在测试的时候在浏览器中输入localhost:8080由于协议不对,所以进行提示Upgrade Required

以下是更为官方解释websocket,附带websocket原文:RFC 6455 - The WebSocket Protocol (ietf.org):

WebSocket 是一种在单个 TCP 连接上全双工通信的协议。它在客户端和服务器之间提供了实时的、持久的连接,使得数据能够以较低的延迟进行传输。 WebSocket 的原理如下:

  1. 客户端发起 WebSocket 握手请求,请求中包含了支持 WebSocket 的协议版本号等信息。
  2. 服务器收到 WebSocket 握手请求后,检查客户端的请求头信息,并进行协议升级处理。如果服务器也支持 WebSocket,则返回一个 101 切换协议的响应给客户端。
  3. 客户端收到服务器的响应后,也会进行协议升级处理。如果协议切换成功,客户端和服务器之间的连接就建立起来了。
  4. 建立连接后,客户端和服务器可以通过发送帧(frame)进行数据的传输。每个帧由一个固定格式的首部和可选的载荷组成。首部包含了帧的类型、长度等信息,载荷则是要传输的数据。
  5. 客户端和服务器都能够发送和接收数据。数据可以是文本或二进制形式。客户端和服务器都可以随时发送帧,而不需要等待对方的响应。
  6. 当客户端或服务器想要关闭连接时,可以发送一个特殊的帧来表示关闭请求。收到关闭请求的一方也会发送一个关闭帧作为响应,并关闭连接。

ws api 使用

找到一篇关于wsAPI介绍 Node Websocket/ws模块 API 参考 - 简书 (jianshu.com)

完整的index.js代码,再进行解释:

js 复制代码
//引入ws库创建一个websocket
var webSocketsServer = require('ws').Server;
//设置端口为8080
var wss = new webSocketsServer({ port: 8080 });
//使用数组保存所有的连接
var clients = [];
// 为服务器添加 connection 事件监听,当有客户端连接到服务端时,立刻将客户端对象保存进数组中
wss.on('connection', function (ws) {
    console.log('Client 创建连接');
    clients.push(ws);
    console.log(`目前共有${clients.length}个客户端连接`);

    // 为每个 client 对象绑定 message 事件,当某个客户端发来消息时,自动触发
    ws.on('message', function (msg) {
        // console.log(msg, typeof msg);
        console.log('收到消息' + msg);
        
        // 遍历 clients 数组中每个其他客户端对象,并发送消息给其他客户端
        for (var c of clients) {
            c.send(msg.toString());
        }
    });

    // 当客户端断开连接时触发该事件
    ws.onclose = function () {
        var index = clients.indexOf(this);
        clients.splice(index, 1);
        console.log("有" + clients.length + "客户端在线")
    }
});

从上述代码可以看出在wss.on给服务器添加 connection 事件监听,当有客户端连接到服务端时,立刻将客户端对象ws保存进数组中,其中每次连接新设备将新的连接通过 clients.push(ws);添加到数组clients中。对每个具体客户端对象ws添加 ws.on('message', function (msg) {})的监听事件。用来接收客户端ws发送的消息,并通过for循环遍历 clients 数组中每个其他客户端对象,通过c.send(msg.toString());将消息发送给其他客户端。当客户端断开连接时触发ws.onclose事件并将对应的客户端对象ws从数组clients移除clients.splice(index, 1);

客户端

当我们写完服务端,我们可以撰写index.html文件。

html 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <input />
    <button>发送</button>
    <button id="close">关闭</button>
    <div>
        <span>所有消息</span>
        <div name="allMsg"></div>
    </div>
</body>
<script>
    //连接websocket
    var ws = new WebSocket("ws://127.0.0.1:8080/");
    //连接成功则进行以下函数
    ws.onopen = function () {
        console.log("连接成功");
    }
    //websocket 放在win对象中,方便调试
    window.ws = ws;
</script>
</html>

运行代码可看见以下界面

我们也可以多开几个窗口来看下效果,如下图创建三个标签,所以后台数组clients会记录三个ws对象。

断开websocket

之前我们在代码最后一行写上 window.ws = ws;目的是可以通过全局对象直观查看ws连接参数,方便我们理解调试

目前我们连接三个客户端,断开连接我们可以通过关闭浏览器标签来强制执行,当然我们也可以调用ws.close()断开websocket连接 在优化下,将这个断开连接事件绑定在关闭按钮上

js 复制代码
   //给关闭按钮添加点击事件
    document.getElementById("close").onclick = function () {
        console.log("断开连接");
        ws.close();
    };

发送消息

我们可以调用websocket的send方法实现发消息,如下,在控制台通过ws.send("这是发送的第一条信息");实现发送消息,服务端对应接收到消息 回顾服务端index.js接收消息如何处理,通过ws.on('message',function (msg) {})监听用户发送消息,通过for循环将消息转发给每个客户端 for (var c of clients) {c.send(msg.toString()); }

js 复制代码
   // 为每个 client 对象绑定 message 事件,当某个客户端发来消息时,自动触发
    ws.on('message', function (msg) {
        // console.log(msg, typeof msg);
        console.log('收到消息' + msg);
        // 遍历 clients 数组中每个其他客户端对象,并发送消息给其他客户端
        for (var c of clients) {
            c.send(msg.toString());
        }
    });

对应的index.html页面稍微做优化下,用户将需要写的内容输入到input框,点击发送按钮时添加点击事件调用ws.send方法发送消息。

js 复制代码
   //websocket发送消息
    document.getElementsByTagName("button")[0].onclick = function () {
        var msg = document.getElementsByTagName("input")[0].value;
        ws.send(msg);
        console.log("发送消息:" + msg);

    }

此时页面还缺少一块内容,接收务端的消息,接收消息可以调用ws.onmessage方法接收处理相关信息,例如将消息展示页面上:

js 复制代码
    //接受参数
    ws.onmessage = function (e) {
        console.log("接受消息",e.data);

        var allMsg = document.getElementsByName("allMsg")[0];
        //将消息添加到页面
        var div = document.createElement("div");
        div.innerHTML = e.data;
        allMsg.appendChild(div);
    }

完整的index.html代码:

html 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <input />
    <button>发送</button>
    <button id="close">关闭</button>
    <div>
        <span>所有消息</span>
        <div name="allMsg"></div>
    </div>
</body>
<script>
    //连接websocket
    var ws = new WebSocket("ws://127.0.0.1:8080/");
    ws.onopen = function () {
        console.log("连接成功");
    }
    //websocket发送消息
    document.getElementsByTagName("button")[0].onclick = function () {
        
        var msg = document.getElementsByTagName("input")[0].value;
        ws.send(msg);
        console.log("发送消息:" + msg);

    }

    //给关闭按钮添加点击事件
    document.getElementById("close").onclick = function () {
        console.log("断开连接");
        ws.close();
    };
    //接受参数
    ws.onmessage = function (e) {
        console.log("接受消息",e.data);

        var allMsg = document.getElementsByName("allMsg")[0];
        //将消息添加到页面
        var div = document.createElement("div");
        div.innerHTML = e.data;
        allMsg.appendChild(div);
    }


    window.ws = ws;
</script>
</html>

最终效果

相关推荐
腾讯TNTWeb前端团队4 小时前
helux v5 发布了,像pinia一样优雅地管理你的react状态吧
前端·javascript·react.js
范文杰8 小时前
AI 时代如何更高效开发前端组件?21st.dev 给了一种答案
前端·ai编程
拉不动的猪8 小时前
刷刷题50(常见的js数据通信与渲染问题)
前端·javascript·面试
拉不动的猪8 小时前
JS多线程Webworks中的几种实战场景演示
前端·javascript·面试
FreeCultureBoy9 小时前
macOS 命令行 原生挂载 webdav 方法
前端
uhakadotcom9 小时前
Astro 框架:快速构建内容驱动型网站的利器
前端·javascript·面试
uhakadotcom9 小时前
了解Nest.js和Next.js:如何选择合适的框架
前端·javascript·面试
uhakadotcom9 小时前
React与Next.js:基础知识及应用场景
前端·面试·github
uhakadotcom10 小时前
Remix 框架:性能与易用性的完美结合
前端·javascript·面试
uhakadotcom10 小时前
Node.js 包管理器:npm vs pnpm
前端·javascript·面试