实现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>

最终效果

相关推荐
DT——5 小时前
Vite项目中eslint的简单配置
前端·javascript·代码规范
学习ing小白7 小时前
JavaWeb - 5 - 前端工程化
前端·elementui·vue
真的很上进8 小时前
【Git必看系列】—— Git巨好用的神器之git stash篇
java·前端·javascript·数据结构·git·react.js
胖虎哥er8 小时前
Html&Css 基础总结(基础好了才是最能打的)三
前端·css·html
qq_278063718 小时前
css scrollbar-width: none 隐藏默认滚动条
开发语言·前端·javascript
.ccl8 小时前
web开发 之 HTML、CSS、JavaScript、以及JavaScript的高级框架Vue(学习版2)
前端·javascript·vue.js
小徐不会写代码8 小时前
vue 实现tab菜单切换
前端·javascript·vue.js
2301_765347548 小时前
Vue3 Day7-全局组件、指令以及pinia
前端·javascript·vue.js
ch_s_t8 小时前
新峰商城之分类三级联动实现
前端·html
辛-夷8 小时前
VUE面试题(单页应用及其首屏加载速度慢的问题)
前端·javascript·vue.js