目录
2.1 使用 JSObject 连接到 Websockets
在RFC 6455规范中描述的WebSocket协议,提供了一种在浏览器和服务器之间建立持久连接来交换数据的方法。数据可以作为“数据包”在两个方向上传递,而无需中断连接也无需额外的 HTTP 请求;对于需要连续数据交换的服务,例如网络游戏,实时交易系统等,WebSocket 尤其有用。
案例原文件在gitee、github仓库上的demo文件中都可找到(/demo/入门案例/JSobject相关)
1、WebSocket介绍
1.1 首先,先了解下WebSocket
WebSocket 协议提供了一种在客户端和服务器之间通过持久连接交换数据的方式。数据可以在两个方向上传递,延迟和开销都很低,而且不会中断连接。WebSocket 提供了一个双向、全双工的通信通道,通过单个 TCP 套接字连接在 HTTP 上运行。这意味着服务器可以独立地向客户端发送数据,而无需客户端请求,反之亦然。
-
要打开一个 WebSocket 连接,我们需要在 url 中使用特殊的协议ws创建new WebSocket:
let socket = new WebSocket("ws://javascript.info");
也可以用wss://这个加密协议,这个就是 Websocket 的 HTTPS。
wss:// 相较于 ws://,不仅是加密的,而且更加可靠;另一方面,wss://是基于 TLS 的 WebSocket,类似于 HTTPS 是基于 TLS 的 HTTP),传输安全层在发送方对数据进行了加密,在接收方进行解密。因此,数据包是通过代理加密传输的。它们看不到传输的里面的内容,且会让这些数据通过。
一旦Socket后,我们应该监听socket 上的事件。一共有 4 个事件:
-
open —— 连接已建立,
-
message —— 接收到数据,
-
error —— WebSocket 错误,
-
close —— 连接已关闭。
如果我们想发送一些东西,socket.send(data)就会这样做,这里有个例子:
let socket = new WebSocket("wss://javascript.info/article/websocket/demo/hello");
socket.onopen = function(e) {
alert("[open] Connection established");
alert("Sending to server");
socket.send("My name is John");
};
socket.onmessage = function(event) {
alert(`[message] Data received from server: ${event.data}`);
};
socket.onclose = function(event) {
if (event.wasClean) {
alert(`[close] Connection closed cleanly, code=${event.code} reason=${event.reason}`);
} else {
// e.g. server process killed or network down
// event.code is usually 1006 in this case
alert('[close] Connection died');
}
};
socket.onerror = function(error) {
alert(`[error]`);
};
可以运行一下,你会看到流程从open→ message→ close。
1.2 建立 WebSocket
当 new WebSocket(url) 被创建后,它将立即开始连接。在连接期间,浏览器(使用 header)问服务器:“你支持 WebSocket 吗?”如果服务器回复说“我支持”,那么通信就以 WebSocket 协议继续进行,该协议根本不是 HTTP。
来看一个示例,这是由new WebSocket("wss://javascript.info/chat")发出的请求的浏览器 header 示例。
GET /chat
Host: javascript.info
Origin: https://javascript.info
Connection: Upgrade
Upgrade: websocket
Sec-WebSocket-Key: Iv8io/9s+lYFgZWcXczP8Q==
Sec-WebSocket-Version: 13
-
Origin —— 客户端页面的源,例如 https://javascript.info。WebSocket 对象是原生支持跨源的。没有特殊的 header 或其他限制。旧的服务器无法处理 WebSocket,因此不存在兼容性问题。但 Origin header 很重要,因为它允许服务器决定是否使用 WebSocket 与该网站通信。
-
Connection: Upgrade —— 表示客户端想要更改协议。
-
Upgrade: websocket —— 请求的协议是 “websocket”。
-
Sec-WebSocket-Key —— 浏览器随机生成的安全密钥。
-
Sec-WebSocket-Version —— WebSocket 协议版本,当前为 13。
如果服务器同意切换为 WebSocket 协议,服务器应该返回响应码 101:
101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: hsBlbuDTkk24srzEOTBUlZAlC2g=
这里Sec-WebSocket-Accept是Sec-WebSocket-Key,是使用特殊的算法重新编码的。浏览器使用它来确保响应与请求相对应。
1.3 扩展和子协议
WebSocket 可能还有其他 header,Sec-WebSocket-Extensions和Sec-WebSocket-Protocol,它们描述了扩展和子协议。
例如:
-
Sec-WebSocket-Extensions: deflate-frame 表示浏览器支持数据压缩。扩展与传输数据有关,扩展了 WebSocket 协议的功能。Sec-WebSocket-Extensions header 由浏览器自动发送,其中包含其支持的所有扩展的列表。
-
Sec-WebSocket-Protocol: soap, wamp 表示我们不仅要传输任何数据,还要传输 SOAP 或 WAMP(“The WebSocket Application Messaging Protocol”)协议中的数据。WebSocket 子协议已经在 IANA catalogue 中注册。因此,此 header 描述了我们将要使用的数据格式。
这个可选的 header 是使用 new WebSocket 的第二个参数设置的。它是子协议数组,例如,如果我们想使用 SOAP 或 WAMP:
let socket = new WebSocket("wss://javascript.info/chat", ["soap", "wamp"]);
服务器应该使用同意使用的协议和扩展的列表进行响应。例如这个请求:
GET /chat
Host: javascript.info
Upgrade: websocket
Connection: Upgrade
Origin: https://javascript.info
Sec-WebSocket-Key: Iv8io/9s+lYFgZWcXczP8Q==
Sec-WebSocket-Version: 13
Sec-WebSocket-Extensions: deflate-frame
Sec-WebSocket-Protocol: soap, wamp
响应:
101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: hsBlbuDTkk24srzEOTBUlZAlC2g=
Sec-WebSocket-Extensions: deflate-frame
Sec-WebSocket-Protocol: soap
在这里服务器响应 —— 它支持扩展 “deflate-frame”,并且仅支持所请求的子协议中的 SOAP。
1.4 数据传输
WebSocket 通信由 “frames”(即数据片段)组成,可以从任何一方发送,并且有以下几种类型:
-
“text frames” —— 包含各方发送给彼此的文本数据。
-
“binary data frames” —— 包含各方发送给彼此的二进制数据。
-
“ping/pong frames” 被用于检查从服务器发送的连接,浏览器会自动响应它们。
-
还有 “connection close frame” 以及其他服务 frames。
在浏览器里,我们仅直接使用文本或二进制 frames。
WebSocket .send() 方法可以发送文本或二进制数据。
socket.send(body) 调用允许 body 是字符串或二进制格式,包括 Blob,ArrayBuffer 等。不需要额外的设置:直接发送它们就可以了。
当我们收到数据时,文本总是以字符串形式呈现。而对于二进制数据,我们可以在 Blob 和 ArrayBuffer 格式之间进行选择。
Blob是高级的二进制对象,它直接与<a>,<img>及其他标签集成在一起,因此,默认以Blob格式是一个明智的选择。但是对于二进制处理,要访问单个数据字节,我们可以将其改为"arraybuffer":
socket.binaryType = "arraybuffer";
socket.onmessage = (event) => {
// event.data 可以是文本(如果是文本),也可以是 arraybuffer(如果是二进制数据)
};
1.5 限速
想象一下:我们的应用程序正在生成大量要发送的数据。但是用户的网速却很慢,可能是在乡下的移动设备上,该怎么办
我们可以反复地调用 socket.send(data)。但是数据将会缓冲(储存)在内存中,并且只能在网速允许的情况下尽快将数据发送出去。
socket.bufferedAmount 属性储存了目前已缓冲的字节数,等待通过网络发送。
我们可以检查它以查看 socket 是否真的可用于传输,例如:
// 每 100ms 检查一次 socket
// 仅当所有现有的数据都已被发送出去时,再发送更多数据
setInterval(() => {
if (socket.bufferedAmount == 0) {
socket.send(moreData());
}
}, 100);
1.6 WebSocket连接关闭
通常,当一方想要关闭连接时(浏览器和服务器都具有相同的权限),它们会发送一个带有数字码(numeric code)和文本形式的原因的 “connection close frame”。
例如:
socket.close([code], [reason]);
-
code 是一个特殊的 WebSocket 关闭码(可选)
-
reason 是一个描述关闭原因的字符串(可选)
然后,另外一方通过close事件处理器获取了关闭码和关闭原因,例如:
// 关闭方:
socket.close(1000, "Work complete");
// 另一方
socket.onclose = event => {
// event.code === 1000
// event.reason === "Work complete"
// event.wasClean === true (clean close)
};
最常见的数字码(完整列表请见RFC6455, §7.4.1。):
-
1000 —— 默认,正常关闭(如果没有指明 code 时使用它),
-
1006 —— 没有办法手动设定这个数字码,表示连接丢失(没有 close frame)。
还有其他数字码,例如:
-
1001 —— 一方正在离开,例如服务器正在关闭,或者浏览器离开了该页面,
-
1009 —— 消息太大,无法处理,
-
1011 —— 服务器上发生意外错误,
WebSocket 码有点像 HTTP 码,但它们是不同的。特别是,小于1000的码都是被保留的,如果我们尝试设置这样的码,将会出现错误。
// 在连接断开的情况下
socket.onclose = event => {
// event.code === 1006
// event.reason === ""
// event.wasClean === false(未关闭 frame)
};
1.7 WebSocket连接状态
要获取连接状态,可以通过带有值的 socket.readyState 属性:
-
0 —— “CONNECTING”:连接还未建立,
-
1 —— “OPEN”:通信中,
-
2 —— “CLOSING”:连接关闭中,
-
3 —— “CLOSED”:连接已关闭。
对WebSocket了解熟悉之后,我们来看看在PagePlug中,如何通过低代码的方式与WebSocket集合起来使用
2、实战案例
PagePlug支持私有化部署,可部署在自己的设备后创建一个新项目,查看安装教程
2.1 使用 JSObject 连接到 Websockets
-
在PagePlug应用中,我们可以通过左侧的➕ 按钮,新建一个JS对象
可以复制这段代码到JS对象中,这段代码主要实现了以下的功能:
-
创建WebSocket连接并处理连接的打开、消息和关闭事件。
-
在连接打开时发送一个"subscribe"事件和["!ticker@arr"]参数。
-
将接收到的消息解析为JSON对象,并打印一个'socketOnMessage'消息。然后,它将解析后的数据存储在socketResponseData中,并返回解析后的响应。
-
提供一个方法(getData)用于获取WebSocket接收到的数据。
export default {
socketResponseData: {},
getData: () => this.socketResponseData,
WEBSOCKET_ENDPOINT: "wss://stream-internal.wazirx.com/stream",
socket: undefined,
socketOnOpen: (data) => {
console.log('onopen', data);
this.sendEvent("subscribe", ["!ticker@arr"]);
},
socketOnMessage: (message) => {
let response = JSON.parse(message?.data);
console.log("socketOnMessage", response);
this.socketResponseData = response?.data;
return response;
},
socketOnClose: (data) => {
console.log('onclose', data);
},
onPageLoad: async() => {
this.socket = new WebSocket(this.WEBSOCKET_ENDPOINT);
this.socket.onopen = this.socketOnOpen;
this.socket.onclose = this.socketOnClose;
this.socket.onmessage = this.socketOnMessage;
},
sendEvent: (eventType, streams) =>{
let tickerObj = {
event: eventType,
streams,
id: Date.now()
};
this.socket.send(JSON.stringify(tickerObj));
}
}
点击下右上角的运行,可以在日志中看到工作情况(或者是在控制台中查看),只要后端数据发生变化,我们就会开始接收消息
补充一个视觉加强的代码,命名为:Utils【该代码功能在1.9.27版本后支持】,这段代码主要实现以下功能:
-
控制Ticker数据的订阅和取消订阅,并更新tickerStatus的状态。
-
判断货币价格是上涨还是下跌。
-
根据市场过滤Ticker数据。
export default {
/* Variable that contains boolean value as to whether Ticker data has started/stopped */
tickerStatus: true,
/*
Function to unsubscribe from Ticker Data if its already running, or to subscribe to start receiving Ticker Data.
Also updates the tickerStatus value.
*/
toggleTickerData: () => {
if (!!this.tickerStatus) {
WebsocketUtils.sendEvent("unsubscribe", ["!ticker@arr"]);
WebsocketUtils.socketResponseData = {};
}
else WebsocketUtils.sendEvent("subscribe", ["!ticker@arr"]);
this.tickerStatus = !this.tickerStatus
},
/* Function to return whether the crypto currency price has increased or decreased */
getHighLow: (item) => item.last - item.open < 0 ? "low" : "high",
/* Function to return Ticker Data filtered based on Market or All markets */
getTickerDataByMarket: () => {
let market = Select1.selectedOptionValue || "all";
return _.toArray(WebsocketUtils.socketResponseData)
.filter((item) => market === "all" || item.quote_unit === market)
}
}
增强图标的视觉表现,显示该何时增加或减少
2.2 将JS对象的内容绑定到组件上
PagePlug独特的灵活性,任意一处皆可编写JS,用{{组件名.属性}}的方式去取值
比如本次的案例中,通过JSobject里的函数去获取值【通过{{}}的形式,JSobject名称 . 函数】
对PagePlug上一些功能使用讲解及介绍,可以点击下方文章查看
1、Formily开发实战——3分钟完成一个登录页的开发,Formily表单与PagePlug低代码的完美融合
2、国内优秀的开源低代码框架:PagePlug,面向研发使用,拒绝重复、低价值的工单循环开发
3、保姆级低代码实战教程——玩转PagePlug表格开发,增删改查分页如此简单
保姆级低代码实战教程——玩转PagePlug表格开发,增删改查分页如此简单_帆软对数据增删改查_PagePlug的博客-CSDN博客面向研发使用的低代码,比低代码区别很大的,相较于轻流、简道云、轻宜搭、微搭、帆软、活字格等等的低代码灵活性及维护更好_帆软对数据增删改查https://blog.csdn.net/AppsmithCN/article/details/1312107304、PagePlug:低代码平台入门教程—10分钟搭建一个用户改查系统
5、B站上也有一些案例教程,欢迎查看研究~~
欢迎点赞、收藏、喜欢三连鼓励下哟 ,避免找不到文章啦