关于websocket:有效技巧Websocket-长连接状态如何保持

WebSocket 是一种反对通过单个 TCP 连贯进行全双工通信的协定,相较于传统的 HTTP 协定,它更适宜须要实时交互的利用场景。此协定在古代 Web 利用中扮演着至关重要的角色,尤其是在须要实时更新和通信的场合下维持长久连贯。本文将探讨 WebSocket 如何无效地保护这些连贯,并通过详尽的教程与示例领导开发者更深刻地了解与利用此技术。 利用场景WebSocket 的长久连接功能在多种利用场景下施展重要作用,包含但不限于: 即时通讯软件实时合作编辑工具多人在线游戏股票交易平台在以上场景中使用 WebSocket,能够实现即时数据推送和疾速的双向交流,从而大幅晋升用户体验。 放弃连贯的策略语法概要通过应用 JavaScript 的 WebSocket API 在客户端和服务器之间建设 WebSocket 连贯的过程相当间接。以下是一些根底语法示例: // 在客户端建设 WebSocket 连贯const socket = new WebSocket('ws://example.com/socket');// 监听接管音讯事件socket.addEventListener('message', (event) => { console.log('收到音讯:', event.data);});// 发送音讯socket.send('您好,服务器!');策略 1:履行心跳机制在 WebSocket 中,一种放弃连贯沉闷的常见办法是定期向服务器发送心跳音讯。以下是心跳机制的一个代码示例: // 定期发送心跳音讯setInterval(() => { if (socket.readyState === WebSocket.OPEN) { socket.send('心跳'); }}, 30000); // 每30秒发送一次策略 2:应用 WebSocket 拦截器WebSocket 拦截器可能在连贯的不同阶段退出自定义逻辑。这种形式使得在解决连贯建设、音讯接管等事件时更加灵便。 // WebSocket 连贯关上拦截器socket.addEventListener('open', (event) => { console.log('连贯已建设'); // 在此处增加自定义逻辑});socket.addEventListener('message', (event) => { console.log('收到音讯:', event.data); // 在此处增加自定义解决逻辑});WebSocket 施行步骤步骤 1:建设 WebSocket 连贯首先,须要在你的我的项目中建设 WebSocket 连贯: ...

March 1, 2024 · 1 min · jiezi

关于websocket:Apifox-WebSocket-调试功能你会用了吗

在 2.2.32 及更高版本的 Apifox 中,反对对 WebSocket API 进行调试。 WebSocket 是一种在单个 TCP 连贯上进行全双工通信的 API 技术。相比于传统的 HTTP 申请,WebSocket 具备更低的提早和更高的效率。它实用于须要长时间放弃连贯并实时传输数据的场景,例如在线游戏、实时聊天等。 一、建设连贯WebSocket 通过一个简略的握手过程来建设连贯。应用 Apifox,只须要在地址栏填写 URL 后点击「连贯」按钮,即可实现握手并建设 WebSocket 连贯。 在 Apifox 中,点击左侧的「+」按钮,抉择「新建 WebSocket 接口(Beta)」; 输出 WebSocket 接口的 URL 以建设连贯,以 ws 或 wss 结尾。同时,你能够自定义握手时所须要传递的参数,比方 Params、Headers、Cookies,以满足鉴权或其余简单场景。 点击「连贯」,胜利后即可无缝地发送和接管音讯。二、发送和接管音讯连贯后,你能够在 「Message 」标签下撰写音讯。除了间接撰写 Text、 JSON、XML、HTML 等文本格式的音讯之外,还能够通过 Base64 或 Hexadecimal 来撰写二进制格局的音讯。编辑器会依据所选的音讯格局,对音讯内容进行语法高亮。如果音讯是 JSON、XML 或 HTML 格局,还能够对输出的内容进行格式化操作。 Apifox 提供了一个全新的工夫线视图,在下方的「Messages」种依照工夫程序集中展现连贯状态、发送的音讯、收到的音讯。点击音讯之后,能够在右侧查看音讯的详情,十分不便。 如果音讯是文本格式,默认会显示格式化后的音讯,也能够手动切换音讯格局和编码;如果音讯是二进制格局,默认会显示音讯的 Hexdump,也能够查看通过 Base64 编码后的音讯和原始音讯。 接口文档,与团队成员合作 Apifox 杰出的接口文档性能在 WebSocket API 上失去了继承。你能够对 Websocket 接口设定状态、责任人、标签,还能够用 Markdown 格局撰写具体的接口阐明。 在实现调试后,你能够点击 「保留」按钮将 WebSocket 接口保留到以后我的项目内,以便团队内的其余成员进行调试;也能够将 WebSocket 的接口文档分享到团队内部,而后间接在浏览器中查看。 ...

April 26, 2023 · 1 min · jiezi

关于websocket:gws-高性能websocket服务器新的选择

gws 是一款由 golang 开发的高性能 websocket 库, 提供 websocket event api : type Event interface { OnOpen(socket *Conn) OnError(socket *Conn, err error) OnClose(socket *Conn, code uint16, reason []byte) OnPing(socket *Conn, payload []byte) OnPong(socket *Conn, payload []byte) OnMessage(socket *Conn, message *Message)}反对大部分 RFC 规范: 接管分片音讯发送敞开帧ping/pongdeflate数据压缩应用bufio读写音讯得益于高效的协定解析器, 相比其余库 gws 领有更高的 IOPS , 更低的提早和 CPU 占用率. 然而因为 bufio 的应用, 内存占用会高一些. $ tcpkali -c 1000 --connect-rate 500 -r 1000 -T 300s -f assets/1K.txt --ws 127.0.0.1:${port}/connect 个性方面, 次要有: ...

April 9, 2023 · 2 min · jiezi

关于websocket:windows-系统下-workerman-在同一个运行窗口中开启多个-websocket-服务

开启多个 ws 服务失败失常状况下,如果你想开启多个 websocket 服务的话只有在一个文件中,输出 new Worker 两次,监听不同端口,应用 Worker::runAll() 命令即可然而你会发现在在 windows 中无奈在一个文件中同时监听两个 websocket 服务,其余零碎比方 Linux 是没有问题的会报 multi workers init in one php file are not support 谬误,意思 windows 中禁止在同一个文件中开启多个 websocket 服务<?phpuse Workerman\Worker;use Workerman\Connection\TcpConnection;require_once __DIR__ . '/vendor/autoload.php';$http_worker = new Worker("http://0.0.0.0:1234");$http_worker->onMessage = function(TcpConnection $connection, $data){ $connection->send('hello http');};$ws_worker = new Worker('websocket://0.0.0.0:2345');$ws_worker->onMessage = function(TcpConnection $connection, $data){ $connection->send('hello websocket');};// 运行所有Worker实例Worker::runAll(); 开启服务失败解决办法windows 版本的 workerman 不反对在同一个文件中实例化多个 Worker。 因而 windows 版本的 workerman 须要将多个 Worker 实例初始化放在不同的文件中才能够解决 比方咱们定义两个文件,一个 start_http.php,另一个是 start_websocket.php,在文件中别离退出以下代码,监听不同端口,而后应用 php 文件名 命令启动这两个文件即可<?phpuse Workerman\Worker;use Workerman\Connection\TcpConnection;require_once __DIR__ . '/vendor/autoload.php';// 须要监听不同的端口$ws_worker = new Worker('websocket://0.0.0.0:xxxx');$ws_worker->onMessage = function(TcpConnection $connection, $data){ $connection->send('hello websocket');};// 运行所有Worker实例(这里只有一个实例)Worker::runAll(); 同一个窗口中运行下面尽管能够解决运行多个 websocket 实例,然而你会发现有几个文件,就会有几个运行窗口如果开启的服务更多,窗口也会更多,那么如何在同一个窗口中运行启动多个服务呢那么如果要解决这个问题,咱们就能够创立一个 init.bat 文件,而后在文件中退出以下代码,而后双击运行即可实现在同一个窗口中启动多个服务/d: 示意切换目录/b: 示意不关上新窗口,在以后窗口继续执行%切换到我的项目根目录%cd /d E:\xxxxxx%启动第一个服务%start /b php start_http.php%启动第二个服务%start /b php start_websocket.php

March 17, 2023 · 1 min · jiezi

关于websocket:弄懂-Websocket-你得知道的这-3-点

1. WebSocket原理WebSocket同HTTP一样也是应用层的协定,然而它是一种双向通信协定,是建设在TCP之上的。 WebSocket是一种在单个TCP连贯上进行全双工通信的协定。WebSocket API也被W3C定为规范。 WebSocket使得客户端和服务器之间的数据交换变得更加简略,容许服务端被动向客户端推送数据。在WebSocket API中,浏览器和服务器只须要实现一次握手, 两者之间就间接能够创立持久性的连贯,并进行双向数据传输。 握手过程: 浏览器、服务器建设TCP连贯,三次握手。这是通信的根底,传输管制层,若失败后续都不执行。TCP连贯胜利后,浏览器通过HTTP协定向服务器传送WebSocket反对的版本号等信息。(开始前的HTTP握手)服务器收到客户端的握手申请后,同样采纳HTTP协定回馈数据。当收到了连贯胜利的音讯后,通过TCP通道进行传输通信。 Websocket默认应用申请协定为:ws://,默认端口:80。对TLS加密申请协定为:wss://,端口:443。 2. WebSocket与HTTP的关系相同点: 都是一样基于TCP的,都是可靠性传输协定。都是应用层协定。 不同点: WebSocket是双向通信协定,模仿Socket协定,能够双向发送或承受信息。HTTP是单向的。WebSocket是须要握手进行建设连贯的。 分割: WebSocket在建设握手时,数据是通过HTTP传输的。然而建设之后,在真正传输时候是不须要HTTP协定的。 3. WebSocket与Socket的关系Socket其实并不是一个协定,而是为了方便使用TCP或UDP而形象进去的一层,是位于应用层和传输管制层之间的一组接口。tcp是牢靠的连贯,且连贯后才能够发送数据;udp是不牢靠的连贯,不连贯就能够发送数。 Socket是应用层与TCP/IP协定族通信的两头软件形象层,它是一组接口。在设计模式中,Socket其实就是一个门面模式,它把简单的TCP/IP协定族暗藏在Socket接口前面,对用户来说,一组简略的接口就是全副,让Socket去组织数据,以合乎指定的协定。 当两台主机通信时,必须通过Socket连贯,Socket则利用TCP/IP协定建设TCP连贯。TCP连贯则更依附于底层的IP协定,IP协定的连贯则依赖于链路层等更低层次。 WebSocket则是一个典型的应用层协定,Socket是传输管制层协定。 如果你在日常工作中须要进行 Websocket 协定测试,但又不晓得应用什么工具,那我举荐你用这一款开源的 API 管理工具——Postcat: 在线 Demo 链接:https://postcat.com/zh/?utm_s... 文档链接:https://docs.postcat.com/?utm... 如果你感觉这个开源我的项目还能够的话,无妨点个 star 反对下他们,如果你感觉还须要持续优化,无妨去提个Issue. Github:https://github.com/Postcatlab... Gitee:https://gitee.com/eolink_admi...

February 21, 2023 · 1 min · jiezi

关于websocket:node-使用websocket实现一个简易聊天

应用nodejs实现一个简易版聊天程序. client端package.json { "name": "client", "version": "1.0.0", "description": "", "main": "server.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1", "start": "node server.js" }, "keywords": [], "author": "", "license": "ISC", "dependencies": { "express": "^4.18.2" }}server.js const express = require("express");const path = require("path");const port = 4000;const app = new express();app.use(express.static(path.resolve(__dirname, "public")));app.listen(port, () => { console.log(`Client server run at ${port}.`);})public/index.html <!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> .chat { display: flex; flex-direction: column; row-gap: 10px; } .row { display: flex; width: 400px; column-gap: 10px; } #display { width: 100%; height: 300px; background-color: white; border: 1px solid black; overflow-y: auto; } #input { height: 100px; width: 400px; } </style></head><body> <div class="chat"> <div class="row"> <input id="sender" type="text" placeholder="发送方" /> <button onclick="onConnect()">connect</button> <button onclick="onDisConnect()">disconnect</button> <button onclick="onClear()">clear</button> </div> <div class="row"> <textarea id="display" readonly></textarea> </div> <div class="row"> <textarea id="input" placeholder="发送音讯"></textarea> <div> <input id="receiver" placeholder="接管方" /> <button onclick="onSend()">send</button> </div> </div> </div> <script> let senderDom = document.getElementById("sender"); let receiverDom = document.getElementById("receiver"); let inputDom = document.getElementById("input"); let displayDom = document.getElementById("display"); let websocketIp = "ws://localhost:4005?name="; let ws; function onConnect() { if (ws) { onDisplay(`You are already online.`); return; } let sender = senderDom.value; ws = new WebSocket(websocketIp + sender); ws.onopen = (e) => { clearDisplay(); onDisplay(`You are online.`); } ws.onclose = (e) => { onDisplay(`You are offline.`); ws = undefined; } ws.onmessage = (e) => { const obj = JSON.parse(e.data); const { type, data } = obj; if (type == "status") { const { user, status } = data; onDisplay(`${user} is ${status == 0 ? "offline" : "online"}.`); } else if (type == "message") { const { sender, message } = data; onDisplay(`${sender}:${message}`); } } } function onDisConnect() { if (!ws) { onDisplay(`You are offline,please connect first.`); return; } ws.close(); } function onClear() { clearDisplay(); } function onSend() { if (!ws) { onDisplay(`You are offline,please connect first.`); return; } let sender = senderDom.value; let receiver = receiverDom.value; let message = inputDom.value; let msg = JSON.stringify({ receiver: receiver, message: message }); ws.send(msg); onDisplay(`${sender}:${message}`); } //utils function onDisplay(value) { displayDom.value += value + "\n\n"; displayDom.scrollTop = displayDom.scrollHeight; } function clearDisplay() { displayDom.value = ""; } </script></body></html>server端package.json ...

December 16, 2022 · 3 min · jiezi

关于websocket:开源了它终于支持-Websocket-协议

要晓得,在 Websocket 协定公布之前,浏览器只能单向通信,客户端能够分割服务端,但服务端不能被动分割客户端。 在这种背景下,音讯推送以及须要实时通信的聊天室等性能实现比拟麻烦,机智的开发者们只能一遍骂骂咧咧一边写轮询的代码. Socket.IO 能解决浏览器没有原生提供双向通信的形式,它为了反对长连贯也是操碎了心,内置了好几种不同的降级计划。直到浏览器发表原生反对 Websocket后,咱们这群搞开发的都乐开了花。 Websocket 始终以来是大家都嚷嚷着提Issue ,要求开发给反对的协定。最近这款开源的 API 管理工具,EOAPI,终于反对了! 话不多说,间接上形容: Websocket 测试先上动图~ 文字步骤: 点击 Tab 加号选中 Websocket 协定输出地址后点击连贯按钮就能够和服务端进行通信啦在 message 输出你想要发送的内容在返回 message 信息流中查看内容 测试完结后,还能够点击测试历史看到历史申请 在线 Demo 链接:https://www.eoapi.io/?utm_sou... 文档链接:https://docs.eoapi.io/?utm_so... Github: https://github.com/eolinker/e... Gitee: https://gitee.com/eolink_admi...

October 21, 2022 · 1 min · jiezi

关于websocket:这个开源-api-管理工具终于支持-Websocket-协议测试插件管理功能了

间隔上一次公布正式版本过了两周,这两周咱们在攒一个大性能—— 反对 Websocket 协定测试! 长期关注咱们的共建者晓得,这个性能预报了几万年,真不是咱们迁延,是 API 协定切实太多了... 有些协定纵横互联网 20 年耸立不倒经久不衰,有些协定声音越来越强劲,还有新秀 gRPC、GraphQL 慢慢锋芒毕露。 咱们针对各种协定了调研,下图是调研的一部分。每种协定有不同的实用场景,分享给大家~ 在 Websocket 协定公布之前,浏览器只能单向通信,客户端能够分割服务端,但服务端不能被动分割客户端。 在这种背景下,音讯推送以及须要实时通信的聊天室等性能实现比拟麻烦,机智的开发者们会一边骂骂咧咧一边哭着写轮询的代码,好生苦楚。 Socket.IO 的诞世也是为了解决浏览器没有原生提供双向通信的形式,它为了反对长连贯也是操碎了心,内置了好几种不同的降级计划。直到浏览器发表原生反对 Websocket后,开发者乐开了花。 Websocket 协定始终以来社区呼声都比拟高,所以咱们抉择优先反对,大家能够降级到 v1.7.0 的 Eoapi 对它进行试用~ Websocket 测试先上动图~ 文字步骤: 点击 Tab 加号选中 Websocket 协定输出地址后点击连贯按钮就能够和服务端进行通信啦在 message 输出你想要发送的内容在返回 message 信息流中查看内容测试完结后,还能够点击测试历史看到历史申请 插件治理随着装置的插件越来越多,咱们须要对插件进行治理,本次迭代优化了插件治理,减少了插件开发,插件配置等性能,话不多说,上图文: 如果临时不想这个插件失效? 之前须要卸载,当初能够插件开关,管制粒度更精密,能够在保留插件配置的前提下不应用插件的性能。 将插件配置放到每个插件的详情页,更好找更不便了~ 预报后续打算反对性能:反对 HTTP API 测试用例插件反对 UI 管制更多协定反对... Eoapi 是一款类 Postman 的开源 API 工具,它更轻量,同时可拓展。 Github:https://github.com/eolinker/e... Gitee:https://gitee.com/eolink_admi... 官网文档:https://www.eoapi.io/?utm_sou... 如果你对于 Eoapi 有任何疑难或者倡议,都能够去 Github 或者 Gitee 找我,提个Issue,我看到了都会及时回复的,最初别忘了 Star 一下哦~

September 14, 2022 · 1 min · jiezi

关于websocket:用golang写一个简易聊天室

这里websocket库用的是github.com/lxzan/gws. 实现上十分的简介, 没有任何依赖; 事件驱动; 反对中间件; 每个连贯自带音讯队列, 不会被单条音讯的解决阻塞通信. 外围接口如下, 是不是相熟的滋味 type EventHandler interface { OnRecover(socket *Conn, exception interface{}) OnOpen(socket *Conn) OnClose(socket *Conn, code Code, reason []byte) OnMessage(socket *Conn, m *Message) OnError(socket *Conn, err error) OnPing(socket *Conn, m []byte) OnPong(socket *Conn, m []byte)}聊天室代码 package mainimport ( "encoding/json" "github.com/lxzan/gws" "net/http" "sync")var handler = &Handler{sessions: sync.Map{}}type Handler struct { sessions sync.Map}func (h *Handler) OnRecover(socket *gws.Conn, exception interface{}) {}// 把连贯保留到sync.Map, 用于websocket连贯之间的通信func (h *Handler) OnOpen(socket *gws.Conn) { name, _ := socket.Storage.Get("name") h.sessions.Store(name.(string), socket)}func (h *Handler) OnClose(socket *gws.Conn, code gws.Code, reason []byte) {}// 通信格局type Request struct { To string `json:"to"` Message string `json:"message"`}func (h *Handler) OnMessage(socket *gws.Conn, m *gws.Message) { var request Request json.Unmarshal(m.Bytes(), &request) me, _ := socket.Storage.Get("name") if me.(string) == request.To { socket.Write(m.MessageType(), m.Bytes()) m.Close() } else { if receiver, ok := h.sessions.Load(request.To); ok { h.OnMessage(receiver.(*gws.Conn), m) } }}func (h *Handler) OnError(socket *gws.Conn, err error) {}func (h *Handler) OnPing(socket *gws.Conn, m []byte) {}func (h *Handler) OnPong(socket *gws.Conn, m []byte) {}func main() { var upgrader = gws.Upgrader{ ServerOptions: &gws.ServerOptions{ LogEnabled: true, CompressEnabled: false, }, // 用户名由url带进来, 存到storage外面 CheckOrigin: func(r *gws.Request) bool { r.Storage.Put("name", r.URL.Query().Get("name")) return true }, } http.HandleFunc("/ws", func(w http.ResponseWriter, r *http.Request) { upgrader.Upgrade(w, r, handler) }) http.ListenAndServe(":3000", nil)}

September 13, 2022 · 1 min · jiezi

关于websocket:CONNMIX-开发-WebSocket-用户消息中心

WebSocket 用户音讯核心应用websocket做音讯核心,通常做法是采纳kafka、redis等中间件搭配实现,应用CONNMIX则无需应用中间件,同时分布式集群能力也无需担心用户量大增后带来的性能问题。 要求connmix >= v1.0.4设计思路客户端在ws连贯胜利后发送音讯执行登录,应用lua调用业务api接口解析登录token数据中的uid,而后将uid保留到连贯的context中。登录胜利后发送音讯订阅一个用户ID的通道 user:<uid>,该uid从context中取出。在发送用户音讯的接口中,调用connmix任意节点的 /v1/mesh/publish 接口往对应 uid 发送实时音讯,所有订阅该通道的ws客户端都将会收到音讯。以上都是增量推送设计,全量通常都是在页面加载时通过一个全量api接口获取。交互协定设计必须登录后能力执行订阅、勾销订阅当用户发送 @user 咱们在 lua 代码中就执行订阅 user:\<uid\> 通道。性能json格局登录{"op":"auth","token":"*"}订阅用户音讯{"op":"subscribe","channel":"@user"}勾销用户音讯{"op":"unsubscribe","channel":"@user"}用户音讯事件{"event":"@user","data":{"uid":1001,"msg":"Hello,World!"}}胜利{"result":true}谬误{"code":1,"msg":"Error"}装置引擎connmix https://connmix.com/docs/1.0/#/zh-cn/install-engine批改配置在 connmix.yaml 配置文件的 options 选项,批改websocket的url门路 options: - name: path value: /message-centerCONNMIX 编码批改 entry.websocket.lua 的 on_message 办法如下: 当音讯为auth类型时,调用 auth_url 接口通过token获取到uid,并保留到context中当音讯为subscribe、unsubscribe时,从context取出uid,执行订阅/勾销订阅对应的通道function on_message(msg) --print(msg) if msg["type"] ~= "text" then conn:close() return end local auth_url = "http://127.0.0.1:8000/websocket_auth" --填写解析token的api接口地址 local conn = mix.websocket() local data, err = mix.json_decode(msg["data"]) if err then mix_log(mix_DEBUG, "json_decode error: " .. err) conn:close() return end local op = data["op"] local channel_raw = data["channel"] local channel_table = mix.str_split(channel_raw, "@") if table.getn(channel_table) ~= 2 then mix_log(mix_DEBUG, "invalid channel: " .. channel_raw) conn:close() return end local channel_type = channel_table[2] if op == "auth" then local token = data["token"] local resp, err = mix.http.request("POST", auth_url, { body = '{"token:"' .. token .. '"}' }) if err then mix_log(mix_DEBUG, "http.request error: " .. err) conn:close() return end if resp.status_code ~= 200 then mix_log(mix_DEBUG, "http.request status_code: " .. resp["status_code"]) conn:close() return end local body_table, err = mix.json_decode(resp["body"]) if err then mix_log(mix_DEBUG, "json_decode error: " .. err) conn:close() return end conn:set_context_value("uid", body_table["uid"]) return end local uid = conn:context_value("uid") if op == "subscribe" and channel_type == "user" then if uid == nil then conn:send('{"code":1,"msg":"Not Auth"}') return end local err = conn:subscribe("user:" .. uid) if err then mix_log(mix_DEBUG, "subscribe error: " .. err) conn:close() return end end if op == "unsubscribe" and channel_type == "user" then if uid == nil then conn:send('{"code":1,"msg":"Not Auth"}') return end local err = conn:unsubscribe("user:" .. uid) if err then mix_log(mix_DEBUG, "unsubscribe error: " .. err) conn:close() return end end conn:send('{"result":true}')endAPI 编码在现有零碎的框架中编写一个登录信息验证接口 /websocket_auth,用于ws登录获取用户uid ...

September 6, 2022 · 2 min · jiezi

关于websocket:httpServer来代理WebSocket通信

1、简介1.1、通信形式 单工:数据只反对在一个方向传输,即单向,在同一时间内只有一方可能承受&发送信息;半双工:容许数据可能双向传输,然而,在某一时刻只容许数据在一个方向传输。相似切换方向的单工通信。http就是半双工通信,先有申请,再有响应;全双工:容许数据同时都能双向传输,相似两个单工通信的联合,要求client & server都有独立接管和发送的能力,在任意时刻都能接管&发送信息,socket就是全双工通信; 1.2、websocketwebsocket实质是一种网络应用层协定,建设在单个TCP连贯上的全双工模式,用来补救了http协定在继续双向通信能力上的有余,容许服务端与客户端之间能够双向被动推送数据。特点: 与http协定有着良好的兼容性,默认端口80(协定标识为ws)或者443(加密传输,协定标识为wss);建设连贯的握手阶段采纳的是http协定,依据这个个性,能够在链路两头引入http代理服务器;数据格式轻量,性能开销小,通信效率高(只有建设连贯后,就能够有限收发报文);报文内容能够是文本,也能够是二进制数据;没有同源的束缚,不存在跨域一说,客户端能够与任意服务器通信(前提是服务器能应答);对外裸露的URL为:ws://${domain}:80/${path},或者wss://${domain}:443/${path} 2、搭建demo2.1、server采纳ws库疾速构建一个websocket server,监听connection事件,收到音讯并且打印后,立马发送给客户端const ws = require('ws'); let wsServer = new ws.Server({ port: 3000,host:'127.0.0.1',path:'/websocket'}); wsServer.on('connection', function (server) { console.log('client connected');server.on('message', function (message) { console.dir(message) console.log(message.toString()); server.send(`hello:${message}`)});});复制代码2.2、client疾速搭建一个websocket client,利用http-server在目录下启动,并且拜访该页面<!DOCTYPE html> <html><head> <title>websocket demo</title></head><body> <h1></h1> <br> <input type='text' id='sendText'> <button onclick='send()'>send</button></body></html><script> const ws = new WebSocket('ws://127.0.0.1:3000/websocket');ws.onopen = function () { console.log('服务器连贯')}ws.onmessage = (msg) => { console.log('来自服务器发来的数据:', msg) alert('服务器返回内容:' + msg.data)}ws.onclose = () => { console.log('服务器敞开')}function send() { if (ws) { let msg = document.getElementById('sendText').value; ws.send(msg) } else { alert('websocket server error') }}</script>复制代码2.3、建设连贯先启动websocket server,而后浏览器申请websocket client页面,抓包申请如下: ...

July 29, 2022 · 3 min · jiezi

关于websocket:WebSocket总结

HeaderSec-WebSocket-ProtocolSec-WebSocket-Protocol报头指定了你心愿应用的一个或多个WebSocket协定,按优先级排序。第一个被服务器反对的将被服务器抉择并在响应中蕴含的Sec-WebSocket-Protocol头中返回。你也能够在头文件中应用它不止一次;后果与在单个报头中应用逗号分隔的子协定标识符列表雷同。 须要WebSocket服务器实现了相干protocol,否则这个字段没什么用。 // socket.io-clientvar socket = io({ protocols: ["control"]});// 原生var Socket = new WebSocket(url, [protocol]);Sec-WebSocket-Version申请头指定客户端心愿应用的WebSocket协定版本,以便服务器能够确认其端是否反对该版本。(个别不须要改变)Sec-WebSocket-Version: versionversion: 客户端与服务器通信时心愿应用的WebSocket协定版本。这个数字应该是IANA WebSocket版本号注册表中列出的最新版本。 WebSocket协定的最新最终版本是版本13。 响应头如果服务器不能应用指定版本的WebSocket协定进行通信,它将响应一个谬误(例如426 Upgrade Required),该谬误在其报头中蕴含一个Sec-WebSocket-Version报头,以及一个以逗号分隔的反对协定版本列表。如果服务器不反对申请的协定版本,则响应中不蕴含Sec-WebSocket-Version头。Sec-WebSocket-Version: supportedVersionssupportVersions: 服务器反对的WebSocket协定版本的逗号分隔列表。 参考文章协定降级机制

June 7, 2022 · 1 min · jiezi

关于websocket:WebSockets简单应用及介绍

简略利用及介绍 对于websocket技术,让我回想起来了之前,在实现一个零碎的时候,咱们想做一个通信的性能,这个时候咱们不晓得怎么做,咱们想做的就是通过循环的拜访接口,然而这样必定还是无奈实现这个性能的,因为的确不是实时通信的成果。 而后接触到了这个websocket技术,查看了介绍过后,咱们感觉这个技术,其实就是将http进行了一个降级,升级成了一个websocket的协定过程。 介绍 websocket技术,实现实时通信的技术,这里咱们通过建设一个TCP链接,而后通过TCP双工通道,进行数据的传输。 下面就是做到实时通信的两种形式,第一个轮询是其实不算真正意义上的实时通信。 解决的问题:http存在的问题 在申请的时候是客户端被动发动的,服务端不能被动发动。申请是一来一回的,并且每一次都是须要加上header头的。这个是十分耗费工夫的。无状态的,这样就会存在一个问题,就是不晓得这个申请的用户是谁。长轮询问题 后面咱们说到了,实现实时的传输的时候,应用ajax就要应用的是轮询。始终拜访,而后期待数据有批改的时候, 这个时候就返回,而后这个申请还须要客户端进行发动。其实就是对资源的节约 GO中websocket的利用在go中,查阅了材料,最初我选用的库是: go get github.com/gorilla/websocket这个库是比拟罕用的。而后咱们来简略的应用一下解说。 应用流程 从下面的图中,咱们能够看到几个要害的流程,也就是最根本的几个流程。 创立webSocket对象 在这里咱们要应用这个库的货色,咱们首先是要先创立这个构造的,所以咱们创立了这个对象: //创立websockt对象var upgrader = websocket.Upgrader{ ReadBufferSize: 1024, WriteBufferSize: 1024,}在这里咱们就创立了一个全局的连贯对象,而后咱们应用这个对象去获取webSocket的连贯 获取webSocket连贯 获取连贯咱们须要应用Upgrede来进行conn的获取,具体的代码如下: conn, err := (&websocket.Upgrader{ CheckOrigin: func(r *http.Request) bool { return isvalida }, }).Upgrade(writer, request, nil)这里咱们能够看到,两头有一个CheckOrigin函数,咱们应用这个函数,能够决定咱们是不是返回这个连贯,这里咱们能够对isvalida进行操作,例如:咱们在做一个聊天的软件,这个时候咱们须要验证这个用户,是不是咱们的用户,咱们验证胜利后咱们将isvalida的值转换为true,当然咱们如果不指定这个CheckOrigin函数,这个时候这个函数返回的就是true。后面是对构造的减少,也就是一个判断函数。前面调用了Upgrade,来获取连贯的对象。 接管音讯和发送音讯 接管音讯 接管音讯,咱们接管客户端发送回来的音讯,而后进行解决,这里要害的函数是:ReadMessage。 func (c *Conn) ReadMessage() (messageType int, p []byte, err error)返回三个参数,第一个参数是返回信息类型,第二个是音讯体,最初是谬误返回 发送音讯 给客户端被动的发送音讯,要害的函数是:WriteMessage。 func (c *Conn) WriteMessage(messageType int, data []byte) error这里有三个参数,第一个是音讯的类型,第二个是音讯体,而后返回一个谬误。 下面就是简略的应用流程和要害的步骤。 ...

May 12, 2022 · 1 min · jiezi

关于websocket:WGCLOUD的web-ssh显示websocket服务连接已断开

问题形容: 明天在应用WGCLOUD的主机列表,SSH连贯Linux服务器的时候,关上后,页面显示websocket服务连贯已断开,如下图所示 解决办法: 这个问题是咱们的server主机防火墙没有开启web ssh服务端口导致的,端口默认是9998,把端口凋谢就好了

March 22, 2022 · 1 min · jiezi

关于websocket:基于sockjs的前端WebSocket二次封装

因业务须要,与后端进行websocket长连贯通信,通过钻研,决定应用sockjs-client和stompjs库,并进行了二次封装。 package.json版本: "sockjs-client": "^1.5.1","stompjs": "^2.3.3",socketManager.js: import SockJS from 'sockjs-client';import Stomp from 'stompjs';import lodash from 'lodash';function subscribeCallBack(data, subscribes) { if (data) { let topic = data.headers.destination; let funces = subscribes.get(topic); funces.forEach((func) => { func(data); }); }}let clientManager = { client: null, connecting: false,//是否正在连接 subscribes: new Map(),//订阅列表 subscribe(topic, onMessage) { if (this.client != null && this.client.connected == true) { //已连贯状态 console.log('减少订阅 已连贯状态'); if (!this.subscribes.has(topic)) { this.client.subscribe(topic, (data) => subscribeCallBack(data, this.subscribes)); this.subscribes.set(topic, [onMessage]); } else { let funces = this.subscribes.get(topic); funces.push(onMessage); } } else { //未连贯状态 console.log('减少订阅 未连贯状态'); if (!this.subscribes.has(topic)) { this.subscribes.set(topic, [onMessage]); } else { let funces = this.subscribes.get(topic); funces.push(onMessage); } } }, subscribesAll() { console.log('订阅全副'); if (lodash.isEmpty(this.client) || this.client.connected != true) { return; } let subscribes = this.subscribes; for (let topic of subscribes.keys()) { this.client.subscribe(topic, (data) => subscribeCallBack(data, subscribes)); } }, disconnect() { console.log('断开连接'); if (lodash.isEmpty(this.client) || this.client.connected != true) { return; } this.client.disconnect(); this.subscribes = new Map(); }, connect(onSuccess, onDisconnect) { try { if (this.connecting == true) { console.log('正在连接中'); return; } this.connecting = true; if (lodash.isEmpty(this.client) || this.client.connected != true) {//未连贯状态 let socket = new SockJS('/bond/notification', null, { timeout: 6000 }); let stompClient = Stomp.over(socket); stompClient.debug = null; console.log('开始连贯'); stompClient.connect ({}, () => { this.client = stompClient; console.log('连贯胜利'); this.subscribesAll();//连贯胜利后开始订阅所有内容 if (onSuccess != null && onSuccess != undefined) { onSuccess(); }; }, (error) => this.errorCallBack(error, onSuccess, onDisconnect) ); } else if (this.client != null && this.client.connected == true) {//已连贯状态间接调用回调 onSuccess(); } } catch (err) { console.log('连贯异样', err); } finally { this.connecting = false; } }, errorCallBack(error, onSuccess, onDisconnect) { console.log('连贯失败'); if (onDisconnect != null && onDisconnect != undefined) { onDisconnect(); } setTimeout(() => {//主动重连 console.log('从新连贯中'); this.connect(onSuccess, onDisconnect); }, 10000); },};export default clientManager;连贯形式: ...

October 29, 2021 · 2 min · jiezi

关于websocket:智汀家庭云开发指南Web扩展开发插件开发智能设备

新建插件包咱们的插件包都是放在plugins文件夹下的,如果你须要新开发一个插件包,你能够在plugins文件夹下新建一个命为xxx的我的项目,目录构造如下 目录构造能够依据插件的复杂程度自行扩大 如何启动插件包我的项目?建好插件包目录后,咱们能够进到智汀专业版我的项目的根目录, 而后通过以下命令能够启动插件包我的项目 【1】前端源码剖析 咱们看下次要的代码插件包次要性能是实现智能设施的管制,智能设施的管制次要是通过Websocket发送指令去管制的,这里援用了智汀封装好的Websocket插件包ws-plugin,插件管制的相干指令能够点击这里插件模块。 这里有两个重要的变量,这两个参数都是从插件链接传进来的 token 用户的身份凭证,这里次要用来做权限管制device_id 设施id,就是发送控制指令时须要晓得是管制哪个设施进入页面时咱们要建设一个Websocket长连贯,用来发送操作指令和同步更新设施状态 2. 如何做权限管制?进入插件时咱们要做好权限管制,无权限用户无奈在插件页面操作。 当咱们Websocket链接胜利后,咱们须要发一个指令去拿用户的操作权限。 指令如下: 发送指令后,后端会返回用户的权限信息,构造如下 咱们能够依据 “can_control” 获取用户的权限,而后拿到权限信息后就能够在页面做对应的操作 3. 如何管制设施?咱们也是通过发送指令的办法管制设施 例如:关上灯其余操作也一样,只是操作的指令有所区别 4. 如何同步设施状态?每个设施都有初始状态,咱们怎么同步设施的初始状态呢 同样的也是发送指令 后端送到指令后会返回设施的初始状态,构造如下: 拿到设施后,咱们就能够对设施进行初始化值 5. 如何公布咱们的插件? 当咱们的插件包开发实现后,就打包编译咱们的前端和后端文件, 填写咱们的配置文件 构造如下: 而后在智汀家庭云上传咱们的插件包,等审核通过后就能够看到咱们的插件包了。 【2】 插件包集成打包咱们的插件包 打包后的文件在根目录下plugin文件下 插件的后端文件也要编译好 最终插件包的目录如下: html文件夹是插件的页面文件,也就是(html,js,css等动态资源文件)yeelight-plugin是插件后端的编译文件config.yaml是插件的配置文件咱们看下插件的配置文件包含哪些信息 这些信息都是很重要的,不能脱漏!咱们的插件能够蕴含多个设施,每个设施有所属的品牌。 智汀用户通过增加装置咱们的插件包就能够通过智汀app发现咱们插件反对的设施,连贯设施,操作咱们的设施。

October 8, 2021 · 1 min · jiezi

关于websocket:智汀家庭云开发指南Web业务功能设备控制

设施管制 次要就是对设施一些操作控制比方:开启/敞开、光的亮度、音量、温度…等等调节操作机制。 设施管制页是一个独立单利用治理(独立插件包),插件开发上面将有独自介绍 设施管制页的展现成果: 设施管制同样是走Websocket通信管制,依据不同类型设施传绝对应参数获取以后管制状态 次要的实现原理: 开关点击传输一个参数类型 “off” 或者 “on” 依据接管的信息启动跟敞开 调节亮度拉拽温度进度条低温或者高温模式,向Websocket输送一个温度值,同样依据接管的后果启动亮度值调节色温拉拽色温进度条,向Websocket输送一个色温值,同样依据接管的后果启动色温值

October 8, 2021 · 1 min · jiezi

关于websocket:智汀家庭云开发指南Web业务功能设备发现

此时,下面设施通信提到的Websocket通信就派上用场了。 咱们先回顾一下,设施发现的流程: 1.启动设施发现智汀智慧首页点击增加设施进入到设施扫描发现页: 设施发现中... 首先依据后端须要发一个Websocket申请肯定参数到服务,此时页面UI执行动画扫面展现成果,期待数据返回后果做解决 2.设施发现完结后接管到数据后果信息后依据是否胜利展现反馈后果: 1)胜利有其余设施存在 则如图: 2)失败或者不存设施 则如图: 3.实现原理代码在全局vuex已存Websocket办法

October 8, 2021 · 1 min · jiezi

关于websocket:智汀家庭云开发指南Web业务功能设备通讯

**对于智汀家庭云Web版而已,与设施的通信都离不开智慧核心的通信。在这里,咱们采纳Websocket技术,以Websocket建设长链接进行通信信息接管传输。** Websocket 介绍Websocket是一种在单个TCP连贯上进行全双工通信的协定,Websocket API也被W3C定为规范,Websocket使得客户端和服务器之间的数据交换变得更加简略,容许服务端被动向客户端推送数据。 Websocket的构造函数、常量、属性、办法,事件 ->Websocket 本利用已封装Websocket办法"ws-plugin"上传npm 官网,开源地址:https://github.com/zhiting-te... “ws-plugin” 的装置“ws-plugin” 的页面引入相干调用办法:实现通信,接下来我就能够获取咱们须要的信息对设施进行一些操作了,很nice!!

October 8, 2021 · 1 min · jiezi

关于websocket:智汀家庭云开发指南Golang附录错误码列表

错误码列表通用0: 胜利1: 服务器异样2: 谬误申请3: 找不到资源 家庭/公司1000: 该家庭不存在1001: 以后家庭创建者不容许退出家庭1002: 请输出家庭名称1003: 家庭名称长度不能超过30 设施2000: 设施已被增加2001: 设施已被绑定2002: 请输出设施名称2003: 设施名称长度不能超过202004: 该设施不存在2005:以后用户未绑定该设施2006: 数据同步失败,请重试2007: 数据已同步,禁止屡次同步数据 房间/区域3000: 该房间不存在3001: 请输出房间名称3002: 房间名称长度不能超过203003: 房间名称反复 场景4000: 与其余场景重名,请批改4001: 场景名称长度不能超过404002: 场景触发条件不存在4003: 场景不存在4004: 您没有创立场景的权限4005: 您没有删除场景的权限4006: 场景类型不容许批改4007: 场景触发条件类型与配置不统一4008: 定时触发条件只能增加一个4009: 工作类型谬误4010: 设施操作类型不存在4011: 设施操作未设置4012: 没有场景或设施的管制权限4013: 设施断连4014: %s不正确 用户5001: 用户名或明码谬误5002: 用户不能删除本人5003: 用户不存在5004: 以后用户名已存在,请从新输出5005: 请输出用户名5006: 用户名只能输出数字、字母、下划线,不能全副是数字5007: 请输出昵称5008: 昵称长度不能大于20位5009: 昵称长度不能小于6位5010: 请输出明码5011: 明码不能少于6位5012: 用户未登录5013: 二维码有效5014: 二维码已过期5015: 二维码创建者无权限5016: 获取二维码失败5017: 该角色不存在5018: 该角色已存在,请从新输出5019: 请输出角色名称5020: 角色名称不能超过20位5021: 以后用户没有权限

October 8, 2021 · 1 min · jiezi

关于websocket:五种IO模型

几个基本概念:阻塞:调用后果返回之前,以后线程会被挂起进入非可执行状态,cpu不会给线程调配工夫片,该过程只有在失去后果之后才会返回。非阻塞:在不能失去后果之前,以后线程不会被阻塞而是立刻返回。 同步:调用者在发动一个性能调用时,在没失去后果之前该调用不会返回。异步:调用者在发动一个性能调用后不能立刻失去后果,当这个调用被解决实现后,会通过状态、告诉和回调来告诉调用者。 linux操作系统中分了内核空间与用户空间,所有的IO操作都得取得内核的反对,用户态的过程无奈间接进行内核的IO操作,内核空间提供了零碎调用,使得用户态的过程能够间接执行IO操作。 再进行五种模型介绍前,咱们来先看下一次网络申请中服务端做了哪些操作。每个客户端会与服务端建设一次socket连贯,服务端获取连贯后,对于所有的数据的读写都须要通过操作系统的内核,通过零碎调用内核将数据复制到用户过程的缓冲区,实现与客户端的交互,依据零碎调用的形式分为阻塞与非阻塞,依据零碎解决利用过程的形式分为同步与异步。 阻塞式IO每一次客户端产生的socket连贯实际上是一个文件描述符(file descriptor),而每一个用户过程读取实际上了是一个文件描述符,这个时候的零碎调用函数会期待网络申请数据的达到,和数据从内核空间复制到用户过程空间,也就是是,第一阶段的IO调用与第二阶段的IO执行都会阻塞,对于多个客户端连贯,只能开拓多个线程来解决。 非阻塞式IO(NIO)为解决阻塞IO模型的阻塞问题,零碎的内核进行了改良,在内核中socket反对了非阻塞状态,socket不再阻塞后,就能够应用一个过程解决客户端的连贯,该进行外部一直进行轮询,查看每一个连贯的网络数据是否已达到,此时轮询产生在用户空间,然而该过程仍然须要本人解决所有的连贯,所以该期间为同步非阻塞IO期间,也就是NIO。 IO多路复用在非阻塞IO模型中,尽管解决了IO调用阻塞的问题,然而产生了新的问题,如果当初有1万个连贯,那用户线程会调用1万次零碎调用read来进行解决,在用户空间这种开销太大,解决的思路就是让用户缩小零碎调用,然而用户本人实现不了,所以就导致了内核产生了进一步变。在内核空间中帮忙用户过程遍历所有的文件描述符,将数据筹备好的文件描述符返回给用户过程,该形式是同步阻塞IO,因为在第一阶段的IO调用会阻塞过程。上面是select与poll示意图:上面是epoll示意图:对于epoll来说在第一阶段的epoll_wait仍然是阻塞的,所以也是同步阻塞IO。select/poll/epoll具体的信息可见四种网络模型里无关IO多路复用的阐明 信号驱动式IO在IO执行的数据筹备阶段,不会阻塞用户过程,当用户过程须要期待数据的时候,会向内核发送一个信号,通知内核须要数据,而后用户过程就持续做别的事了,而当内核中的数据筹备好之后,内核会给用户过程发一个信和,用户过程收到信号后立马调用recvfrom去查收数据,该不O模型应用的较少。 异步IO(AIO)利用过程通过aio_read告知内核启动某个操作,且在整个操作实现之后再告诉利用过程,包含将数据从内核空间拷贝到用户空间。信号驱动IO是内核告诉咱们何时能够启动一个IO操作,而异步IO模型是由内核告诉咱们IO操作何时实现,是真正意义上的无阻塞IO操作。 总结前四种模型的次要区别在于第一阶段,因为它们的第二阶段都是一样的:在数据从内核拷贝到利用过程的缓冲区期间过程都会阻塞。相同,异步IO模型在这两创优都不会阻塞。 最初再提一下间接内存间接内存并不是虚拟机运行时数据区的一部分,也不是java虚拟机标准中定义的内存区域。间接内存申请空间消耗更高的性能,间接内存IO读写的性能要优于一般的堆内存,对于java程序来说,零碎内核读取堆类的对象须要依据代码段计算其偏移量来获取对象地址,效率较慢,不太适宜网络IO的场景,对于间接内存来说更加适宜IO操作,内核读取寄存在间接内存中的对象较为不便,因为其地址就是袒露的过程虚拟地址,不须要jvm翻译。那么就能够应用mmap开拓一块间接内存mapbuffer和内核空间共享,并且该间接内存能够间接映射到磁盘上的文件,这样就能够通过调用本地的put而不必调用零碎调用write就能够将数据间接写入磁盘,RandomAccessFile类就是通过开拓mapbuffer实现的读写磁盘。 参考的文章:从5种网络IO模型到零拷贝

October 3, 2021 · 1 min · jiezi

关于websocket:四种网络通信模型

本文波及到socket模型/多过程模型/多线程模型/IO多路复用模型,上面进行开展。 根底的socket模型 让客户端和服务端能在网络中进行通信得应用socket编程,它能够跨主机间通信,这是过程间通信里比拟非凡的的中央。单方在进行网络通信前得各自创立一个socket,在创立的过程中能够指定网络层应用的是IPv4还是IPv6传输层应用的是TCP还是UDP。咱们具体来看下服务端的socket编程过程是怎么的。服务端首先调用socket()函数,创立网络协议为IPv4,以及传输协定为TCP的socket,接着调用bind函数,给这个sokcet绑定一个IP地址和端口,绑定IP地址和端口的目标: 绑定端口的目标:当内核收到TCP报文,通过TCP头里的端口号找到咱们的应用程序,而后将数据传递给咱们。绑定IP的目标:一台机器是能够有多个网卡的,每个网卡都有对应的IP地址,当绑定一个网卡时,内核在收到该网卡上的包才会发给对应的应用程序。绑定完 IP 地址和端口后,就能够调用 listen() 函数进行监听,此时对应 TCP 状态图中的 listen,如果咱们要断定服务器中一个网络程序有没有启动,能够通过 netstat 命令查看对应的端口号是否有被监听。服务端进入了监听状态后,通过调用 accept() 函数,来从内核获取客户端的连贯,如果没有客户端连贯,则会阻塞期待客户端连贯的到来。 上面咱们再来看下客户端发动连贯的过程,客户端在创立好socket后,调用connect()函数发动连贯,函数参数须要指明服务端的IP与端口,而后就是TCP的三次握手。在TCP连贯的过程中,服务器的内核实际上为每个socket保护了两个队列: 一个是还没有齐全建设连贯的队列,称为TCP半连贯队列,这个队列都是没有实现三次扬的连贯,此时服务端处于syn_rcvd状态。一个是曾经建设连贯的队列,称为TCP全连贯队列,这个队列都是实现了三次扬的连贯,此时服务端处于established状态。当TCP全连贯队列一为空后,服务端的accept()函数就会从内核中的TCP全连贯队列里取出一个曾经实现连贯的socket返回应用程序,后续数据传输都应用这个socket。须要留神的是,监听的socket与真正用来传数据的sokcet是两个:一个叫作监听socket,一个叫作已连贯soket。建设连贯后,客户端与服务端就开始互相传输数据了,单方都能够通过read()和write()函数来读写数据。这就是TCP协定的socket程序的调用过程。 多过程模型 下面提到的TCP socket调用流程是最简略、最根本的,它基本上只能一对一通信,因为应用的是同步阻塞的形式,当服务端还没解决完一个客户端的网络IO时,或读写操作产生阻塞时,其它客户端是无奈与服务端连贯的。那在这种单过程模式下服务器单机实践最大能连贯多少客户端呢?TCP连贯是由四元组惟一确认的,这里的四元组指:本机IP,本机端口,对端IP,对端端口。服务器作为服务方,通常会在本地固定监听一个端口,因而服务里的本机IP与本机端口是固定的,所以最大TCP连接数=客户端*客户端端口数。对于IPv4,客户端的IP数最多为2的32次方,客户端的端数最多为2的16次方,也就是服务端单机最大TCP连接数约为2的48次方,不过这是咱们没有思考其它限度因素的,次要的限度有: 文件描述符,socket实际上是一个文件,也就会对应一个文件描述符,在linux下,单个过程关上的文件描述符是有限度的,默认为1024,不过咱们能够进行批改零碎内存,每个TCP连贯在内核中都有对应的数据结构,意味着每个连贯都是会占用肯定内存的显然这种单过程模式反对的连接数是很无限的。 多过程模型 基于最原始的阻塞网络IO,如果服务器要反对多个客户端,其中比拟传统的形式,就是应用多过程模型,也就是为每个客户端调配一个过程来解决。服务器的主过程负责监听客户的连贯,一旦与客户端连贯实现,accept函数就会返回一个‘已连贯socket’,这时通过fork函数创立一个子过程,实际上就是将父过程所有相干的资源都复制一份,像文件描述符、内存地址空间、程序计数器、执行的代码等。咱们能够通过返回值来辨别父子过程,父子过程各有分工:父过程只须要关怀‘监听socket’而不必关怀‘已连贯socket’,子过程则只关怀‘已连贯socket’而不必关怀‘监听socket’,能够看下图:子过程占用着系统资源,随着子过程数量上的减少,还有过程间上下文切换(上下文切换不仅蕴含了虚拟内存、栈、全局变量等用户空间的资源,还包含了内核堆栈、寄存器等内核空间的资源),服务端必定是应答不了。 多线程模型 既然过程间上下文切换很耗性能,于是轻量级的线程就呈现了。线程是运行在过程中的一个‘逻辑流’,一个过程中能够运行多个线程,同过程里的多个线程能够共享过程的局部资源,像文件描述符列表、过程空间、代码、全局数据、堆、共享库,因为这些资源是共享的所以在应用的时候不须要切换,而保须要切换线程的公有数据、寄存器等不共享的数据,所以这比过程间的上下文切换开销要小很多。因为将已连贯的socket退出到的队列是全局的,每个线程都会操作,为了防止多线程间的竞争,线程在操作这个队列前须要加锁。理念上多线程模型比多过程模型反对的连贯要多,但线程间的调度、共享资源的竞争也很耗性能,另外线程也不是有限的,一个线程会占1M的内存空间,如果须要解决的连贯特地耗时那性能就会直线降落。 IO多路复用 既然为每个申请调配一个过程/线程的形式不适合,那有没可能只应用一个过程来保护多个socket呢?当然是有的,就是IO多路复用技术。一个过程尽管任一时刻只能解决一个申请,然而解决每个申请将耗时管制在极短的工夫内,这样1秒内就能够解决上千个申请,将工夫拉长来看,多个申请复用了一个过程,这就是多路复用,这种思维很相似一个CPU并发多个过程,所以也叫时候多路复用。咱们相熟的select/poll/epoll内核提供给用户态的多路复用零碎调用,过程能够通过一个零碎调用函数从内核中取得多个事件。select/poll/epoll 是如何获取网络事件的呢?在获取事件时,先把所有连贯(文件描述符)传给内核,再由内核返回产生了事件的连贯,而后在用户态中再解决这些连贯对应的申请即可。select/poll这两种实现的形式差不多所以放在一起说。select实现多路复用的形式是,将已连贯的socket都放到一个文件描述符汇合,而后调用select函数将文件描述符汇合拷贝到内核里,让内核来查看是否有网络事件产生,查看的形式就是通过遍历文件描述符汇合的形式,当查看到有事件产生后,将此socket标记为可读或可写,接着再将整个文件描述符汇合拷贝回用户态里,而后用户态还须要再次遍历找到可读或可写的socket而后再对其解决。所以,对于socket这种形式,须要进行2次遍历文件描述符汇合,一次是在内核态一次是在用户态,而且还会产生2次拷贝文件描述符汇合,先从用户空间传入内核空间,由内核空间批改后,再传出到用户空间中。select应用固定长度的bitsMap示意文件描述符汇合,文件描述符的个数是有限度的,在linux零碎中,由内核中的FD_SETSIZE限度,默认最大值为1024,只能监听0~1023的文件描述符。poll不再应用BitsMap来存储所关注的文件描述符,而是应用动静数组,以链表模式来组织,冲破了select的文件描述符个数限度,不过还是会受到系统文件描述符限度。poll与select并没有太大的本质区别,都是应用线性构造存储过程关注的socket汇合,因而都南非要遍历文件描述符汇合来找到可读或可写的socket,工夫复杂度为O(n),而且也须要在用户态与内核态之间拷贝文件描述符汇合,这种形式随着并发数上来,性能的损耗会呈指数级别增长。epollepoll通过两个方面来解决select/poll的问题。 epoll在内核里应用红黑树来跟踪过程所有待检测的文件描述符,将须要监控的socket通过epoll_ctl()函数退出内核中的红黑树里,红黑树的操作工夫复杂度个别是O(logn),通过对红黑树的操作,就不须要像select/poll每次操作时都传入整个socket汇合,只须要传入一个待检测的socket,缩小了内核和用户空间大量的数据拷贝和内存调配。epoll应用事件驱动的机制,内核保护了一个链表来记录就绪事件,当某个socket有事件产生时,通过回调函数内核会将其退出到这个就绪事件列表中,当用户调用epoll_wait()函数时,只会返回有事件产生的文件描述符的个数,不须要像select/poll那样轮询扫描整个socket汇合,从而大大提高了检测是的效率。 epoll的形式即便监听的socket数量增多时,效率也不会大幅度降低,可能同时监听的socket数量下限为零碎定义的过程关上的最大文件描述符个数。 epoll反对的两种事件触发模式,别离是边缘触发与程度触发。 边缘触发:当被监听的socket描述符上有可读事件产生时,服务器端只会从epoll_wait中世界杯一次,即便过程没有调用read函数从内核读取数据,也仍然只昏迷一次,因而咱们程序要保障一次性将内核缓冲区的数据读取完程度触发:录被监听的socket上有可读事件产生时,服务器端一直地从epoll_wait中昏迷,直到内核缓冲区数据被read函数读完才完结,目标是通知咱们有事件须要读取。举个例子,你的快递被放到了一个快递箱里,如果快递箱只会通过短信告诉你一次,即便你始终没有去取,它也不会再发送第二条短信揭示你,这个形式就是边缘触发;如果快递箱发现你的快递没有被取出,它就会不停地发短信告诉你,直到你取出了快递,它才消停,这个就是程度触发的形式。一般来说,边缘触发的效率比程度触发的效率要高,因为边缘触发能够缩小 epoll_wait 的零碎调用次数,零碎调用也是有肯定的开销的的,毕竟也存在上下文的切换。select/poll 只有程度触发模式,epoll 默认的触发模式是程度触发,然而能够依据利用场景设置为边缘触发模式。多路复用 API 返回的事件并不一定可读写的,如果应用阻塞 I/O, 那么在调用 read/write 时则会产生程序阻塞,因而最好搭配非阻塞 I/O,以便应答极少数的非凡状况。 总结 最根底的TCP的socket编程,它是阻塞IO模型,基本上只能一对一通信,那为了服务更多的客户端,咱们须要改良网络IO模型。比拟传统的形式是应用多过程/线程模型,每来一个客户端连贯,就调配一个过程/线程,而后后续的读写都在对应的过程/线程。当客户端一直增大时,过程/线程的高度还有上下文切换及它们占用的内存都会有成瓶颈。为了解决下面这个问题,就呈现了IO的多路复用,能够只在一个过程里解决多个文件的IO,linux下有三种提供IO多路复用的API,别离是是:select/poll/epoll。select与poll没有实质的区别,它们外部都是应用‘线性构造’来存储过程关注的socket汇合。在应用的时候,首先须要把关注的 Socket 汇合通过 select/poll 零碎调用从用户态拷贝到内核态,而后由内核检测事件,当有网络事件产生时,内核须要遍历过程关注 Socket 汇合,找到对应的 Socket,并设置其状态为可读/可写,而后把整个 Socket 汇合从内核态拷贝到用户态,用户态还要持续遍历整个 Socket 汇合找到可读/可写的 Socket,而后对其解决。很显著发现,select 和 poll 的缺点在于,当客户端越多,也就是 Socket 汇合越大,Socket 汇合的遍历和拷贝会带来很大的开销。于是呈现了epoll,它是通过在内核应用红黑树来存储所有待检测的文件描述符,因为不须要每次都传入整个socket汇合,就缩小了内核和用户空间大量的数据拷贝和内存调配,另一点就是应用事件驱动的机制在内核保护了一个链表来记录就绪事件而不须要将select/poll轮询整个汇合,大大提高了检测的效率。另外,epoll 反对边缘触发和程度触发的形式,而 select/poll 只反对程度触发,一般而言,边缘触发的形式会比程度触发的效率高。 本文次要参考: 小林coding解答IO多路复用的文章

October 3, 2021 · 1 min · jiezi

关于websocket:智汀家庭云开发指南Golang设备模块

1.品牌品牌指的是智能设施的品牌,SA通过插件的模式对该品牌下的设施进行发现管制。实践上来说一个品牌对应一个插件服务。您能够通过我的项目 根目录下的品牌查看SA反对的品牌。对于插件服务的详细信息能够参考 plugin 2.设施的相干操作在SA中是通过一个个命令对设施进行操作的,如果您想应用这些命令操作某一品牌的设施,首先应该装置该品牌的插件。在SA中装置、更新、 移除插件。请参考 plugin SA解决设施命令的流程:客户端通过websocket音讯的模式将对应的操作命令发送给SA,SA通过grpc的形式将音讯转发给插件服务,插件 服务解决后,将解决的后果通过grpc的形式发送给SA,SA将处理结果以websocket音讯返回给客户端。 (1)设施的发现与增加发现设施 发现设施需向SA发送以下格局的websocket音讯,字段阐明: domain: 插件名称;service:设施命令。 { "domain": "", "id": 1, "service": "discover"}胜利后SA会返回以下音讯 { "id": 1, "success": true, "result": {"device": { "id": "21394770a79648e6a3416239e1ebecb9", "address": "192.168.0.195:55443", "identity": "0x0000000012ed37c8", "name": "yeelight_ceiling17_0x0000000012ed37c8", "model": "ceiling17", "sw_version": "5", "manufacturer": "yeelight", "power": "on", "bright": 80, "color_mode": 2, "ct": 3017, "rgb": 0, "hue": 0, "sat": 0 } }}manufacturer之后的字段为设施属性,取决于设施的类型 增加设施 将发现设施操作获取的设施次要信息通过增加设施接口以下列格局发送到SA。如果增加的设施为SA,则type为smart_assistant {"device": {"name": "nisi dolore eu est","brand_id": "commodo es","address": "pariatur sint","identity": "velit ut ad","model": "proident veniam","type": "nisi Lorem in officia irure","sw_version": "qui ut","manufacturer": "aute Lorem pariatur volu","plugin_id": "dolore reprehenderit"}}SA会将该设施长久化保留在数据库中,之后便可通过插件管制设施。 ...

September 30, 2021 · 1 min · jiezi

关于websocket:Django3使用WebSocket实现WebShell

前言最近工作中须要开发前端操作近程虚拟机的性能,简称WebShell. 基于以后的技术栈为react+django,调研了一会发现大部分的后端实现都是django+channels来实现websocket服务.大抵看了下感觉这不够乏味,翻了翻django的官网文档发现django原生是不反对websocket的,但django3之后反对了asgi协定能够本人实现websocket服务. 于是选定gunicorn+uvicorn+asgi+websocket+django3.2+paramiko来实现WebShell. 实现websocket服务应用django自带的脚手架生成的我的项目会主动生成asgi.py和wsgi.py两个文件,一般利用大部分用的都是wsgi.py配合nginx部署线上服务. 这次次要应用asgi.py实现websocket服务的思路大抵网上搜一下就能找到,次要就是实现 connect/send/receive/disconnect这个几个动作的解决办法.这里 How to Add Websockets to a Django App without Extra Dependencies 就是一个很好的实例, 但过于简略........: 思路# asgi.py import osfrom django.core.asgi import get_asgi_applicationfrom websocket_app.websocket import websocket_applicationos.environ.setdefault('DJANGO_SETTINGS_MODULE', 'websocket_app.settings')django_application = get_asgi_application()async def application(scope, receive, send): if scope['type'] == 'http': await django_application(scope, receive, send) elif scope['type'] == 'websocket': await websocket_application(scope, receive, send) else: raise NotImplementedError(f"Unknown scope type {scope['type']}")# websocket.pyasync def websocket_application(scope, receive, send): pass# websocket.pyasync def websocket_application(scope, receive, send): while True: event = await receive() if event['type'] == 'websocket.connect': await send({ 'type': 'websocket.accept' }) if event['type'] == 'websocket.disconnect': break if event['type'] == 'websocket.receive': if event['text'] == 'ping': await send({ 'type': 'websocket.send', 'text': 'pong!' })实现下面的代码提供了思路,比拟残缺的能够参考这里 websockets-in-django-3-1 根本能够复用了其中最外围的实现局部我放上面: ...

August 25, 2021 · 4 min · jiezi

关于websocket:Kali-Linux-网络安全-使用-WireShark-对常用协议抓包并分析原理

前言<font color=#999AAA >作为一款高效收费的抓包工具,wireshark能够捕捉并形容网络数据包,其最大的劣势就是收费、开源以及多平台反对,在GNU通用公共许可证的保障范畴下,用户能够收费获取软件和代码,并领有对其源码批改和定制的权力,现在其已是寰球最宽泛的网络数据包剖析软件之一。接下来我就带大家用WireShark 实战:应用 WireShark 对罕用协定抓包并剖析原理</font> 罕用协定剖析-ARP 协定地址解析协定(英语:Address Resolution Protocol,缩写:ARP)是一个通过解析网络层地址来找寻数据链路层地址的网络传输协定,它在 IPv4 中极其重要。ARP 是通过网络地址来定位 MAC 地址。 开始抓包---过滤 arp咱们应用 nmap 来基于 ARP 协定进行扫描 ┌──(root xuegod53)-[~]└─# nmap -sn 192.168.1.1咱们看一下咱们抓取到的数据包剖析第一个申请包查看 Address Resolution Protocol (request) ARP 申请包内容: Address Resolution Protocol (request) #ARP 地址解析协定 request 示意申请包Hardware type: Ethernet (1) #硬件类型Protocol type: IPv4 ( 0x0800 ) #协定类型Hardware size: 6 #硬件地址Protocol size: 4 #协定长度Opcode:_ request ( 1 ) #操作码,该值为 1 示意 ARP 申请包Sender MAC address: VMware_f1:35:ee (00:0c:29:f1:35:ee) #源 MAC 地址Sender IP address: 192.168.1.53 . #源 IP 地址Target MAC address: 00:00:00_ 00: 00:00 (00: 00: 00 :00: 00:00) #指标 MAC 地址Target IP address: 192.168.1.1 #指标 IP 地址咱们来剖析第二个数据包 ARP 的应答数据包查看: Address Resolution Protocol (reply) ARP 地址解析协定 ...

August 3, 2021 · 2 min · jiezi

关于websocket:高效的全双工即时通信-WebSocket

1. 历史演进概览什么是 WebSocket?它是定义客户端和服务端如何通过 Web 进行通信的一种网络协议,该通信协议于2011年被IETF(互联网工程工作组)定为规范RFC 6455,并由RFC7936补充标准。 在 WebSocket 协定之前,还有几种计划可用于实现即时通信。上面将顺次介绍。 1.1 HTTP1.0/1.1在HTTP 1.0/1.1 中,客户端和服务端建设通信,须要通过发送申请与期待响应。因为 HTTP 是无状态的的,每一次申请和响应都须要在 header 中携带大量信息,并且因为其半双工个性,同一时间流量只能单向流动,即申请发送进来,须要始终期待响应的到来,能力做下一步的操作,肯定水平上造成了通信低效。 1.2 轮询在须要显示股票价格走势等这一类对信息实时性要求较高的场景,如果每次都由用户手动去刷新浏览器获取最新信息,会存在显著滞后性。因而能够设定一个定时器,每个一段时间,就像服务端发送申请获取最新后果,这种办法被称为”轮询“(polling)。 如果能明确晓得服务端数据更新的距离,那么轮询是一个不错的计划。然而大部分时候,是无奈预测数据什么时候更新的,每个固定周期都发送申请查问,会产生很多不必要的申请,造成节约。 1.3 长轮询长轮询(long polling)则是基于轮询的另一种计划,它也被称为 Comet 或者反向 AJAX。服务端收到客户端申请后,会放弃申请关上,直到有客户端可用的信息或者超时了,才返回可给客户端。这么解决绝对于一般的轮询,长处是能够缩小不必要的申请。然而当信息更新很频繁时,长轮询绝对于一般轮询的劣势就不再显著。 1.4 HTTP Streaming后面波及的轮询,每一次申请响应完结后,都会敞开连贯。下一次申请,客户端仍然须要在申请头中携带大量信息。而 HTTP Streaming 流化技术的实现机制是客户端发送一个申请,服务端发送一个继续更新和放弃关上的凋谢响应。每当服务端有客户端可用信息时,就更新响应,然而连贯始终保持关上。通过这种形式来躲避频繁申请带来的不必要开销。 HTTP Stream 存在的毛病是和灵便的全双工通信还存在着间隔,还是以单向通信为主,服务端能够自在地发数据给客户端,但客户端缺没方法。 1.5 WebSocketWebSocket 是一种全双工,高实时,双向,单套接字长连贯。由一次HTTP申请可降级为 WebSocket 连贯,可重用客户端到服务端,服务端到客户端的同一连贯。它和 HTTP 同属于计算机网络七层模型的应用层,基于TCP传输,二者的差异性如下。 个性HTTPWebSocket内容MIME 音讯文本、二进制音讯传输半双工全双工2. 连贯治理WebSocket 协定的具体运作次要分为三局部:握手建设连贯,数据传输,敞开连贯。 2.1 握手建设2.1.1 HTTP 申请降级在上方图中能够看到 WebSocket 连贯建设的大抵流程为一次 HTTP的申请与响应,而后便可建设连贯。 初始阶段客户端客户端发送 HTTP Upgrade Request,在 Request Header 中告知服务端将降级为 WebSocket。 其中红色字段为必选。 ...

May 17, 2021 · 2 min · jiezi

关于visual-studio-code:vscode成功变身社交平台看我如何给vscode扩展一个聊天室

家喻户晓,Visual Studio Code(VS code)是微软家提供的一个轻量而凋谢的收费开源代码编辑器,吸引了不少开发者投入到vscode下进行日常生产,而平时的编码工作又是异样的枯燥乏味,于是笔者在这里折腾了一下,给VS Code装上了一个能够聊天和听歌的插件。 插件名称:BBBUG.COMVisual Studio Code下一个集音乐/聊天等一体的在线聊天室插件,反对多房间和创立私人房间,反对房间加密和切换房间模式。 装置形式:1. 在Visual Studio Code插件平台搜寻 BBBUG 等关键词,找到上面的插件: 2. 点击 install 装置插件后,插件即可主动启动运行,接下来咱们须要替换一下VS Code的ffmpeg组件。微软在VS Code上应用的Electron蕴含的ffmpeg被解决过了,这里不替换的话将无奈听歌。首先查问一下本人VS Code的Eletron版本号: 查到版本号后,到 https://npm.taobao.org/mirror... 下载对应版本的 FFmpeg 插件,并替换掉VS Code装置目录下的同文件即可。 请留神,这里须要依据你本人电脑的版本和操作系统进行下载,替换之后重启VS Code即可听到音乐了。 如何聊天和点歌?本插件与 www.bbbug.com 聊天室后端买通,这里咱们能够间接应用 邮箱验证码 登录后进入聊天室: 当然,如果你以前在 https://www.bbbug.com 聊天室有注册过账号,这里也能够间接应用你的ID或邮箱+你的明码登录,我这里间接应用邮箱验证码登录了。 这里登录胜利后右下角会有相干的提示信息,你能够点击状态栏上的各个按钮进行点歌、切换房间或者是发动聊天。 当然,插件也提供了两个十分棒的快捷键进行无鼠标的操作: MacOS: Command + `Windows: Alt + `如何创立一个本人的房间?以后版本的插件暂不反对间接创立房间,你能够在 www.bbbug.com 网站上创立好本人的房间后再VS Code上登录就能够啦。 我的项目阐明这里介绍完了如何应用这个插件,顺带也介绍一下我的项目吧 :) BBBUG音乐聊天室是一个纯学习应用的开源聊天室我的项目,以后所有代码已在Gitee上开源,也欢送你来奉献更多有意思的小工具呀,开源地址: https://gitee.com/bbbug_com

April 5, 2021 · 1 min · jiezi

关于websocket:开发了一个一起听歌聊天的开源项目

我的项目简介BBBUG一个在线听歌的聊天室,反对多房间和创立私人房间,反对房间加密和切换房间模式,体验一下:www.bbbug.com 我的项目仓库地址:https://github.com/HammCn/ | https://gitee.com/bbbug_com 已实现性能1、一般文字与图片表情音讯聊天性能2、歌曲搜寻、点歌、切歌、顶歌、珍藏歌曲等性能3、歌曲实时同步播放给房间所有人、反对房主电台模式4、可创立房间、房主可禁言或禁止房间用户点歌5、批改个人资料与设置等6、ESC快捷沉迷式听歌体验7、反对设置房间二级域名与绑定独立域名等(因为性能调整,临时砍掉了)8、反对白天模式与暗黑模式两种主题,可自在设置9、“摸一摸”等互动玩法我的项目架构图服务端架构图 二级域名与顶级域名设计图 歌曲同步流程图 局部我的项目截图Web前端: 暗黑模式: Vscode插件端: iOS APP端 Windows客户端: 微信小程序端 能够扫描这个码体验一下 欢送大家提倡议呀~轻喷。

March 26, 2021 · 1 min · jiezi

关于websocket:websocket

websocket在什么背景下诞生?短轮询(Polling)短轮询(Polling)的实现思路就是浏览器端每隔几秒钟向服务器端发送http申请,服务端在收到申请后,不管是否有数据更新,都间接进行响应。在服务端响应实现,就会敞开这个Tcp连贯,如下图所示: 示例代码实现如下: function Polling() { fetch(url).then(data => { // somthing }).catch(err => { console.log(err); });}setInterval(polling, 5000);长处:能够看到实现非常简单,它的兼容性也比拟好的只有反对http协定就能够用这种形式实现。毛病:然而它的毛病也很显著就是十分的耗费资源,因为建设Tcp连贯是十分耗费资源的,服务端响应实现就会敞开这个Tcp连贯,下一次申请再次建设Tcp连贯。长轮询(Long-Polling)客户端发送申请后服务器端不会立刻返回数据,服务器端会阻塞申请连贯不会立刻断开,直到服务器端有数据更新或者是连贯超时才返回,客户端才再次发出请求新建连贯、如此重复从而获取最新数据。大抵成果如下: 客户端的代码如下: function LongPolling() { fetch(url).then(data => { LongPolling(); }).catch(err => { LongPolling(); console.log(err); });}LongPolling();长处: 长轮询和短轮询比起来,显著缩小了很多不必要的http申请次数,相比之下节约了资源。毛病:连贯挂起也会导致资源的节约。WebSocketWebSocket是一种协定,是一种与HTTP 等同的网络协议,两者都是应用层协定,都基于 TCP 协定。然而 WebSocket 是一种双向通信协定,在建设连贯之后,WebSocket 的 server 与 client 都能被动向对方发送或接收数据。同时,WebSocket在建设连贯时须要借助 HTTP 协定,连贯建设好了之后 client 与 server 之间的双向通信就与 HTTP 无关了。 相比于短轮询、长轮询的每次“申请-应答”都要client 与 server 建设连贯的模式,WebSocket 是一种长连贯的模式。就是一旦WebSocket 连贯建设后,除非client 或者 server 中有一端被动断开连接,否则每次数据传输之前都不须要HTTP 那样申请数据。 另外,短轮询、长轮询服务端都是被动的响应,属于单工通信。而websocket客户端、服务端都能被动的向对方发送音讯,属于全双工通信。 WebSocket 对象提供了一组 API,用于创立和治理 WebSocket 连贯,以及通过连贯发送和接收数据。浏览器提供的WebSocket API很简洁,调用示例如下: var ws = new WebSocket('wss://example.com/socket'); // 创立平安WebSocket 连贯(wss)ws.onerror = function (error) { ... } // 错误处理ws.onclose = function () { ... } // 敞开时调用ws.onopen = function () { ws.send("Connection established. Hello server!");} // 连贯建设时调用向服务端发送音讯ws.onmessage = function(msg) { ... }// 接管服务端发送的音讯复制代码HTTP、WebSocket 等应用层协定,都是基于 TCP 协定来传输数据的。咱们能够把这些高级协定了解成对 TCP 的封装。既然大家都应用 TCP 协定,那么大家的连贯和断开,都要遵循 TCP 协定中的三次握手和四次握手 ,只是在连贯之后发送的内容不同,或者是断开的工夫不同。对于 WebSocket 来说,它必须依赖 HTTP 协定进行一次握手 ,握手胜利后,数据就间接从 TCP 通道传输,与 HTTP 无关了。 ...

March 22, 2021 · 2 min · jiezi

关于websocket:台灯SAA申请欢迎光临

led灯泡实用规范:iec60968+澳洲偏差iec62031led 灯管实用规范:iec60598-1+澳洲偏差iec61347-1+澳洲偏差iec61347-2-13iec62031 led灯具实用规范:iec60598-1+澳洲偏差iec61347-1+澳洲偏差iec61347-2-13iec62031iec60598-2-x+澳洲偏差 LED台灯实用规范as/nzs60598-2-8 +澳洲偏差iec61347-1+澳洲偏差iec61347-2-13iec62031 留神:如果产品附带电源插头或电源线,须要提供相干saa认证信息。 其它元器件只须要合乎vde和ul认证。(cb报告+澳洲偏差)安规测试容易呈现不合格的我的项目a)灯管双端进电(需改单端进电)b)高级电线只满足根本绝缘(外加套管)c)塑料外壳资料测试:球压、针焰、灼热丝d)led驱动电源耐压测试:高级到次级电路3750ve)变压器构造(挡墙、三层绝缘线构造)f)内部电源线需加固定架:60n,25次拉力测试 深圳九方检测有限公司

March 11, 2021 · 1 min · jiezi

关于websocket:Websocket

1、为什么用Websocket? 解决服务端被动给客户端推送数据;失常ajax,axios只能通过客户端被动调用;能力实现服务端到客户端的数据传送教程:http://www.ruanyifeng.com/blo... 2、内容 (1) wsUrl + wsConfig //链接地址 + Websocket配置(2) createWebSocket //建设Websocket(3) initEventHandle //初始化实现(4) reconnect //重连3、wsUrl + wsConfig const wsUrl = "ws://192.168.0.79:8087/home/"var heartCheck = { timeout: 3000, //每三秒心跳一次 timeoutObj: null, serverTimeoutObj: null, reset: function() { clearTimeout(this.timeoutObj); clearTimeout(this.serverTimeoutObj); return this; }, start: function(type = 1) { const self = this; self.timeoutObj = setTimeout(function() { //这里发送一个心跳,后端收到后,返回一个心跳音讯, onmessage拿到返回的心跳就阐明连贯失常 ws.readyState == 1 && ws.send("-1"); self.serverTimeoutObj = setTimeout(function() { //如果超过肯定工夫还没重置,阐明后端被动断开了 //如果onclose会执行reconnect,进行重连 //如果间接执行reconnect 会触发onclose导致重连两次 ws.close(); }, type != 1 ? 1000 : 10000) }, type != 1 ? 1000 : self.timeout) } }4、建设Websocket链接 createWebSocket ...

January 6, 2021 · 2 min · jiezi

关于websocket:websocket-swoole-swoft

1.Websocket1.OSI七层与TCP/IP五层模型 2.socketSocket实际上是对TCP/IP协定的封装,自身并不是协定,而是一个调用接口(API).Socket的呈现只是使得程序员更不便地应用TCP/IP协定栈而已,是对TCP/IP协定的形象,从而造成了咱们晓得的一些最根本的函数接口.比方create、listen、connect、accept、send、read和write.3.简介WebSocket 是一种网络通信协定.WebSocket 协定在2008年诞生,2011年成为国际标准。所有浏览器都曾经反对了.服务器能够被动向客户端推送信息,客户端也能够被动向服务器发送信息,属于服务器推送技术的一种。`长链接`# 服务器推送技术1 Webpush2 HTTP server push3 Pushlet4 Long polling5 Flash XMLSocket relays6 Reliable Group Data Delivery (RGDD)7 Push notification4.与HTTP的比照 5.特点(1)建设在 TCP 协定之上,服务器端的实现比拟容易。(2)与 HTTP 协定有着良好的兼容性。默认端口也是`80`和`443`,并且握手阶段采纳 HTTP 协定,因而握手时不容易屏蔽,能通过各种 HTTP 代理服务器。(3)数据格式比拟轻量,性能开销小,通信高效。(4)能够发送文本,也能够发送二进制数据。(5)没有`同源限度`,客户端能够与任意服务器通信。(6)协定标识符是`ws`(如果加密,则为wss),服务器网址就是 URL。 (scheme)ws://example.com:80/uriwss://example.com:80/uri6.示例# 客户端let ws = new WebSocket("wss://echo.websocket.org");ws.onopen = function(evt) { console.log("Connection open ..."); ws.send("Hello WebSockets!");};ws.onmessage = function(evt) { console.log( "Received Message: " + evt.data); ws.close();};ws.onclose = function(evt) { console.log("Connection closed.");};# 服务端php -> socket_create(), new Socketpython -> socket,go -> gorilla/websocketnode -> socket.io / socket.io -client# 调试https://jsbin.com/?js,console,output# webSocket.readyStatereadyState属性返回实例对象的以后状态,共有四种。CONNECTING:值为0,示意正在连接。OPEN:值为1,示意连贯胜利,能够通信了。CLOSING:值为2,示意连贯正在敞开。CLOSED:值为3,示意连贯曾经敞开,或者关上连贯失败。2.swoole1.简介作者 韩天峰 pecl开发组成员php扩大Swoole 是一个 PHP 的 `协程` `高性能` 网络通信引擎,应用 C/C++ 语言编写,提供了多种通信协议的网络服务器和客户端模块。能够不便疾速的实现 TCP/UDP服务、高性能Web、WebSocket服务、物联网、实时通信、游戏、微服务等,使 PHP 不再局限于传统的 Web 畛域。4.4+之后, 全面协程化, PHP 协程框架 # 文档https://www.swoole.com/php-fpm` 解决申请 ...

November 30, 2020 · 7 min · jiezi

关于websocket:Springboot整合WebSocket实现网页版聊天快来围观

        前几天写了一篇《SpringBoot疾速入门》一文,而后周末趁着有工夫,在这个Springboot框架根底上整合了WebSocket技术写了一个网页版聊天性能。         如果小伙伴找不到那套框架了,能够看下之前的文章找到Springboot疾速入门一文 往期举荐 Springboot 残缺搭建疾速入门,必看! 通过该文章能够理解服务端与客户端之间的通信机制,以及理解相干的Http协定等技术内容。 话不多说,先来看看运行的过程: 页面写的非常简略,后续也会陆续将其优化和欠缺。 注释 一、HTTP相干常识 HTTP协定         http是一个简略的申请-响应协定,它通常运行在TCP之上。它指定了客户端可能发送给服务器什么样的音讯以及失去什么样的响应。申请和响应音讯的头以ASCII码模式给出;而音讯内容则具备一个相似MIME的格局。这个简略模型是晚期Web胜利的有功之臣,因为它使开发和部署十分地含糊其辞 http 为短连贯:客户端发送申请都须要服务器端回送响应。申请完结后,被动开释链接,因而为短连贯。通常的做法是,不须要任何数据,也要放弃每隔一段时间向服务器发送"放弃连贯"的申请。这样能够保障客户端在服务器端是"上线"状态。         HTTP连贯应用的是"申请-响应"形式,不仅在申请时建设连贯,而且客户端向服务器端申请后,服务器才返回数据。 二、Socket相干常识 1. 要想明确 Socket,必须要了解 TCP 连贯。 ① TCP 三次握手:握手过程中并不传输数据,在握手后服务器与客户端才开始传输数据,现实状态下,TCP 连贯一旦建设,在通信单方中的任何一方被动断开连接之前 TCP 连贯会始终放弃上来。 ② Socket 是对 TCP/IP 协定的封装,Socket 只是个接口不是协定,通过 Socket 咱们能力应用 TCP/IP 协定,除了 TCP,也能够应用 UDP 协定来传递数据。 ③ 创立 Socket 连贯的时候,能够指定传输层协定,能够是 TCP 或者 UDP,当用 TCP 连贯,该Socket就是个TCP连贯,反之。 ...

November 29, 2020 · 6 min · jiezi

关于websocket:WebSocket硬核入门200行代码教你徒手撸一个WebSocket服务器

本文原题“Node.js - 200 多行代码实现 Websocket 协定”,为了晋升内容品质,有较大订正。 1、引言最近正在钻研 WebSocket 相干的常识,想着如何能本人实现 WebSocket 协定。到网上收罗了一番材料后用 Node.js 实现了一个WebSocket协定服务器,倒也没有设想中那么简单,除去正文语句和 console 语句后,大概 200 行代码左右。 本文分享了自已开发一个WebSocket服务端实现过程中须要的常识储备,以及具体的代码实现含意等,非常适合想在短时间内对WebSocket协定从入门到精通的Web端即时通讯开发者浏览。 如果你想要写一个WebSocket 服务器,首先须要读懂对应的网络协议 RFC6455,不过这对于个别人来说有些 “艰涩”,英文且不说,还得咬文嚼字了解 网络编程 含意。 好在 WebSocket 技术呈现比拟早,所以早就有人翻译了残缺的 RFC6455中文版,网上也有很多针对该协定的分析文章,很多文章里还有现成的实现代码能够参考,所以说实现一个简略的 WebSocket 服务并非难事。 本文更偏差实战(in action),会从常识储备、具体代码剖析以及注意事项角度去解说如何用 Node.js 实现一个简略的 WebSocket 服务,至于 WebSocket 概念、定义、解释和用处等基础知识不会波及,因为这些常识在本文所列的参考文章中轻松找到。 情谊提醒:本文对应的源码,请从文末“11、代码下载”一节下载之。 学习交换: 即时通讯技术交换群:215477170 [举荐]挪动端IM开发入门文章:《新手入门一篇就够:从零开发挪动端IM》开源IM框架源码:https://github.com/JackJiang2011/MobileIMSDK(本文同步公布于:http://www.52im.net/thread-3175-1-1.html) 2、对于作者作者网名:JSCON简时空作者微博:https://weibo.com/271111536博客地址:https://segmentfault.com/u/jsconGithub主页:https://boycgit.github.io/3、基本常识在学习本文内容之前,我认为很有必要简略理解一下Web端即时通讯技术的“过来”和“当初”,因为新时代的开发者(没有经验过短轮询、长轮询、Comet技术的这波人),很难了解WebSocket对于Web端的即时通讯技术来说,意味着什么。 所谓“忆苦思甜”,理解了Web端即时通讯技术的过来,方知WebSocket这种技术的宝贵。。。 3.1 旧时代的Web端即时通讯技术自从Web端即时通讯的概念提出后,“实时”性便成为了Web开发者们津津有味的话题。实时化的Web利用,凭借其响应迅速、无需刷新、节俭网络流量的个性,不仅让开发者们眼前一亮,更是为用户带来绝佳的网络体验。 但很多开发者可能并不分明,旧时代的Web端“实时”通信,次要基于 Ajax的拉取和Comet的推送。 大家都晓得Ajax,这是一种借助浏览器端JavaScript实现的异步无刷新申请性能:要客户端按需向服务器发出请求,并异步获取来自服务器的响应,而后依照逻辑更新以后页面的相应内容。 然而这仅仅是拉取啊,这并不是真正的“实时”:短少服务器端的主动推送! 因而,咱们不得不应用另一种略简单的技术 Comet,只有当这两者配合起来,这个Web利用才勉强算是个“实时”的Web端利用! ▲ Ajax和Comet技术原理(图片援用自《Web端即时通讯技术盘点》) 3.2 WebSocket协定呈现 随着HTML5规范的呈现,WebSocket技术横空出世,随着HTML5规范的宽泛遍及,越来越多的古代浏览器开始全面反对WebSocket技术了。 至于WebSocket,我想大家或多或少都据说过。 WebSocket是一种全新的协定。它将TCP的Socket(套接字)利用在了web page上,从而使通信单方建设起一个放弃在活动状态连贯通道,并且属于全双工(单方同时进行双向通信)。 事实是:WebSocket协定是借用HTTP协定的 _101 switch protocol_ 来达到协定转换的,从HTTP协定切换成WebSocket通信协议。 再简略点来说,它就如同将 Ajax 和 Comet 技术的特点联合到了一起,只不过性能要高并且应用起来要不便的多(不便当然是之指在客户端方面了)。 4、WebSocket常识储备如果要本人写一个 WebSocket 服务,次要有两个难点: ...

October 21, 2020 · 4 min · jiezi

关于websocket:客户端与服务端单双工通信方案

轮询轮询是咱们比拟相熟的一种计划,有短轮询和长轮询,短轮询是在特定的工夫距离,由客户端发送HTTP申请,来获取服务器数据,其数据实时性型和性能取决于申请的工夫距离。 长轮询当服务器收到客户端发来的申请后,服务器端不会间接进行响应,而是先将这个申请挂起,而后判断服务器端数据是否有更新。如果有更新,则进行响应,如果始终没有数据,直到超时(服务端设置)才返回。客户端响应解决完服务器返回的信息后,再次发出请求,从新建设连贯。

October 20, 2020 · 1 min · jiezi

关于websocket:syncplayer使用websocket实现异地同步播放视频

本文作者:星空有限原文链接:https://liyangzone.com/2020/0...GoEasy已获作者受权转载,GoEasy转载时有改变,感激作者的分享。前段时间我有这样一个需要,想和一个异地的人一起看电影,先后在网上找了一些计划,不过那几个案都有一些毛病 coplay: 一个浏览器插件,只能播放各大视频网站的视频,视频资源无限,我想要看的视频没有,比方一些经典电影和美剧之类微光APP: 还是下面的问题,而且只有手机端向日葵等远程桌面: 受限于网络问题,卡顿很重大,体验不好作为一个对用户体验有谋求的切图仔,我是一个下载党,看电影必须下载到本地看,根本不看视频网站上的玩意 那么有没有能实现同步播放本地文件的计划呢,答案是必定的,通过我的一些摸索和钻研,我实现了本地文件的同步播放,同时反对PC和手机端,而且还反对外挂字幕等高级性能,如何实现请往下看。 性能介绍&个性:一个能够同步看视频的播放器,可用于异地同步观影、观剧,反对多人同时观看。本我的项目有两个版本,web版运行在浏览器上,可跨平台,不限操作系统、设施,性能简略实用于要求不高的用户。还有基于SPlayer(射手影音)DIY的客户端版本(windows、MAC),播放4K高清文件、外挂字幕,通通没问题。 演示demo:web版同步成果客户端与web版同步成果 原理:基于websocket实现,与一些用websocket实现的聊天室相似,只不过这个聊天室里的音讯换成了播放暂停的动作和工夫信息,客户端收到音讯后执行相应的动作:播放、暂停、快进,以达到同时播放的成果。 我的项目所用到的node.jssocketioHTML5 video APIvue.js如何应用:本我的项目的外围是websocket,所以至多须要一台服务器提供websocket服务,websocket服务能够本人部署,能够应用第三方平台GoEasy提供的websocket服务。 1、本人部署:websocket服务器能够是一台具备公网IP的云服务器,也能够是一台具备公网IP的一般PC,没有公网IP也能够。你也能够应用zerotier或其余VPN工具将两台设施组成一个大局域网,让它们能相互通信。websocket服务器操作系统不限,只有有node.js环境。 websocket服务端部署办法:装置node.js环境,将server目录挪动到服务器上,进入server目录,执行以下命令: 装置我的项目依赖包 # 装置我的项目依赖包npm install # 启动websocket服务node index.js2、应用GoEasy的websocket服务注册GoEasy开发者账号并创立一个利用,取得appkey,复制到本我的项目相应地位即可。 GoEasy官网:https://www.goeasy.io 无论是应用哪种websocket服务都能够,本我的项目写了两套代码,只需将不必的那套正文掉即可(默认GoEasy)。 除了websocket服务器之外,还须要两个http服务端,一个是web服务端(提供html、css、js等文件的拜访),一个是视频服务端(提供视频文件拜访)。 你能够将web服务部端署到以下地位: 具备公网IP的服务器github-pages或国内的码云提供的动态web服务localhost(本地服务器),同一个局域网内的设施拜访该服务器内网IP视频文件只需一个视频地址就行,也有以下几种抉择: 具备公网IP的服务器localhost(本地服务器),同一个局域网内的设施拜访该服务器内网IP第三方视频地址 应用场景1: 云服务器带宽足够大(至多要大于播放视频的码率),云服务器既能够作为websocket服务端,也能够作为http服务端。上图中所有设施都拜访云服务器的ip或域名。 应用场景2: 云服务器的带宽很小,这时候它只能作为websocket服务端,这时能够用上图中的PC1和PC2作为http服务端,PC1和PHONE1在一个内网拜访PC1的内网IP,PC2和PHONE2在一个内网拜访PC2的内网IP,PC3可作为本人的http服务端,PHONE3若是有提供视频文件的服务端,也能够应用。应用场景3: 须要应用zerotier或其余VPN工具将异地设施组成一个大局域网,其中任意一台PC均可作为websocket服务端和http服务端(须要上传带宽足够大)。上图中各设施都拜访那台PC的内网ip即可。 最简略的应用办法,下载nginx开启一个本地服务器,下载本我的项目client文件夹放到到nginx根目录里,视频文件也放到外面。注册goeasy开发者账号并创立一个利用,取得appkey,并填入到appkey到代码(script/main.js)相应地位。而后浏览器关上 192.168.3.58/client/,填入你的视频地址192.168.3.58/movie/xxx.mp4或网络视频地址,对方也这样操作一番,即可实现同步播放视频。 web版本的性能比较简单,而且受限于网络问题,快进之类的操作须要缓冲一段时间。如果你不满足web版性能,对用户体验有更高的要求,如反对更多文件格式、播放高清本地视频文件、外挂字幕等,我也找到了另一种形式来满足你的需要。 那就是DIY一个开源的播放器的源码:SPlayer(射手影音)。 射手影音官网:https://splayer.org 源码地址:https://github.com/chiflix/sp... 在以electron + 播放器为关键字一番搜寻之后,我找到了这个基于electron实现的开源播放器,并下载了源码来钻研。 通过一番钻研之后,我找到了管制视频播放、暂停、快进的代码地位,并将管制同步的代码移植了进去,从而也实现了同步性能,并且与web版兼容。 具体方法请看:批改教程 本我的项目局部图标款式来源于此我的项目: coplay 本我的项目github地址:点击返回 ,欢送⭐⭐⭐STAR⭐⭐⭐ 对于GoEasy:GoEasy是一个成熟稳固的企业级websocket PAAS服务平台,开发人员不须要思考websocket服务端的搭建,只须要几行代码,就能够轻松实现客户端与客户端之间,服务器与客户端之间的的websocket通信。 GoEasy作为国内当先的第三方websocket音讯推送平台,具备极佳的兼容性。除了兼容所有常见的浏览器以外,同时也兼容uni-app,各种小程序,以及vue、react-native、cocos、laya、egret等常见的前端框架。 同时GoEasy曾经内置websocket中必备的心跳,断网重连,音讯补发,历史音讯和客户端高低线揭示等个性,开发人员也不须要本人搭建websocket服务解决集群高可用,平安和性能问题。GoEasy曾经稳固运行了5年,反对千万级并发,胜利撑持过很多知名企业的重要流动,安全性和可靠性都是久经考验。 有趣味本人搭建websocket的话,能够参考这篇技术分享《搭建websocket音讯推送服务,必须要思考的几个问题》

October 20, 2020 · 1 min · jiezi

关于websocket:WebSocket从入门到精通半小时就够

本文原题“WebSocket:5分钟从入门到精通”,作者“程序猿小卡_casper”,原文链接见文末参考资料局部。本次收录时有改变。 1、引言自从HTML5里的WebSocket呈现后,彻底改变了以往Web端即时通讯技术的根底通道这个“痛点”(在此之前,开发者们不得不弄出了诸如:短轮询、长轮询、Comet、SSE等技术,堪称苦之久矣...),现在再也不必纠结到底该用“轮询”还是“Comet”技术来保证数据的实时性了,幸福来的就是如此忽然 ^-^。 WebSocket现在不仅在Web利用里应用宽泛,也缓缓被开发者们利用到各种那些本来应用TCP、UDP这类协定的富客户端(比方挪动端中)。 有鉴于此,对于即时通讯方向的开发者来说,全面深刻的理解WebSocket是十分有必要的,面视时也少不了会考查这方面的常识。 所以,即时通讯网在建站至今的几年工夫里,继续整顿了一大批跟Web端即时通讯无关的技术文章(这基中尤其WebSocket方面的文章最多)。本文也是一篇对于WebSocket从入门到精通的文章,内容由浅入深,比拟适宜想要在短时间内较深刻的理解WebSocket协定的开发者学习。 (本文同步公布于:http://www.52im.net/thread-3134-1-1.html) 2、相干文章《WebSocket详解(一):初步意识WebSocket技术》《WebSocket详解(二):技术原理、代码演示和利用案例》《WebSocket详解(三):深刻WebSocket通信协议细节》《WebSocket详解(四):刨根问底HTTP与WebSocket的关系(上篇)》《WebSocket详解(五):刨根问底HTTP与WebSocket的关系(下篇)》《WebSocket详解(六):刨根问底WebSocket与Socket的关系》3、注释概览WebSocket的呈现,使得浏览器具备了实时双向通信的能力。 本文将由浅入深,介绍WebSocket如何建设连贯、替换数据的细节,以及数据帧的格局。此外,还简要介绍了针对WebSocket的平安攻打,以及协定是如何抵挡相似攻打的。 4、什么是WebSocket4.1 根本介绍HTML5开始提供的一种浏览器与服务器进行全双工通信的网络技术,属于应用层协定。它基于TCP传输协定,并复用HTTP的握手通道。 对大部分web开发者来说,下面这段形容有点干燥,其实只有记住几点: 1)WebSocket能够在浏览器里应用;2)反对双向通信;3)应用很简略。4.2 有哪些长处说到长处,这里的比照参照物是HTTP协定,概括地说就是:反对双向通信,更灵便,更高效,可扩展性更好。 1)反对双向通信,实时性更强;2)更好的二进制反对;3)较少的管制开销。连贯创立后,ws客户端、服务端进行数据交换时,协定管制的数据包头部较小。在不蕴含头部的状况下,服务端到客户端的包头只有2~10字节(取决于数据包长度),客户端到服务端的的话,须要加上额定的4字节的掩码。而HTTP协定每次通信都须要携带残缺的头部;4)反对扩大。ws协定定义了扩大,用户能够扩大协定,或者实现自定义的子协定。(比方反对自定义压缩算法等)对于前面两点,没有钻研过WebSocket协定标准的同学可能了解起来不够直观,但不影响对WebSocket的学习和应用。 4.3 须要学习哪些货色对网络应用层协定的学习来说,最重要的往往就是连贯建设过程、数据交换过程。当然,数据的格局是逃不掉的,因为它间接决定了协定自身的能力。好的数据格式能让协定更高效、扩展性更好。 下文次要围绕上面几点开展: 1)如何建设连贯;2)如何替换数据;3)数据帧格局;4)如何维持连贯。5、入门演示代码在正式介绍协定细节前,先来看一个简略的例子,有个直观感触。例子包含了WebSocket服务端、WebSocket客户端(网页端)。残缺代码能够在这里 找到。 这里服务端用了ws这个库。相比大家相熟的socket.io,ws实现更轻量,更适宜学习的目标。 5.1 服务端代码如下,监听8080端口。当有新的连贯申请达到时,打印日志,同时向客户端发送音讯。当收到到来自客户端的音讯时,同样打印日志。 var app = require('express')();var server = require('http').Server(app);var WebSocket = require('ws');var wss = newWebSocket.Server({ port: 8080 });wss.on('connection', function connection(ws) {    console.log('server: receive connection.');    ws.on('message', functionincoming(message) {        console.log('server: received: %s', message);    });    ws.send('world');});app.get('/', function(req, res) {  res.sendfile(__dirname + '/index.html');});app.listen(3000);5.2 客户端代码如下,向8080端口发动WebSocket连贯。连贯建设后,打印日志,同时向服务端发送音讯。接管到来自服务端的音讯后,同样打印日志。 <script>  var ws = new WebSocket('ws://localhost:8080');  ws.onopen = function() {    console.log('ws onopen');    ws.send('from client: hello');  };  ws.onmessage = function(e) {    console.log('ws onmessage');    console.log('from server: '+ e.data);  };</script>5.3 运行后果可别离查看服务端、客户端的日志,这里不开展。 ...

October 14, 2020 · 3 min · jiezi

关于websocket:使用-WebSocket-连接-MQTT-服务器

近年来随着 Web 前端的疾速倒退,浏览器新个性层出不穷,越来越多的利用能够在浏览器端通过浏览器渲染引擎实现,Web 利用的即时通信形式 WebSocket 也因而失去了宽泛的利用。 WebSocket 是一种在单个 TCP 连贯上进行全双工通信的协定。WebSocket 通信协议于2011年被 IETF 定为规范 RFC 6455,并由 RFC 7936 补充标准。WebSocket API 也被 W3C 定为规范。 WebSocket 使得客户端和服务器之间的数据交换变得更加简略,容许服务端被动向客户端推送数据。在 WebSocket API 中,浏览器和服务器只须要实现一次握手,两者之间就间接能够创立持久性的连贯,并进行双向数据传输。 1 MQTT 协定第 6 章 具体约定了 MQTT 在 WebSocket [RFC6455] 连贯上传输须要满足的条件,协定内容不在此具体赘述。 两款客户端比拟Paho.mqtt.jsPaho 是 Eclipse 的一个 MQTT 客户端我的项目,Paho JavaScript Client 是其中一个基于浏览器的库,它应用 WebSockets 连贯到 MQTT 服务器。相较于另一个 JavaScript 连贯库来说,其性能较少,不举荐应用。 MQTT.jsMQTT.js 是一个齐全开源的 MQTT 协定的客户端库,应用 JavaScript 编写,可用于 Node.js 和浏览器。在 Node.js 端能够通过全局装置应用命令行连贯,同时反对 MQTT/TCP、MQTT/TLS、MQTT/WebSocket 连贯;值得一提的是 MQTT.js 还对微信小程序有较好的反对。 ...

September 25, 2020 · 3 min · jiezi

关于websocket:小程序websocket开发指南

背景:个别与服务端交互频繁的需要,能够应用轮询机制来实现。然而一些业务场景,比方游戏大厅、直播、即时聊天等,这些需要都能够或者说更适宜应用长连贯来实现,一方面能够缩小轮询带来的流量节约,另一方面能够缩小对服务的申请压力,同时也能够更实时的与服务端进行音讯交互。背景常识HTTP vs WebSocket名词解释HTTP:是一个用于传输超媒体文档(如HTML)的应用层的无连贯、无状态协定。WebSocket:HTML5开始提供的一种浏览器与服务器进行全双工通信的网络技术,属于应用层协定,基于TCL传输协定,并复用HTTP的握手通道。 特点HTTPWebSocket 建设在TCP协定之上,服务器端的实现比拟容易;与HTTP协定有着良好的兼容性。默认端口也是80和443,并且握手阶段采纳HTTP协定,因而握手时不容易屏蔽,能通过各种HTTP代理服务器;数据格式比拟轻量,性能开销小,通信高效;能够发送文本(text),也能够发送二进制数据(ArrayBuffer);没有同源限度,客户端能够与任意服务器通信;协定标识符是ws(如果加密,则为wss),服务器网址就是URL;二进制数组名词解释ArrayBuffer对象:代表原始的二进制数据。代表内存中的一段二进制数据,不能间接读写,只能通过“视图”(TypedArray和DataView)进行操作(以指定格局解读二进制数据)。“视图”部署了数组接口,这意味着,能够用数组的办法操作内存。TypedArray对象:代表确定类型的二进制数据。用来生成内存的视图,通过9个构造函数,能够生成9种数据格式的视图,数组成员都是同一个数据类型,比方: Unit8Array:(无符号8位整数)数组视图Int16Array:(16位整数)数组视图Float32Array:(32位浮点数)数组视图DataView对象:代表不确定类型的二进制数据。用来生成内存的视图,能够自定义格局和字节序,比方第一个字节是Uint8(无符号8位整数)、第二个字节是Int16(16位整数)、第三个字节是Float32(32位浮点数)等等,数据成员能够是不同的数据类型。举个栗子ArrayBuffer也是一个构造函数,能够调配一段能够存放数据的间断内存区域 var buf = new ArrayBuffer(32); // 生成一段32字节的内存区域,每个字节的值默认都是0为了读写buf,须要为它指定视图。 DataView视图,是一个构造函数,须要提供ArrayBuffer对象实例作为参数:var dataView = new DataView(buf); // 不带符号的8位整数格局dataView.getUnit8(0) // 0TypedArray视图,是一组构造函数,代表不同的数据格式。var x1 = new Init32Array(buf); // 32位带符号整数x1[0] = 1;var x2 = new Unit8Array(buf); // 8位不带符号整数x2[0] = 2;x1[0] // 2 两个视图对应同一段内存,一个视图批改底层内存,会影响另一个视图TypedArray(buffer, byteOffset=0, length?) buffer:必须,视图对应的底层ArrayBuffer对象byteOffset:可选,视图开始的字节序号,默认从0开始,必须与所要建设的数据类型统一,否则会报错var buffer = new ArrayBuffer(8);var i16 = new Int16Array(buffer, 1);// Uncaught RangeError: start offset of Int16Array should be a multiple of 2因为,带符号的16位整数须要2个字节,所以byteOffset参数必须可能被2整除。 length:可选,视图蕴含的数据个数,默认直到本段内存区域完结note:如果想从任意字节开始解读ArrayBuffer对象,必须应用DataView视图,因为TypedArray视图只提供9种固定的解读格局。 ...

September 14, 2020 · 3 min · jiezi

关于websocket:gin-websocket-一对一聊天

依赖包github.com/gin-gonic/gingithub.com/gorilla/websocket代码创立ws/ws.gopackage wsimport ( "encoding/json" "github.com/gin-gonic/gin" "github.com/gorilla/websocket" "log" "net/http")// ClientManager is a websocket managertype ClientManager struct { Clients map[string]*Client Broadcast chan []byte Register chan *Client Unregister chan *Client}// Client is a websocket clienttype Client struct { ID string Socket *websocket.Conn Send chan []byte}// Message is return msgtype Message struct { Sender string `json:"sender,omitempty"` Recipient string `json:"recipient,omitempty"` Content string `json:"content,omitempty"`}// Manager define a ws server managervar Manager = ClientManager{ Broadcast: make(chan []byte), Register: make(chan *Client), Unregister: make(chan *Client), Clients: make(map[string]*Client),}// Start is 我的项目运行前, 协程开启start -> go Manager.Start()func (manager *ClientManager) Start() { for { log.Println("<---管道通信--->") select { case conn := <-Manager.Register: log.Printf("新用户退出:%v", conn.ID) Manager.Clients[conn.ID] = conn jsonMessage, _ := json.Marshal(&Message{Content: "Successful connection to socket service"}) conn.Send <- jsonMessage case conn := <-Manager.Unregister: log.Printf("用户来到:%v", conn.ID) if _, ok := Manager.Clients[conn.ID]; ok { jsonMessage, _ := json.Marshal(&Message{Content: "A socket has disconnected"}) conn.Send <- jsonMessage close(conn.Send) delete(Manager.Clients, conn.ID) } case message := <-Manager.Broadcast: MessageStruct :=Message{} json.Unmarshal(message, &MessageStruct) for id, conn := range Manager.Clients { if id!=creatId(MessageStruct.Recipient,MessageStruct.Sender){ continue } select { case conn.Send <- message: default: close(conn.Send) delete(Manager.Clients, conn.ID) } } } }}func creatId(uid,touid string) string { return uid+"_"+touid}func (c *Client) Read() { defer func() { Manager.Unregister <- c c.Socket.Close() }() for { c.Socket.PongHandler() _, message, err := c.Socket.ReadMessage() if err != nil { Manager.Unregister <- c c.Socket.Close() break } log.Printf("读取到客户端的信息:%s", string(message)) Manager.Broadcast <- message }}func (c *Client) Write() { defer func() { c.Socket.Close() }() for { select { case message, ok := <-c.Send: if !ok { c.Socket.WriteMessage(websocket.CloseMessage, []byte{}) return } log.Printf("发送到到客户端的信息:%s", string(message)) c.Socket.WriteMessage(websocket.TextMessage, message) } }}//TestHandler socket 连贯 中间件 作用:降级协定,用户验证,自定义信息等func WsHandler(c *gin.Context) { uid := c.Query("uid") touid := c.Query("to_uid") conn, err := (&websocket.Upgrader{CheckOrigin: func(r *http.Request) bool { return true }}).Upgrade(c.Writer, c.Request, nil) if err != nil { http.NotFound(c.Writer, c.Request) return } //能够增加用户信息验证 client := &Client{ ID: creatId(uid,touid), Socket: conn, Send: make(chan []byte), } Manager.Register <- client go client.Read() go client.Write()}创立main.gopackage mainimport ( "github.com/gin-gonic/gin" "im/ws")//serverfunc main() { gin.SetMode(gin.ReleaseMode) //线上环境 go ws.Manager.Start() r := gin.Default() r.GET("/ws",ws.WsHandler) r.GET("/pong", func(c *gin.Context) { c.JSON(200, gin.H{ "message": "pong", }) }) r.Run(":8282") // listen and serve on 0.0.0.0:8080}运行服务端go run mian.go创立client.html<html><head> <title>Golang Chat</title><script src="http://libs.baidu.com/jquery/1.4.2/jquery.min.js"></script><meta charset="UTF-8" /> <script type="text/javascript"> $(function() { function getUrlParam(name) { var reg = new RegExp("(^|&)"+ name +"=([^&]*)(&|$)"); //结构一个含有指标参数的正则表达式对象 var r = window.location.search.substr(1).match(reg); //匹配指标参数 if (r!=null) return unescape(r[2]); return null; //返回参数值 } var conn; var msg = $("#msg"); var log = $("#log"); uid=getUrlParam("uid"); to_uid=getUrlParam("to_uid"); function appendLog(msg) { var d = log[0] var doScroll = d.scrollTop == d.scrollHeight - d.clientHeight; msg.appendTo(log) if (doScroll) { d.scrollTop = d.scrollHeight - d.clientHeight; } } $("#form").submit(function() { if (!conn) { return false; } if (!msg.val()) { return false; } var json = {"sender":uid,"recipient":to_uid,"content":msg.val()}; //创建对象; var jsonStr = JSON.stringify(json); //转为JSON字符串 conn.send(jsonStr); msg.val(""); return false }); if (window["WebSocket"]) { conn = new WebSocket("ws://localhost:8282/ws?uid="+uid+"&to_uid="+to_uid); conn.onclose = function(evt) { appendLog($("<div><b>Connection Closed.</b></div>")) } conn.onmessage = function(evt) { appendLog($("<div/>").text(evt.data)) } } else { appendLog($("<div><b>WebSockets Not Support.</b></div>")) } }); </script> <style type="text/css"> html { overflow: hidden; } body { overflow: hidden; padding: 0; margin: 0; width: 100%; height: 100%; background: gray; } #log { background: white; margin: 0; padding: 0.5em 0.5em 0.5em 0.5em; position: absolute; top: 0.5em; left: 0.5em; right: 0.5em; bottom: 3em; overflow: auto; } #form { padding: 0 0.5em 0 0.5em; margin: 0; position: absolute; bottom: 1em; left: 0px; width: 100%; overflow: hidden; } </style></head><body><div id="log"></div><form id="form"> <input type="submit" value="发送" /> <input type="text" id="msg" size="64"/></form></body></html>本人搭建nginx或apache等web服务,别离在两个窗口运行 ...

August 11, 2020 · 3 min · jiezi

关于websocket:websocket协议

应用http协定的问题场景:客户端的展现随着服务端保护的状态的扭转而实时扭转。 可能会采取的形式: 1 轮询:client按设置好的工夫距离被动拜访server,有可能server返回有用数据,有可能server无有用数据返回,但都是一个建设连贯-request-response-敞开连贯的过程。 2 长轮询:client拜访server,若server有数据返回,则返回,敞开连贯,client持续发申请;若server没有数据返回,连贯放弃,期待直到server有数据返回,敞开连贯,client持续发申请。建设连贯-request-(wait)-response-敞开连贯。 3 推:client和server之间保护长连贯,当server有返回的时候被动推给client。 问题: http协定是一种无状态的,基于申请响应模式的协定。 半双工协定:能够在客户端和服务端2个方向上传输,然而不能同时传输。同一时刻,只能在一个方向上传输。 响应数据不实时,空轮询对资源的节约。 HTTP音讯简短(轮询中每次http申请携带了大量无用的头信息)。 HTTP1.0 每个申请会关上一个新连贯,个别关上和敞开连贯破费的工夫远大于数据传输的工夫,对于HTTPS更是。 HTTP1.1 服务器不会在发送响应后立刻敞开连贯,能够在同一个socket上期待客户端的新申请 Websocket 协定WebSocket是一种标准,是Html5标准的一部分。WebSocket通信协议于2011年被IETF定为规范RFC 6455,并被RFC7936所补充标准。WebSocket API也被W3C定为规范。 单个 TCP 连贯上进行全双工通信的协定。 浏览器和服务器只须要实现一次握手,两者之间就间接能够创立持久性的连贯,并进行双向数据传输。websocket是全双工,没有严格的clientserver概念。 opening handshakerequest: GET /chat HTTP/1.1 Host: server.example.comUpgrade: websocket //申请降级到WebSocket 协定Connection: Upgrade //通道类型,keep-alive:通道长连,close:申请结束后通道断开,Upgrade:降级协定Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw== //客户端随机生成的Key,校验服务器合法性,生成形式:随机16字节再被base64编码Sec-WebSocket-Protocol: chat, superchat //子协定,特定于具体利用的音讯格局或编排Sec-WebSocket-Version: 13 Origin: http://example.comresponse: HTTP/1.1 101 Switching Protocols //非101依然是httpUpgrade: websocket //服务端协定已切换到WebSocketConnection: Upgrade Sec-WebSocket-Accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk=//indicates whether the server is willing to accept the connection.//用于校验WebSocket服务端是否非法,生成形式:客户端申请参数中的 Sec-WebSocket-Key+258EAFA5-E914-47DA-95CA-C5AB0DC85B11(GUID),//SHA-1 hash 再进行base64//GUID:which is unlikely to be used by network endpoints that do not understand the WebSocket protocol.Sec-WebSocket-Protocol: chatclose handshake ...

August 10, 2020 · 3 min · jiezi

关于websocket:websocket协议

应用http协定的问题场景:客户端的展现随着服务端保护的状态的扭转而实时扭转。 可能会采取的形式: 1 轮询:client按设置好的工夫距离被动拜访server,有可能server返回有用数据,有可能server无有用数据返回,但都是一个建设连贯-request-response-敞开连贯的过程。 2 长轮询:client拜访server,若server有数据返回,则返回,敞开连贯,client持续发申请;若server没有数据返回,连贯放弃,期待直到server有数据返回,敞开连贯,client持续发申请。建设连贯-request-(wait)-response-敞开连贯。 3 推:client和server之间保护长连贯,当server有返回的时候被动推给client。 问题: http协定是一种无状态的,基于申请响应模式的协定。 半双工协定:能够在客户端和服务端2个方向上传输,然而不能同时传输。同一时刻,只能在一个方向上传输。 响应数据不实时,空轮询对资源的节约。 HTTP音讯简短(轮询中每次http申请携带了大量无用的头信息)。 HTTP1.0 每个申请会关上一个新连贯,个别关上和敞开连贯破费的工夫远大于数据传输的工夫,对于HTTPS更是。 HTTP1.1 服务器不会在发送响应后立刻敞开连贯,能够在同一个socket上期待客户端的新申请 Websocket 协定WebSocket是一种标准,是Html5标准的一部分。WebSocket通信协议于2011年被IETF定为规范RFC 6455,并被RFC7936所补充标准。WebSocket API也被W3C定为规范。 单个 TCP 连贯上进行全双工通信的协定。 浏览器和服务器只须要实现一次握手,两者之间就间接能够创立持久性的连贯,并进行双向数据传输。websocket是全双工,没有严格的clientserver概念。 opening handshakerequest: GET /chat HTTP/1.1 Host: server.example.comUpgrade: websocket //申请降级到WebSocket 协定Connection: Upgrade //通道类型,keep-alive:通道长连,close:申请结束后通道断开,Upgrade:降级协定Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw== //客户端随机生成的Key,校验服务器合法性,生成形式:随机16字节再被base64编码Sec-WebSocket-Protocol: chat, superchat //子协定,特定于具体利用的音讯格局或编排Sec-WebSocket-Version: 13 Origin: http://example.comresponse: HTTP/1.1 101 Switching Protocols //非101依然是httpUpgrade: websocket //服务端协定已切换到WebSocketConnection: Upgrade Sec-WebSocket-Accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk=//indicates whether the server is willing to accept the connection.//用于校验WebSocket服务端是否非法,生成形式:客户端申请参数中的 Sec-WebSocket-Key+258EAFA5-E914-47DA-95CA-C5AB0DC85B11(GUID),//SHA-1 hash 再进行base64//GUID:which is unlikely to be used by network endpoints that do not understand the WebSocket protocol.Sec-WebSocket-Protocol: chatclose handshake ...

August 10, 2020 · 3 min · jiezi

关于websocket:SpringBoot整合WebSocket和AOP的问题

背景最近在我的项目上用了WebSocket,然而遇到了一个很奇怪的问题,利用启动时抛出 Failed to register @ServerEndpoint class配置类package com.smec.fin.config.websocket;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.web.socket.server.standard.ServerEndpointExporter;/** * @Description: * @author: wei.wang * @since: 2020/7/28 10:29 * @history: 1.2020/7/28 created by wei.wang */@Configurationpublic class WebSocketConfig { @Bean public ServerEndpointExporter serverEndpointExporter() { return new ServerEndpointExporter(); }}接管连贯的类package com.smec.fin.service.impl;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.stereotype.Component;import javax.websocket.*;import javax.websocket.server.ServerEndpoint;import java.io.IOException;import java.util.concurrent.CopyOnWriteArraySet;/** * @Description: * @author: wei.wang * @since: 2020/7/28 10:46 * @history: 1.2020/7/28 created by wei.wang */@ServerEndpoint("/websocket")@Componentpublic class WebSocketServer { private static Logger logger = LoggerFactory.getLogger(WebSocketServer.class); /** * 动态变量,用来记录以后在线连接数。 */ private static volatile int onlineCount = 0; /** * concurrent包的线程平安Set,用来寄存每个客户端对应的WebSocket对象,目前只有一个客户端,所以应用Set */ private static CopyOnWriteArraySet<WebSocketServer> webSocketSet = new CopyOnWriteArraySet<WebSocketServer>(); /** * 与某个客户端的连贯会话,须要通过它来给客户端发送数据 */ private Session session; /** * 连贯建设胜利调用的办法 */ @OnOpen public void onOpen(Session session) { this.session = session; //退出set中 webSocketSet.add(this); //在线数加1 addOnlineCount(); logger.info("开始监听,以后在线人数为{}", getOnlineCount()); } /** * 连贯敞开调用的办法 */ @OnClose public void onClose() { logger.info("有一连贯敞开!以后在线人数为" + getOnlineCount()); //从set中删除 webSocketSet.remove(this); //在线数减1 subOnlineCount(); logger.info("有一连贯敞开!以后在线人数为" + getOnlineCount()); } /** * 收到客户端音讯后调用的办法 * * @param message 客户端发送过去的音讯 */ @OnMessage public void onMessage(String message) { logger.info("收到信息:" + message); System.out.println("webSocketSet Size " + webSocketSet.size()); } /** * 发送音讯 * * @param message */ public void sendMessageToPad(String message) { for (WebSocketServer item : webSocketSet) { try { item.sendMessage(message); } catch (IOException e) { e.printStackTrace(); } } } /** * @param session * @param error */ @OnError public void onError(Session session, Throwable error) { logger.error("产生谬误"); error.printStackTrace(); } /** * 实现服务器被动推送 */ public void sendMessage(String message) throws IOException { this.session.getBasicRemote().sendText(message); } public static synchronized int getOnlineCount() { return onlineCount; } public static synchronized void addOnlineCount() { WebSocketServer.onlineCount++; } public static synchronized void subOnlineCount() { WebSocketServer.onlineCount--; }}看起来都没问题,咱们把异样信息都打进去 ...

July 29, 2020 · 2 min · jiezi

关于websocket:个人学习系列-WebSocket与Spring-Boot整合

WebSocket 是一种网络通信协定。它与HTTP协定最大的不同在于,HTTP协定做不到服务器被动向客户端推送信息。WebSocket其最大的特点在于:服务器能够被动向客户端推送信息,客户端也能够被动向服务器发送信息,是真正的双向平等对话,属于服务器推送技术的一种。 Spring Boot我的项目搭建后面的步骤就不再赘述了,咱们间接从pom.xml外面增加的依赖开始吧。 1. pom.xml文件配置<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId></dependency><dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId></dependency><dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-websocket</artifactId></dependency><dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional></dependency>2. application.yml配置server: port: 8888spring: freemarker: prefix: classpath:/templates/ suffix: .html3. index.html页面<!DOCTYPE html><html><head> <title>WebSoket Demo</title> <script type="text/javascript"> //验证浏览器是否反对WebSocket协定 if (!window.WebSocket) { alert("WebSocket not supported by this browser!"); } let ws; const log = function (s) { if (document.readyState !== "complete") { log.buffer.push(s); } else { document.getElementById("contentId").innerHTML += (s + "\n"); } }; function display() { ws = new WebSocket("ws://localhost:8888/websocket"); ws.onmessage = function (event) { //监听音讯 log(event.data); }; ws.onclose = function (event) { // 敞开WebSocket console.log("ws close: " + event); }; ws.onopen = function (event) { // 关上WebSocket console.log("ws open:" + event); // 发送一个初始化音讯 ws.send("Hello, Server!"); }; ws.onerror = function (event) { // WebSocket异样 console.log("ws error:" + event); }; } function sendMsg() { // 发送音讯 const msg = document.getElementById("messageId"); ws.send(msg.value); } </script></head><body onload="display();"> <div id="valueLabel"></div> <textarea rows="20" cols="30" id="contentId"></textarea> <br/> <input name="message" id="messageId"/> <button id="sendButton" onClick="sendMsg()">Send</button></body></html>4. 管制层/** * 测试控制器 * @author zhouzhaodong */@Controllerpublic class TestController { @RequestMapping("/") public String view(){ return "index"; }}5. WebSocket配置类/** * WebSocket配置类 * @author zhouzhaodong */@Configurationpublic class WebsocketConfiguration { @Bean public ServerEndpointExporter serverEndpointExporter() { return new ServerEndpointExporter(); }}6. WebSocket服务端/** * WebSocket服务端 * @author zhouzhaodong */@ServerEndpoint("/websocket")@Component@Slf4jpublic class MyWebsocketServer { /** * 寄存所有在线的客户端 */ private static final Map<String, Session> CLIENTS = new ConcurrentHashMap<>(); @OnOpen public void onOpen(Session session) { log.info("有新的客户端连贯了: {}", session.getId()); //将新用户存入在线的组 CLIENTS.put(session.getId(), session); } /** * 客户端敞开 * @param session session */ @OnClose public void onClose(Session session) { log.info("有用户断开了, id为:{}", session.getId()); //将掉线的用户移除在线的组里 CLIENTS.remove(session.getId()); } /** * 产生谬误 * @param throwable e */ @OnError public void onError(Throwable throwable) { throwable.printStackTrace(); } /** * 收到客户端发来音讯 * @param message 音讯对象 */ @OnMessage public void onMessage(String message) { log.info("服务端收到客户端发来的音讯: {}", message); this.sendAll(message); } /** * 群发音讯 * @param message 音讯内容 */ private void sendAll(String message) { for (Map.Entry<String, Session> sessionEntry : CLIENTS.entrySet()) { sessionEntry.getValue().getAsyncRemote().sendText(message); } }}7. 启动我的项目,拜访http://localhost:8888/查看我的项目: ...

July 28, 2020 · 2 min · jiezi

关于websocket:细说websocket快速重连机制

网易智慧企业web前端开发工程师 马莹莹 引言 在一个欠缺的即时通讯利用中,websocket是极其要害的一环,它为web利用的客户端和服务端提供了一种全双工的通信机制,但因为它自身以及其底层依赖的TCP连贯的不稳定性,开发者不得不为其设计一套残缺的保活、验活、重连计划,能力在理论利用中保障利用的即时性和高可用性。就重连而言,其速度重大影响了下层利用的“即时性”和用户体验,试想关上网络一分钟后,微信还不能收发音讯的话,是不是要抓狂? 因而,如何在网络变更时疾速复原websocket的可用,就变得尤为重要。 疾速理解websocet Websocket诞生于2008年,在2011年成为国际标准,当初所有的浏览器都已反对。它是一种全新的应用层协定,是专门为web客户端和服务端设计的真正的全双工通信协议, 能够类比HTTP协定来理解websocket协定。它们的不同点: HTTP的协定标识符是http,websocket的是wsHTTP申请只能由客户端发动,服务器无奈被动向客户端推送音讯,而websocket能够HTTP申请有同源限度,不同源之间通信须要跨域,而websocket没有同源限度相同点: 都是应用层的通信协议默认端口一样,都是80或443都能够用于浏览器和服务器间的通信都基于TCP协定两者和TCP的关系图: 图片起源 重连过程拆解 首先思考一个问题,何时须要重连? 最容易想到的是websocket连贯断了,为了接下来能收发音讯,咱们须要再发动一次连贯。但在很多场景下,即使websocket连贯没有断开,实际上也不可用了,比方设施切换网络、链路两头路由解体、服务器负载继续过高无奈响应等,这些场景下的websocket都没有断开,但对下层来说,都没方法失常的收发数据了。因而在重连前,咱们须要一种机制来感知连贯是否可用、服务是否可用,而且要能疾速感知,以便可能疾速从不可用状态中复原。 一旦感知到了连贯不可用,那便能够弃旧图新了,弃用并断开旧连贯,而后发动一次新连贯。这两个步骤看似简略,但若想达到快,且不是那么容易的。 首先是断开旧连贯,对客户端来说,如何疾速疾速断开?协定规定客户端必须要和服务器协商后能力断开websocket连贯,然而当客户端曾经分割不上服务器、无奈协商时,如何断开并疾速复原? 其次是疾速发动新连贯。此快非彼快,这里的快并非是立刻发动连贯,立刻发动连贯会对服务器带来不可预估的影响。重连时通常会采纳一些退却算法,提早一段时间后再发动重连。但如何在重连距离和性能耗费间做出衡量?如何在“失当的工夫点”疾速发动连贯? 带着这些疑难,咱们来细看下这三个过程。 疾速感知何时须要重连 须要重连的场景能够细分为三种,一是连贯断开了,二是连贯没断然而不可用,三是连贯对端的服务不可用了。 第一种场景很简略,连贯间接断开了,必定须要重连了。 而对于后两者,无论是连贯不可用,还是服务不可用,对下层利用的影响都是不能再收发即时消息了,所以从这个角度登程,感知何时须要重连的一种简略粗犷的办法就是通过心跳包超时:发送一个心跳包,如果超过特定的工夫后还没有收到服务器回包,则认为服务不可用,如下图中左侧的计划;这种办法最间接。那如果想要疾速感知呢,就只能多发心跳包,放慢心跳频率。然而心跳太快对挪动端流量、电量的耗费又会太多,所以应用这种办法没方法做到疾速感知,能够作为检测连贯和服务可用的兜底机制。 如果要检测连贯不可用,除了用心跳检测,还能够通过判断网络状态来实现,因为断网、切换wifi、切换网络是导致连贯不可用的最间接起因,所以在网络状态由offline变为online时,大多数状况下须要重连下,但也不肯定,因为webscoket底层是基于TCP的,TCP连贯不能敏锐的感知到应用层的网络变动,所以有时候即使网络断开了一小会,对websocket连贯是不会有影响的,网络复原后,依然可能失常地进行通信。因而在网络由断开到连贯上时,立刻判断下连贯是否可用,能够通过发一个心跳包判断,如果可能失常收到服务器的心跳回包,则阐明连贯仍是可用的,如果期待超时后仍没有收到心跳回包,则须要重连,如上图中的右侧。这种办法的长处是速度快,在网络复原后可能第一工夫感知连贯是否可用,不可用的话能够疾速执行复原,但它只能笼罩应用层网络变动导致websocket不可用的状况。 综上,定时发送心跳包检测的计划贵在稳固,可能笼罩所有场景,但速度不太可;而判断网络状态的计划速度快,无需期待心跳距离,较为灵活,但笼罩场景较为局限。因而,咱们能够联合两种计划:定时以不太快的频率发送心跳包,比方40s/次、60s/次等,具体能够依据利用场景来定,而后在网络状态由offline变为online时立刻发送一次心跳,检测以后连贯是否可用,不可用的话立刻进行复原解决。这样在大多数状况下,下层的利用通信都能较快从不可用状态中复原,对于少部分场景,有定时心跳作为兜底,在一个心跳周期内也可能复原。 疾速断开旧连贯 通常状况下,在发动下一次连贯前,如果旧连贯还存在的话,应该先把旧连贯断开,这样一来能够开释客户端和服务器的资源,二来能够防止之后误从旧连贯收发数据。 咱们晓得websocket底层是基于TCP协定传输数据的,连贯两端别离是服务器和客户端,而TCP的TIME_WAIT状态是由服务器端维持的,因而在大多数失常状况下,应该由服务器发动断开底层TCP连贯,而不是客户端。也就是说,要断开websocket连贯时,如果是服务器收到批示要断开websocket,那它应该立刻发动断开TCP连贯;如果是客户端收到批示要断开websocket,那它应该发信号给服务器,而后期待底层TCP连贯被服务器断开或直至超时。 那如果客户端想要断开旧的websocket,能够分websocket连贯可用和不可用两种状况来探讨。当旧连贯可用时,客户端能够间接给服务器发送断开信号,而后服务器发动断开连接即可;当旧连贯不可用时,比方客户端切换了wifi,客户端发送了断开信号,然而服务器收不到,客户端只能迟迟期待,直至超时能力被容许断开。超时断开的过程相对来说是比拟久的,那有没有方法能够快点断开? 下层利用无奈扭转只能由服务器发动断开连接这种协定层面的规定,所以只能从应用逻辑动手,比方在下层通过业务逻辑保障旧连贯齐全生效,模仿连贯断开,而后在发动新连贯,复原通信。这种办法相当于尝试断开旧连贯不行时,间接弃之,而后就能疾速进入下一流程,所以在应用时肯定要确保在业务逻辑上旧连贯已齐全生效,比方:保障丢掉从旧连贯收到所有数据、旧连贯不能妨碍新连贯的建设,旧连贯超时断开后不能影响新连贯和下层业务逻辑等等。 疾速发动新连贯 有IM开发教训的同学应该有所理解,遇到因网络起因导致的重连时,是万万不能立刻发动一次新连贯的,否则当呈现网络抖动时,所有的设施都会立刻同时向服务器发动连贯,这无异于黑客通过发动大量申请耗费网络带宽引起的拒绝服务攻打,这对服务器来说几乎是劫难。所以在重连时通常采纳一些退却算法,提早一段时间再发动重连,如下图中左侧的流程。 如果要疾速连上呢?最间接的做法就是缩短重试距离,重试距离越短,在网络复原后就能越快的复原通信。然而太频繁的重试对性能、带宽、电量的耗费就比较严重。如何在这之间做一个较好的衡量呢? 一种比拟正当的形式是随着重试次数增多,逐步增大重试距离;另一方面监听网络变动,在网络状态由offline变为online这种比拟可能重连上的时刻,能够适当地减小重连距离,如上图中的右侧(随重试次数的增多,重连距离也会变大),两种形式配合应用。 除此之外,还能够联合业务逻辑,依据胜利重连上的可能性适当的调整距离,如网络未连贯时或利用在后盾时重连距离能够调大一些,网络失常的状态下能够适当调小一些等等,放慢重连上的速度。 结尾 最初总结一下,本文在结尾将websocket断网重连细分为三个步骤:确定何时须要重连、断开旧连贯和发动新连贯。而后别离剖析了在websocket的不同状态下、不同的网络状态下,如何疾速实现这个三个步骤:首先通过定时发送心跳包的形式检测以后连贯是否可用,同时监测网络复原事件,在复原后立刻发送一次心跳,疾速感知以后状态,判断是否须要重连;其次失常状况下由服务器断开旧连贯,与服务器失去分割时间接弃用旧连贯,下层模仿断开,来实现疾速断开;最初发动新连贯时应用退却算法提早一段时间再发动连贯,同时思考到资源节约和重连速度,能够在网络离线时调大重连距离,在网络失常或网络由offline变为online时放大重连距离,使之尽可能快地重连上。 参考: https://tools.ietf.org/html/rfc6455https://www.ruanyifeng.com/blog/2017/05/websocket.html理解网易云信,来自网易外围架构的通信与视频云服务>> 更多技术干货,欢送关注vx公众号“网易智慧企业技术+”。系列课程提前看,精品礼物收费得,还可直接对话CTO。 听网易CTO讲述前沿察看,看最有价值技术干货,学网易最新实践经验。网易智慧企业技术+,陪你从思考者成长为技术专家。

July 24, 2020 · 1 min · jiezi

关于websocket:把-B-站的视频弹幕搬到小程序中这项技术可以轻松搞定

作者|通晓云 2019 年 12 月,Bilibili( B 站)颁布了 2019 年度弹幕,「AWSL」荣登榜首。此外,「泪目」、「名局面」、「妙啊」等弹幕也入选了十大弹幕热词。 弹幕,日本弹幕视频分享网站( niconico 动画)的舶来品,由 AcFun(A 站)和 Bilibili ( B 站)引进到国内并流行。明天,中国各大视频网站都开始减少弹幕性能,弹幕未然成为年轻人须要的一种观影体验。 awsl 源于「啊,我死了」的拼音缩写,是对幸福、喜爱、兴奋等各种青睐之情的情绪表白 弹幕的利用弹幕是悬浮于视频上方的实时评论,给观看者提供一种「实时互动」的错觉 。用户通过弹幕进行评论、内容补充与互动,将单向的内容输入变成双向的文化交流,也为平台发明一个良好的用户生态环境。 现在弹幕已成为社交平台、视频平台上的视频标配,但基于弹幕的属性,在不影响用户的浏览体验前提下,是可利用到更多场景中。 场景一: 在电商小程序中把下单音讯和用户评估搬到商品详情页中,实时的出现给正在浏览商品的潜在客户。兴许正是因为眼前飘过的「好友**在一秒前已下单」、「还有**分钟复原原价」,客户便提交订单实现交易。不仅无效促成用户转化,还节俭了用户的抉择工夫。 场景二: 当你关上外卖或社区小程序,还在苦恼中午吃什么时,屏幕开始闪过大家的美食分享,此时的弹幕正如你征询了敌人「明天吃什么」后的答复一样,宽广网友的举荐将帮你解决这个「吃什么」的难题,并点击进入商家店铺下单。 弹幕性能用得好,或将成为平台或商家引流、进步用户黏性和转化的强有力形式。 为小程序加上视频弹幕并不难弹幕为用户互动与信息流传提供了无效的形式,但性能开发并不难。 视频弹幕借助「实时数据同步服务」,实时同步数据变动动作(新增、更新或删除)的变动。当用户输出弹幕内容,点击发送弹幕为数据表新增一条弹幕数据,小程序端即可收到新增的数据行,并进行弹幕显示。 弹幕小程序界面 「实时数据同步服务」是通晓云行将上线的服务,它是基于 WebSocket 实现客户端和服务端的实时双向通信。咱们将其封装成了一个简略的 API 供开发者应用,能够让你无需编写简单的逻辑便可在客户端之间同步数据,对于有实时数据同步需要的业务场景很有帮忙。游戏玩家的实时榜单、平台的站内信告诉、实时聊天等业务场景,均可通过该服务轻松搞定。 为了让企业及开发者能够更好的理解「实时数据同步服务」及其应用办法,通晓云将于 7 月 29 日(周三)推出直播课程,以视频弹幕微信小程序 Demo 为示例,手把手教大家如何开发,为本人的小程序减少乏味的弹幕性能。在此之前,咱们还小范畴凋谢了实时数据同步服务内测申请,让你能够提前体验并与通晓云外围工程师间接交换。???? 点此立刻申请 通晓云是 ifanr 旗下的技术服务平台。不仅向开发者提供了后端云服务,通过便捷易用的 SDK ,使得集体开发者能够应用通晓云又快又省的实现小程序开发,同时也提供了企业级定制服务,帮忙各行各业实现小程序畛域的策略布局。 目前通晓云曾经在多个畛域帮忙企业实现数字化转型: 在批发电商畛域,推出定制化的直播电商小程序,满足企业的线上业务需要。同时上线无代码服务小电商,企业只需三分钟,即可创立本人的电商小程序。在酒店游览畛域,已帮忙国家旅游局、热门景区和酒店,推出城市体验、景区语音导览、酒店服务等小程序。在政务畛域,结合实际利用场景,提供智慧政企小程序,帮忙政企部门疾速实现数字化转型,进步工作效率与成果。在教育领域,联手教育厅、高校党委,推出在线教育、校园导览等小程序。在医疗衰弱畛域,推出包含在线挂号、线上问诊、药物购买等性能的小程序。在往年疫情期间,爱范儿联手熊猫吃短信疾速上线服务于寰球用户的小程序——「疫小搜」,帮忙大家疾速理解左近疫况,远离危险。通晓云作为一个 serverless 云服务平台,将充分发挥技术劣势,让企业上云更轻松。

July 17, 2020 · 1 min · jiezi

websocket-websocket创建聊天室

首先,关于websocket教程,参考详细教程 WebSocket 是一种网络通信协议,很多高级功能都需要它。 WebSocket出现的原因因为 HTTP 协议有一个缺陷:通信只能由客户端发起,单向请求,如果服务端有状态变化,却无法及时通知客户端。(“轮询”效率低,要一直保持连接或者重复连接) WebSocket特点它的最大特点就是,服务器可以主动向客户端推送信息,客户端也可以主动向服务器发送信息,是真正的双向平等对话,属于服务器推送技术的一种。其他特点包括:(1)建立在 TCP 协议之上,服务器端的实现比较容易。(2)与 HTTP 协议有着良好的兼容性。默认端口也是80和443,并且握手阶段采用 HTTP 协议,因此握手时不容易屏蔽,能通过各种 HTTP 代理服务器。(3)数据格式比较轻量,性能开销小,通信高效。(4)可以发送文本,也可以发送二进制数据。(5)没有同源限制,客户端可以与任意服务器通信。(6)协议标识符是ws(如果加密,则为wss),服务器网址就是 URL。 ws://example.com:80/some/path客户端简单示例:

June 10, 2020 · 1 min · jiezi

互动直播中的前端技术-即时通讯

本文作者:吴杰前言在疫情期间,上班族开启了远程办公,体验了各种远程办公软件。老师做起了主播,学生们感受到了被钉钉支配的恐惧,歌手们开启了在线演唱会,许多综艺节目也变成了在线直播。在这全民互动直播的时期,我们来聊聊互动直播中的即时通讯技术在前端中的使用。 即时通讯技术即时通讯(Instant Messaging,简称IM)是一个实时通信系统,允许两人或多人使用网络实时的传递文字消息、文件、语音与视频交流。如何来实现呢,通常我们会使用服务器推送技术来实现。常见的有以下几种实现方式。 轮询(polling)这是一种我们几乎都用到过的的技术实现方案。客户端和服务器之间会一直进行连接,每隔一段时间就询问一次。前端通常采取setInterval或者setTimeout去不断的请求服务器数据。 优点:实现简单,适合处理的异步查询业务。缺点:轮询时间通常是死的,太长就不是很实时,太短增加服务器端的负担。不断的去请求没有意义的更新的数据也是一种浪费服务器资源的做法。 长轮询(long-polling)客户端发送一个请求到服务端,如果服务端没有新的数据,就保持住这个连接直到有数据。一旦服务端有了数据(消息)给客户端,它就使用这个连接发送数据给客户端。接着连接关闭。 优点:对比轮询做了优化,有较好的时效性。缺点:占较多的内存资源与请求数。 iframe流iframe流就是在浏览器中动态载入一个iframe, 让它的地址指向请求的服务器的指定地址(就是向服务器发送了一个http请求),然后在浏览器端创建一个处理数据的函数,在服务端通过iframe与浏览器的长连接定时输出数据给客户端,iframe页面接收到这个数据就会将它解析成代码并传数据给父页面从而达到即时通讯的目的。 优点:对比轮询做了优化,有较好的时效性。缺点:兼容性与用户体验不好。服务器维护一个长连接会增加开销。一些浏览器的的地址栏图标会一直转菊花。 Server-sent Events(sse)sse与长轮询机制类似,区别是每个连接不只发送一个消息。客户端发送一个请求,服务端保持这个连接直到有新消息发送回客户端,仍然保持着连接,这样连接就可以消息的再次发送,由服务器单向发送给客户端。 优点:HTML5 标准;实现较为简单;一个连接可以发送多个数据。缺点:兼容性不好(IE,Edge不支持);服务器只能单向推送数据到客户端。 WebSocketHTML5 WebSocket规范定义了一种API,使Web页面能够使用WebSocket协议与远程主机进行双向通信。与轮询和长轮询相比,巨大减少了不必要的网络流量和等待时间。 WebSocket属于应用层协议。它基于TCP传输协议,并复用HTTP的握手通道。但不是基于HTTP协议的,只是在建立连接之前要借助一下HTTP,然后在第一次握手是升级协议为ws或者wss。 优点:开销小,双向通讯,支持二进制传输。缺点:开发成本高,需要额外做重连保活。 在互动直播场景下,由于本身的实时性要求高,服务端与客户端需要频繁双向通信,因此与它十分契合。 搭建自己的IM系统上面简单的概述了下即时通讯的实现技术,接下来我们就聊聊如何实现自己的IM系统。 从零开始搭建IM系统还是一件比较复杂与繁琐的事情。自己搭建推荐基于socket.io来实现。socket.io对即时通讯的封装已经很不错了,是一个比较成熟的库,对不同浏览器做了兼容,提供了各端的方案包括服务端,我们不用关心底层是用那种技术实现进行数据的通信,当然在现代浏览器种基本上是基于WebSocket来实现的。市面上也有不少IM云服务平台,比如云信,借助第三方的服务也可以快速集成。下面就介绍下前端怎么基于socket.io集成开发。 基础的搭建服务端集成socket.io(有java版本的),服务端即成可以参考下这里,客户端使用socket.io-client集成。参考socket.io官方api,订阅生命周期与事件,通过订阅的方式或来实现基础功能。在回调函数执行解析包装等逻辑,最终抛给上层业务使用。 import io from 'socket.io-client';import EventEmitter from 'EventEmitter';class Ws extends EventEmitter { constructor (options) { super(); //... this.init(); } init () { const socket = this.link = io('wss://x.x.x.x'); socket.on('connect', this.onConnect.bind(this)); socket.on('message', this.onMessage.bind(this)); socket.on('disconnect', this.onDisconnect.bind.(this); socket.on('someEvent', this.onSomeEvent.bind(this)); } onMessage(msg) { const data = this.parseData(msg); // ... this.$emit('message', data); }}消息收发与服务器或者其他客户端进行消息通讯时通常会基于业务约定协议来封装解析消息。由于都是异步行为,需要有唯一标识来处理消息回调。这里用自增seq来标记。 ...

June 8, 2020 · 2 min · jiezi

CabloyJS-V320支持Socket-IO

CabloyJS v3.2.0引入了Socket IO,并且实现了统一的在线推送和离线推送机制 效果演示1. IM用户向系统发送一条消息,系统通过websocket在线通道向用户推送一条回复 2. 进度条系统通过websocket在线通道向前端实时推送任务的进度 项目配置升级到该版本,请更新以下项目配置: 1. 增加redis连接信息请依次修改测试环境、开发环境、生产环境的配置,这里以开发环境为例 {project}/src/backend/config/config.local.js // redis ... const __redisConnectionDefaultIO = Object.assign({}, __redisConnectionDefault, { keyPrefix: `io_${appInfo.name}:`, }); config.redisConnection = { ... io: __redisConnectionDefaultIO, }; config.redis = { clients: { redlock: config.redisConnection.default, ... io: config.redisConnection.io, }, };2. Nginx配置在Nginx配置中添加/socket.io/的转向 {project}/docker-compose/config/nginx/conf.d/nginx.conf ... location /socket.io/ { proxy_http_version 1.1; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Host $server_name; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header Host $http_host; proxy_set_header X-NginX-Proxy true; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_pass http://$node_ip:$node_port$request_uri; proxy_redirect off; proxy_buffer_size 64k; proxy_buffers 4 32k; proxy_busy_buffers_size 64k; }

June 4, 2020 · 1 min · jiezi

Win10-2004版开始菜单和搜索框挡重叠解决方法win10专业版

最近有用户反馈Win10 2004版升级之后发现点击开始菜单会自动弹出搜索框并挡住开始菜单,而且搜索框居中已经无效了。那么找到以下方法可以帮助大家解决这个问题。 解决方法如下: 把当初为设置搜索框居中而修改的注册表再修改回去新建文本文件,粘贴一下代码,并修改为.reg注册表文件,即可。 Windows Registry Editor Version 5.00 [HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Search] "ImmersiveSearch"=- [-HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Search\Flighting\Override] "ImmersiveSearchFull"=dword:00000001 "CenterScreenRoundedCornerRadius"=dword:00000009 如果以上方法管用,大家是记得回来点赞! 本文来源于win10专业版,转载请注明来源与出处。

June 4, 2020 · 1 min · jiezi

win7系统视频文件预览功能的操作方案win10专业版

也许还有很多朋友不清楚win7系统视频文件预览功能的问题该如何解决,其实win7系统视频文件预览功能的问题不是很难,但是大部分网友还是不清楚应该怎么办,因此我就在电脑上汇集整顿出win7系统视频文件预览功能的少许解决办法。只需要遵循1、首先在桌面空白处右击“新建一文本文档”,然后把下面的内容复制粘贴进去;2、复制粘贴到记事本之后,将文件保存为。reg格式(注册表文件),然后双击该文件将其导入到注册表中模式就可以完成了,下面就是小编给大家整理的关于win7系统视频文件预览功能的具体操作流程了! 1、首先在桌面空白处右击“新建一文本文档”,然后把下面的内容复制粘贴进去; 1. Windows Registry Edit Version 5.00 2. [-HKEY_CLASSES_ROOT.aviShellEx] 3. [-HKEY_CLASSES_ROOT.asfShellEx] 4. [-HKEY_CLASSES_ROOT.wmvShellEx] 5. [-HKEY_CLASSES_ROOT.rmShellEx] 6. [-HKEY_CLASSES_ROOT.rmvbShellEx] 2、复制粘贴到记事本之后,将文件保存为。reg格式(注册表文件),然后双击该文件将其导入到注册表中; 3、然后任意进入一个文件夹内,单击窗口上方的“工具→文件夹选项”,在打开的对话框中切换到“查看”选项卡,然后在“高级设置”栏目下找到并在“始终显示图标,从不显示缩略图”前面打上勾,设置完毕后单击“确定”按钮退出。 关于关闭win7视频文件预览功能的方法小编就为大家介绍到这里了,感兴趣的朋友不妨试试上面的方法吧,希望对你们有所帮助哦 本文来源于win10专业版,转载请注明来源与出处。

June 4, 2020 · 1 min · jiezi

websocket实现一个聊天室

上次讲了websoket的基础知识和应用插件,今天我们来用websocket来实现一个聊天程序。先看几张界面截图: 聊天界面:历史消息查看:用户上线提醒:在线体验地址:地址 功能结构图可以看到,该聊天室主要分为三个部分:消息实时推送,聊天界面与交互实现,用户认证模块。下面讲讲这个3个模块的实现方式。 整体架构前端:vue-cli搭建+websock客户端 后端:nodeJs+websock服务端+JWT认证 消息推送模块实现主要用到了websock的双工通信功能:服务端核心代码: const sendDataType = { // 发送消息 sendMsg: 1, // 发送在线用户数 userOnlineCount: 2, // 发送用户身份信息 sendName: 3, // 发送在线用户列表 sendUserList: 4}Object.freeze(sendDataType)class WsChat { constructor(port = 30002) { this.wss = new WebSocket.Server( {port: port }, ); // 连接成功,初始化事件 this.wss.on('connection', (ws, req) => { this.initWsEvent(ws) }); } initWsEvent(ws) { //收到消息 ws.on('message', message => { logger.writeInfo('message', message) this.onMessage(message, ws) }); ws.on("close", () => { //将已经断开的,删除掉 this.onClose() }); }}上面的代码不难看出,服务端定义了一个枚举:sendDataType 来告诉客户端收到消息的类型,分别是: ...

May 29, 2020 · 1 min · jiezi

使用socketio实现多房间通信聊天室

websocket的实现有很多种,像ws和socket.io,这里使用的是socket.io来实现多房间的效果。 这里的使用没有使用socket.io官方提供的namespace和room,而是完全通过一个namespace实现的。数据传输使用JSON格式,封装了消息规范 消息体规范const actionType = { join:'JOIN',//加入 leave:'LEAVE',//离开 talk:'TALK',//消息 action:'ACTION',//用户操作 push:'PUSH'//系统推送}//消息体class MSG { constructor(type,body){ this.type = type; this.body= body; }}安装使用npm install socket.io-rooms --savedemo演示把项目从github上clone下来后,执行npm start,然后打开example/index.html即可品尝到演示效果 使用方式服务端Serverconst {User,Rooms} = require('socket.io-rooms')const server = require('http').createServer();const io = require('socket.io')(server);//大厅io.on('connection', client => { let user = new User(); client.emit('user',user); client.on('join', data => { /\* 加入某个房间 \*/ Rooms.join(data,user,io,client) }); client.on('message',msg=>{ if(user.roomId){ // io.to(user.roomId).emit('message',msg) if(msg.type == 'update'){ user.update(msg.body); } msg.user = user.uid; Rooms.send(user.roomId,msg) }else{ io.emit('message',msg) } console.log(msg) }) client.on('disconnect', () => { /\* … \*/ console.log("连接断开") Rooms.leave(user) });});server.listen(80);这里传输统一使用`JSON`格式,消息`title`也以`message`为主,这里端口写的80,你可以使用其他端口,如果你是Express,也可以共用80端口。 ...

October 15, 2019 · 1 min · jiezi

SpringBoot整合websocket

什么是WebSocket?WebSocket 是 HTML5 开始提供的一种在单个 TCP 连接上进行全双工通讯的协议。 WebSocket 使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。在 WebSocket API 中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。 在 WebSocket API 中,浏览器和服务器只需要做一个握手的动作,然后,浏览器和服务器之间就形成了一条快速通道。两者之间就直接可以数据互相传送。 话不多说,马上进入干货时刻。 maven依赖SpringBoot2.0对WebSocket的支持简直太棒了,直接就有包可以引入 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-websocket</artifactId> </dependency> WebSocketConfig启用WebSocket的支持也是很简单,几句代码搞定 import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.web.socket.server.standard.ServerEndpointExporter;/** * 开启WebSocket支持 * @author zhengkai */@Configuration public class WebSocketConfig { @Bean public ServerEndpointExporter serverEndpointExporter() { return new ServerEndpointExporter(); } } WebSocketServer因为WebSocket是类似客户端服务端的形式(采用ws协议),那么这里的WebSocketServer其实就相当于一个ws协议的Controller直接@ServerEndpoint("/websocket")、@Component启用即可,然后在里面实现@OnOpen,@onClose,@onMessage等方法 import java.io.IOException;import java.util.concurrent.CopyOnWriteArraySet;import javax.websocket.OnClose;import javax.websocket.OnError;import javax.websocket.OnMessage;import javax.websocket.OnOpen;import javax.websocket.Session;import javax.websocket.server.ServerEndpoint;import org.springframework.stereotype.Component;import cn.hutool.log.Log;import cn.hutool.log.LogFactory;import lombok.extern.slf4j.Slf4j;@ServerEndpoint("/websocket/{sid}")@Componentpublic class WebSocketServer { static Log log=LogFactory.get(WebSocketServer.class); //静态变量,用来记录当前在线连接数。应该把它设计成线程安全的。 private static int onlineCount = 0; //concurrent包的线程安全Set,用来存放每个客户端对应的MyWebSocket对象。 private static CopyOnWriteArraySet<WebSocketServer> webSocketSet = new CopyOnWriteArraySet<WebSocketServer>(); //与某个客户端的连接会话,需要通过它来给客户端发送数据 private Session session; //接收sid private String sid=""; /** * 连接建立成功调用的方法*/ @OnOpen public void onOpen(Session session,@PathParam("sid") String sid) { this.session = session; webSocketSet.add(this); //加入set中 addOnlineCount(); //在线数加1 log.info("有新窗口开始监听:"+sid+",当前在线人数为" + getOnlineCount()); this.sid=sid; try { sendMessage("连接成功"); } catch (IOException e) { log.error("websocket IO异常"); } } /** * 连接关闭调用的方法 */ @OnClose public void onClose() { webSocketSet.remove(this); //从set中删除 subOnlineCount(); //在线数减1 log.info("有一连接关闭!当前在线人数为" + getOnlineCount()); } /** * 收到客户端消息后调用的方法 * * @param message 客户端发送过来的消息*/ @OnMessage public void onMessage(String message, Session session) { log.info("收到来自窗口"+sid+"的信息:"+message); //群发消息 for (WebSocketServer item : webSocketSet) { try { item.sendMessage(message); } catch (IOException e) { e.printStackTrace(); } } } /** * * @param session * @param error */ @OnError public void onError(Session session, Throwable error) { log.error("发生错误"); error.printStackTrace(); } /** * 实现服务器主动推送 */ public void sendMessage(String message) throws IOException { this.session.getBasicRemote().sendText(message); } /** * 群发自定义消息 * */ public static void sendInfo(String message,@PathParam("sid") String sid) throws IOException { log.info("推送消息到窗口"+sid+",推送内容:"+message); for (WebSocketServer item : webSocketSet) { try { //这里可以设定只推送给这个sid的,为null则全部推送 if(sid==null) { item.sendMessage(message); }else if(item.sid.equals(sid)){ item.sendMessage(message); } } catch (IOException e) { continue; } } } public static synchronized int getOnlineCount() { return onlineCount; } public static synchronized void addOnlineCount() { WebSocketServer.onlineCount++; } public static synchronized void subOnlineCount() { WebSocketServer.onlineCount--; }}消息推送至于推送新信息,可以再自己的Controller写个方法调WebSocketServer.sendInfo();即可 ...

October 4, 2019 · 2 min · jiezi

使用Promise封装Websocket

await 后面若是跟的Promise,则接受Promise resolve的值。Promise reject的值需要try...catch住,或者await 后面的表达式跟着.catch()//私有变量const options = Symbol("options");const intUniqueId = Symbol("intUniqueId");//const arrQueueRequest = Symbol("arrQueueRequest");const arrQueueNotice = Symbol("arrQueueNotice");export default class SocketConn { /** * 构造函数,初始化数据 * @param {*} objOption */ constructor(objOption) { this[options] = { connWbSK: null, //Websocket实例 secure: objOption.secure || "wss", hostName: objOption.hostName || "127.0.0.1", portNo: objOption.portNo || "80", user: objOption.user, password: objOption.password }; this[intUniqueId] = 0; //请求的id,也是请求的key值(在请求序列里根据id找对应的包) this[arrQueueRequest] = {};//请求序列 this[arrQueueNotice] = {};//server的通知序列 }; /***************私有函数start*********** */ /** * 处理请求对应的响应 */ _onRequestMessage = (objResp) => { let {id} = objResp;//从响应里获取id this[arrQueueRequest][id].resolve(objResp);//只返回resolve函数,不reject,可以在外面捕获 delete this[arrQueueRequest][id];//删除序列id对应数据包 }; _onNoticeMessage = (objResp) => { //处理通知 } /***************私有函数end*********** */ /***************公开函数start*********** */ //公开函数 open = (onSocketClose) => { //await只能接收resolve的值,reject的值需要在外面catch return new Promise((resolve, reject) => { let _private = this[options], url = "" + _private.secure + "://" + _private.hostName; if (_private.portNo) { url += ":" + _private.portNo; } url += "?"; url += "user=" + _private.user; url += "&password=" + _private.password; _private.connWbSK = new WebSocket(url); _private.connWbSK.onopen = (event) => {//成功连接上 console.log("onopen") resolve(true); } _private.connWbSK.onmessage = (event) => {//收到server发来的消息 let objResp = JSON.parse(event.data); //如果是我们发送的请求 if(objResp.packet_type === "response"){ this._onRequestMessage(objResp); }else{//如果是server的通知 this._onNoticeMessage(objResp); } } _private.connWbSK.onerror = (event) => { // reject(false); }; //传入onclose事件,以便于server主动断开时触发 _private.connWbSK.onclose = (event) => { onSocketClose(event); }; }); }; /** * 外部调用的发送方法 */ send = (serviceName, methodName, objData, dataFormat, timeout) => { return new Promise((resolve, reject) => { let objPkg, objPkgSendReq; this[intUniqueId] += 1; //构建发送包 objData = JSON.stringify(objData);//先序列化 objData = Base64.encode(objData);//加密 objPkg = { id: this[intUniqueId], packet_type: "request", service: serviceName, method: methodName, data: objData }; objPkgSendReq = JSON.stringify(objPkg); this[arrQueueRequest][objPkg.id] = { id: objPkg.id, strReq: objPkgSendReq, reject: reject, resolve: resolve, dataFormat: dataFormat, timeoutId: null } this[options].connWbSK.send(this[arrQueueRequest][objPkg.id].strReq); }) }; //主动关闭连接,并且重置数据 closeSocketAndResetVar = () => { if(this[options].connWbSK){ this[options].connWbSK.close(); this[options].connWbSK = null; } }; /***************公开函数end*********** */}使用 ...

September 9, 2019 · 2 min · jiezi

压测工具如何选择-ablocustJmetergo压测工具单台机器100w连接压测实战

本文介绍压测是什么,解释压测的专属名词,教大家如何压测。介绍市面上的常见压测工具(ab、locust、Jmeter、go实现的压测工具、云压测),对比这些压测工具,教大家如何选择一款适合自己的压测工具,本文还有两个压测实战项目: 单台机器对HTTP短连接 QPS 1W+ 的压测实战单台机器100W长连接的压测实战目录1、项目说明 1.1 go-stress-testing1.2 项目体验2、压测 2.1 压测是什么2.2 为什么要压测2.3 压测名词解释 2.3.1 压测类型解释2.3.2 压测名词解释2.3.3 机器性能指标解释2.3.4 访问指标解释3.4 如何计算压测指标3、常见的压测工具 3.1 ab3.2 locust3.3 Jmeter3.4 云压测 3.4.1 云压测介绍3.4.2 阿里云 性能测试 PTS3.4.3 腾讯云 压测大师 LM4、go-stress-testing go语言实现的压测工具 4.1 介绍4.2 用法4.3 实现4.4 go-stress-testing 对 Golang web 压测5、压测工具的比较 5.1 比较5.2 如何选择压测工具6、单台机器100w连接压测实战 6.1 说明6.2 内核优化6.3 客户端配置6.4 准备6.5 压测数据7、总结8、参考文献1、项目说明1.1 go-stress-testinggo 实现的压测工具,每个用户用一个协程的方式模拟,最大限度的利用CPU资源 1.2 项目体验可以在 mac/linux/windows 不同平台下执行的命令参数说明: -c 表示并发数 -n 每个并发执行请求的次数,总请求的次数 = 并发数 * 每个并发执行请求的次数 -u 需要压测的地址 ...

August 28, 2019 · 5 min · jiezi

基于WebSocket的web端IM即时通讯应用的开发精进

基于WebSocket的web端IM即时通讯应用的开发-精进上篇关于websocket的文章,登录确认session是通过与springsecurity结合,其登录后默认的session实现了principle,自动就能转换为websocketsession。 而当前的需求是自己实现的简单登录创建的httpsession怎么转换到websocketsession。 跟着下面的例子模拟体验 登录的时候,创建session 并存用户唯一标识 HttpSession httpsession = request.getSession();httpsession.setAttribute("userName",username);通过修改websocketconfig,最终将当前登录连接的websocket调整成跟用户名相关的连接。 //SessionAuthHandshakeInterceptor 定制的是simpSessionAttributes //setHandshakeHandler 定制的是simpUser registry.addEndpoint("/im-websocket").setAllowedOrigins("*").addInterceptors(new SessionAuthHandshakeInterceptor()) .setHandshakeHandler(new DefaultHandshakeHandler(){ @Override protected Principal determineUser(ServerHttpRequest request, WebSocketHandler wsHandler, Map<String, Object> attributes) { String userName= (String)attributes.get("userName"); UserPrincipal userPrincipal=new UserPrincipal(userName); return userPrincipal; } }) .withSockJS(); 其中SessionAuthHandshakeInterceptor核心代码,其将session的username转存到websocketsession的参数里。由上面的determineUser转为实现了Principal的用户类里。 public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map<String, Object> attributes) throws Exception { HttpSession session = getSession(request); if(session == null ){ return false; } attributes.put("userName",session.getAttribute("userName").toString()); return true; } 监控链接情况以下演示以两个用户分别连接、订阅用户列表公共通道,订阅私聊通道,发送单条信息为例 ...

July 12, 2019 · 2 min · jiezi

火币-websocket-获取-k-线数据和其它数据-API

火币 websocket 获取 k 线数据,和其它数据 API火币官方文档地址: https://huobiapi.github.io/do...pako(解压gzip): https://github.com/nodeca/pako坑死,接火币 websocket 地址 wss://api.huobi.pro/ws 的时候接到怀疑人生,怎么都无法建立连接,一度以为自己没有像其它 get/post 接口那样传入该传的参数,又以为是 wss 走的是 SSL 通道,所以才没能建立连接,还以为是需要服务端转接一下。 但,最后的最后,竟然发现是因为地址的问题,这个地址根本接入不了,至少我这边是接入不进去。 后来换这个地址: wss://api.hadax.com/ws就顺利的接入了 火币 websocket 规则先建立连接发送订阅请求,这个官方文档有说明火币服务器返回订阅成功与否的回馈信息如果成功就定时发送 gzip 压缩后的数据,解压 gzip 你需要 pako https://github.com/nodeca/pako我们自己客户端这边接收数据,并解压数据,才能获取到真正的 json 数据自己做前端该做的相应操作,压入数据,展示数据什么的。但,在这个期间,还有一个动作需要执行,火币服务器会每隔 5 秒,向客户端发送一条 ping 数据,客户端接收到这种信息的时候,需要 send 一条对应的 pong 数据,内容是 ping 的数据体(相应的数据格式如下),如果服务器在发送两条 ping 数据后没有收到 客户端返回的 pong 数据,火币服务器就会关闭连接。 关于其它信息的获取,查看官方文档关于 websocket 的说明就可以了。 // 服务器发送的 ping 数据{ ping: 1562741680416 }// 客户端返回服务器的 pong 数据{ pong: 1562741680416 }执行代码// K 线相关let hburl = 'wss://api.huobipro.com/ws'; // 实时币种价格let haurl = 'wss://api.hadax.com/ws';let requestK = { // 请求对应信息的数据 req: "market.bchusdt.kline.1min", id: "bchusdt", from: Math.round(new Date().getTime()/1000) - 60, to: Math.round(new Date().getTime()/1000)};let subK = { // 订阅数据 sub: "market.bchusdt.kline.1min", id: "bchusdt"};let socketK = new WebSocket(haurl);socketK.onopen = function () { console.log("connection establish"); socketK.send(JSON.stringify(subK)); socketK.send(JSON.stringify(requestK));};socketK.onmessage = function (event) { let blob = event.data; let reader = new FileReader(); reader.onload = function (e) { let ploydata = new Uint8Array(e.target.result); let msg = pako.inflate(ploydata, {to: 'string'}); handleData(msg); }; reader.readAsArrayBuffer(blob, "utf-8");};socketK.onclose = function () { console.log('connection closed');};// 处理接收到的信息function handleData(msg) { let data = JSON.parse(msg); if (data.ping) { // 如果是 ping 消息 sendHeartMessage(data.ping); } else if (data.status === 'ok') { // 响应数据 handleReponseData(data); } else { // 数据体 console.log(data) }}// 发送响应信息function sendHeartMessage(ping) { socketK.send(JSON.stringify({"pong": ping}));}function handleReponseData(data) {}

July 10, 2019 · 1 min · jiezi

Golang轻便的实时日志类似slack收集应用

wslog原理利用github.com上无数的slack hook 日志工具sdk遵循 slack hook API 规范 https://api.slack.com/incomin...wslog暴露Http API来收集slack hook api 规范的json日志wslog提供websocket API像前端实时展示收集的日志,提供http api 展示搜索历史日志视频DEMO视频演示地址 https://www.bilibili.com/vide... 1. 为什么要开发这个应用我们程序员再开发中需要不停的查看日志来解决bug,我在google上一直都没有找到一款轻便简洁有效的日志收集应用.而这款应用可以结局一下疼点: linux下查看日志学习曲线较陡, tail/cat/grep/sed 命令眼花缭乱ELK日志服务器部署困难繁琐,其次对机器的性能内存要求很高,ELK基于elasticSearch/java内存无底洞.使用Slack Hook收集日志: 国内网络加载slack界面非常吃力,各种js/css下载失败,更甚对于免费用户日志数量还有数量条数限制(<10000条).钉钉Bot Hooks收集日志: 钉钉办公工具是大资本加剥削工薪阶级的工具(哈哈),你怎么能使用钉钉来解析日志的收集展示呢?2. wslog的优势2.1 部署简单wslog 后端基于golang开发,一次编译多平台可执行文件.wslog UI基于浏览器,支持任意平台.wslog 数据库使用SQLite3和go语言内存数据库,没有任何数据库运维工作和数据库限制.前端代码和后端代码都编译到一个可执行二进制文件中,双击二进制文件就可以执行. 2.2 多平台支持wslog 支持单机运行,可以支持windows/linux服务器运行.支持各种主流操作系统windows/linux/mac/中标麒麟/国产linux操作系统.支持任意架构arm/x86,支持树莓派系统... 2.3 实时日志输出wslog采用websocket通讯,像聊天工具那样实时输出日志,也可以在日志历史列表中快捷查看日志 2.4 日志分类日志从功能来说,可分为诊断日志、统计日志、审计日志. wslog日志支持debug/info/warning/error/fatal等日志级别 2.5 日志全文检索wslog 可以轻松的在数百万条日志中快速的定位你的日志 2.6 全面兼容支持slack-hook日志sdk进入wslog->hook 创建hook,复制hook_url,把hook_url粘贴替换之前slack_hook sdk 配置. 2.7 全面兼容支持slack_hook API的第三方日志收集SDKgo: logrus slackruspython: slack-loggerjava: slack appender for Log4jC#: Microsoft.Extensions.Logging.Slackphp: laravel/lumen slack日志javascript: Slack logger nodejs libraryswift: SwiftyBeaver slack3. 编译/安装可以访问在线demo网站 http://felix.mojotv.cn ...

July 10, 2019 · 1 min · jiezi

你可能不知道的浏览器实时通信方案

本文主要探讨现阶段浏览器端可行的实时通信方案,以及它们的发展历史。 这里以sockjs作为切入点,这是一个流行的浏览器实时通信库,提供了'类Websocket'、一致性、跨平台的API,旨在浏览器和服务器之间创建一个低延迟、全双工、支持跨域的实时通信信道. 主要特点就是仿生Websocket,它会优先使用Websocket作为传输层,在不支持WebSocket的环境回退使用其他解决方案,例如XHR-Stream、轮询. 所以sockjs本身就是浏览器实时通信方案的编年史, 本文也是按照由新到老这样的顺序来介绍这些解决方案. 类似sockjs的解决方案还有 socket.io如果你觉得文章不错,请不要吝惜你的点赞????,鼓励笔者写出更精彩的文章 目录 WebSocketXHR-streamingEventSourceHtmlFilePollingLong polling扩展WebSocketWebSocket其实不是本文的主角,而且网上已经有很多教程,本文的目的是介绍WebSocket之外的一些回退方案,在浏览器不支持Websocket的情况下, 可以选择回退到这些方案. 在此介绍Websocket之前,先来了解一些HTTP的基础知识,毕竟WebSocket本身是借用HTTP协议实现的。 HTTP协议是基于TCP/IP之上的应用层协议,也就是说HTTP在TCP连接中进行请求和响应的,浏览器会为每个请求建立一个TCP连接,请求等待服务端响应,在服务端响应后关闭连接: 后来人们发现为每个HTTP请求都建立一个TCP连接,太浪费资源了,能不能不要着急关闭TCP连接,而是将它复用起来, 在一个TCP连接中进行多次请求。 这就有了HTTP持久连接(HTTP persistent connection, 也称为HTTP keep-alive), 它利用同一个TCP连接来发送和接收多个HTTP请求/响应。持久连接的方式可以大大减少等待时间, 双方不需要重新运行TCP握手,这对前端静态资源的加载也有很大意义: Ok, 现在回到WebSocket, 浏览器端用户程序并不支持和服务端直接建立TCP连接,但是上面我们看到每个HTTP请求都会建立TCP连接, TCP是可靠的、全双工的数据通信通道,那我们何不直接利用它来进行实时通信? 这就是Websocket的原理! 我们这里通过一张图,通俗地理解一下Websocket的原理: 通过上图可以看到,WebSocket除最初建立连接时需要借助于现有的HTTP协议,其他时候直接基于TCP完成通信。这是浏览器中最靠近套接字的API,可以实时和服务端进行全双工通信. WebSocket相比传统的浏览器的Comet)(下文介绍)技术, 有很多优势: 更强的实时性。基于TCP协议的全双工通信更高效。一方面是数据包相对较小,另一方面相比传统XHR-Streaming和轮询方式更加高效,不需要重复建立TCP连接更好的二进制支持。 Websocket定义了二进制帧,相对HTTP,可以更轻松地处理二进制内容保持连接状态。 相比HTTP无状态的协议,WebSocket只需要在建立连接时携带认证信息,后续的通信都在这个会话内进行可以支持扩展。Websocket定义了扩展,用户可以扩展协议、实现部分自定义的子协议。如部分浏览器支持压缩等它的接口也非常简单: const ws = new WebSocket('ws://localhost:8080/socket'); // 错误处理ws.onerror = (error) => { ... } // 连接关闭ws.onclose = () => { ... } // 连接建立ws.onopen = () => { // 向服务端发送消息 ws.send("ping"); }// 接收服务端发送的消息ws.onmessage = (msg) => { if(msg.data instanceof Blob) { // 处理二进制信息 processBlob(msg.data); } else { // 处理文本信息 processText(msg.data); }}本文不会深入解析Websocket的协议细节,有兴趣的读者可以看下列文章: ...

July 8, 2019 · 2 min · jiezi

websocket-实战好友聊天

JS websocket 实战——好友聊天原文地址:websocket 实战——好友聊天 还不了解 websocket 的同鞋,请先学习阮一峰老师的 WebSocket 教程 websocketwebsocket 在实际项目中有着很广的应用,如好友聊天,异步请求,react-hot-loader 的热更新等等本文前端采用原生 WebSocket,后端采用 express-ws 库 实现聊天通信后端 mongodb 数据存储采用 mongoose 操作,不了解的可以先看看 文档 哦聊天原理很简单,如下图: 简单版本先撸个简单版本,能够实现用户与服务器之间的通信 前端:WsRequest 封装类class WsRequest { ws: WebSocket constructor(url: string) { this.ws = new WebSocket(url) this.initListeners() } initListeners() { this.ws.onopen = _event => console.log('client connect') this.ws.onmessage = event => console.log(`来自服务器的信息:${event.data}`) this.ws.onclose = _event => console.log('client disconnect') } send(content: string) { this.ws.send(content) } close() { this.ws.close() }}// 使用const ws = new WsRequest('your_websocket_url') // 如: ws://localhost:4000/wsws.send('hello from user')服务端:WsRouter 封装类,使用单例模式import expressWs, { Application, Options } from 'express-ws';import ws, { Data } from 'ws';import { Server as hServer } from 'http';import { Server as hsServer } from 'https';class WsRouter { static instance: WsRouter; wsServer: expressWs.Instance; clientMap: Map<string, ws>; // 保存所有连接的用户 id constructor( private path: string, private app: Application, private server?: hServer | hsServer, private options?: Options ) { this.wsServer = expressWs(this.app, this.server, this.options); this.app.ws(this.path, this.wsMiddleWare); this.clientMap = new Map(); } static getInstance(path: string, app: Application, server?: hServer | hsServer, options: Options = {}) { if (!this.instance) { this.instance = new WsRouter(path, app, server, options); } return this.instance; } wsMiddleWare = (wServer: any, _req: any) => { this.clientMap.set(id, wServer); this.broadcast('hello from server'); // send data to users wServer.on('message', async (data: Data) => console.log(`来自用户的信息:${data.toString()}`)); wServer.on('close', (closeCode: number) => console.log(`a client has disconnected: ${closeCode}`)); } broadcast(data: Data) { // 全体广播 this.clientMap.forEach((client: any) => { if (client.readyState === ws.OPEN) { client.send(data); } }); }}export default WsRouter.getInstance;// 使用:bootstrap.tsconst server = new InversifyExpressServer(container);// 注:本项目后端使用的是 [Inversify](https://github.com/inversify) 框架// 具体传的 private server?: hServer | hsServer 参数值,请类比改变server.setConfig((app: any) => WsRouter('/ws/:id', app))server.build().listen(4000);升级版本要实现好友通信,在前后端的 send 方法中,当然要指定 from 和 to 的用户 ...

June 29, 2019 · 4 min · jiezi

Lua-Web快速开发指南10-利用MQ实现异步任务订阅发布消息队列

本章节我们将学习如何使用MQ库. MQ库简介MQ库实现了各类消息代理中间件(Message Broker)的连接协议, 目前支持:redis、mqtt、stomp协议. MQ库基于上述协议实现了: 生产者 -> 消费者与订阅 -> 发布模型, 可以在不依赖其它服务的情况下独立完成任务. API介绍cf框架提供了多种MQ的封装, 当我们需要使用的时候需要根据实际的协议进行选择: -- local MQ = require "MQ.mqtt"-- local MQ = require "MQ.redis"-- local MQ = require "MQ.stomp"MQ:new(opt)此方法将会创建一个的MQ对象实例. opt是一个table类型的参数, 可以传递如下值: host - 字符串类型, 消息队列的域名或者IP地址.port - int类型, 消息队列监听的端口.auth/db - 字符串类型, 仅在redis协议下用作登录认证或者db选择(没有可以不填写).username/password - 字符串类型, 仅在stomp/mqtt协议下用作登录认证(没有可以不填写).vhost - 字符串类型, 仅在使用某些特定消息队列server的时候填写(例如:rabbit).keepalive - int类型, 仅在使用mqtt的时候用来出发客户端主动发出心跳包的时间.以redis broker为示例: local MQ = require "MQ.redis"local mq = MQ:new { host = "localhost", port = 6379, -- db = 0, -- auth = "123456789",}MQ:on(pattern, function)此方法用来订阅一个指定pattern. 当broker将消息传递到cf后, function将会被调用. ...

June 25, 2019 · 5 min · jiezi

为skynet移植一个luawebsocke库

简介目前大部分游戏、移动互联网、H5客户端主要由JavaScript、Lua、C#、C++等语言进行逻辑开发, 其主要通讯方案便是基于HTTP协议的接口请求与Websocket的推送方案. 起因skynet内部实现了一套同步非阻塞socket库, 并且提供了TCP通讯方案进行数据流分割. 所谓的TCP数据流分割. 就是根据一定方式读取数据的一种流程. 最为常见的数据分割方案应该是: 2字节头部 + 数据载荷. 另一种通用方案是将头部扩展为4字节, 这样在头部信息中可以包含协议版本或者消息类型还可以进行平滑的进行协议升级扩展. 这些方案一般用于定制C/S网络协议. 绝大多数场景中并没有必须使用到这个场景, 且维护一套这样的协议也是需要占用开发周期的.本人也使用过国内的开发者基于skynet编写的websockket开发库. 就使用上来说效果不是很理想, 且期间遇到的一系列问题也需要自己实际定制化后才能解决. 刚好近期由于正在为开发的Lua Web框架编写Websocket使用教程, 那么干脆趁这个机会为skynet移植一套专用的websocket库. 编写完成后, 我将它随意的命名为: skynet-lua-websocket. 开始移植工作1. 握手流程skynet要使用Websocket协议进行通讯需要实现HTTP/1.1版本中101响应方法. skyent.httpd库可以完成HTTP协议的解析工作, 但是我们并没有使用到它. 究其原因是因为websocket实现交互并不复杂且所以无需依赖其它应用层协议库的实现. HTTP本身就是一个基于文本的交互协议, 我们可以通过文本分割方案来完成它. 当连接到来时我们需要定义一个方法来处理握手协议交互(do_handshak). 在握手期间我们需要等待客户端发送有效的HTTP请求数据(协议、方法、版本、头部等等). 在握手期间我们不能忘记给它加上一个超时限制(set_timeout), 这个限制需要然客户端在指定时间范围内完成握手. 否则, 只能断开连接来节省服务器资源开销. 当HTTP请求数据接收完毕后, 我们需要对头部信息进行简单的验证. 这个验证过程并不会很复杂, 因为我们只需要知道头部信息是否完整有效即可. 在验证完成与通过的时候, 我们需要返回一个协议升级成功的101回应来通知客户端, 可以使用websocket规定的协议进行通讯并且开始监听链接是否有数据即可. 2. 消息交互Websocket协议规范中定义了一些常用的消息(控制帧). 目前为止, 我们也仅需要使用到这些消息: text、binary、ping、pong、close. text/binary可以分为一种客户端请求消息, 它定义了客户端发送到服务端的数据是什么类型. 这通常在项目开发初期已经约定好传输协议, 所以无需过多考虑. ping/pong通常是成对出现的; 它一般用作心跳检查(虽然没有人这样做)与交互测试工作. close一般主动推送关闭消息, 一般情况下接收到这样的消息的处理方式为关闭连接. 3. 事件处理事件处理方式就仿照JavaScript设计定义了4种回调函数类型(on_open、 on_message、 on_error、on_close), 这样能简化代码编写难度. 在每个客户端连接到来的时候为用户初始化ctor方法并为其注入ws对象用于与客户端进行通讯(send、close). 当客户端连接建立完成后会在应用层触发on_open方法, 让开发者此时做一些相关的初始化的操作. 期间与客户端连接保持的期间内定义了on_message方法用于接收客户端的数据, 对需要回应的数据可以使用self.ws:send方法进行消息回应. ...

June 22, 2019 · 1 min · jiezi

Lua-Web快速开发指南8-利用httpd提供Websocket服务

Websocket的技术背景WebSocket是一种在单个TCP连接上进行全双工通信的协议, WebSocket通信协议于2011年被IETF定为标准RFC 6455并由RFC7936补充规范. WebSocket使得客户端和服务器之间的数据交换变得更加简单, 使用WebSocket的API只需要完成一次握手就直接可以创建持久性的连接并进行双向数据传输. WebSocket支持的客户端不仅限于浏览器(Web应用), 在现今应用市场内的众多App客户端的长连接推送服务都有一大部分是基于WebSocket协议来实现交互的. Websocket由于使用HTTP协议升级而来, 在协议交互初期需要根据正常HTTP协议交互流程. 因此, Websocket也很容易建立在SSL数据加密技术的基础上进行通信. 协议WebSocket与HTTP协议实现类似但也略有不同. 前面提到: WebSocket协议在进行交互之前需要进行握手, 握手协议的交互就是利用HTTP协议升级而来. 众所周知, HTTP协议是一种无状态的协议. 对于这种建立在请求->回应模式之上的连接, 即使在HTTP/1.1的规范上实现了Keep-alive也避免不了这个问题. 所以, Websocket通过HTTP/1.1协议的101状态码进行协议升级协商, 在服务器支持协议升级的条件下将回应升级请求来完成HTTP->TCP的协议升级. 原理客户端将在经过TCP3次握手之后发送一次HTTP升级连接请求, 请求中不仅包含HTTP交互所需要的头部信息, 同时也会包含Websocket交互所独有的加密信息. 当服务端在接受到客户端的协议升级请求的时候, 各类Web服务实现的实际情况, 对其中的请求版本、加密信息、协议升级详情进行判断. 错误(无效)的信息将会被拒绝. 在两端确认完成交互之后, 双方交互的协议将会从抛弃原有的HTTP协议转而使用Websocket特有协议交互方式. 协议规范可以参考RFC文档. 优势在需要消息推送、连接保持、交互效率等要求下, 两种协议的转变将会带来交互方式的不同. 首先, Websocket协议使用头部压缩技术将头部压缩成2-10字节大小并且包含数据载荷长度, 这显著减少了网络交互的开销并且确保信息数据完整性. 如果假设在一个稳定(可能)的网络环境下将尽可能的减少连接建立开销、身份验证等带来的网络开销, 同时还能拥有比HTTP协议更方便的数据包解析方式. 其次, 由于基于Websocket的协议的在请求->回应上是双向的, 所以不会出现多个请求的阻塞连接的情况. 这也极大程度上减少了正常请求延迟的问题. 最后, Websocket还能给予开发者更多的连接管控能力: 连接超时、心跳判断等. 在合理的连接管理规划下, 这可提供使用者更优质的开发方案. APIcf框架中的httpd库内置了Websocket路由, 提供了上述Websocket连接管理能力. Websocket路由需要开发者提供一个lua版的class对象来抽象路由处理的过程, 这样的抽象能简化代码编写难度. lua classclass 意译为'类'. 是对'对象'的一种抽象描述, 多用于各种面相对象编程语言中. lua没有原生的class类型, 但是提供了基本构建的元方法. cf为了方便描述内置对象与内置库封装, 使用lua table的相关元方法建立了最基本的class模型. 几乎大部分内置库都依赖cf的class库. 同时为了简化class的学习成本, 去除了class原本拥有的'多重继承'概念. 将其仅作为类定义, 用于完成从class->object的初始化工作. ...

June 18, 2019 · 2 min · jiezi

socketio

写了一个socket.io服务,实现了用户区分、公聊、私聊等。。。码云地址https://gitee.com/liuoomei/so... app.js //app.jsvar express = require('express')var app = express();var server = require('http').Server(app);// var io = require('socket.io')(server);var path = require('path');app.use(express.static(path.join(__dirname, 'public')))app.get('/', function(req, res){ res.sendFile(path.join(__dirname, 'index.html'));});module.exports = app;bin/www #!/usr/bin/env node/** * Module dependencies. */let app = require('../app');let debug = require('debug')('mysocket:server');let http = require('http');let _ = require('underscore');/** * Get port from environment and store in Express. */let userLs = []let port = normalizePort(process.env.PORT || '3000');app.set('port', port);/** * Create HTTP server. */let server = http.createServer(app);/** * Listen on provided port, on all network interfaces. */server.listen(port);let io = require('socket.io')(server);io.on('connection', function(socket){ socket.on('login', function(userid){ let obj = { userid, id: socket.id } userLs.push(obj) console.log(userid + '建立了链接'); socket.emit('userLs',userLs) socket.emit('login',socket.id) }); // 判断用户离线事件可以通过socket.io自带的disconnect事件完成,当一个用户断开连接,disconnect事件就会触发 socket.on('disconnect', function(){ let _user = _.where(userLs,{id:socket.id}) console.log('_user',_user) console.log(socket.id + '中断了链接'); userLs = userLs.filter(it =>{ return _user.every(item =>{ return it.id != item.id }) }) // do somethings console.log('del',userLs) }); socket.on('message', function (data) { //服务端像所以也没发送数据 let _user = _.where(userLs,{id:socket.id}) io.sockets.emit('message', {id:_user[0].id,userid:_user[0].userid,message:data.message}); //给所有人(包括自己)发送消息 // socket.broadcast.emit('message', data.message); //给所有人(不包括自己)发送消息 }); socket.on('sayTo', function (data) { let toMsg = data.message; let toId = data.id; // nodejs的underscore扩展中的findWhere方法,可以在对象集合中,通过对象的属性值找到该对象并返回。 let _user = _.where(userLs,{id:toId}) if(_user){ let toSocket = _.findWhere(io.sockets.sockets, {id: toId}); // 通过该连接对象(toSocket)与链接到这个对象的客户端进行单独通信 socket.emit('message', {id:socket.id,message:toMsg}) //向建立该连接的客户端广播 toSocket.emit('message', {id:socket.id,message:toMsg}); }else{ socket.emit('error','该用户不在线') } // socket.emit() :向建立该连接的客户端广播 // socket.broadcast.emit() :向除去建立该连接的客户端的所有客户端广播 // io.sockets.emit() : 向所有客户端广播,等同于上面两个的和 });});server.on('error', onError);server.on('listening', onListening);/** * Normalize a port into a number, string, or false. */function normalizePort(val) { let port = parseInt(val, 10); if (isNaN(port)) { // named pipe return val; } if (port >= 0) { // port number return port; } return false;}/** * Event listener for HTTP server "error" event. */function onError(error) { if (error.syscall !== 'listen') { throw error; } let bind = typeof port === 'string' ? 'Pipe ' + port : 'Port ' + port; // handle specific listen errors with friendly messages switch (error.code) { case 'EACCES': console.error(bind + ' requires elevated privileges'); process.exit(1); break; case 'EADDRINUSE': console.error(bind + ' is already in use'); process.exit(1); break; default: throw error; }}/** * Event listener for HTTP server "listening" event. */function onListening() { let addr = server.address(); let bind = typeof addr === 'string' ? 'pipe ' + addr : 'port ' + addr.port; debug('Listening on ' + bind); console.log(`服务已启动,端口:${addr.port}`)}public/index.html ...

June 17, 2019 · 3 min · jiezi

基于-ThinkJS-的-WebSocket-通信详解

基于 ThinkJS 的 WebSocket 通信详解 前言我们的项目是基于 ThinkJS + Vue 开发的,最近实现了一个多端实时同步数据的功能,所以想写一篇文章来介绍下如何在 ThinkJS 的项目中利用 WebSocket 实现多端的实时通信。ThinkJS 是基于 Koa 2 开发的企业级 Node.js 服务端框架,文章中会从零开始实现一个简单的聊天室,希望读者们能有所收获。 WebSocketWebSocket 是 HTML5 中提出的一种协议。它的出现是为了解决客户端和服务端的实时通信问题。在 WebSocket 出现之前,如果想实现实时消息传递一般有两种方式: 客户端通过轮询不停的向服务端发送请求,如果有新消息客户端进行更新。这种方式的缺点很明显,客户端需要不停向服务器发送请求,然而 HTTP 请求可能包含较长的头部,其中真正有效的数据可能只是很小的一部分,显然这样会浪费很多带宽资源HTTP 长连接,客户端通过 HTTP 请求连接到服务端后, 底层的 TCP 连接不会马上断开,后续的信息还是可以通过同一个连接来传输。这种方式有一个问题是每个连接会占用服务端资源,在收到消息后连接断开,就需要重新发送请求。如此循环往复。可以看到,这两种实现方式的本质还是客户端向服务端“Pull”的过程,并没有一个服务端主动“Push”到客户端的方式,所有的方式都是依赖客户端先发起请求。为了满足两方的实时通信, WebSocket 应运而生。 WebSocket 协议首先,WebSocket 是基于 HTTP 协议的,或者说借用了 HTTP 协议来完成连接的握手部分。其次,WebSocket 是一个持久化协议,相对于 HTTP 这种非持久的协议来说,一个 HTTP 请求在收到服务端回复后会直接断开连接,下次获取消息需要重新发送 HTTP 请求,而 WebSocket 在连接成功后可以保持连接状态。下图应该能体现两者的关系: 在发起 WebSocket 请求时需要先通过 HTTP 请求告诉服务端需求将协议升级为 WebSocket。 浏览器先发送请求: GET / HTTP/1.1Host: localhost:8080Origin: [url=http://127.0.0.1:3000]http://127.0.0.1:3000[/url]Connection: UpgradeUpgrade: WebSocketSec-WebSocket-Version: 13Sec-WebSocket-Key: w4v7O6xFTi36lq3RNcgctw==服务端回应请求: ...

June 12, 2019 · 4 min · jiezi

WebSocket不再轮询

1.前言本文先讲解WebSocket的应用场景和特点,然后通过前后端示例代码讲解,展示在实际的开发中的应用。1.1. 应用场景WebSocket是一种在单个TCP连接上进行全双工通信的协议, 是为了满足基于 Web 的日益增长的实时通信需求而产生的。我们平时接触的大多数是HTTP的接口,但是在有些业务场景中满足不了我们的需求,这时候就需要用到WebSocket。简单举两个例子: (1) 页面地图上要实时显示在线人员坐标:传统基于HTTP接口的处理方式是轮询,每次轮询更新最新的坐标信息。 (2)手机的付款码页面,在外界设备扫描付款码支付成功后,手机付款码页面提示“支付成功”并自动关闭:传统方式还是轮询,付款码页面一直调用接口,直到从服务器获取成功支付的状态后,手机提示“支付成功”并关闭付款码页面。 HTTP 协议有一个缺陷:通信只能由客户端发起。这种单向请求的特点,注定了如果服务器有连续的状态变化,客户端要获知就非常麻烦。我们只能使用"轮询":每隔一段时候,就发出一个询问,了解服务器有没有新的信息。但这种方式即浪费带宽(HTTP HEAD 是比较大的),又消耗服务器 CPU 占用(没有信息也要接受请求)。 在WebSocket API尚未被众多浏览器实现和发布的时期,开发者在开发需要接收来自服务器的实时通知应用程序时,不得不求助于一些“hacks”来模拟实时连接以实现实时通信,最流行的一种方式是长轮询 。 长轮询主要是发出一个HTTP请求到服务器,然后保持连接打开以允许服务器在稍后的时间响应(由服务器确定)。为了这个连接有效地工作,许多技术需要被用于确保消息不错过,如需要在服务器端缓存和记录多个的连接信息(每个客户)。虽然长轮询是可以解决这一问题的,但它会耗费更多的资源,如CPU、内存和带宽等,要想很好的解决实时通信问题就需要设计和发布一种新的协议 1.2. WebSocket定义WebSocket是一种协议,是一种与HTTP 同等的网络协议,两者都是应用层协议,都基于 TCP 协议。但是 WebSocket 是一种双向通信协议,在建立连接之后,WebSocket 的 server 与 client 都能主动向对方发送或接收数据。同时,WebSocket在建立连接时需要借助 HTTP 协议,连接建立好了之后 client 与 server 之间的双向通信就与 HTTP 无关了。 相比于传统HTTP 的每次“请求-应答”都要client 与 server 建立连接的模式,WebSocket 是一种长连接的模式。就是一旦WebSocket 连接建立后,除非client 或者 server 中有一端主动断开连接,否则每次数据传输之前都不需要HTTP 那样请求数据。 WebSocket 对象提供了一组 API,用于创建和管理 WebSocket 连接,以及通过连接发送和接收数据。浏览器提供的WebSocket API很简洁,调用示例如下:HTTP、WebSocket 等应用层协议,都是基于 TCP 协议来传输数据的。我们可以把这些高级协议理解成对 TCP 的封装。既然大家都使用 TCP 协议,那么大家的连接和断开,都要遵循 TCP 协议中的三次握手和四次握手 ,只是在连接之后发送的内容不同,或者是断开的时间不同。对于 WebSocket 来说,它必须依赖 HTTP 协议进行一次握手 ,握手成功后,数据就直接从 TCP 通道传输,与 HTTP 无关了。 ...

June 10, 2019 · 1 min · jiezi

JAVA前后端分离开发环境搭建详细教程JeecgBoot快速开发平台

目录索引: 后端开发工具前端开发工具Nodejs镜像WebStorm入门配置 JeecgBoot采用前后端分离的架构,官方推荐开发工具 前端开发: Webstrom 或者 IDEA 后端开发: Eclipse安装lombok插件 或者 IDEA 开发工具下载:https://pan.baidu.com/s/1tZmFuViGz5IwHhzmA-FN6A 提取码:frya 后端开发工具序号工具参考1eclipse安装lombok插件https://blog.csdn.net/qq_2564...2Eclipse自定义皮肤主题https://blog.csdn.net/StillOn...3Eclipse常用快捷键https://blog.csdn.net/zhangda...前端开发工具序号工具描述参考1Nodejs/Npm安装JavaScript运行环境,此处使用到它的包管理器npmhttp://www.jianshu.com/p/03a7...2Yarn安装下载包工具https://yarnpkg.com/zh-Hans/d...3WebStorm安装与使用WEB前端开发工具https://blog.csdn.net/u011781...配置Nodejs镜像npm config set registry https://registry.npm.taobao.org --globalnpm config set disturl https://npm.taobao.org/dist --globalyarn config set registry https://registry.npm.taobao.org --globalyarn config set disturl https://npm.taobao.org/dist --globalWebStorm-2018.1.3 开发工具入门配置序号标题链接1WebStorm安装与使用https://blog.csdn.net/u011781...2webstorm 2018 激活破解https://blog.csdn.net/q358591...3修改webstorm主题https://blog.csdn.net/master_...4Webstorm切换快捷键风格(Webstorm快捷键与eclipse对比介绍)https://blog.csdn.net/gsying1...5WebStorm SVN用法https://blog.csdn.net/hysh_ke...6‘svn’不是内部或外部命令问题解决https://blog.csdn.net/mitea90...7设置webstorm的vue新建文件模板(后面篇章)https://blog.csdn.net/diligen...8WebStorm卡顿拉取svn慢解决https://blog.csdn.net/WYA1993...前端Webstorm开发界面:后端Eclipse开发界面:

June 8, 2019 · 1 min · jiezi

基于WebSocket的web端IM即时通讯应用的开发

基于WebSocket的web端IM即时通讯应用的开发功能列表:1、Web端的IM应用2、支持上线、下线、实时在线提醒3、单聊、群聊的建立4、普通文字、表情、图片的传输(子定义富文本)5、单人的顶级提醒,多对话的窗口的提醒6、调用图灵机器人的自动回复演示核心技术列表1、websocket、sockjs、stomp2、前端展示涉及的jquery、vue、elementUI、jquerybase64js3、后端springboot、jsoup、spring-security、spring-websocket成果展示:技术实现说明:Websocket部分 web端的IM应用,要想实现两个客户端的通信,必然要通过服务器进行信息的转发。例如A要和B通信,则应该是A先把信息发送给IM应用服务器,服务器根据A信息中携带的接收者将它再转发给B,同样B到A也是这种模式。而要实现web端的实时通讯,websocket也是其中最好的方式,其他的协议如长轮询、短轮询、iframe数据、htmlfile等。 在实际开发中,我们通常使用的是一些别人写好的实时通讯的库,比如socket.io、sockjs(我们本次使用了他,类似jquery,对其他即时通讯技术做了封装),他们的原理就是将上面(还有一些其他的如基于Flash的push)的一些技术进行了在客户端和服务端的封装,然后给开发者一个统一调用的接口。这个接口在支持websocket的环境下使用websocket,在不支持它的时候启用上面所讲的一些hack技术。 WebSocket是HTML5的一种新通信协议(ws协议),是一个消息架构,不强制使用任何特定的消息协议,它依赖于应用层解释消息的含义;与处在应用层的HTTP不同,WebSocket处在TCP上非常薄的一层,会将字节流转换为文本/二进制消息,因此,对于实际应用来说,WebSocket的通信形式层级过低,因此,可以在 WebSocket 之上使用 STOMP协议,来为浏览器 和 server间的 通信增加适当的消息语义。 STOMP(Simple Text-Orientated Messaging Protocol) 面向消息的简单文本协议。 同 HTTP 在 TCP 套接字上添加请求-响应模型层一样,STOMP 在 WebSocket 之上提供了一个基于帧的线路格式层,用来定义消息语义; STOMP 源码http://cdn.bootcss.com/stomp.js/2.3.3/stomp.js,有兴趣的可以看一下能大致了解其原理和用法。本例程序核心代码: <!--TO 创建socket连接 并订阅相关频道-->var socket = new SockJS('/im-websocket');stompClient = Stomp.over(socket);//设置stomp 控制台日志为不输出stompClient.debug=null;stompClient.connect({}, function (frame) { // 相当于连接 ws://localhost:8080/gs-guide-websocket/041/hk5tax0r/websocket hk5tax0r就是sessionid console.log("正在连接",socket._transport.url); //订阅通用私聊频道 群组也通过这里实现 stompClient.subscribe('/user/topic/private', function (greeting) { } ); //订阅用户上线下线的公共频道 stompClient.subscribe('/topic/userlist', function (greeting) { });},function errorCallBack (error) { // 连接失败时(服务器响应 ERROR 帧)的回调方法 });数据发送如下://第一个参数对应controller的 @MessageMapping注解 /app为后台定义的通用前缀//第三个参数为内容字符串stompClient.send("/app/private", {}, JSON.stringify(message));//发送服务器 ...

June 6, 2019 · 2 min · jiezi

nodejs-websocket-socketio

为什么需要 WebSocket?因为个人对概念理解不是很深,文字表达能力不强,如果有关HTTP等方面描述不准确,欢迎纠正,谢谢大家 初次接触 WebSocket 的人,都会问同样的问题:我们已经有了 HTTP 协议,为什么还需要另一个协议?它能带来什么好处?答案很简单,因为 HTTP 协议有一个缺陷:通信只能由客户端发起。 举例来说:我们想了解今天的天气,只能是客户端向服务器发出请求,服务器返回查询结果。HTTP 协议做不到服务器主动向客户端推送信息。 这种单向请求的特点,注定了如果服务器有连续的状态变化,客户端要获知就非常麻烦。我们只能使用"轮询":设置定时器每隔一段时候,就发出一个"询问"(简单理解为ajax请求),了解服务器有没有新的信息。最典型的场景就是聊天室。 ---- 参考了阮一峰老师的文章: WebSocket 教程 轮询请求的缺点:不停地链接,断开,链接,断开请求,浪费很多服务器资源浪费带宽移动端浪费流量websocket的优点:没有同源限制,客户端可以与任意服务器通信,不涉及到跨域的问题。双向通信,服务器可以向客户端主动发送数据。数据格式比较轻量,性能开销小,通信高效。websocket为什么高效普通的http通信是基于字符的通信(超文本), websocket一开始是文本协议, 但是链接建立后编程了二进制协议, 数据无需转换等。socket.io的使用socket.io是一个封装后的库,原生的 websocket 因为比较复杂,需要自己处理请求头,设置持续链接等等。因此选择使用的socket.io安装npm i socket.io -Dsocket主要有两个方法:sock.emit('name', data) 主动发送数据sock.on('name', function(data){ })` 接收数据 1.服务端 // server.js const http = require('http') const io = require('socket.io') let server = http.createServer((req, res)=>{}) server.listen(8080) // 建立ws websocket简称ws let wsServer = io.listen(server); wsServer.on('connection', sock=>{ sock.on('aaa', function(a,b){ // name -> 'aaa' 要与前台的 name 保持一致 console.log(a) console.log(b) console.log(arguments) }) // 'aaa'事件名与前台的一致 setInterval(function(){ sock.emit('bbb', '服务器发来的数据') // name -> 'bbb' 要与前台的 name 保持一致 }, 2000) })2. 客户端<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <script src='http://localhost:8080/socket.io/socket.io.js'></script> <!-- 其实你的本地目录并不存在 XXX/socket.io/socket.io.js 文件,当向后台发送请求后,socket会判断req.url,做一个类似于下面的处理,读取 socket-io.js: if(req.url == '/socket.io/socket.io.js'){ fs.readFile('node_modules/socket.io-client/dist/socket-io.js') } 当然我们也可以直接将socket-io.js复制出来,直接用script引用,但是如果socket更新后我们的代码可能不是最新版,会出现一些问题。 因此不建议这样使用:<script src='./socket.io.js'></script> --> <script> let sock = io.connect('ws://localhost:8080/') // 这里是 ws 协议,不是 http 协议 // sock.emit // sock.on sock.emit('aaa', 'maruihua', 5 ) sock.on('bbb', data => console.log(data)) </script></head><body></body></html>上面的代码直接复制下来就能使用。怎么运行nodejs服务我就不再讲了啊。怎么样,是不是特别简单妈妈再也不用担心我的学习了~ ...

May 29, 2019 · 1 min · jiezi

Go语言xtermjswebsocket-Web终端堡垒机

1.前言因为公司业务需要在自己的私有云服务器上添加添加WebSsh终端,同时提供输入命令审计功能. 从google上可以了解到xterm.js是一个非常出色的web终端库,包括VSCode很多成熟的产品都使用这个前端库.使用起来也比较简单. 难点是怎么把ssh命令行转换成websocket通讯,来提供Stdin,stdout输出到xterm.js中,接下来就详解技术细节. 全部代码都可以在我的Github.com/dejavuzhou/felix中可以查阅到. 2.知识储备linux下载stdin,stdou和stderr简单概念熟悉Golang官方库golang.org/x/crypto/ssh了解gorilla/websocket的基本用法gin-gonic/gin,当然你也可以使用其他的路由包替代,或者直接使用标准库(前端)websocket(前端)xterm.js3.数据逻辑图Golang堡垒机主要功能就是把SSH协议数据使用websocket协议转发给xterm.js浏览器. 堡垒机Golang服务UML 4.代码实现4.1创建gin Handler func注册gin路由 api.GET("ws/:id", internal.WsSsh) ssh2ws/internal/ws_ssh.go package internalimport ( "bytes" "github.com/dejavuzhou/felix/flx" "github.com/dejavuzhou/felix/models" "github.com/dejavuzhou/felix/utils" "github.com/gin-gonic/gin" "github.com/gorilla/websocket" "github.com/sirupsen/logrus" "net/http" "strconv" "time")var upGrader = websocket.Upgrader{ ReadBufferSize: 1024, WriteBufferSize: 1024 * 1024 * 10, CheckOrigin: func(r *http.Request) bool { return true },}// handle webSocket connection.// first,we establish a ssh connection to ssh server when a webSocket comes;// then we deliver ssh data via ssh connection between browser and ssh server.// That is, read webSocket data from browser (e.g. 'ls' command) and send data to ssh server via ssh connection;// the other hand, read returned ssh data from ssh server and write back to browser via webSocket API.func WsSsh(c *gin.Context) { v, ok := c.Get("user") if !ok { logrus.Error("jwt token can't find auth user") return } userM, ok := v.(*models.User) if !ok { logrus.Error("context user is not a models.User type obj") return } cols, err := strconv.Atoi(c.DefaultQuery("cols", "120")) if wshandleError(c, err) { return } rows, err := strconv.Atoi(c.DefaultQuery("rows", "32")) if wshandleError(c, err) { return } idx, err := parseParamID(c) if wshandleError(c, err) { return } mc, err := models.MachineFind(idx) if wshandleError(c, err) { return } client, err := flx.NewSshClient(mc) if wshandleError(c, err) { return } defer client.Close() startTime := time.Now() ssConn, err := utils.NewSshConn(cols, rows, client) if wshandleError(c, err) { return } defer ssConn.Close() // after configure, the WebSocket is ok. wsConn, err := upGrader.Upgrade(c.Writer, c.Request, nil) if wshandleError(c, err) { return } defer wsConn.Close() quitChan := make(chan bool, 3) var logBuff = new(bytes.Buffer) // most messages are ssh output, not webSocket input go ssConn.ReceiveWsMsg(wsConn, logBuff, quitChan) go ssConn.SendComboOutput(wsConn, quitChan) go ssConn.SessionWait(quitChan) <-quitChan //write logs xtermLog := models.TermLog{ EndTime: time.Now(), StartTime: startTime, UserId: userM.ID, Log: logBuff.String(), MachineId: idx, MachineName: mc.Name, MachineIp: mc.Ip, MachineHost: mc.Host, UserName: userM.Username, } err = xtermLog.Create() if wshandleError(c, err) { return } logrus.Info("websocket finished")}代码详解 ...

May 27, 2019 · 6 min · jiezi

Apache-httpclient的execute方法调试

因为工作需要,想研究一下execute执行的逻辑。 在这一行调用execute: response = getHttpClient().execute(get);getHttpClient的实现: private HttpClient getHttpClient() { if (this.m_httpClient == null) { this.m_httpClient = HttpClientBuilder.create().build(); } return this.m_httpClient; }我在代码里声明的HttpClient只是一个接口, 实现类是InternalHttpClient。 首先根据传入的请求决定出目标-target host 投递到RedirectExec执行。 后者又投递到RetryExec执行。 收到307重定向: redirectsEnabled标志位为true: 再看当前的请求确实被redirect了吗? original url: 我的后台服务器返回的307,落到了分支HttpStatus.SC_TEMPORARY_REDIRECT处: 看来Apache的库认为只有HEAD和GET才能被redirect: 重定向最大次数:50 准备重试了: 要获取更多Jerry的原创文章,请关注公众号"汪子熙":

May 25, 2019 · 1 min · jiezi

WebSocket实战在-Node-和-React-之间进行实时通信

翻译:疯狂的技术宅原文:https://blog.logrocket.com/we... 本文首发微信公众号:前端先锋欢迎关注,每天都给你推送新鲜的前端技术文章 Web 为了支持客户端和服务器之间的全双工(或双向)通信已经走过了很长的路。这是 WebSocket 协议的主要目的:通过单个 TCP 套接字连接在客户端和服务器之间提供持久的实时通信。 WebSocket 协议只有两个议程:1)打开握手,2)帮助数据传输。一旦服务器和客户端握手成功,他们就可以随意地以较少的开销相互发送数据。 WebSocket 通信使用WS(端口80)或WSS(端口443)协议在单个 TCP 套接字上进行。根据 Can I Use,撰写本文时除了 Opera Mini 之外几乎所有的浏览器支持 WebSockets 。 现状从历史上看,创建需要实时数据通讯(如游戏或聊天应用程序)的 Web 应用需要滥用 HTTP 协议来建立双向数据传输。尽管有许多种方法用于实现实时功能,但没有一种方法与 WebSockets 一样高效。 HTTP 轮询、HTTP流、Comet、SSE —— 它们都有自己的缺点。 HTTP 轮询解决问题的第一个尝试是定期轮询服务器。 HTTP 长轮询生命周期如下: 客户端发出请求并一直等待响应。服务器推迟响应,直到发生更改、更新或超时。请求保持“挂起”,直到服务器有东西返回客户端。当服务器端有一些更改或更新时,它会将响应发送回客户端。客户端发送新的长轮询请求以侦听下一组更改。长轮询中存在很多漏洞 —— 标头开销、延迟、超时、缓存等等。 HTTP 流式传输这种机制减少了网络延迟的痛苦,因为初始请求无限期地保持打开状态。即使在服务器推送数据之后,请求也永远不会终止。 HTTP 流中的前三步生命周期方法与 HTTP 轮询是相同的。 但是,当响应被发送回客户端时,请求永远不会终止,服务器保持连接打开状态,并在发生更改时发送新的更新。 服务器发送事件(SSE)使用 SSE,服务器将数据推送到客户端。聊天或游戏应用不能完全依赖 SSE。 SSE 的完美用例是类似 Facebook 的新闻 Feed:每当有新帖发布时,服务器会将它们推送到时间线。 SSE 通过传统 HTTP 发送,并且对打开的连接数有限制。 这些方法不仅效率低下,维护它们的代码也使开发人员感到厌倦。 WebSocketWebSockets 旨在取代现有的双向通信技术。当涉及全双工实时通信时,上述现有方法既不可靠也不高效。 WebSockets 类似于 SSE,但在将消息从客户端传回服务器方面也很优秀。由于数据是通过单个 TCP 套接字连接提供的,因此连接限制不再是问题。 ...

May 18, 2019 · 3 min · jiezi

Swoole-WebSocket-的应用

概述这是关于 Swoole 学习的第三篇文章:Swoole WebSocket 的应用。 第二篇:Swoole Task 的应用第一篇:Swoole Timer 的应用什么是 WebSocket ? WebSocket 是一种在单个TCP连接上进行全双工通信的协议。WebSocket 使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。 在 WebSocket API 中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。 我们利用 WebSocket 进行及时通讯,今天实现一个 视频弹幕效果。 实现弹幕其实就和群聊类似,将消息推送给所有的客户端,只不过前端的展示所有不同。 本地版本: 后端 PHP 7.2.6、Swoole 4.3.1。前端 HTML5 WebSocket、Canvas。废话不多说,先看效果。 批量版: 手动版: 代码server.php <?phpclass Server{ private $serv; public function __construct() { $this->serv = new swoole_websocket_server("0.0.0.0", 9501); $this->serv->set([ 'worker_num' => 2, //开启2个worker进程 'max_request' => 4, //每个worker进程 max_request设置为4次 'task_worker_num' => 4, //开启4个task进程 'dispatch_mode' => 4, //数据包分发策略 - IP分配 'daemonize' => false, //守护进程(true/false) ]); $this->serv->on('Start', [$this, 'onStart']); $this->serv->on('Open', [$this, 'onOpen']); $this->serv->on("Message", [$this, 'onMessage']); $this->serv->on("Close", [$this, 'onClose']); $this->serv->on("Task", [$this, 'onTask']); $this->serv->on("Finish", [$this, 'onFinish']); $this->serv->start(); } public function onStart($serv) { echo "#### onStart ####".PHP_EOL; echo "SWOOLE ".SWOOLE_VERSION . " 服务已启动".PHP_EOL; echo "master_pid: {$serv->master_pid}".PHP_EOL; echo "manager_pid: {$serv->manager_pid}".PHP_EOL; echo "########".PHP_EOL.PHP_EOL; } public function onOpen($serv, $request) { echo "#### onOpen ####".PHP_EOL; echo "server: handshake success with fd{$request->fd}".PHP_EOL; $serv->task([ 'type' => 'login' ]); echo "########".PHP_EOL.PHP_EOL; } public function onTask($serv, $task_id, $from_id, $data) { echo "#### onTask ####".PHP_EOL; echo "#{$serv->worker_id} onTask: [PID={$serv->worker_pid}]: task_id={$task_id}".PHP_EOL; $msg = ''; switch ($data['type']) { case 'login': $msg = '我来了...'; break; case 'speak': $msg = $data['msg']; break; } foreach ($serv->connections as $fd) { $connectionInfo = $serv->connection_info($fd); if ($connectionInfo['websocket_status'] == 3) { $serv->push($fd, $msg); //长度最大不得超过2M } } $serv->finish($data); echo "########".PHP_EOL.PHP_EOL; } public function onMessage($serv, $frame) { echo "#### onMessage ####".PHP_EOL; echo "receive from fd{$frame->fd}:{$frame->data},opcode:{$frame->opcode},fin:{$frame->finish}".PHP_EOL; $serv->task(['type' => 'speak', 'msg' => $frame->data]); echo "########".PHP_EOL.PHP_EOL; } public function onFinish($serv,$task_id, $data) { echo "#### onFinish ####".PHP_EOL; echo "Task {$task_id} 已完成".PHP_EOL; echo "########".PHP_EOL.PHP_EOL; } public function onClose($serv, $fd) { echo "#### onClose ####".PHP_EOL; echo "client {$fd} closed".PHP_EOL; echo "########".PHP_EOL.PHP_EOL; }}$server = new Server();index.php ...

May 15, 2019 · 3 min · jiezi

websocket-浅析

概述WebSocket 是 HTML5 开始提供的一种在单个 TCP 连接上进行全双工通讯的协议。在 WebSocket API 中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。 在JS中创建WebSocket后,会有一个HTTP请求发向浏览器以发起请求。在取得服务器响应后,建立的连接会使用HTTP升级将HTTP协议转换为WebSocket协议。 ajax轮询和 websocket 连接示意图 握手连接WebSocket是应用层协议,是TCP/IP协议的子集,通过HTTP/1.1协议的101状态码进行握手。WebSocket协议的建立需要先借助HTTP协议,在服务器返回101状态码之后,就可以进行websocket全双工双向通信了。 web端的请求头部: 1. 首先浏览器发送http 的get 请求 客户端端口 60992 服务端端口 8181 2. 紧接着服务端返回 101 字段说明Connection必须设置Upgrade。Upgrade字段必须设置Websocket。Sec-WebSocket-Key是随机的字符串,服务器端会用这些数据来构造出一个SHA-1的信息摘要。把“Sec-WebSocket-Key”加上一个特殊字符串“258EAFA5-E914-47DA-95CA-C5AB0DC85B11”,然后计算SHA-1摘要,之后进行BASE-64编码,将结果做为“Sec-WebSocket-Accept”头的值,返回给客户端。如此操作,可以尽量避免普通HTTP请求被误认为Websocket协议。Sec-WebSocket-Version 表示支持的Websocket版本。RFC6455要求使用的版本是13,之前草案的版本均应当弃用。Origin字段是可选的,通常用来表示在浏览器中发起此Websocket连接所在的页面,类似于Referer。但是,与Referer不同的是,Origin只包含了协议和主机名称。其他一些定义在HTTP协议中的字段,如Cookie等,也可以在Websocket中使用。传输消息客户端向服务端发送消息 nick 999 客户端代码:简单的创建websocket 连接,连接成功后发送一条消息, 内容为 nick 999 var nick = getQueryString('nick')||'江芊' var url = 'ws://localhost:8181/' var Socket = new WebSocket(url); Socket.onopen = function(evt) { console.log('open', evt) Socket.send('nick ' + nick) } Socket.onclose = function(evt){ console.log('close', evt) } Socket.onmessage = function(evt){ console.log('message', evt) var data = JSON.parse(evt.data) console.log(data) updataMsg(data) } Socket.onerror = function(evt){ console.log('error', evt) } ...

May 10, 2019 · 2 min · jiezi

reactreactrouterreduxNodejssocketio写一个聊天webapp

一、项目预览之前看一个写聊天器的教程,自己也跟着教程做了一遍,由于懒得去找图片和一些图标我就用教程中的素材来做,主要是用了react+react-router+redux+Node.js+socket.io的技术栈,接下来就是项目的预览 1.首先在/login下能看到有登录和注册按钮 2.点击注册按钮,路由跳到/register,注册一个账号,用户和密码都为LHH,选择“牛人”,点击注册,之后路由会跳到/geniusinfo,即牛人完善信息页,选择一个头像并完善信息后点击保存按钮 3.可以看到已经进入有三个tab选项的内容页面了,点击“我”,路由跳转到/me即可看到个人中心内容,但此时boss和消息的tab页仍没有内容,可以按照之前步骤注册一个Boss账号,只需在注册的时候选择Boss选项 4.现在在LHH和LCE账号分别能看到的列表 5.点击进入聊天室,输入内容 二、接下来对项目的主要内容进行解释1.项目的除掉node_modules后的目录├─build│ └─static│ ├─css│ └─js├─config│ └─jest├─public├─scripts├─server└─src ├─component │ ├─authroute │ ├─avatar-selector │ ├─boss │ ├─chat │ ├─dashboard │ ├─genius │ ├─img │ ├─logo │ ├─msg │ ├─navlink │ │ └─img │ ├─user │ └─usercard ├─container │ ├─bossinfo │ ├─geniusinfo │ ├─login │ └─register └─redux其中build文件夹的内容为npm run build打包后的内容,在项目中如果启用后端接口也可访问 2.入口页面import React from 'react';import ReactDOM from 'react-dom';import { createStore, applyMiddleware, compose } from 'redux';import thunk from 'redux-thunk';import { Provider } from 'react-redux';// eslint-disable-next-lineimport { BrowserRouter } from 'react-router-dom';import App from './app'import reducers from './reducer'import './config'import './index.css'const store = createStore(reducers, compose( applyMiddleware(thunk), window.devToolsExtension?window.devToolsExtension():f=>f))// boss genius me msg 4个页面ReactDOM.render( (<Provider store={store}> <BrowserRouter> <App></App> </BrowserRouter> </Provider> ), document.getElementById('root') )使用react-redux的Provider,可实现全局的状态存储,子组件可通过props获得存储在全局的状态 ...

May 2, 2019 · 4 min · jiezi

Spring-Cloud-Zuul-1x的websocket支持实践

一、组件Spring Cloud Netflix Edgware.SR3spring-cloud-starter-zuul 1.3.5.RELEASEnginx 1.14.0Vue + SockJS + STOMP 二、实现• 下游服务添加websocket支持,包括依赖、配置和使用• zuul添加spring-cloud-netflix-zuul-websocket依赖• zuul配置文件添加如下配置:下游使用websocket的服务和对应的websocket设置zuul: ws: brokerages: 下游使用websocket的服务名: end-points: /endpoint brokers: /brokers destination-prefixes:• zuul添加websocket配置类@Configurationpublic class WebsokcetConfig { @Bean public ZuulPropertiesResolver zuulPropertiesResolver() { return wsBrokerage -> discoveryClient.getInstances(wsBrokerage.getId()).stream().findAny() .orElseGet(() -> new DefaultServiceInstance("", "", 0, false)) .getUri().toString(); } @Bean public WebSocketHttpHeadersCallback webSocketHttpHeadersCallback() { return userAgentSession -> new WebSocketHttpHeaders() {{ add("Authorization", new CustomBasicAuthRequestInterceptor(username, password).encodeBase64()); }}; }}• 前端使用sockJs和stomp连接网关的websocketfunction connect() { var socket = new SockJS('http://zuul的地址/endpoint', null, { 'transports': ['websocket'] }); stompClient = Stomp.over(socket); stompClient.connect({}, function (frame) { setConnected(true); console.log('Connected: ' + frame); // 订阅用户消息通知 stompClient.subscribe("/brokers",handleNotification); }); function handleNotification(message) { showGreeting(message); }}• nginx添加对websocket的支持 location /gateway { //添加如下配置 proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_set_header Host $host:$server_port; }

April 30, 2019 · 1 min · jiezi

java实现socketio客户端功能

基于java做一个socket.io客户端前言最近公司这边让我去订阅一个第三方机构的websocket server,也是头疼,免不了和对方各种沟通,大家都很忙,收到回复很慢,开发方向也不知道。先是做了一个普通websocket的客户端,但后面了解到对方是基于socket.io做的一个server,又重新做了一个基于socket.io-client-java开源库的客户端。涉及到公司商业机密,所以做了一个demo,转自https://blog.csdn.net/q56231293811/article/details/84873776<!--more--> 1. Client(Socket.IO Client Library for Java)先上代码 package com.dasnnj.practice.share.socket;import io.socket.client.IO;import io.socket.client.Socket;import java.util.Arrays;/** * Description <P> TODO : socket.io client端 <P> * * @author DASNNJ <a href="mailto:dasnnj@outlook.com"> dasnnj@outlook.com </a> * @date 2019-04-27 18:32 */public class Client { public static void main(String[] args) { String url = "http://localhost:9999"; try { IO.Options options = new IO.Options(); options.transports = new String[]{"websocket"}; //失败重试次数 options.reconnectionAttempts = 10; //失败重连的时间间隔 options.reconnectionDelay = 1000; //连接超时时间(ms) options.timeout = 500; final Socket socket = IO.socket(url, options); //监听自定义msg事件 socket.on("msg", objects -> System.out.println("client: 收到msg->" + Arrays.toString(objects))); //监听自定义订阅事件 socket.on("sub", objects -> System.out.println("client: " + "订阅成功,收到反馈->" + Arrays.toString(objects))); socket.on(Socket.EVENT_CONNECT, objects -> { socket.emit("sub", "我是訂閲對象"); System.out.println("client: " + "连接成功"); }); socket.on(Socket.EVENT_CONNECTING, objects -> System.out.println("client: " + "连接中")); socket.on(Socket.EVENT_CONNECT_TIMEOUT, objects -> System.out.println("client: " + "连接超时")); socket.on(Socket.EVENT_CONNECT_ERROR, objects -> System.out.println("client: " + "连接失败")); socket.connect(); } catch (Exception ex) { ex.printStackTrace(); } }} 流程:启动client会创建scoket,并将uri,options等参数set进去监听一些事件(可自定义),也就是将event为key,回调为value,put 进callbacks(其为ConcurrentMap) ...

April 28, 2019 · 2 min · jiezi

八问WebSocket协议为你快速解答WebSocket热门疑问

一、引言WebSocket是一种比较新的协议,它是伴随着html5规范而生的,虽然还比较年轻,但大多主流浏览器都已经支持。它使用方面、应用广泛,已经渗透到前后端开发的各种场景中。 对http一问一答中二式流程(就是从所周之的“长轮询”技要啦)的不满,催生了支持双向通信的WebSocket诞生。WebSocket是个不太干净协议。 本文将从8个常见的疑问入手,为还不了解WebSocket协议的开发者快速普及相关知识,从而节省您学习WebSocket的时间。 另外,如果您对Web端的即时通讯技术还完全不了解,那么《新手入门贴:详解Web端即时通讯技术的原理》、《Web端即时通讯技术盘点:短轮询、Comet、Websocket、SSE》这两篇文章请您务必抽时间读一读。 学习交流: 即时通讯/推送技术开发交流4群:101279154[推荐]移动端IM开发入门文章:《新手入门一篇就够:从零开发移动端IM》(本文同步发布于:http://www.52im.net/thread-24...) 二、参考文章《WebSocket详解(一):初步认识WebSocket技术》 《WebSocket详解(二):技术原理、代码演示和应用案例》 《WebSocket详解(三):深入WebSocket通信协议细节》 《WebSocket详解(四):刨根问底HTTP与WebSocket的关系(上篇)》 《WebSocket详解(五):刨根问底HTTP与WebSocket的关系(下篇)》 《WebSocket详解(六):刨根问底WebSocket与Socket的关系》 《理论联系实际:从零理解WebSocket的通信原理、协议格式、安全性》 三、更多资料Web端即时通讯新手入门贴: 《新手入门贴:详解Web端即时通讯技术的原理》 Web端即时通讯技术盘点请参见: 《Web端即时通讯技术盘点:短轮询、Comet、Websocket、SSE》 关于Ajax短轮询: 找这方面的资料没什么意义,除非忽悠客户,否则请考虑其它3种方案即可。 有关Comet技术的详细介绍请参见: 《Comet技术详解:基于HTTP长连接的Web端实时通信技术》 《WEB端即时通讯:HTTP长连接、长轮询(long polling)详解》 《WEB端即时通讯:不用WebSocket也一样能搞定消息的即时性》 《开源Comet服务器iComet:支持百万并发的Web端即时通讯方案》 更多WebSocket的详细介绍请参见: 《新手快速入门:WebSocket简明教程》 《Socket.IO介绍:支持WebSocket、用于WEB端的即时通讯的框架》 《socket.io和websocket 之间是什么关系?有什么区别?》 有关SSE的详细介绍文章请参见: 《SSE技术详解:一种全新的HTML5服务器推送事件技术》 更多WEB端即时通讯文章请见: http://www.52im.net/forum.php... 四、1问WebSocket:WebSocket协议只能浏览器发起么?不是。目前此协议的受众的也不仅仅是web开发者。 WebSocket只是一种协议,它和http协议一样,使用类似okhttp的组件,可以在任何地方进行调用,甚至可以借助WebSocket实现RPC框架。 五、2问WebSocket:WebSocket和HTTP什么关系?WebSocket和http一样,都是处于OSI模型中的最高层:应用层。 WebSocket借助http协议进行握手,握手成功后,就会变身为TCP通道,从此与http不再相见。 使用netstat或者ss,能够看到对应的连接,它与处于抽象层的socket,在外观上没有区别。 更多WebSocket和HTTP的关系,以及与Socket的区别,可进一步阅读以下文章: 《WebSocket详解(四):刨根问底HTTP与WebSocket的关系(上篇)》 《WebSocket详解(五):刨根问底HTTP与WebSocket的关系(下篇)》 《WebSocket详解(六):刨根问底WebSocket与Socket的关系》 六、3问WebSocket:WebSocket和长轮询有什么区别?长轮询,就是客户端发送一个请求,服务端将一直在这个连接上等待(当然有一个超长的超时时间),直到有数据才返回,它依然是一个一问一答的模式。比如著名的comted。 WebSocket在握手成功后,就是全双工的TCP通道,数据可以主动从服务端发送到客户端,处于链接两端的应用没有任何区别。 WebSocket创建的连接和Http的长连接是不一样的。由于Http长连接底层依然是Http协议,所以它还是一问一答,只是Hold住了一条命长点的连接而已。 长轮询和Http长连接是阻塞的I/O,但WebSocket可以是非阻塞的(具体是多路复用)。 这方面更深入的资料,请进一步学习: 《Comet技术详解:基于HTTP长连接的Web端实时通信技术》 《WEB端即时通讯:HTTP长连接、长轮询(long polling)详解》 七、4问WebSocket:如何创建一个WebSocket连接?WebSocket的连接创建是借助Http协议进行的。这样设计主要是考虑兼容性,在浏览器中就可以很方便的发起请求,看起来比较具有迷惑性。 下图是一个典型的由浏览器发起的ws请求,可以看到和http请求长的是非常相似的。 但是,它只是请求阶段长得像而已: 请求的地址,一般是:ws://***,或者是使用了SSL/TLS加密的安全协议wss:,用来标识是WebSocket请求。 1)首先,通过Http头里面的Upgrade域,请求进行协议转换。如果服务端支持的话,就可以切换到WebSocket协议。简单点讲:连接已经在那了,通过握手切换成ws协议,就是切换了连接的一个状态而已。 2)Connection域可以认为是与Upgrade域配对的头信息。像nginx等代理服务器,是要先处理Connection,然后再发起协议转换的。 3)Sec-WebSocket-Key 是随机的字符串,服务器端会用这些数据来构造出一个 SHA-1 的信息摘要。如此操作,可以尽量避免普通 HTTP 请求被误认为 WebSocket 协议。 其他的,像Sec-WebSocket*字样的头信息,表明了客户端支持的子协议以及其他信息。像loT中很流行的mqtt,就可以作为WebSocket的子协议。 ...

April 25, 2019 · 1 min · jiezi

happyChat开发系列:使用websocket.io实现双向通信的乐聊大前端开发

一、前言乐聊是一个自己用websocket写一个完整的应用,虽然功能比较欠缺,但是实现了基本的文字聊天,以及群聊,私聊,机器人聊天等功能。因为这个自己做了PC端,无线端(手机端),以及使用cordova打包成一个android的apk。实现了一个大前端的项目,虽然现在android端还是有点有问题,在修改bug。二、websocket的原理介绍1、为什么需要websocket?因为 HTTP 协议有一个缺陷:通信只能由客户端发起。举例来说,我们想了解今天的天气,只能是客户端向服务器发出请求,服务器返回查询结果。HTTP 协议做不到服务器主动向客户端推送信息。这种单向请求的特点,注定了如果服务器有连续的状态变化,客户端要获知就非常麻烦。我们只能使用"轮询":每隔一段时候,就发出一个询问,了解服务器有没有新的信息。最典型的场景就是聊天室。轮询的效率低,非常浪费资源(因为必须不停连接,或者 HTTP 连接始终打开)。2、简介websocket特点:服务器可以主动向客户端推送信息,客户端也可以主动向服务器发送信息,是真正的双向平等对话,属于服务器推送技术的一种。其他特点包括:(1)建立在 TCP 协议之上,服务器端的实现比较容易。(2)与 HTTP 协议有着良好的兼容性。默认端口也是80和443,并且握手阶段采用 HTTP 协议,因此握手时不容易屏蔽,能通过各种 HTTP 代理服务器。(3)数据格式比较轻量,性能开销小,通信高效。(4)可以发送文本,也可以发送二进制数据。(5)没有同源限制,客户端可以与任意服务器通信。(6)协议标识符是ws(如果加密,则为wss),服务器网址就是 URL。三、websocket.io使用1、前端在vue项目中,在index.html中 <script src=“https://cdnjs.cloudflare.com/ajax/libs/socket.io/2.2.0/socket.io.js"></script><script> // const socketWeb = io(‘http://localhost:3000’); const socketWeb = io(‘http://chat.chengxinsong.cn’); const userInfo = JSON.parse(sessionStorage.getItem(“HappyChatUserInfo”)) if (userInfo) { socketWeb.emit(‘update’, userInfo.user_id); }</script>2、后端后端是使用koa作为后端const app = new Koa();const server = require(“http”).createServer(app.callback());const io = require(“socket.io”)(server);io.on(“connection”, socket => { const socketId = socket.id; /登录/ socket.on(“login”, async userId => { await socketModel.saveUserSocketId(userId, socketId); }); // 更新soketId socket.on(“update”, async userId => { await socketModel.saveUserSocketId(userId, socketId); }); //私聊 socket.on(“sendPrivateMsg”, async data => { const arr = await socketModel.getUserSocketId(data.to_user); const RowDataPacket = arr[0]; const socketid = JSON.parse(JSON.stringify(RowDataPacket)).socketid; io.to(socketid).emit(“getPrivateMsg”, data); }); // 群聊 socket.on(“sendGroupMsg”, async data => { io.sockets.emit(“getGroupMsg”, data); }); //加好友请求 socket.on(“sendRequest”, async data => { console.log(“sendRequest”, data); const arr = await socketModel.getUserSocketId(data.to_user); const RowDataPacket = arr[0]; const socketid = JSON.parse(JSON.stringify(RowDataPacket)).socketid; console.log(‘给谁的socketid’,socketid) io.to(socketid).emit(“getresponse”, data); }); socket.on(“disconnect”, data => { console.log(“disconnect”, data); });});四、简介和功能乐聊,一个快乐聊天的应用,支持PC端和无线端和安卓APP。(1)PC端和无线端线上地址:http://chat.chengxinsong.cn(2)下载安卓APP地址:暂时还有点小问题待解决,后边放出地址版本v 1.0.0- 1、支持注册用户和邮件激活用户;- 2、支持登陆- 3、支持机器人聊天;- 4、支持加好友,一对一聊天;- 5、支持创建群,加群,一对多聊天;- 6、支持删除好友,退出群- 7、支持个人信息编辑- 8、支持添加好友备注- 9、支持聊天中文字发送- 10、支持浏览器:Chrome,Firefox,Safari,IE9及以上; 版本v 1.1.01、支持聊天中图片发送2、支持聊天中表情发送待续五、运行截图等等。。。六、前后端源码前端代码:https://github.com/saucxs/hap…后端代码:https://github.com/saucxs/hap…七、最后欢迎fork和star,有问题提issue ...

April 16, 2019 · 1 min · jiezi

WebSocket

1、websocket 是什么?解决客户端与服务端实时通信而产生的技术。先通过HTTP/HTTPS协议进行握手后创建一个用于交换数据的TCP连接,然后服务端与客户端通过此TCP连接进行实时通信。服务器和客户端可以在给定的时间范围内的任意时刻,相互推送信息。以前我们实现推送技术,用的都是轮询,在特定时间间隔由浏览器自动发出请求,主动拉取服务器消息。需要不断的向服务器发送请求,会占用大量的带宽和服务器资源。2、使用websocket在支持WebSocket的浏览器中,在创建socket之后。可以通过onopen,onmessage,onclose、onerror四个事件实现对socket进行响应只读属性 readyState 表示连接状态,可以是以下值: 0 - 表示连接尚未建立。 1 - 表示连接已建立,可以进行通信。 2 - 表示连接正在进行关闭。 3 - 表示连接已经关闭或者连接不能打开。 // window.WebSocket “WebSocket” in window 检测浏览器是否支持 WebSocketvar ws = new WebSocket(“ws://localhost:8080”);// 申请一个WebSocket对象,参数是需要连接的服务器端的地址// 同http协议使用http://开头一样,WebSocket协议的URL使用ws://开头// 另外安全的WebSocket协议使用wss://开头。// 指定连接成功后的回调函数ws.onopen = function() { console.log(“open”); ws.send(“hello”); // 用于向服务器发送数据};// 或者ws.addEventListener(‘open’, function (event) { ws.send(‘Hello Server!’);});// 指定收到服务器数据后的回调函数ws.onmessage = function(evt) { console.log(evt.data)};// 指定连接关闭后的回调函数ws.onclose = function(evt) { console.log(“WebSocketClosed!”);};// 指定报错时的回调函数ws.onerror = function(evt) { console.log(“WebSocketError!”);};// 关闭websocketwebsocket.close();

April 8, 2019 · 1 min · jiezi

使用koa和socket.io简单搭建多人聊天流程

koa与socket.io简单流程分析:1. 服务端触发初始化io.on(‘connection’, socket => {});2. 客户端发送say会话socket.emit(‘say’, ‘我是客户端’); 3. 服务端监听say会话socket.on(‘say’,data => {}); 4. 服务端发送message会话socket.emit(‘message’, {hello: ‘你是谁’});5. 客户端接收message消息socket.on(‘message’, (data) => {});服务端:const koa = require(‘koa’);const app = new koa();const server = require(‘http’).Server(app.callback())const io = require(‘socket.io’)(server);const port = 8081;server.listen(process.env.PORT || port, () => { console.log(app run at : http://127.0.0.1:${port});})io.on(‘connection’, socket => { console.log(‘socket初始化完成’); socket.on(‘say’, data => { console.log(data, ‘接收到的信息’) socket.emit(‘message’, {hello: ‘你是谁’}) })})客户端<!DOCTYPE html><html lang=“en”><head> <meta charset=“UTF-8”> <title>Document</title> <style> input { background-color: #fff; background-image: none; border-radius: 4px; border: 1px solid #bfcbd9; box-sizing: border-box; color: #1f2d3d; font-size: inherit; height: 40px; line-height: 1; outline: 0; padding: 3px 10px; } .el-button–primary { color: #fff; background-color: #20a0ff; border-color: #20a0ff; } .el-button { display: inline-block; line-height: 1; white-space: nowrap; cursor: pointer; background: #00aac5; border: 1px solid #c4c4c4; color: #fff; margin: 0; padding: 10px 15px; border-radius: 4px; outline: 0; text-align: center; } </style></head><body> <div> <div id=“content”> </div> </div> <div> <input type=“text” id=“input”> <button class=“el-button el-button–primary el-button–large” type=“button” onclick=“say()"><span>发送消息</span></button> </div> <script src=”./socket.io.js"></script> <script> // 建立连接 var socket = io.connect(‘http://localhost:8081’); // 监听 message 会话 socket.on(‘message’, function (data) { let html = document.createElement(‘p’) html.innerHTML = 系统消息:&lt;span&gt;${data.hello}&lt;/span&gt; document.getElementById(‘content’).appendChild(html) console.log(data); }); // 按钮点击事件 function say() { let t = document.getElementById(‘input’).value if (!t) return let html = document.createElement(‘p’) html.innerHTML = 你细声说:&lt;span&gt;${t}&lt;/span&gt; document.getElementById(‘content’).appendChild(html) socket.emit(‘say’, { my: t}); } // 监听 news 会话 socket.on(’news’, function (data) { console.log(data); let html = document.createElement(‘p’) html.innerHTML = 小皮咖说:&lt;span&gt;我知道了,你说“${data.hello}”&lt;/span&gt; document.getElementById(‘content’).appendChild(html) }); // 监听吃饭会话 socket.on(’eating’, function (data) { console.log(data); let html = document.createElement(‘p’) html.innerHTML = 小皮咖说:${data.hello} document.getElementById(‘content’).appendChild(html) }); </script></body></html> ...

April 5, 2019 · 2 min · jiezi

webSocket 二进制传输基础准备-UTF16 转UTF8

前言今天学习一下编码,先回顾一下昨天的基础准备工作。下面进行了解UTF-8与UTF-16的二进制编码方式。为啥要了解这个,因为js中所有是string类型都是使用UTF-16编码的因此我们与后端进行通信时,需要转换成与之一致的编码。(后端或者前端转换)UTF-8编码方式注: 1. Unicode码范围 用十六进制表示 3. 8位二进制为一字节Unicode码范围UTF-8编码方式占用字节U+0000 ~ U+007F0xxxxxxx1U+0080 ~ U+ 07FF110xxxxx 10xxxxxx2U+0800 ~ U+FFFF1110xxxx 10xxxxxx 10xxxxxx3U+10000 ~ U+10FFFF11110xxx 10xxxxxxx 10xxxxxx 10xxxxxx4Unicode码转换UTF-8Unicode编码表使用,转换到UTF-8编码在Unicode中汉字 “一”编码为U+4E00,“丁"编码为 U+4E01这样想必就看得懂表了下面进行开始转换吧回顾昨日的二进制与十六进制U+4E00用十六进制表示 0x4E00转换二进制,按位转换4 = 0011E = 14 = 11100 = 00000 = 00000x4E00 = 0100 1110 0000 0000 = 199680x0800< 0x4E00 < 0xFFFF 得出是三个字节。UTF-8三字节的编码方式从 0100 1110 0000 0000 变成 1110 xxxx 10 xxxxxx 10 xxxxxx格式由从末位到首位进行顺位插入的方式 0100 111000 0000001110 xxxx 10 xxxxxx 10 xxxxxx1110 0100 10 111000 10 000000其中利用js的按位操作符 符号操作符进行转换先替换原码(从末位到首位)的第7位8位第一字节utf-8 3字节中的第一字节格式 为 10 xxxxxx所有截取6位(原码与编码对应的 为x的位值,要保持不变, 编码其中的x位值全部为原码)先利用按位与的特性(全1为1 否则为0)进行截取原码的末6位二进制 111111 = 63十进制0100 1110 0000 0000 & 0000 0000 0011 1111 = 00000019968 & 63 = 000000UTF-8的第一字节格式为 10 xxxxxx所以利用按位或的特性(遇1为1,全0 为0)来变换UTF-8 3字节中的第一个字节的编码方式将x替代为0 得出 10 000000二进制 10 000000 = 128;按位或00 000000 | 10 000000 = 10 0000000 | 128 = 128第二个字节UTF-8 3字节中的第二字节依然是10 xxxxxx格式,所以只需要从第6位开始进行截取6位利用带符号右移操作符 a >> b 首位开始补 b 个 首位值 右侧舍去b个位先舍去末6位0100 1110 0000 0000 >> 6 = 000 000 0100 1110 0019968 >> 6 = 312利用按位与进行截取000 000 0100 1110 00 & 111111 = 111 000312 & 63 = 56继续利用按位或进行变换00 111000 | 10 000000 = 10 11100056 | 128 = 184第三字节utf-8 3字节的第三字节编码方式为 1110 xxxx 所以只需要从第12开始截取4位从末位第12位开始截取4位,利用带符号右移操作符 a >> b 首位开始补 b 个 首位值 右侧舍去b个位0100 1110 0000 0000 >> 12 = 000 000 0000 010019968 >> 12 = 4利用按位与进行截取 4位二进制 1111 = 150100 & 1111 = 01004 & 15 = 4;利用按位或进行变换二进制 1110 0000 = 2240000 0100 | 1110 0000 = 1110 01004 | 224 = 228三个字节组合起来228 184 128 = 1110 0100 1011 1000 1000 0000(二进制) = 14 4 11 8 8 0( 四位转一位十进制) =0xe4b880(十六进制)utf-16转utf-80x4E00 = 0xe4b880python编码转换b’\xe4\xb8\x80’.decode(‘utf-8’) = “一” ...

April 3, 2019 · 2 min · jiezi

webscoket 二进制数据传输基础准备工作

二进制与十六进制二进制用 0 1 表示 2= 10十六进制 前缀0x 用0123456789ABCDEF表示 2= 0x2二进制与十六进制的转换十六进制的每位 等于二进制的四位十六进制 0xF = 15(十进制) = 1111(二进制) 十六进制每位最大就是二进制的 1111 = 15(十进制)二进制2 = 10十六进制2 = 0x2 = 0010规律很清晰了吧20 = 10100(6位 口算得出要补齐8位)0001 01000001 = 10100 = 420 = 0001 0100 = 0x14二进制字节一个字节是8个二进制位0000 0000 - 1111 1111 = 0-255 代表一个字节可以表示256个符号UTF编码字节UTF-16UTF-16大部分使用两个字节编码,编码超出 65535 的使用四个字节0x0000 - 0xFFFF 两个字节0x010000 - 0x10FFFF 四个字节UTF-80x0000 - 0x007F 一个字节0x0080 - 0x07FF 两个字节0x0800 - 0xD7FF 、0xE000 - 0xFFFF 三个字节0x010000 - 0x10FFFF 四个字节注: Unicode在范围 D800-DFFF 中不存在任何字符有符号32位整数节所有的按位操作符的操作数都会被转成补码(two’s complement)形式的有符号32位整数。反码、补码 负数js中的二进制转换是不会转换符号的1的反码 补码 以及 -11 = 0000 0000 0000 0000 0000 0000 0000 0001反码 1 =0、0 =1 下面会讲述按位操作符 按位非 ~ 其实就是反码1111 1111 1111 1111 1111 1111 1111 1110补码后 末位+11111 1111 1111 1111 1111 1111 1111 1111 = -1按位操作符按位操作符(Bitwise operators) 将其操作数(operands)当作32位的比特序列(由0和1组成),而不是十进制、十六进制或八进制数值。例如,十进制数9,用二进制表示则为1001。按位操作符操作数字的二进制形式,但是返回值依然是标准的JavaScript数值。&、| 在转码中比较常用按位与 &1为真,0为假 全真则真 遇假为假js 内置方法var a = 1;a.toString(2);十转二2 = 103 = 11二进制进行按位与运算 从左到右1 && 1 = 10 && 1 = 02 & 3 = 10 = 2按位或 |1为真,0为假 遇真则真 全假为假8 = 10009 = 1001二进制按位或运算 从左到右1 || 1 = 10 || 0 = 00 || 0 = 00 || 1 = 1 8 | 9 = 1001 = 9按位异或 ^1为真,0为假。不同为真 相同为假4 = 1005 = 101二进制按位异或运算 从左到右1 1 = 00 0 = 00 1 = 14 ^ 5 = 001 = 1按位非 ~1为真,0为假 对每一项进行非操作,遇真则假,遇假则真。(速算, ~x =-1*x-1)6 = 00000000 00000000 00000000 00000110 11111111 11111111 11111111 11111001 = -7按位移动操作符有两个操作数 左侧为要被移动的数字(十进制,但是操作是内部对二进制操作)右侧为移动的长度方向根据操作符移动,返回根据被操作数相同类型的结果,必须小于32位左移操作符 <<a << b 右侧末位开始补 b个0 左侧舍去b个位11 = 00000000 00000000 00000000 0000101111 << 2 右侧末位开始补 b个000000000 00000000 00000000 00001011 00 左侧舍去b个位000000 00000000 00000000 00001011 00格式化00000000 00000000 00000000 00101100 = 44-44 = (反码)11111111 11111111 11111111 11010011(补码)11111111 11111111 11111111 11010100-11 =(反码)11111111 11111111 11111111 11110100 (补码)11111111 11111111 11111111 11110101-11 << 2右侧末位开始补 b个0 11111111 11111111 11111111 11110101 00 左侧舍去b个位111111 11111111 11111111 11110101 00格式化11111111 11111111 11111111 11010100 = -44带符号右移操作符( 记住左侧首位 0 代表正 ,1 代表负) >>a >> b 左侧首位开始补 b 个 首位值 右侧侧舍去b个位12 = 00000000 00000000 00000000 0000110012 >> 2补 b 个 首位值 00 00000000 00000000 00000000 00001100右侧舍去b个位00 00000000 00000000 00000000 000011格式化00000000 00000000 00000000 00000011 = 3-3 = (反码)11111111 11111111 11111111 11111100(补码)11111111 11111111 11111111 11111101-12 >> 2-12 = (反码)11111111 11111111 11111111 11110011(补码)11111111 11111111 11111111 11110100-12 >> 2补 b 个 首位值 11 11111111 11111111 11111111 11110100右侧舍去b个位11 11111111 11111111 11111111 111101格式化11111111 11111111 11111111 11111101 = -3 ...

April 2, 2019 · 2 min · jiezi

WebSocket协议 8 问

WebSocket是一种比较新的协议,它是伴随着html5规范而生的,虽然还比较年轻,但大多主流浏览器都已经支持。它使用方面、应用广泛,已经渗透到前后端开发的各种场景中。对http一问一答中二式流程的不满,催生了支持双向通信的WebSocket诞生。WebSocket是个不太干净协议。一、WebSocket协议只能浏览器发起么?不是。目前此协议的受众的也不仅仅是web开发者。WebSocket只是一种协议,它和http协议一样,使用类似okhttp的组件,可以在任何地方进行调用,甚至可以借助WebSocket实现RPC框架。二、WebSocket和HTTP什么关系?WebSocket和http一样,都是处于OSI模型中的最高层:应用层。WebSocket借助http协议进行握手,握手成功后,就会变身为TCP通道,从此与http不再相见。使用netstat或者ss,能够看到对应的连接,它与处于抽象层的socket,在外观上没有区别。三、WebSocket和长轮询有什么区别?长轮询,就是客户端发送一个请求,服务端将一直在这个连接上等待(当然有一个超长的超时时间),直到有数据才返回,它依然是一个一问一答的模式。比如著名的comted。WebSocket在握手成功后,就是全双工的TCP通道,数据可以主动从服务端发送到客户端,处于链接两端的应用没有任何区别。WebSocket创建的连接和Http的长连接是不一样的。由于Http长连接底层依然是Http协议,所以它还是一问一答,只是Hold住了一条命长点的连接而已。长轮询和Http长连接是阻塞的I/O,但WebSocket可以是非阻塞的(具体是多路复用)。四、如何创建一个连接?WebSocket的连接创建是借助Http协议进行的。这样设计主要是考虑兼容性,在浏览器中就可以很方便的发起请求,看起来比较具有迷惑性。下图是一个典型的由浏览器发起的ws请求,可以看到和http请求长的是非常相似的。但是,它只是请求阶段长得像而已:请求的地址,一般是:ws://***,或者是使用了SSL/TLS加密的安全协议wss:,用来标识是WebSocket请求。1、 首先,通过Http头里面的Upgrade域,请求进行协议转换。如果服务端支持的话,就可以切换到WebSocket协议。简单点讲:连接已经在那了,通过握手切换成ws协议,就是切换了连接的一个状态而已。1、Connection域可以认为是与Upgrade域配对的头信息。像nginx等代理服务器,是要先处理Connection,然后再发起协议转换的。Sec-WebSocket-Key 是随机的字符串,服务器端会用这些数据来构造出一个 SHA-1 的信息摘要。如此操作,可以尽量避免普通 HTTP 请求被误认为 WebSocket 协议。其他的,像Sec-WebSocket*字样的头信息,表明了客户端支持的子协议以及其他信息。像loT中很流行的mqtt,就可以作为WebSocket的子协议。使用javascript,可以很容易连接一个WebSocket服务端。<script> var ws = new WebSocket(‘ws://localhost:80’); ws.onopen = function () { console.log(‘ws onopen’); ws.send(‘from client: hello’); }; ws.onmessage = function (e) { console.log(‘ws onmessage’); console.log(‘from server: ’ + e.data); }; …</script>五、如何处理数据?WebSocket是通过事件通知的方式运行的。它包含四个事件和两个动作(发送和关闭)。WebSocket的事件事件钩子备注openonopen连接建立时触发messageonmessage客户端接收服务端数据时触发erroronerror通信发生错误时触发closeonclose连接关闭时触发数据可直接通过Socket.send()方法进行传输。通过chrome的Inspect->Network->WS,可以看到页面上的WebSocket连接。如图Opcode为2,表明它是一个二进制帧。WebSocket有类似tcp协议的帧格式,在此不做过多解释。参考:(https://tools.ietf.org/html/r…心跳心跳对应的ping、pong操作,opcode分别是0x9、0xA。收到心跳的一方需要自行更新心跳的更新时间。同使用Netty,我们到底在开发些什么?介绍的类似,在一些移动环境中,需要更加智能的控制心跳。六、如何使用Nginx做负载均衡?nginx官网已经给出了例子。主要是Upgrade和Connection头的设置。map $http_upgrade $connection_upgrade { default upgrade; ’’ close;}location /chat/ { proxy_pass http://backend; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection $connection_upgrade;}需要注意的是,nginx做负载均衡,不需要配置ip_hash等参数,nginx天然支持。由于ip_hash仅使用ip地址的前三个数字做hash,还有可能造成服务端的不均衡。七、java服务端怎么实现?可以实现javax.WebSocket下的包,简单的实现ws服务端。目前基本可以通过注解的方式去编写代码,比如ServerEndpoint。推荐使用基于netty的netty-socketio进行服务端的编写。由于使用的是netty,所以能够在多个层面进行切入,获取一些统计数据,执行一些控制指令。socketio是一套解决方案,它有多个语言的客户端,并处理了市面上大多数的兼容问题。八、WebSocket能干些啥?通知功能保持一个长连接,当服务端游新的消息,能够实时的推送到使用方。像知乎的点赞通知、评论等,都可以使用WebSocket通信。某些使用H5的客户端,为了简化开发,也会使用WebSocket进行消息的通知,由于它是实时推送的,会有更好的用户体验。数据收集一些次优级别的数据,比如行为日志、trace、异常执栈收集等,都可以开辟专门的WebSocket通道进行传输。这能够增加信息的集中度,并能及时的针对用户的行为进行合适的配置推送。由于大多数浏览器内核都支持,它将使客户端APM编程模型变得简单。加密 && 认证虽然使用Fiddler、Charles等能够抓到很多WebSocket包。但如果同时开启SSL,传输加密后的二进制数据,会大幅增加破解的成本,会安全的多。反向控制钩子这个…由于是双工长连接,服务端完全可以推送一些钩子命令,甚至直接是代码,在客户端进行执行。比如截个屏,录个音,种个小马。用户只要通过了授权申请,剩下的就随你发挥了。支付宝偷偷调用你的相机给你拍照的梗,我是相信的。End想当年,cometd的出现,惊为天人,振奋了很久。但技术日新月异,cometd已经衰老,而Socket.io得到了快速发展。WebSocket经过一段时间的混沌期,规范已经越来越完善,使用也越来越方便,不需要再处理那么多的兼容。但它的本质,还是新瓶装旧酒,换汤不换药。WebSocket的发展得益于HTML5规范的制定。规范的意义,就是约束厂商们天马行空的实现,以及指明发展的方向。这当然有典型的反例,那就是ie。现在,只有一群公认的**,还坚持在用。

March 31, 2019 · 1 min · jiezi

nginx: [emerg] unknown connection_upgrade variable解决与思考

问题一天更新完主分支后启动nginx,结果报错:nginx: [emerg] unknown “connection_upgrade” variable解决方法网上查,发现是nginx配置文件出了问题,将下面map代码块补上即可。 http { map $http_upgrade $connection_upgrade { default upgrade; ’’ close; } server { location / { #… proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection $connection_upgrade; } }}思考虽然问题解决了,但后来我想知道为什么会出现这种情况,以及解决方法的真正原理。在这篇博客可以知道问题出在了nginx代理websocket上。什么是websocket传统的http通讯模式是:客户端发起请求,服务端接收请求并作出响应。而websocket协议复用了http的握手通道,具体指的是,客户端通过HTTP请求与WebSocket服务端协商升级协议。第一步,建立连接,客户端使用http报文的格式发起协议升级的请求,服务端响应协议升级。第二步,交换数据,客户端与服务端可以使用websocket协议进行双向通讯。nginx反向代理websocket首先,客户端发起协议升级的请求,而nginx在拦截时需要识别出这是一个协议升级(upgrade)的请求,所以必须显式设置升级(Upgrade head)和连接头(Connection head),如下: location /sockjs-node/ { proxy_pass http://127.0.0.1:4200/sockjs-node/; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection $connection_upgrade;}完成后,nginx将其作为WebSocket连接处理。接着,服务器响应升级请求,由nginx做代理进行处理,这时,需要进行如下配置:http { map $http_upgrade $connection_upgrade { default upgrade; ’’ close; } server { … }}这时就出现了一开始我所修改的地方,结合上面那段的内容,我大概可以猜出来map 代码段该作用主要是根据客户端请求中 $http_upgrade 的值,来构造改变 $connection_upgrade 的值,即根据变量 $http_upgrade 的值创建新的变量 $connection_upgrade。由于我没有进行map映射,它不知道connection_upgrade是什么,所以就会出现unknown “connection_upgrade” variable错误。总结即使是小小的一点改动,背后也会隐藏庞大的信息。如果止步于解决问题,而不是探索问题,就永远不会有进步。本人水平有限,欢迎各位在评论区指出不足,你们的反馈就是我的进步动力!参考文档:https://www.nginx.com/blog/we…https://www.cnblogs.com/chyin…

March 30, 2019 · 1 min · jiezi

WebSocket入门

WebSocket 是 HTML5 开始提供的一种在单个 TCP 连接上进行全双工通讯的协议。WebSocket 使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。在 WebSocket API 中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。现在,很多网站为了实现推送技术,所用的技术都是 Ajax 轮询。轮询是在特定的的时间间隔(如每1秒),由浏览器对服务器发出HTTP请求,然后由服务器返回最新的数据给客户端的浏览器。这种传统的模式带来很明显的缺点,即浏览器需要不断的向服务器发出请求,然而HTTP请求可能包含较长的头部,其中真正有效的数据可能只是很小的一部分,显然这样会浪费很多的带宽等资源。HTML5 定义的 WebSocket 协议,能更好的节省服务器资源和带宽,并且能够更实时地进行通讯。浏览器通过 JavaScript 向服务器发出建立 WebSocket 连接的请求,连接建立以后,客户端和服务器端就可以通过 TCP 连接直接交换数据。当你获取 Web Socket 连接后,你可以通过 send() 方法来向服务器发送数据,并通过 onmessage 事件来接收服务器返回的数据。DEMO如下// client side filelet socket = new WebSocket(‘ws://localhost:9999’);// 当连接成功执行回调函数socket.onopen = function () { console.log(‘客户端连接成功’); // 向服务器发送一个消息 socket.send(‘这是客户端发给服务器的消息’);}// 绑定事件是用加属性的方式socket.onmessage = function (res) { console.log(‘收到服务器端的响应是’, res);}// server side file/* npm i ws -S // 用ws模块启动一个websocket服务器,监听 9999 端口 /let WebSocketServer = require(‘ws’).Server; let wsServer = new WebSocketServer({port: 9999});wsServer.on(‘connection’, socket=>{ console.log(‘服务器连接成功’); socket.on(‘message’, msg=>{ console.log(‘从客户端接收到的信息为’+msg); socket.send(‘这是从服务器发送到客服端的信息’); })})简单模拟智能客服聊天<template> <div class=“wrap”> <ul> <li v-for="(item, index) in dialogs" :key=“index”> <p :class=“item.self ? ‘fr’: ‘fl’">{{item.sendContent || item.acceptContent}}</p> </li> </ul> <div class=“send-cont”> <input v-model=“sendCont”> <input type=“button” @click=“send” value=“发送”> </div> </div></template><script>let socket = new WebSocket(“ws://localhost:9999”);export default { data() { return { dialogs: [], sendCont: “”, isConnect: false }; }, mounted() { socket.onopen = () => { this.isConnect = true; }; }, methods: { send() { if (this.isConnect) { console.log(“客户端连接成功”); // 向服务器发送一个消息 socket.send(this.sendCont); this.dialogs.push({ sendContent: this.sendCont, self: true }); // 绑定事件是用加属性的方式 socket.onmessage = res => { setTimeout(() => { this.dialogs.push({ acceptContent: res.data }); }, Math.random() * 2000); }; } } }};</script><style scoped> { padding: 0; margin: 0;}.fl { float: left;}.fr { float: right;}li::before { content: “.”; display: block; height: 0; clear: both; visibility: hidden;}.wrap { position: relative; width: 380px; margin: auto; height: 600px; background-color: #eee; padding: 10px;}.wrap ul { overflow: auto; height: 550px;}li { list-style: none; margin: 2px;}li:nth-child(even) p { background-color: rgb(86, 107, 177);}li p { font-size: 20px; font-family: “Microsoft Yahei”, serif, Arial, Helvetica, sans-serif; border-radius: 6px; padding: 4px; margin: 2px 4px; white-space: wrap; text-align: left;}li p.fr { background-color: rgb(61, 185, 30);}.send-cont { position: absolute; bottom: 10px; z-index: 999; width: 98%; margin: auto;}.send-cont input { display: inline-block; outline: none; border: 2px solid #080; border-radius: 6px; line-height: 30px; font-size: 16px; text-align: left;}.send-cont input:first-child { width: 70%;}.send-cont input[type=“button”] { width: 20%; background-color: #080; color: #fff; text-align: center; padding: 1px;}</style>const contents = [‘你好’, ‘hi’, ‘hello’, ’nice to meet you’, ‘how are you’, ‘how do you do’]let WebSocketServer = require(‘ws’).Server; let wsServer = new WebSocketServer({port: 9999});wsServer.on(‘connection’, socket=>{ console.log(‘服务器连接成功’); socket.on(‘message’, msg=>{ console.log(‘从客户端接收到的信息为’+msg); socket.send(contents[parseInt(Math.random()*contents.length)]); })}) ...

March 30, 2019 · 2 min · jiezi

Web 实时推送技术的总结

前言随着 Web 的发展,用户对于 Web 的实时推送要求也越来越高 ,比如,工业运行监控、Web 在线通讯、即时报价系统、在线游戏等,都需要将后台发生的变化主动地、实时地传送到浏览器端,而不需要用户手动地刷新页面。本文对过去和现在流行的 Web 实时推送技术进行了比较与总结。本文完整的源代码请猛戳Github博客,纸上得来终觉浅,建议大家动手敲敲代码。一、双向通信HTTP 协议有一个缺陷:通信只能由客户端发起。举例来说,我们想了解今天的天气,只能是客户端向服务器发出请求,服务器返回查询结果。HTTP 协议做不到服务器主动向客户端推送信息。这种单向请求的特点,注定了如果服务器有连续的状态变化,客户端要获知就非常麻烦。在WebSocket协议之前,有三种实现双向通信的方式:轮询(polling)、长轮询(long-polling)和iframe流(streaming)。1.轮询(polling)轮询是客户端和服务器之间会一直进行连接,每隔一段时间就询问一次。其缺点也很明显:连接数会很多,一个接受,一个发送。而且每次发送请求都会有Http的Header,会很耗流量,也会消耗CPU的利用率。优点:实现简单,无需做过多的更改缺点:轮询的间隔过长,会导致用户不能及时接收到更新的数据;轮询的间隔过短,会导致查询请求过多,增加服务器端的负担// 1.html<div id=“clock”></div><script> let clockDiv = document.getElementById(‘clock’); setInterval(function(){ let xhr = new XMLHttpRequest; xhr.open(‘GET’,’/clock’,true); xhr.onreadystatechange = function(){ if(xhr.readyState == 4 && xhr.status == 200){ console.log(xhr.responseText); clockDiv.innerHTML = xhr.responseText; } } xhr.send(); },1000);</script>//轮询 服务端let express = require(’express’);let app = express();app.use(express.static(__dirname));app.get(’/clock’,function(req,res){ res.end(new Date().toLocaleString());});app.listen(8080);启动本地服务,打开http://localhost:8080/1.html,得到如下结果:2.长轮询(long-polling)长轮询是对轮询的改进版,客户端发送HTTP给服务器之后,看有没有新消息,如果没有新消息,就一直等待。当有新消息的时候,才会返回给客户端。在某种程度上减小了网络带宽和CPU利用率等问题。由于http数据包的头部数据量往往很大(通常有400多个字节),但是真正被服务器需要的数据却很少(有时只有10个字节左右),这样的数据包在网络上周期性的传输,难免对网络带宽是一种浪费。优点:比 Polling 做了优化,有较好的时效性缺点:保持连接会消耗资源; 服务器没有返回有效数据,程序超时。// 2.html 服务端代码同上<div id=“clock”></div><script>let clockDiv = document.getElementById(‘clock’)function send() { let xhr = new XMLHttpRequest() xhr.open(‘GET’, ‘/clock’, true) xhr.timeout = 2000 // 超时时间,单位是毫秒 xhr.onreadystatechange = function() { if (xhr.readyState == 4) { if (xhr.status == 200) { //如果返回成功了,则显示结果 clockDiv.innerHTML = xhr.responseText } send() //不管成功还是失败都会发下一次请求 } } xhr.ontimeout = function() { send() } xhr.send()}send()</script>3.iframe流(streaming)iframe流方式是在页面中插入一个隐藏的iframe,利用其src属性在服务器和客户端之间创建一条长连接,服务器向iframe传输数据(通常是HTML,内有负责插入信息的javascript),来实时更新页面。优点:消息能够实时到达;浏览器兼容好缺点:服务器维护一个长连接会增加开销;IE、chrome、Firefox会显示加载没有完成,图标会不停旋转。// 3.html<body> <div id=“clock”></div> <iframe src="/clock" style=“display:none”></iframe></body>//iframe流 let express = require(’express’)let app = express()app.use(express.static(__dirname))app.get(’/clock’, function(req, res) { setInterval(function() { let date = new Date().toLocaleString() res.write( &lt;script type="text/javascript"&gt; parent.document.getElementById('clock').innerHTML = "${date}";//改变父窗口dom元素 &lt;/script&gt; ) }, 1000)})app.listen(8080)启动本地服务,打开http://localhost:8080/3.html,得到如下结果:上述代码中,客户端只请求一次,然而服务端却是源源不断向客户端发送数据,这样服务器维护一个长连接会增加开销。以上我们介绍了三种实时推送技术,然而各自的缺点很明显,使用起来并不理想,接下来我们着重介绍另一种技术–websocket,它是比较理想的双向通信技术。二、WebSocket1.什么是websocketWebSocket是一种全新的协议,随着HTML5草案的不断完善,越来越多的现代浏览器开始全面支持WebSocket技术了,它将TCP的Socket(套接字)应用在了webpage上,从而使通信双方建立起一个保持在活动状态连接通道。一旦Web服务器与客户端之间建立起WebSocket协议的通信连接,之后所有的通信都依靠这个专用协议进行。通信过程中可互相发送JSON、XML、HTML或图片等任意格式的数据。由于是建立在HTTP基础上的协议,因此连接的发起方仍是客户端,而一旦确立WebSocket通信连接,不论服务器还是客户端,任意一方都可直接向对方发送报文。初次接触 WebSocket 的人,都会问同样的问题:我们已经有了 HTTP 协议,为什么还需要另一个协议?2.HTTP的局限性HTTP是半双工协议,也就是说,在同一时刻数据只能单向流动,客户端向服务器发送请求(单向的),然后服务器响应请求(单向的)。服务器不能主动推送数据给浏览器。这就会导致一些高级功能难以实现,诸如聊天室场景就没法实现。3.WebSocket的特点支持双向通信,实时性更强可以发送文本,也可以发送二进制数据减少通信量:只要建立起WebSocket连接,就希望一直保持连接状态。和HTTP相比,不但每次连接时的总开销减少,而且由于WebSocket的首部信息很小,通信量也相应减少了相对于传统的HTTP每次请求-应答都需要客户端与服务端建立连接的模式,WebSocket是类似Socket的TCP长连接的通讯模式,一旦WebSocket连接建立后,后续数据都以帧序列的形式传输。在客户端断开WebSocket连接或Server端断掉连接前,不需要客户端和服务端重新发起连接请求。在海量并发和客户端与服务器交互负载流量大的情况下,极大的节省了网络带宽资源的消耗,有明显的性能优势,且客户端发送和接受消息是在同一个持久连接上发起,实时性优势明显。接下来我看下websocket如何实现客户端与服务端双向通信:// websocket.html<div id=“clock”></div><script>let clockDiv = document.getElementById(‘clock’)let socket = new WebSocket(‘ws://localhost:9999’)//当连接成功之后就会执行回调函数socket.onopen = function() { console.log(‘客户端连接成功’) //再向服务 器发送一个消息 socket.send(‘hello’) //客户端发的消息内容 为hello}//绑定事件是用加属性的方式socket.onmessage = function(event) { clockDiv.innerHTML = event.data console.log(‘收到服务器端的响应’, event.data)}</script>// websocket.jslet express = require(’express’)let app = express()app.use(express.static(__dirname))//http服务器app.listen(3000)let WebSocketServer = require(‘ws’).Server//用ws模块启动一个websocket服务器,监听了9999端口let wsServer = new WebSocketServer({ port: 9999 })//监听客户端的连接请求 当客户端连接服务器的时候,就会触发connection事件//socket代表一个客户端,不是所有客户端共享的,而是每个客户端都有一个socketwsServer.on(‘connection’, function(socket) { //每一个socket都有一个唯一的ID属性 console.log(socket) console.log(‘客户端连接成功’) //监听对方发过来的消息 socket.on(‘message’, function(message) { console.log(‘接收到客户端的消息’, message) socket.send(‘服务器回应:’ + message) })})启动本地服务,打开http://localhost:3000/websocket.html,得到如下结果:三、Web 实时推送技术的比较方式类型技术实现优点缺点适用场景轮询Pollingclient→server客户端循环请求1、实现简单 2、 支持跨域1、浪费带宽和服务器资源 2、 一次请求信息大半是无用(完整http头信息) 3、有延迟 4、大部分无效请求适于小型应用长轮询Long-Pollingclient→server服务器hold住连接,一直到有数据或者超时才返回,减少重复请求次数1、实现简单 2、不会频繁发请求 3、节省流量 4、延迟低1、服务器hold住连接,会消耗资源 2、一次请求信息大半是无用WebQQ、Hi网页版、Facebook IM长连接iframeclient→server在页面里嵌入一个隐蔵iframe,将这个 iframe 的 src 属性设为对一个长连接的请求,服务器端就能源源不断地往客户端输入数据。1、数据实时送达 2、不发无用请求,一次链接,多次“推送”1、服务器增加开销 2、无法准确知道连接状态 3、IE、chrome等一直会处于loading状态Gmail聊天WebSocketserver⇌clientnew WebSocket()1、支持双向通信,实时性更强 2、可发送二进制文件3、减少通信量1、浏览器支持程度不一致 2、不支持断开重连网络游戏、银行交互和支付综上所述:Websocket协议不仅解决了HTTP协议中服务端的被动性,即通信只能由客户端发起,也解决了数据同步有延迟的问题,同时还带来了明显的性能优势,所以websocket是Web 实时推送技术的比较理想的方案,但如果要兼容低版本浏览器,可以考虑用轮询来实现。给大家推荐一个好用的BUG监控工具Fundebug,欢迎免费试用!欢迎关注公众号:前端工匠,你的成长我们一起见证!如果你感觉有收获,欢迎给我打赏,以激励我更多输出优质开源内容参考文章WebSocket 教程珠峰前端架构课Web 实时推送技术的总结WebSocket(1): 服务端“实时推送”的演变长连接/websocket/SSE等主流服务器推送技术比较 ...

March 14, 2019 · 2 min · jiezi

一个最简单的WebSocket hello world demo

服务器端代码不超过42行:const WSServer = require("./server.js");var counter = 0;function createWebsocket() { var host = “127.0.0.1”; var port = “9999”; var wsServer = WSServer.startServer(host, port); console.log(“WebSocket server listens to: " + host + “:” + port); wsServer.on(‘open’, (data) => { console.log(‘WS Client has connected: ’ + data); setInterval(function(){ counter++; WSServer.broadcast(“Jerry: " + counter ); }, 3000); }); wsServer.on(‘dataWS’, (data) => { console.log(‘Receive Data from WebUI : ’ + data); }); wsServer.on(‘disconnect’, (data) => { console.log(‘WSServer disconnect:’ + data.name); }); wsServer.on(‘close’, (data) => { console.log(‘WSServer close: ’ + data.name); }); wsServer.on(’end’, (data) => { console.log(‘WSServer Close: ‘+data.name); }); wsServer.on(’error’, (data) => { });}createWebsocket();代码第10行创建一个WebSocket服务器,监听在9999端口上:第15~18行每隔3秒发送一个字符串到浏览器,用一个计数器标识每次发送的请求。代码里所需的server.js我已经上传到我的Github上了:https://github.com/i042416/Kn…使用nodejs启动这个服务器:网页端代码:<html><script src=“socket.io.dev.js”></script><script>console.log(“1”); var socket = io(‘ws://127.0.0.1:9999’); socket.on(‘connect’, function(){ console.log(“connected!”); }); socket.on(’event’, function(data){ console.log(“event: " + data); }); socket.on(’news’, function(data){ console.log(“data from server: " + JSON.stringify(data,2,2)); }); socket.on(‘disconnect’, function(){ console.log(“disconnect…”); });</script></html>浏览器端每隔三秒收到服务器推送的消息,打印在console上:要获取更多Jerry的原创文章,请关注公众号"汪子熙”: ...

March 10, 2019 · 1 min · jiezi

SpringBoot 实战 (十七) | 整合 WebSocket 实现聊天室

微信公众号:一个优秀的废人。如有问题,请后台留言,反正我也不会听。前言昨天那篇介绍了 WebSocket 实现广播,也即服务器端有消息时,将消息发送给所有连接了当前 endpoint 的浏览器。但这无法解决消息由谁发送,又由谁接收的问题。所以,今天写一篇实现一对一的聊天室。今天这一篇建立在昨天那一篇的基础之上,为便于更好理解今天这一篇,推荐先阅读:「SpringBoot 整合WebSocket 实现广播消息 」准备工作Spring Boot 2.1.3 RELEASESpring Security 2.1.3 RELEASEIDEAJDK8pom 依赖因聊天室涉及到用户相关,所以在上一篇基础上引入 Spring Security 2.1.3 RELEASE 依赖<!– Spring Security 依赖 –><dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId></dependency>Spring Security 的配置虽说涉及到 Spring Security ,但鉴于篇幅有限,这里只对这个项目相关的部分进行介绍,具体的 Spring Security 教程,后面会出。这里的 Spring Security 配置很简单,具体就是设置登录路径、设置安全资源以及在内存中创建用户和密码,密码需要注意加密,这里使用 BCrypt 加密算法在用户登录时对密码进行加密。 代码注释很详细,不多说。package com.nasus.websocket.config;import org.springframework.context.annotation.Configuration;import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;import org.springframework.security.config.annotation.web.builders.HttpSecurity;import org.springframework.security.config.annotation.web.builders.WebSecurity;import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;@Configuration// 开启Spring Security的功能@EnableWebSecuritypublic class WebSecurityConfig extends WebSecurityConfigurerAdapter{ @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() // 设置 SpringSecurity 对 / 和 “/login” 路径不拦截 .mvcMatchers("/","/login").permitAll() .anyRequest().authenticated() .and() .formLogin() // 设置 Spring Security 的登录页面访问路径为/login .loginPage("/login") // 登录成功后转向 /chat 路径 .defaultSuccessUrl("/chat") .permitAll() .and() .logout() .permitAll(); } @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.inMemoryAuthentication() // 在内存中分配两个用户 nasus 和 chenzy ,用户名和密码一致 // BCryptPasswordEncoder() 是 Spring security 5.0 中新增的加密方式 // 登陆时用 BCrypt 加密方式对用户密码进行处理。 .passwordEncoder(new BCryptPasswordEncoder()) .withUser(“nasus”) // 保证用户登录时使用 bcrypt 对密码进行处理再与内存中的密码比对 .password(new BCryptPasswordEncoder().encode(“nasus”)).roles(“USER”) .and() // 登陆时用 BCrypt 加密方式对用户密码进行处理。 .passwordEncoder(new BCryptPasswordEncoder()) .withUser(“chenzy”) // 保证用户登录时使用 bcrypt 对密码进行处理再与内存中的密码比对 .password(new BCryptPasswordEncoder().encode(“chenzy”)).roles(“USER”); } @Override public void configure(WebSecurity web) throws Exception { // /resource/static 目录下的静态资源,Spring Security 不拦截 web.ignoring().antMatchers("/resource/static**"); }}WebSocket 的配置在上一篇的基础上另外注册一个名为 “/endpointChat” 的节点,以供用户订阅,只有订阅了该节点的用户才能接收到消息;然后,再增加一个名为 “/queue” 消息代理。@Configuration// @EnableWebSocketMessageBroker 注解用于开启使用 STOMP 协议来传输基于代理(MessageBroker)的消息,这时候控制器(controller)// 开始支持@MessageMapping,就像是使用 @requestMapping 一样。@EnableWebSocketMessageBrokerpublic class WebSocketConfig implements WebSocketMessageBrokerConfigurer { @Override public void registerStompEndpoints(StompEndpointRegistry registry) { //注册一个名为 /endpointNasus 的 Stomp 节点(endpoint),并指定使用 SockJS 协议。 registry.addEndpoint("/endpointNasus").withSockJS(); //注册一个名为 /endpointChat 的 Stomp 节点(endpoint),并指定使用 SockJS 协议。 registry.addEndpoint("/endpointChat").withSockJS(); } @Override public void configureMessageBroker(MessageBrokerRegistry registry) { // 广播式配置名为 /nasus 消息代理 , 这个消息代理必须和 controller 中的 @SendTo 配置的地址前缀一样或者全匹配 // 点对点增加一个 /queue 消息代理 registry.enableSimpleBroker("/queue","/nasus/getResponse"); }}控制器 controller指定发送消息的格式以及模板。详情见,代码注释。@Autowired//使用 SimpMessagingTemplate 向浏览器发送信息private SimpMessagingTemplate messagingTemplate;@MessageMapping("/chat")public void handleChat(Principal principal,String msg){ // 在 SpringMVC 中,可以直接在参数中获得 principal,principal 中包含当前用户信息 if (principal.getName().equals(“nasus”)){ // 硬编码,如果发送人是 nasus 则接收人是 chenzy 反之也成立。 // 通过 messageingTemplate.convertAndSendToUser 方法向用户发送信息,参数一是接收消息用户,参数二是浏览器订阅地址,参数三是消息本身 messagingTemplate.convertAndSendToUser(“chenzy”, “/queue/notifications”,principal.getName()+"-send:" + msg); } else { messagingTemplate.convertAndSendToUser(“nasus”, “/queue/notifications”,principal.getName()+"-send:" + msg); }}登录页面<!DOCTYPE html><html xmlns=“http://www.w3.org/1999/xhtml" xmlns:th=“http://www.thymeleaf.org” xmlns:sec=“http://www.thymeleaf.org/thymeleaf-extras-springsecurity3"><meta charset=“UTF-8” /><head> <title>登陆页面</title></head><body><div th:if="${param.error}"> 无效的账号和密码</div><div th:if="${param.logout}"> 你已注销</div><form th:action=”@{/login}” method=“post”> <div><label> 账号 : <input type=“text” name=“username”/> </label></div> <div><label> 密码: <input type=“password” name=“password”/> </label></div> <div><input type=“submit” value=“登陆”/></div></form></body></html>聊天页面<!DOCTYPE html><html xmlns:th=“http://www.thymeleaf.org”><meta charset=“UTF-8” /><head> <title>Home</title> <script th:src="@{sockjs.min.js}"></script> <script th:src="@{stomp.min.js}"></script> <script th:src="@{jquery.js}"></script></head><body><p> 聊天室</p><form id=“nasusForm”> <textarea rows=“4” cols=“60” name=“text”></textarea> <input type=“submit”/></form><script th:inline=“javascript”> $(’#nasusForm’).submit(function(e){ e.preventDefault(); var text = $(’#nasusForm’).find(’textarea[name=“text”]’).val(); sendSpittle(text); }); // 连接 SockJs 的 endpoint 名称为 “/endpointChat” var sock = new SockJS("/endpointChat"); var stomp = Stomp.over(sock); stomp.connect(‘guest’, ‘guest’, function(frame) { // 订阅 /user/queue/notifications 发送的消息,这里与在控制器的 // messagingTemplate.convertAndSendToUser 中订阅的地址保持一致 // 这里多了 /user 前缀,是必须的,使用了 /user 才会把消息发送到指定用户 stomp.subscribe("/user/queue/notifications", handleNotification); }); function handleNotification(message) { $(’#output’).append("<b>Received: " + message.body + “</b><br/>”) } function sendSpittle(text) { stomp.send("/chat", {}, text); } $(’#stop’).click(function() {sock.close()});</script><div id=“output”></div></body></html>页面控制器 controller@Controllerpublic class ViewController { @GetMapping("/nasus") public String getView(){ return “nasus”; } @GetMapping("/login") public String getLoginView(){ return “login”; } @GetMapping("/chat") public String getChatView(){ return “chat”; }}测试预期结果应该是:两个用户登录系统,可以互相发送消息。但是同一个浏览器的用户会话的 session 是共享的,这里需要在 Chrome 浏览器再添加一个用户。具体操作在 Chrome 的 设置–>管理用户–>添加用户:两个用户分别访问 http://localhost:8080/login 登录系统,跳转至聊天界面:相互发送消息:完整代码https://github.com/turoDog/De…如果觉得对你有帮助,请给个 Star 再走呗,非常感谢。后语如果本文对你哪怕有一丁点帮助,请帮忙点好看。你的好看是我坚持写作的动力。另外,关注之后在发送 1024 可领取免费学习资料。资料详情请看这篇旧文:Python、C++、Java、Linux、Go、前端、算法资料分享 ...

March 6, 2019 · 2 min · jiezi

SpringBoot 实战 (十六) | 整合 WebSocket 基于 STOMP 协议实现广播消息

前言如题,今天介绍的是 SpringBoot 整合 WebSocket 实现广播消息。什么是 WebSocket ?WebSocket 为浏览器和服务器提供了双工异步通信的功能,即浏览器可以向服务器发送信息,反之也成立。WebSocket 是通过一个 socket 来实现双工异步通信能力的,但直接使用 WebSocket ( 或者 SockJS:WebSocket 协议的模拟,增加了当前浏览器不支持使用 WebSocket 的兼容支持) 协议开发程序显得十分繁琐,所以使用它的子协议 STOMP。STOMP 协议简介它是高级的流文本定向消息协议,是一种为 MOM (Message Oriented Middleware,面向消息的中间件) 设计的简单文本协议。它提供了一个可互操作的连接格式,允许 STOMP 客户端与任意 STOMP 消息代理 (Broker) 进行交互,类似于 OpenWire (一种二进制协议)。由于其设计简单,很容易开发客户端,因此在多种语言和多种平台上得到广泛应用。其中最流行的 STOMP 消息代理是 Apache ActiveMQ。STOMP 协议使用一个基于 (frame) 的格式来定义消息,与 Http 的 request 和 response 类似 。广播接下来,实现一个广播消息的 demo。即服务端有消息时,将消息发送给所有连接了当前 endpoint 的浏览器。准备工作SpringBoot 2.1.3IDEAJDK8Pom 依赖配置<dependencies> <!– thymeleaf 模板引擎 –> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> <!– web 启动类 –> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!– WebSocket 依赖 –> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-websocket</artifactId> </dependency> <!– test 单元测试 –> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies>代码注释很详细,不多说。配置 WebSocket实现 WebSocketMessageBrokerConfigurer 接口,注册一个 STOMP 节点,配置一个广播消息代理@Configuration// @EnableWebSocketMessageBroker注解用于开启使用STOMP协议来传输基于代理(MessageBroker)的消息,这时候控制器(controller)// 开始支持@MessageMapping,就像是使用@requestMapping一样。@EnableWebSocketMessageBrokerpublic class WebSocketConfig implements WebSocketMessageBrokerConfigurer { @Override public void registerStompEndpoints(StompEndpointRegistry registry) { //注册一个 Stomp 的节点(endpoint),并指定使用 SockJS 协议。 registry.addEndpoint("/endpointNasus").withSockJS(); } @Override public void configureMessageBroker(MessageBrokerRegistry registry) { // 广播式配置名为 /nasus 消息代理 , 这个消息代理必须和 controller 中的 @SendTo 配置的地址前缀一样或者全匹配 registry.enableSimpleBroker("/nasus"); }}消息类客户端发送给服务器:public class Client2ServerMessage { private String name; public String getName() { return name; } public void setName(String name) { this.name = name; }}服务器发送给客户端:public class Server2ClientMessage { private String responseMessage; public Server2ClientMessage(String responseMessage) { this.responseMessage = responseMessage; } public String getResponseMessage() { return responseMessage; } public void setResponseMessage(String responseMessage) { this.responseMessage = responseMessage; }}演示控制器代码@RestControllerpublic class WebSocketController { @MessageMapping("/hello") // @MessageMapping 和 @RequestMapping 功能类似,浏览器向服务器发起消息,映射到该地址。 @SendTo("/nasus/getResponse") // 如果服务器接受到了消息,就会对订阅了 @SendTo 括号中的地址的浏览器发送消息。 public Server2ClientMessage say(Client2ServerMessage message) throws Exception { Thread.sleep(3000); return new Server2ClientMessage(“Hello,” + message.getName() + “!”); }}引入 STOMP 脚本将 stomp.min.js (STOMP 客户端脚本) 和 sockJS.min.js (sockJS 客户端脚本) 以及 Jquery 放在 resource 文件夹的 static 目录下。演示页面<!DOCTYPE html><html xmlns:th=“http://www.thymeleaf.org”><head> <meta charset=“UTF-8” /> <title>Spring Boot+WebSocket+广播式</title></head><body onload=“disconnect()"><noscript><h2 style=“color: #ff0000”>貌似你的浏览器不支持websocket</h2></noscript><div> <div> <button id=“connect” onclick=“connect();">连接</button> <button id=“disconnect” disabled=“disabled” onclick=“disconnect();">断开连接</button> </div> <div id=“conversationDiv”> <label>输入你的名字</label><input type=“text” id=“name” /> <button id=“sendName” onclick=“sendName();">发送</button> <p id=“response”></p> </div></div><script th:src=”@{sockjs.min.js}"></script><script th:src=”@{stomp.min.js}"></script><script th:src=”@{jquery.js}"></script><script type=“text/javascript”> var stompClient = null; function setConnected(connected) { document.getElementById(‘connect’).disabled = connected; document.getElementById(‘disconnect’).disabled = !connected; document.getElementById(‘conversationDiv’).style.visibility = connected ? ‘visible’ : ‘hidden’; $(’#response’).html(); } function connect() { // 连接 SockJs 的 endpoint 名称为 “/endpointNasus” var socket = new SockJS(’/endpointNasus’); // 使用 STOMP 子协议的 WebSocket 客户端 stompClient = Stomp.over(socket); stompClient.connect({}, function(frame) { setConnected(true); console.log(‘Connected: ’ + frame); // 通过 stompClient.subscribe 订阅 /nasus/getResponse 目标发送的信息,对应控制器的 SendTo 定义 stompClient.subscribe(’/nasus/getResponse’, function(respnose){ // 展示返回的信息,只要订阅了 /nasus/getResponse 目标,都可以接收到服务端返回的信息 showResponse(JSON.parse(respnose.body).responseMessage); }); }); } function disconnect() { // 断开连接 if (stompClient != null) { stompClient.disconnect(); } setConnected(false); console.log(“Disconnected”); } function sendName() { // 向服务端发送消息 var name = $(’#name’).val(); // 通过 stompClient.send 向 /hello (服务端)发送信息,对应控制器 @MessageMapping 中的定义 stompClient.send("/hello”, {}, JSON.stringify({ ’name’: name })); } function showResponse(message) { // 接收返回的消息 var response = $("#response"); response.html(message); }</script></body></html>页面 Controller注意,这里使用的是 @Controller 注解,用于匹配 html 前缀,加载页面。@Controllerpublic class ViewController { @GetMapping("/nasus") public String getView(){ return “nasus”; }}测试结果打开三个窗口访问 http://localhost:8080/nasus ,初始页面长这样:三个页面全部点连接,点击连接订阅 endpoint ,如下图:在第一个页面,输入名字,点发送 ,如下图:在第一个页面发送消息,等待 3 秒,结果是 3 个页面都接受到了服务端返回的信息,广播成功。源码下载:https://github.com/turoDog/De…如果觉得对你有帮助,请给个 Star 再走呗,非常感谢。后语如果本文对你哪怕有一丁点帮助,请帮忙点好看。你的好看是我坚持写作的动力。另外,关注之后在发送 1024 可领取免费学习资料。资料详情请看这篇旧文:Python、C++、Java、Linux、Go、前端、算法资料分享 ...

March 5, 2019 · 2 min · jiezi

TCP socket和web socket的区别

小编先习惯性的看了下某中文百科网站对Web Socket的介绍,觉得很囧。如果大家按照这个答案去参加BAT等互联网公司的前端开发面试,估计会被鄙视。还是让我们阅读一些英文材料吧。让我们直接看stackoverflow上的原文,然后翻译:原文地址:https://stackoverflow.com/que…这个讨论有超过8万的阅读量。首先我们来阅读这段有166个赞的回答:When you send bytes from a buffer with a normal TCP socket, the send function returns the number of bytes of the buffer that were sent. 当我们向一个通常的TCP套接字发送一段来自内存buffer中的字节数据时,send系统调用返回的是实际发送的字节数。If it is a non-blocking socket or a non-blocking send then the number of bytes sent may be less than the size of the buffer. 如果发送数据的目的方套接字是一个非阻塞套接字或者是对写操作非阻塞的套接字,那么send返回的已发送字节数可能小于buffer中待发送字节数。If it is a blocking socket or blocking send, then the number returned will match the size of the buffer but the call may block. 如果是阻塞套接字,两者会相等,因为顾名思义,如果send系统调用没有把所有待发送数据全部发送,则API调用不会返回。With WebSockets, the data that is passed to the send method is always either sent as a whole “message” or not at all. Also, browser WebSocket implementations do not block on the send call.而Web socket和TCP socket的区别,从发送的数据来看,不再是一系列字节,而是按照一个完整的"消息体"发送出去的,这个"消息体"无法进一步再分割,要么全部发送成功,要么压根就不发送,不存在像TCP套接字非阻塞操作那样出现部分发送的情况。换言之,Web Socket里对套接字的操作是非阻塞操作。这个区别在维基百科上也有清晰阐述:Websocket differs from TCP in that it enables a stream of messages instead of a stream of bytes再来看接收方的区别。原文:But there are more important differences on the receiving side of things. When the receiver does a recv (or read) on a TCP socket, there is no guarantee that the number of bytes returned correspond to a single send (or write) on the sender side. It might be the same, it may be less (or zero) and it might even be more (in which case bytes from multiple send/writes are received). With WebSockets, the receipt of a message is event driven (you generally register a message handler routine), and the data in the event is always the entire message that the other side sent.同理,在TCP套接字的场景下,接收方从TCP套接字读取的字节数,并不一定等于发送方调用send所发送的字节数。而WebSocket呢?WebSocket的接收方从套接字读取数据,根本不是像TCP 套接字那样直接用recv/read来读取, 而是采取事件驱动机制。即应用程序注册一个事件处理函数,当web socket的发送方发送的数据在接收方应用从内核缓冲区拷贝到应用程序层已经处于可用状态时 ,应用程序注册的事件处理函数以回调(callback)的方式被调用。看个例子:我通过WebSocket发送一个消息“汪子熙”:在调试器里看到的这个字符串作为回调函数的输入参数注入到函数体内:Chrome开发者工具里观察到的WebSocket消息体:下次面试被面试官问到TCP和WebSocket套接字的区别,相信大家应该能够知道如何回答了。要获取更多Jerry的原创文章,请关注公众号"汪子熙": ...

February 24, 2019 · 2 min · jiezi

【译】 WebSocket 协议第六章——发送与接收消息(Sending and Receiving Data)

概述本文为 WebSocket 协议的第六章,本文翻译的主要内容为 WebSocket 消息发送与接收相关内容。发送与接收消息(协议正文)6.1 发送数据为了通过 WebSocket 连接发送一条 WebSocket 消息,终端必须遵循以下几个步骤:终端必须保证 WebSocket 连接处于 OPEN 状态(见第 4.1 节和第 4.2.2 节)。如果 WebSocket 连接的任意一端的状态发生了改变,终端必须中止以下步骤。终端必须将数据按照第 5.2 节定义的 WebSocket 帧进行封装。如果需要发送的数据过大或者在终端希望开始发消息时,如果数据在整体性这一点上不可用,那么终端可能会选择通过在第 5.4 节中定义的一系列帧来进行封装。包含数据的第一帧操作码(帧操作码)必须根据第 5.2 节中的内容设置的合适的值,以便接收者将数据解析为文本或者二进制数据。最后一个包含数据的帧的 FIN ( FIN 帧)字段必须和第 5.2 节中定义的一样设置为 1 。如果数据被发送到了客户端,数据帧必须和第 5.3 节中定义的一样添加掩码。如果在 WebsSocket 连接中有协商扩展(第 9 章),在这些扩展中的定义和注意事项也许要额外考虑。被格式化的帧必须通过底层的网络连接进行传输。6.2 接收数据为了接收 WebSocket 数据,终端需要监听底层网络连接。输入的数据必须通过第 5.2 节定义的 WebSocket 帧进行解析。如果收到了一个控制帧(第 5.5 节),那么这个帧必须如 5.5 节中定义的方式进行处理。如果收到的是一个数据帧,那么终端必须注意 5.2 节中的定义在操作码(帧操作码)中的数据类型。在这一帧中的“应用数据”被定义为消息的数据。如果帧中包含未分片的数据(第 5.4 节),那么就认为:一条 WebSocket 消息的数据和类型被收到了。如果帧是分片数据的一部分,那么随后的帧包含的“应用数据”连起来就是数据的格式。当通过 FIN 字段(FIN帧)表示的最后一个片段被收到时,我们可以说:一条 WebSocket 消息的数据(由片段组装起来的“应用数据”数据组成)和类型(注意分片消息的第一帧)已经被收到了。接下来的数据帧必须是属于一条新的 WebSocket 消息。扩展(第 9 章)可能改变数据如何理解的方式,具体包括消息的内容边界。扩展,除了在“应用数据”之前添加“扩展数据”之外,也可以修改“应用数据”(例如压缩它)。像第 5.3 节中说的那样,服务端在收到客户端的数据帧时必须去除掩码。

February 20, 2019 · 1 min · jiezi

【译】WebSocket协议第五章——数据帧(Data Framing)

概述本文为WebSocket协议的第五章,本文翻译的主要内容为WebSocket传输的数据相关内容。数据帧(协议正文)5.1 概览在WebSocket协议中,数据是通过一系列数据帧来进行传输的。为了避免由于网络中介(例如一些拦截代理)或者一些在第10.3节讨论的安全原因,客户端必须在它发送到服务器的所有帧中添加掩码(Mask)(具体细节见5.3节)。(注意:无论WebSocket协议是否使用了TLS,帧都需要添加掩码)。服务端收到没有添加掩码的数据帧以后,必须立即关闭连接。在这种情况下,服务端可以发送一个在7.4.1节定义的状态码为1002(协议错误)的关闭帧。服务端禁止在发送数据帧给客户端时添加掩码。客户端如果收到了一个添加了掩码的帧,必须立即关闭连接。在这种情况下,它可以使用第7.4.1节定义的1002(协议错误)状态码。(这些规则可能会在将来的规范中放开)。基础的数据帧协议使用操作码、有效负载长度和在“有效负载数据”中定义的放置“扩展数据”与“引用数据”的指定位置来定义帧类型。特定的bit位和操作码为将来的协议扩展做了保留。一个数据帧可以在开始握手完成之后和终端发送了一个关闭帧之前的任意一个时间通过客户端或者服务端进行传输(第5.5.1节)。5.2 基础帧协议在这节中的这种数据传输部分的有线格式是通过ABNFRFC5234来进行详细说明的。(注意:不像这篇文档中的其他章节内容,在这节中的ABNF是对bit组进行操作。每一个bit组的长度是在评论中展示的。在线上编码时,最高位的bit是在ABNF最左边的)。对于数据帧的高级的预览可以见下图。如果下图指定的内容和这一节中后面的ABNF指定的内容有冲突的话,以下图为准。 0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+——-+-+————-+——————————-+ |F|R|R|R| opcode|M| Payload len | Extended payload length | |I|S|S|S| (4) |A| (7) | (16/64) | |N|V|V|V| |S| | (if payload len==126/127) | | |1|2|3| |K| | | +-+-+-+-+——-+-+————-+ - - - - - - - - - - - - - - - + | Extended payload length continued, if payload len == 127 | + - - - - - - - - - - - - - - - +——————————-+ | |Masking-key, if MASK set to 1 | +——————————-+——————————-+ | Masking-key (continued) | Payload Data | +——————————– - - - - - - - - - - - - - - - + : Payload Data continued … : + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + | Payload Data continued … | +—————————————————————+FIN: 1 bit 表示这是消息的最后一个片段。第一个片段也有可能是最后一个片段。RSV1,RSV2,RSV3: 每个1 bit 必须设置为0,除非扩展了非0值含义的扩展。如果收到了一个非0值但是没有扩展任何非0值的含义,接收终端必须断开WebSocket连接。Opcode: 4 bit 定义“有效负载数据”的解释。如果收到一个未知的操作码,接收终端必须断开WebSocket连接。下面的值是被定义过的。 %x0 表示一个持续帧 %x1 表示一个文本帧 %x2 表示一个二进制帧 %x3-7 预留给以后的非控制帧 %x8 表示一个连接关闭包 %x9 表示一个ping包 %xA 表示一个pong包 %xB-F 预留给以后的控制帧Mask: 1 bit mask标志位,定义“有效负载数据”是否添加掩码。如果设置为1,那么掩码的键值存在于Masking-Key中,根据5.3节描述,这个一般用于解码“有效负载数据”。所有的从客户端发送到服务端的帧都需要设置这个bit位为1。Payload length: 7 bits, 7+16 bits, or 7+64 bits 以字节为单位的“有效负载数据”长度,如果值为0-125,那么就表示负载数据的长度。如果是126,那么接下来的2个bytes解释为16bit的无符号整形作为负载数据的长度。如果是127,那么接下来的8个bytes解释为一个64bit的无符号整形(最高位的bit必须为0)作为负载数据的长度。多字节长度量以网络字节顺序表示(译注:应该是指大端序和小端序)。在所有的示例中,长度值必须使用最小字节数来进行编码,例如:长度为124字节的字符串不可用使用序列126,0,124进行编码。有效负载长度是指“扩展数据”+“应用数据”的长度。“扩展数据”的长度可能为0,那么有效负载长度就是“应用数据”的长度。Masking-Key: 0 or 4 bytes 所有从客户端发往服务端的数据帧都已经与一个包含在这一帧中的32 bit的掩码进行过了运算。如果mask标志位(1 bit)为1,那么这个字段存在,如果标志位为0,那么这个字段不存在。在5.3节中会介绍更多关于客户端到服务端增加掩码的信息。Payload data: (x+y) bytes “有效负载数据”是指“扩展数据”和“应用数据”。Extension data: x bytes 除非协商过扩展,否则“扩展数据”长度为0 bytes。在握手协议中,任何扩展都必须指定“扩展数据”的长度,这个长度如何进行计算,以及这个扩展如何使用。如果存在扩展,那么这个“扩展数据”包含在总的有效负载长度中。Application data: y bytes 任意的“应用数据”,占用“扩展数据”后面的剩余所有字段。“应用数据”的长度等于有效负载长度减去“扩展应用”长度。基础数据帧协议通过ABNF进行了正式的定义。需要重点知道的是,这些数据都是二进制的,而不是ASCII字符。例如,长度为1 bit的字段的值为%x0 / %x1代表的是一个值为0/1的单独的bit,而不是一整个字节(8 bit)来代表ASCII编码的字符“0”和“1”。一个长度为4 bit的范围是%x0-F的字段值代表的是4个bit,而不是字节(8 bit)对应的ASCII码的值。不要指定字符编码:“规则解析为一组最终的值,有时候是字符。在ABNF中,字符仅仅是一个非负的数字。在特定的上下文中,会根据特定的值的映射(编码)编码集(例如ASCII)”。在这里,指定的编码类型是将每个字段编码为特定的bits数组的二进制编码的最终数据。ws-frame =frame-fin; 长度为1 bitframe-rsv1; 长度为1 bitframe-rsv2; 长度为1 bitframe-rsv3; 长度为1 bitframe-opcode; 长度为4 bitframe-masked; 长度为1 bitframe-payload-length; 长度为7或者7+16或者7+64 bit[frame-masking-key]; 长度为32 bitframe-payload-data; 长度为大于0的n8 bit(其中n>0)frame-fin =%x0,除了以下为1的情况%x1,最后一个消息帧长度为1 bitframe-rsv1 =%x0 / %x1,长度为1 bit,如果没有协商则必须为0frame-rsv2 =%x0 / %x1,长度为1 bit,如果没有协商则必须为0frame-rsv3 =%x0 / %x1,长度为1 bit,如果没有协商则必须为0frame-opcode =frame-opcode-non-controlframe-opcode-controlframe-opcode-contframe-opcode-non-control%x1,文本帧%x2,二进制帧%x3-7,保留给将来的非控制帧长度为4 bitframe-opcode-control%x8,连接关闭%x9,ping帧%xA,pong帧%xB-F,保留给将来的控制帧长度为4 bitframe-masked%x0,不添加掩码,没有frame-masking-key%x1,添加掩码,存在frame-masking-key长度为1 bitframe-payload-length%x00-7D,长度为7 bit%x7E frame-payload-length-16,长度为7+16 bit%x7F frame-payload-length-63,长度为7+64 bitframe-payload-length-16%x0000-FFFF,长度为16 bitframe-payload-length-63%x0000000000000000-7FFFFFFFFFFFFFFF,长度为64 bitframe-masking-key4(%x00-FF),当frame-mask为1时存在,长度为32 bitframe-payload-dataframe-masked-extension-data frame-masked-application-data,当frame-masked为1时frame-unmasked-extension-data frame-unmasked-application-data,当frame-masked为0时frame-masked-extension-data(%x00-FF),保留给将来的扩展,长度为n8,其中n>0frame-masked-application-data(%x00-FF),长度为n8,其中n>0frame-unmasked-extension-data(%x00-FF),保留给将来的扩展,长度为n8,其中n>0frame-unmasked-application-data(%x00-FF),长度为n*8,其中n>05.3 客户端到服务端添加掩码添加掩码的数据帧必须像5.2节定义的一样,设置frame-masked字段为1。掩码值像第5.2节说到的完全包含在帧中的frame-masking-key上。它是用于对定义在同一节中定义的帧负载数据Payload data字段中的包含Extension data和Application data的数据进行添加掩码。掩码字段是一个由客户端随机选择的32bit的值。当准备掩码帧时,客户端必须从允许的32bit值中须知你咋一个新的掩码值。掩码值必须是不可被预测的;因此,掩码必须来自强大的熵源(entropy),并且给定的掩码不能让服务器或者代理能够很容易的预测到后续帧。掩码的不可预测性对于预防恶意应用作者在网上暴露相关的字节数据至关重要。RFC 4086讨论了安全敏感的应用需要一个什么样的合适的强大的熵源。掩码不影响Payload data的长度。进行掩码的数据转换为非掩码数据,或者反过来,根据下面的算法即可。这个同样的算法适用于任意操作方向的转换,例如:对数据进行掩码操作和对数据进行反掩码操作所涉及的步骤是相同的。表示转换后数据的八位字节的i(transformed-octet-i )是表示的原始数据的i(original-octet-i)与索引i模4得到的掩码值(masking-key-octet-j)经过异或操作(XOR)得到的:j = i MOD 4transfromed-octed-i = original-octet-i XOR masking-key-octet-j在规范中定义的位于frame-payload-length字段的有效负载的长度,不包括掩码值的长度。它只是Payload data的长度。如跟在掩码值后面的字节数组的数。5.4 消息分片消息分片的主要目的是允许发送一个未知长度且消息开始发送后不需要缓存的消息。如果消息不能被分片,那么一端必须在缓存整个消息,因此这个消息的长度必须在第一个字节发送前就需要计算出来。如果有消息分片,服务端或者代理可以选择一个合理的缓存长度,当缓存区满了以后,就想网络发送一个片段。第二个消息分片使用的场景是不适合在一个逻辑通道内传输一个大的消息占满整个输出频道的多路复用场景。多路复用需要能够将消息进行自由的切割成更小的片段来共享输出频道。(注意:多路复用的扩展不在这个文档中讨论)。除非在扩展中另有规定,否则帧没有语义的含义。如果客户端和服务的没有协商扩展字段,或者服务端和客户端协商了一些扩展字段,并且代理能够完全识别所有的协商扩展字段,在这些扩展字段存在的情况下知道如何进行帧的合并和拆分,代理就可能会合并或者拆分帧。这个的一个含义是指在缺少扩展字段的情况下,发送者和接收者都不能依赖特定的帧边界的存在。消息分片相关的规则如下:一个未分片的消息包含一个设置了FIN字段(标记为1)的单独的帧和一个除0以外的操作码。一个分片的消息包含一个未设置的FIN字段(标记为0)的单独的帧和一个除0以外的操作码,然后跟着0个或者多个未设置FIN字段的帧和操作码为0的帧,然后以一个设置了FIN字段以及操作码为0的帧结束。一个分片的消息内容按帧顺序组合后的payload字段,是等价于一个单独的更大的消息payload字段中包含的值;然而,如果扩展字段存在,因为扩展字段定义了Extension data的解析方式,因此前面的结论可能不成立。例如:Extension data可能只出现在第一个片段的开头,并适用于接下来的片段,或者可能每一个片段都有Extension data,但是只适用于特定的片段。在Extension data不存在时,下面的示例演示了消息分片是如何运作的。示例:一个文本需要分成三个片段进行发送,第一个片段包含的操作码为0x1并且未设置FIN字段,第二个片段的操作码为0x0并且未设置FIN字段,第三个片段的操作码为0x0并且设置了FIN字段。控制帧(见5.5节)可能被插入到分片消息的中间。控制帧不能被分片。消息片段必须在发送端按照顺序发送给接收端。除非在扩展中定义了这种嵌套的逻辑,否则一条消息分的片不能与另一条消息分的片嵌套传输。终端必须有能力来处理在分片的消息中的控制帧。发送端可能会创建任意大小的非控制消息片段。客户端和服务端必须同时支持分片和不分片消息。控制帧不能被分片,并且代理不允许改变控制帧的片段。如果有保留字段被使用并且代理不能理解这些字段的值时,那么代理不能改变消息的片段。在扩展字段已经被协商过,但是代理不知道协商扩展字段的具体语义时,代理不能改变任意消息的片段。同样的,扩展不能看到WebSocket握手(并且得不到通知内容)导致WebSocket的连接禁止改变连接过程中任意的消息片段。作为这些规则的结论,所有的消息片段都是同类型的,并且设置了第一个片段的操作码(opccode)字段。控制帧不能被分片,所有的消息分片类型必须是文本或者二进制,或者是保留的任意一个操作码。注:如果控制帧没有被打断,心跳(ping)的等待时间可能会变很长,例如在一个很大的消息之后。因此,在分片的消息传输中插入控制帧是有必要的。实践说明:如果扩展字段不存在,接收者不需要使用缓存来存储下整个消息片段来进行处理。例如:如果使用一个流式API,再收到部分帧的时候就可以将数据交给上层应用。然而,这个假设对以后所有的WebSocket扩展可能不一定成立。5.5 控制帧控制帧是通过操作码最高位的值为1来进行区分的。当前已经定义的控制帧操作码包括0x8(关闭),0x9(心跳Ping)和0xA(心跳Pong)。操作码0xB-0xF没有被定义,当前被保留下来做为以后的控制帧。控制帧是用于WebSocket的通信状态的。控制帧可以被插入到消息片段中进行传输。所有的控制帧必须有一个126字节或者更小的负载长度,并且不能被分片。5.5.1 关闭(Close)控制帧的操作码值是0x8。关闭帧可能包含内容(body)(帧的“应用数据”部分)来表明连接关闭的原因,例如终端的断开,或者是终端收到了一个太大的帧,或者是终端收到了一个不符合预期的格式的内容。如果这个内容存在,内容的前两个字节必须是一个无符号整型(按照网络字节序)来代表在7.4节中定义的状态码。跟在这两个整型字节之后的可以是UTF-8编码的的数据值(原因),数据值的定义不在此文档中。数据值不一定是要人可以读懂的,但是必须对于调试有帮助,或者能传递有关于当前打开的这条连接有关联的信息。数据值不保证人一定可以读懂,所以不能把这些展示给终端用户。从客户端发送给服务端的控制帧必须添加掩码,具体见5.3节。应用禁止在发送了关闭的控制帧后再发送任何的数据帧。如果终端收到了一个关闭的控制帧并且没有在以前发送一个关闭帧,那么终端必须发送一个关闭帧作为回应。(当发送一个关闭帧作为回应时,终端通常会输出它收到的状态码)响应的关闭帧应该尽快发送。终端可能会推迟发送关闭帧直到当前的消息都已经发送完成(例如:如果大多数分片的消息已经发送了,终端可能会在发送关闭帧之前将剩余的消息片段发送出去)。然而,已经发送关闭帧的终端不能保证会继续处理收到的消息。在已经发送和收到了关闭帧后,终端认为WebSocket连接以及关闭了,并且必须关闭底层的TCP连接。服务端必须马上关闭底层的TCP连接,客户端应该等待服务端关闭连接,但是也可以在收到关闭帧以后任意时间关闭连接。例如:如果在合理的时间段内没有收到TCP关闭指令。如果客户端和服务端咋同一个时间发送了关闭帧,两个终端都会发送和接收到一条关闭的消息,并且应该认为WebSocket连接已经关闭,同时关闭底层的TCP连接。5.5.2 心跳Ping心跳Ping帧包含的操作码是0x9。关闭帧可能包含“应用数据”。如果收到了一个心跳Ping帧,那么终端必须发送一个心跳Pong 帧作为回应,除非已经收到了一个关闭帧。终端应该尽快恢复Pong帧。Pong帧将会在5.5.3节讨论。终端可能会在建立连接后与连接关闭前中间的任意时间发送Ping帧。注意:Ping帧可能是用于保活或者用来验证远端是否仍然有应答。5.5.3 心跳Pong心跳Ping帧包含的操作码是0xA。5.5.2节详细说明了Ping帧和Pong帧的要求。作为回应发送的Pong帧必须完整携带Ping帧中传递过来的“应用数据”字段。如果终端收到一个Ping帧但是没有发送Pong帧来回应之前的pong帧,那么终端可能选择用Pong帧来回复最近处理的那个Ping帧。Pong帧可以被主动发送。这会作为一个单项的心跳。预期外的Pong包的响应没有规定。5.6 数据帧数据帧(例如非控制帧)的定义是操作码的最高位值为0。当前定义的数据帧操作吗包含0x1(文本)、0x2(二进制)。操作码0x3-0x7是被保留作为非控制帧的操作码。数据帧会携带应用层/扩展层数据。操作码决定了携带的数据解析方式:文本“负载字段”是用UTF-8编码的文本数据。注意特殊的文本帧可能包含部分UTF-8序列;然而,整个消息必须是有效的UTF-8编码数据。重新组合消息后无效的UTF-8编码数据处理见8.1节。二进制“负载字段”是任意的二进制数据,二进制数据的解析仅仅依靠应用层。5.7 示例一个单帧未添加掩码的文本消息0x81 0x05 0x48 0x65 0x6c 0x6c 0x6f (内容为"Hello")一个单帧添加掩码的文本消息0x81 0x85 0x37 0xfa 0x21 0x3d 0x7f 0x9f 0x4d 0x51 0x58 (内容为Hello")一个分片的未添加掩码的文本消息0x01 0x03 0x48 0x65 0x6c (内容为"Hel")0x80 0x02 0x6c 0x6f (内容为”lo")未添加掩码的Ping请求和添加掩码的Ping响应(译者注:即Pong)0x89 0x05 0x48 0x65 0x6c 0x6c 0x6f (包含内容为”Hello", 但是文本内容是任意的)0x8a 0x85 0x37 0xfa 0x21 0x3d 0x7f 0x9f 0x4d 0x51 0x58 (包含内容为”Hello", 匹配ping的内容)256字节的二进制数据放入一个未添加掩码数据帧0x82 0x7E 0x0100 [256 bytes of binary data]64KB二进制数据在一个非掩码帧中0x82 0x7F 0x0000000000010000 [65536 bytes of binary data]5.8 扩展性这个协议的设计初衷是允许扩展的,可以在基础协议上增加能力。终端的连接必须在握手的过程中协商使用的所有扩展。在规范中提供了从0x3-0x7和0xB-0xF的操作码,在数据帧Header中的“扩展数据”字段、frame-rsv1、frame-rsv2、frame-rsv3字段都可以用于扩展。扩展的协商讨论将在以后的9.1节中详细讨论。下面是一些符合预期的扩展用法。下面的列表不完整,也不是规范中内容。“扩展数据”可以放置在“负载数据“中的应用数据”之前的位置。保留的字段可以在每一帧需要时被使用。保留的操作码的值可以被定义。如果需要更多的操作码,那么保留的操作码字段可以被定义。保留的字段或者“扩展”操作码可以在“负载数据”之中的分配额外的位置来定义,这样可以定义更大的操作码或者更多的每一帧的字段。 ...

February 20, 2019 · 2 min · jiezi

WebSocket 协议 RFC 文档(全中文翻译)

概述经过半年的捣鼓,终于将 WebSocket 协议(RFC6455)全篇翻译完成。现在将所有章节全部整理到一篇文章中,方便大家阅读。如果大家想看具体的翻译文档,可以去我的GitHub中查看。具体章节如下:【译】WebSocket 协议——摘要( Abstract )【译】WebSocket 协议第一章——介绍( Introduction )【译】WebSocket 协议第二章——一致性要求( Conformance Requirements )【译】WebSocket 协议第三章——WebSocket网址( WebSocket URIs )【译】WebSocket 协议第四章——连接握手( Opening Handshake )【译】WebSocket 协议第五章——数据帧(Data Framing)【译】WebSocket 协议第六章——发送与接收消息(Sending and Receiving Data)【译】 WebSocket 协议第七章——关闭连接(Closing the Connection)【译】 WebSocket 协议第八章——错误处理(Error Handling)【译】 WebSocket 协议第九章——扩展(Extension)【译】 WebSocket 协议第十章——安全性考虑(Security Considerations)【译】 WebSocket 协议第十一章——IANA 注意事项(IANA Considerations)【译】 WebSocket 协议第十二章——使用其他规范中的WebSocket协议WebSocket 协议第十三章——致谢(略)WebSocket 协议第十四章——参考文献(略)总结通过翻译一篇文档,能够从头到尾精细的读完一篇文章,比较适合需要精读的文章和内容。大家有相关类型的需要,建议大家可以尝试下。

February 20, 2019 · 1 min · jiezi

【译】 WebSocket 协议第十一章——IANA 注意事项(IANA Considerations)

概述本文为 WebSocket 协议的第十一章,本文翻译的主要内容为 WebSocket 的 IANA 相关注意事项。IANA 注意事项(协议正文)11.1 注册新 URI 协议11.1.1 注册 “ws” 协议ws URI 定义了 WebSocket 服务器和资源名称。URI 协议名称ws状态永久URI 协议语法使用 ABNF (RFC5234)语法和来自 URI 规范 RFC3986 的 ABNF 终端:“ws:” “//” authority path-abempty [ “?” query ]path-abempty 和 query RFC3986 部分组成了发送给服务端的资源名称,来标记需要的服务类型。其他的部分在 RFC3986 中定义了含义。URI 协议含义这个方案的唯一操作就是使用 WebSocket 协议打开一个连接。编码注意事项按照上面定义的语法排除的主机部分中的字符必须按照 RFC3987 中的规定从 Unicode 转换为 ASCII 或其替换字符。为了实现基于方案的规范化,国际化网域名称(IDN)主机组件的形式与 punycode 码之间的相互转化认为是等价的(见 RFC3987 第 5.3.3 节)。除了由上面语法排除的字符外,其他组件的字符在第一次转换为 UTF-8 字符时,必须从 Unicode 码转化到 ASCII 码,然后使用百分比编码格式替换对应的定义在 URI RFC3896 字符和国际化资源标识符(IRI) RFC3987 规范。应用/协议使用这个 URI 协议规范WebSocket Protocol互操作性注意事项使用 WebSocket 时需要使用 1.1 或者更高版本的 HTTP 协议。安全性注意事项见”安全性注意事项”一节。联系HYBI WG <hybi@ietf.org\&gt;作者/更改控制者IETF <iesg@ietf.org\&gt;关联RFC 645511.1.2 注册 “wss”协议一个 wss 的 URI 定义了一个 WebSocket 服务器和资源名称,表明通过这个链接的传输需要通过 TLS(包含标准的 TLS 能力例如数据保密性和完整性以及终端认证)来进行保护。URI 协议名称wss状态永久URI 协议语法使用 ABNF (RFC5234)语法和来自 URI 规范 RFC3986 的 ABNF 终端:“wss:” “//” authority path-abempty [ “?” query ]path-abempty 和 query RFC3986 部分组成了发送给服务端的资源名称,来标记需要的服务类型。其他的部分在 RFC3986 中定义了含义。URI 协议含义这个方案的唯一操作就是使用 WebSocket 协议打开一个连接,通过 TLS 加密。编码注意事项按照上面定义的语法排除的主机部分中的字符必须按照 RFC3987 中的规定从 Unicode 转换为 ASCII 或其替换字符。为了实现基于方案的规范化,国际化网域名称(IDN)主机组件的形式与 punycode 码之间的相互转化认为是等价的(见 RFC3987 第 5.3.3 节)。除了由上面语法排除的字符外,其他组件的字符在第一次转换为 UTF-8 字符时,必须从 Unicode 码转化到 ASCII 码,然后使用百分比编码格式替换对应的定义在 URI RFC3896 字符和国际化资源标识符(IRI) RFC3987 规范。应用/协议使用这个 URI 协议规范WebSocket Protocol互操作性注意事项使用 WebSocket 时需要使用 1.1 或者更高版本的 HTTP 协议。安全性注意事项见”安全性注意事项”一节。联系HYBI WG <hybi@ietf.org\&gt;作者/更改控制者IETF <iesg@ietf.org\&gt;关联RFC 645511.2 注册“WebSocket”协议升级关键值这一节描述一个在 HTTP 升级凭证注册的关键值,在 RFC2817 定义。凭证名称WebSocket作者/更改控制者IETF <iesg@ietf.org\&gt;关联RFC 645511.3 注册新的 HTTP 头字段Sec-WebSocket-Key这一节描述一个注册在永久消息头字段名称中的头字段,在 RFC3864 定义。头字段名称Sec-WebSocket-Key应用协议http状态标准作者/更改控制者IETF说明文档RFC 6455关联信息这个头字段只用于 WebSocket 开始握手。Sec-WebSocket-Key 头字段是用在 WebSocket 开始握手阶段。它是通过客户端发送给服务端,这部分信息用于服务端证明收到一个有效的 WebSocket 握手操作的认证。这可以帮助确认服务端不会接收可能被用来向 WebSocket 服务任意发送数据的非 WebSocket 客户端的连接(例如 HTTP 客户端)。Sec-WebSocket-Key 头字段禁止在一个 HTTP 请求中出现多次。11.3.2 Sec-WebSocket-Extensions这一节描述一个注册在永久消息头字段名称中的头字段,在 RFC3864 定义。头字段名称Sec-WebSocket-Extensions应用协议http状态标准作者/更改控制者IETF说明文档RFC 6455关联信息这个头字段只用于 WebSocket 开始握手。Sec-WebSocket-Extensions 头字段是用于 WebSocket 开始握手阶段。它最开始是通过客户端发送给服务端,然后通过服务端发送给客户端,来对一个在连接中的协议级的扩展进行协商。Sec-WebSocket-Extensions 头字段可能会在一个 HTTP 请求中出现多次(这个逻辑是等价于一个单独的 Sec-WebSocket-Extensions 头字段包含所有值)。然而,Sec-WebSocket-Extensions 头字段在 HTTP 响应中不能出现超过1次。11.3.3 Sec-WebSocket-Accept这一节描述一个注册在永久消息头字段名称中的头字段,在 RFC3864 定义。头字段名称Sec-WebSocket-Accept应用协议http状态标准作者/更改控制者IETF说明文档RFC 6455关联信息这个头字段只用于 WebSocket 开始握手。Sec-WebSocket-Accpet 头字段是用于 WebSocket 开始握手阶段。它是通过服务端发送给客户端,用来确认服务端会初始化一个 WebSocket 连接。Sec-WebSocket-Accpet 头在一个 HTTP 响应中不允许出现超过1次。11.3.4 Sec-WebSocket-Protocol这一节描述一个注册在永久消息头字段名称中的头字段,在 RFC3864 定义。头字段名称Sec-WebSocket-Protocol应用协议http状态标准作者/更改控制者IETF说明文档RFC 6455关联信息这个头字段只用于 WebSocket 开始握手。Sec-WebSocket-Protocol 头字段是用于 WebSocket 开始握手阶段。它是从客户端发送给服务端,然后从服务端返回给服务端来确认连接的子协议。这个机制能够让双方选择一个子协议,同时向服务端确认可以支持这个子协议。Sec-WebSocket-Protocol 头字段可以在一个 HTTP 请求中出现多次(这个逻辑是等价于一个单独的 Sec-WebSocket-Protocol 头字段包含所有值)。然而, Sec-WebSocket-Protocol 头字段在 HTTP 响应中不能出现超过1次。11.3.5 Sec-WebSocket-Version这一节描述一个注册在永久消息头字段名称中的头字段,在 RFC3864 定义。头字段名称Sec-WebSocket-Version应用协议http状态标准作者/更改控制者IETF说明文档RFC 6455关联信息这个头字段只用于 WebSocket 开始握手。Sec-WebSocket-Version 头字段是用于 WebSocket 开始握手阶段。它是从客户端发送给服务端来表示这个连接使用的协议版本。它能够让服务端正确的进行开始握手和接下来的数据发送,以及在服务端不能够在一个安全方式下正确解析数据时关闭连接。Sec-WebSocket-Version 头字段在服务端理解的版本不匹配从客户端收到的版本导致的 WebSocket 握手失败时,也从服务端发送给客户端。在这种情况下,这个头字段包含服务端支持的协议版本。注意这里不期望更高的版本号需要向前兼容低版本号。Sec-WebSocket-Version 头字段可以在一个 HTTP 响应中出现多次(这个逻辑等价于一个单独的Sec-WebSocket-Version包含所有的值)。然而,Sec-WebSocket-Version 头字段不能在 HTTP 请求中出现超过1次。11.4 WebSocket 扩展名注册表这个规范根据RFC5526中规定的原则为 WebSocket 协议创建了一个新的 IANA 注册表,用于 WebSocket 扩展名称。作为此注册表的一部分,IANA 包含了一下信息:扩展定义这个扩展的定义,将在 Sec-WebSocket-Extensions 头字段中使用,在此规范的第 11.3.2 节注册。这个值必须满足在此规范第 9.1 节中定义的扩展凭证要求。扩展通用名扩展名称,一般称为扩展名。扩展定义对定义与 WebSocket 协议一起使用的扩展的文档的引用。已知不兼容扩展已知的不兼容的扩展定义列表。WebSocket 扩展名是受到“先到先得” IANA 注册政策 RFC5226 限制的。这个注册表里没有初始值。11.5 WebSocket 子协议名注册表这个规范根据RFC5526中规定的原则为 WebSocket 协议创建了一个新的 IANA 注册表,用于 WebSocket 扩展名称。作为此注册表的一部分,IANA 包含了一下信息:子协议定义子协议的标识符将在 Sec-WebSocket-Protocol 头字段中使用,在此规范的第 11.3.4 节中注册。这个之必须符合此规范第 4.1 节中的第 10 项要求—换句话说,这个之必须是 RFC2616 中定义的凭证。子协议通用名子协议名称,通常被称为子协议。子协议定义对定义与 WebSocket 协议一起使用的子协议的文档的引用。WebSocket 子协议名是受到“先到先得” IANA 注册政策 RFC5226 限制的。11.6 WebSocket 版本号注册表该规范根据 RFC5226 中规定的原则为 WebSocket 协议创建了一个新的 IANA 注册表,用于 WebSocket 版本号。作为此注册表的一部分,IANA 包含了一下信息:版本号版本号是用于在此规范第 4.1 节中制定的 Sec-WebSocket-Version 字段。这个值必须是一个范围在 0 到 255(含)之间的非负整数。参考RFC 请求新版本号或者带有版本号的草稿名称(见下文)。状态临时或者标准。见下面描述。版本号被指定为“临时”或者“标准”。“标准”的版本号被记录在 RFC 文档中,被认为是一个重大、稳定的 WebSocket 协议版本,例如定义在这个 RFC 中的版本。“标准”版本号是受到 “IETF 评论” IANA 注册政策 RFC5526 限制的。“临时”版本是记录在网络草案和用于帮助实现者识别 WebSocket 协议的已部署版本并与之互操作,例如开发后但是发布前的 RFC 版本。“临时”版本号是受到 “专家评论” IANA 注册政策 RFC5526 、 最初的指定专家如HYBI 工作组主席(或者,如果工作组关闭,那么是 IETF 应用领域的领域主任)限制的。IANA 已经向注册表中添加了如下的初始值。版本号引用状态0draft-ietf-hybi-thewebsocketprotocol-00临时1draft-ietf-hybi-thewebsocketprotocol-01临时2draft-ietf-hybi-thewebsocketprotocol-02临时3draft-ietf-hybi-thewebsocketprotocol-03临时4draft-ietf-hybi-thewebsocketprotocol-04临时5draft-ietf-hybi-thewebsocketprotocol-05临时6draft-ietf-hybi-thewebsocketprotocol-06临时7draft-ietf-hybi-thewebsocketprotocol-07临时8draft-ietf-hybi-thewebsocketprotocol-08临时9保留 10保留 11保留 12保留 13RFC6455 11.7 WebSocket 关闭码注册表该规范根据 RFC5226 中规定的原则为 WebSocket 协议创建了一个新的 IANA 注册表,用于 WebSocket 关闭码。作为此注册表的一部分,IANA 包含了一下信息:状态码状态码表示定义在此文档第 7.4 节中 WebSocket 连接关闭的原因。这个状态码是一个在 1000 到 4999(含)之间的整数。含义状态码含义。每一个状态码有一个特定的含义。联系保留状态代码的实体的联系人。关联请求状态码的固定文档和含义定义。对于 1000-2999 的状态码来说是必须的,推荐使用 3000-3999 范围的状态码。WebSocket 关闭状态码根据它的范围有不同的注册要求。使用在这个协议和它的后续的版本或者扩展的请求版本号是受到“标准行为”、“规范要求”(这意味着“指定专家”)或者“IESG 评论” IANA注册表政策限制的,应该在 1000-2999 范围内授权。被类库、框架和应用使用的状态码是受限制于“先到先得”IANA 注册表政策,应该在 3000-3999 范围内授权。4000-4999 范围的状态码是私用的。请求应指明它们是否正在通过扩展、类库、框架或者应用使用请求WebSocket协议的状态代码(或者将来的协议的版本)。IANA已经向注册表中添加了如下初始值。状态码含义联系人关联1000正常关闭hybi@ietf.orgRFC64551001离开hybi@ietf.orgRFC64551002协议错误hybi@ietf.orgRFC64551003不支持的数据类型hybi@ietf.orgRFC64551004保留hybi@ietf.orgRFC64551005没有收到状态码hybi@ietf.orgRFC64551006异常关闭hybi@ietf.orgRFC64551007无效的帧数据hybi@ietf.orgRFC64551008违反政策hybi@ietf.orgRFC64551009消息太大hybi@ietf.orgRFC64551010强制扩展hybi@ietf.orgRFC64551011内部服务器错误hybi@ietf.orgRFC64551015TLS握手hybi@ietf.orgRFC645511.8 WebSocket 操作码注册表该规范根据 RFC5226 中规定的原则为 WebSocket 协议创建了一个新的 IANA 注册表,用于 WebSocket 操作码。作为此注册表的一部分,IANA 包含了一下信息:操作码操作码表示定义在第 5.2 节中的 WebSocket 帧的帧类型。操作码是一个范围在 0 到 15(含)的数字。含义操作码的含义。关联请求操作码的规范。WebSocket 操作码是受到“标准行为”IANA 注册表政策 RFC5266 限制的。IANA 已经向注册表中注册了一下初始值。操作码含义关联0连续帧RFC64551文本帧RFC64552二进制帧RFC64558连接关闭帧RFC64559心跳 Ping 帧RFC645510心跳 Pong 帧RFC645511.9 WebSocket 帧头 bit 字段注册表该规范根据 RFC5226 中规定的原则为 WebSocket 协议创建了一个新的 IANA 注册表,用于 WebSocket 帧头 bit 字段。这个注册表控制分配的 bit 位为第 5.2 节中的 RSV1、RSV2 和 RSV3。这些 bit 位是保留给将来的版本或者文档中的扩展。WebSocket 帧头 bit 字段是受到“标准行为”IANA 注册表政策 RFC5266 限制的。 ...

February 20, 2019 · 3 min · jiezi

【译】 WebSocket 协议第十二章——使用其他规范中的WebSocket协议

概述本文为 WebSocket 协议的第十二章,本文翻译的主要内容为如何使用其他规范中的 WebSocket 协议。使用其他规范中的WebSocket协议(协议正文)WebSocket协议旨在由另一规范使用,以提供动态作者定义内容的通用机制。例如,在定义脚本 API 的规范中定义 WebSocket 协议。例如一个规范首先需要建立 WebSocket 连接,提供该算法:目标资源,包含一个主机名(host)和一个端口(port)。资源名称,允许在一个主机和端口上识别多个服务。安全标记,当这个值为 true 时,连接应该被加密,如果为 false 时则不需要。原始RFC6454的ASCII序列化,负责连接。可选的,基于 WebSocket 连接的通过一个字符串定义的协议。主机、端口、资源名称和安全标记通常是使用解析 WebSocket URI 组件,通过 URI 来获取。如果 URI 中没有指定这些 WebSocket 字段,那么这个解析将失败。如果在任意时间连接被关闭了,那么规范需要使用关闭 WebSocket 连接算法(第 7.1.1 节)。第 7.1.4 节定义了什么时候WebSocket 连接关闭。当连接打开时,文档需要处理收到一条 WebSocket 消息(第 6.2 节)的场景。为了向已经建立的连接发送一些数据,文档需要处理发送 WebSocket 消息(第 6.1 节)。

February 20, 2019 · 1 min · jiezi

【译】 WebSocket 协议第十章——安全性考虑(Security Considerations)

概述本文为 WebSocket 协议的第九章,本文翻译的主要内容为 WebSocket 安全性相关内容。10 安全性考虑(协议正文)这一章描述了一些 WebSocket 协议的可用的安全性考虑。这一章的小节描述了这些特定的安全性考虑。10.1 非浏览器客户端WebSocket 协议防止在受信任的应用例如 Web 浏览器中执行的恶意 JavaScript 代码,例如通过检查Origin头字段(见下面)。见第 1.6 节去了解更多详情。这种假设在更有能力的客户端的情况下不成立。这个协议可以被网页中的脚本使用,也可以通过宿主直接使用。这些宿主是代表自己的利益的,因此可以发送假的Origin头字段来欺骗服务端。因此服务端对于他们正在和已知的源的脚本直接通信的假设需要消息,并且必须认为他们可能通过没有预期的方式访问。特别地,服务端不应该相信任何输入都是有效的。示例:如果服务端使用输入的内容作为一部分的 SQL 查询语句,所有的输入文本都必须在传递给 SQL 服务器时进行编码,以免服务端受到 SQL 注入攻击。10.2 源考虑只处理特定站点,不打算处理任何 Web 页面的数据服务器应该验证Origin字段是否是他们预期的。如果服务端收到的源字段是不接受的,那么他应该通过包含 HTTP 禁止状态码为 403 的请求响应作为 WebSocket 握手的响应。当不信任的一方是 JavaScript 应用作者并存在受信任的客户端中运行时,Origin字段可以避免出现这种攻击的情况。客户端可以连接到服务端,通过协议中的Origin字段,确定是否开放连接的权限给 JavaScript 应用。这么做的目的不是组织非浏览器应用建立连接,而是保证在受信任的浏览器中可能运行的恶意 JavaScript 代码并不会构建一个假的 WebSocket 握手。10.3 基础设施攻击(添加掩码)除了终端可能会成为通过 WebSocket 被攻击的目标之外,网络基础设施的另外一部分,例如代理,也有可能是攻击的对象。这个协议发展后,通过一个实验验证了部署在外部的缓存服务器由于一系列在代理上面的攻击导致投毒。一般形式的攻击就是在攻击者控制下建立一个与服务端的连接,实现一个与 WebSocket 协议建立连接相似的 HTTP UPGRADE 连接,然后通过升级以后的连接发送数据,看起来就像是针对已知的特定资源(在攻击中,这可能类似于广泛部署的脚本,用于跟踪广告服务网络上的点击或资源)进行 GET 请求。远端服务器可能会通过一些看上去像响应数据的来响应假的 GET 请求,然后这个响应就会按照非零百分比的已部署中介缓存,因此导致缓存投毒。这个攻击带来的影响就是,如果一个用户可以正常的访问一个攻击者控制的网网站,那么攻击者可以针对这个用户进行缓存投毒,在相同缓存的后面其他用户会运行其他源的恶意脚本,破坏 Web 安全模型。为了避免对中介服务的此类攻击,使用不符合 HTTP 的数据帧中为应用程序的数据添加前缀是不够的,我们不可能详细的检查和测试每一个不合标准的中介服务有没有跳过这种非 HTTP 帧,或者对帧载荷处理不正确的情况。因此,采用的防御措施是对客户端发送给服务端的所有数据添加掩码,这样的话远端的脚本(攻击者)就不能够控制发送的数据如何出现在线路上,因此就不能够构造一条被中介误解的 HTPT请求。客户端必须为每一帧选择一个新的掩码值,使用一个不能够被应用预测到的算法来进行传递数据。例如,每一个掩码值可以通过一个加密强随机数生成器来生成。如果相同的值已经被使用过或者已经存在一种方式能够判断出下一个值如何选择时,攻击这个可以发送一个添加了掩码的消息,来模拟一个 HTTP 请求(通过在线路上接收攻击者希望看到的消息,使用下一个被使用的掩码值来对数据进行添加掩码,当客户端使用它时,这个掩码值可以有效地反掩码数据)。当从客户端开始传递第一帧时,这个帧的有效载荷(应用程序提供的数据)就不能够被客户端应用程序修改,这个策略是很重要的。否则,攻击者可以发送一个都是已知值(例如全部为 0)的初始值的很长的帧,计算收到第一部分数据时使用过的掩码,然后修改帧中尚未发送的数据,以便在添加掩码时显示为 HTTP 请求。(这与我们在之前的段落中描述的使用已知的值和可预测的值作为掩码值,实际上是相同的问题。)如果另外的数据已经发送了,或者要发送的数据有所改变,那么新的数据或者修改的数据必须使用一个新的数据帧进行发送,因此也需要选择一个新的掩码值。简短来说,一旦一个帧的传输开始后,内容不能够被远端的脚本(应用)修改。受保护的威胁模型是客户端发送看似HTTP请求的数据的模型。因此,从客户端发送给服务端的频道数据需要添加掩码值。从服务端到客户端的数据看上去像是一个请求的响应,但是,为了完成一次请求,客户端也需要可以伪造请求。因此,我们不认为需要在双向传输上添加掩码。(服务端发送给客户端的数据不需要添加掩码)尽管通过添加掩码提供了保护,但是不兼容的 HTTP 代理仍然由于客户端和服务端之间不支持添加掩码而受到这种类型的攻击。10.4 指定实现的限制在从多个帧重新组装后,对于帧大小或总消息大小具有实现和必须避免自己超过相关的多平台特定限制带来的影响。(例如:一个恶意的终端可能会尝试耗尽对端的内存或者通过发送一个大的帧(例如:大小为 2 ** 60)或发送一个长的由许多分片帧构成的流来进行拒绝服务攻击)。这些实现应该对帧的大小和组装过后的包的总大小有一定的限制。10.5 WebSocket 客户端认证这个协议在 WebSocket 握手时,没有规定服务端可以使用哪种方式进行认证。WebSocket 服务器可以使用任意 HTTP 服务器通用的认证机制,例如: Cookie、HTTP 认证或者 TLS 认证。10.6 连接保密性和完整性连接保密性是基于运行 TLS 的 WebSocket 协议(wss 的 URLs)。WebSocket 协议实现必须支持 TLS,并且应该在与对端进行数据传输时使用它。如果在连接中使用 TLS,TLS带来的连接的收益非常依赖于 TLS 握手时的算法的强度。例如,一些 TLS 的加密算法不提供连接保密性。为了实现合理登记的保护措施,客户端应该只使用强 TLS 算法。“Web 安全:用户接口指南”(W3C.REC-wsc-ui-20100812)讨论了什么是强 TLS 算法。RFC5246 的附录 A.5和附录 D.3提供了另外的指导。10.7 处理无用数据传入的数据必须经过客户端和服务端的认证。如果,在某个时候,一个终端面对它无法理解的数据或者违反了这个终端定义的输入安全规范和标准,或者这个终端在开始握手时没有收到对应的预期值时(在客户端请求中不正确的路径或者源),终端应该关闭 TCP 连接。如果在成功的握手后收到了无效的数据,终端应该在进入关闭 WebSocket流程前,发送一个带有合适的状态码(第 7.4 节)的关闭帧。使用一个合适的状态码的关闭帧有助于诊断这个问题。如果这个无效的数据是在 WebSocket 握手时收到的,服务端应该响应一个合适的 HTTP 状态码(RFC2616)。使用错误的编码来发送数据是一类通用的安全问题。这个协议指定文本类型数据(而不是二进制或者其他类型)的消息使用 UTF-8 编码。虽然仍然可以得到长度值,但实现此协议的应用程序应使用这个长度来确定帧实际结束的位置,发送不合理的编码数据仍然会导致基于此协议构建的应用程序可能会导致从数据的错误解释到数据丢失或潜在的安全漏洞出现。10.8 在 WebSocket 握手中使用 SHA-1在这个文档中描述的 WebSocket 握手协议是不依赖任意 SHA-1 的安全属性,流入抗冲击性和对第二次前映像攻击的抵抗力(就像 RFC4270 描述的一样)。 ...

February 20, 2019 · 1 min · jiezi

【译】 WebSocket 协议第九章——扩展(Extension)

概述本文为 WebSocket 协议的第九章,本文翻译的主要内容为 WebSocket 扩展相关内容。扩展(协议正文)WebSocket 可以请求该规范中提到的扩展,WebSocket 服务端可以接受其中一些或者所有的客户端请求的扩展。服务端禁止响应客户端没有请求过的扩展。如果扩展参数需要在客户端和服务端之间进行协商,这些参数必须根据参数所应用的扩展的规范来选择。9.1 协商扩展客户端通过 Sec-WebSocket-Extensions 请求头字段来请求扩展,请求头字段遵守 HTTP 的规则,它的值是通过 ABNF 定义的。注意这一节是通过 ABNF 语法/规则,包括“implied *LWS rule”。如果我们客户端或者服务端在协商扩展收到了一个没有符合下面的 ABNF 规则的值,接收到错误的数据的这一方需要立刻让 WebSocket 关闭连接。Sec-WebSocket-Extensions = extension-listextension-list = 1#extensionextension = extension-token *( “;” extension-param )extension-token = registered-tokenregistered-token = tokenextension-param = token [ “=” (token | quoted-string) ] ; 使用带引号的语法变量时,在引号字符后面的变量的值必须符合token变量 ABNF规范。注意,就像其他的 HTTP 请求头字段一样,这个请求头字段可以被切割成几行或者几行合并成一行。因此,下面这两段是等价的:Sec-WebSocket-Extensions: fooSec-WebSocket-Extensions: bar; baz=2是等价于:Sec-WebSocket-Extensions: foo, bar; baz=2任何一个扩展凭证都必须是一个注册过的凭证。(见底 11.4 节)。扩展所使用的任何参数都必须是定义给这个扩展的。注意,客户端只能建议使用任意存在的扩展而不能使用它们,除非服务端表示他们希望使用这个扩展。注意扩展的顺序是重要的。多个扩展中的任意的互相作用都可以被定义在这个定义扩展的文档中。在没有此类定义的情况下,客户端在其请求中列出的头字段表示其希望使用的头字段的首选项,其中列出的第一个选项是最可取的。服务器在响应中列出的扩展表示连接实际使用的扩展。如果扩展修改了数据或者帧,对数据的操作顺序应该被假定为和链接开始握手的服务端响应的列举的扩展中的顺序相同。例如,如果有两个扩展”foo”和”bar”,并且服务端发送的头字段Sec-WebSocket-Extensions的值为”foo,bar”,那么对数据的操作顺序就是bar(foo(data)),是对数据本身的更改(例如压缩)或者“堆叠”的帧的更改。可接受的扩展标头字段的非规范性示例(请注意,长线被折叠以便于阅读)如下:Sec-WebSocket-Extensions: deflate-streamSec-WebSocket-Extensions: mux; max-channels=4; flow-control, deflate-streamSec-WebSocket-Extensions: private-extension服务端接受一个或者多个扩展字段,这些扩展字段是包含客户端请求的Sec-WebSocket-Extensions头字段扩展中的。任何通过服务端构成的能够响应来自客户端请求的参数的扩展参数,将由每个扩展定义。9.2 已知扩展扩展为实现方式提供了一个机制,即选择使用附加功能协议。这个文档中不定义任何扩展,但是实现跨越使用单独定义的扩展。

February 20, 2019 · 1 min · jiezi

【译】 WebSocket 协议第七章——关闭连接(Closing the Connection)

概述本文为 WebSocket 协议的第七章,本文翻译的主要内容为 WebSocket 连接关闭相关内容。关闭连接(协议正文)7.1 定义7.1.1 关闭 WebSocket 连接要关闭 WebSocket 连接,终端需要关闭底层的 TCP 连接。终端需要使用一个方法来干净的关闭TCP连接,还有 TLS 会话,如果可能的话,抛弃后面可能受到的任意字符。终端可能会在需要的时候,通过任何方式来关闭连接,例如在收到攻击时。在底层的 TCP 连接中,通常大多数情况下,服务端应该先关闭,所以是服务端而不是客户端保持 TIME_WAIT 状态(因为客户端先关闭的话,这会阻止服务端在2 MSL 内重新打开这条连接,而如果服务器处于 TIME_WAIT 状态下,如果收到了一个带有更大序列号的新的 SYN 包时,也能够立即响应重新打开连接,从而不会对服务器产生影响)。反常情况(例如在合理的时间后,服务端收到一个 TCP 关闭包)下,客户端应该开始关闭 TCP 连接。像这样的,当服务端进入关闭 WebSocket 连接状态时,它应该立刻准备关闭 TCP 连接,然后当客户端客户端准备关闭连接时,他应该等待服务端的 TCP 关闭包。用 C 语言的 Berkeley socket 作为例子来展示如何彻底的关闭连接,一端需要用 SHUP_WR 调用 shutdown() 方法,调用 recv() 直到获得一个值为 0 的表示对面也准备有序关闭连接的返回值,然后最后调用 close() 来关闭 socket 通道。7.1.2 开始进行 WebSocket 关闭握手用一个状态码 code (第 7.4 节)和一个可选的关闭原因 reason (第 7.1.6 节)来开始 WebSocket 关闭握手,终端必须发送一个在第 5.5.1 节中描述的一样的关闭帧,将状态码设置为 code 字段,将关闭原因设置为 reaons 字段。一旦终端已经发送和收到了关闭控制帧,那么终端应该像第 7.1.1 节中定义的一样关闭 WebSocket 连接。7.1.3 已经开始 WebSocket 关闭握手在发送或者收到了关闭帧时,我们可以说已经开始 WebSocket 关闭握手,并且 WebSocket 连接的状态已经到了“关闭中”(CLOSING)状态。7.1.4 WebSocket 连接已关闭当底层的 TCP 连接关闭后,我们可以说WebSocket 连接已关闭,并且 WebSocket 连接已经到了”关闭“(CLOSED)状态。如果 TCP 连接在 WebSocket 关闭握手完成之后已经关闭,那么我们可以说 WebSocket 连接已经被彻底关闭。如果 WebSocket 连接没有被建立,我们也说WebSocket已经关闭,但是不彻底。7.1.5 WebSocket 关闭状态码就像在第 5.5.1 和第 7.4 节中定义的一样,关闭帧可以包含一个关闭的状态码和指定的原因。WebSocket 连接的关闭可能是同时由另一个终端发起。WebSocket 关闭状态码是在第 7.4 节中定义的在第一关闭帧中的由实现该协议的应用程序接收的状态码。如果关闭帧中没有包含状态码,WebSocket 关闭状态码被默认为1005。如果WebSocket 已经关闭并且终端没有收到任何的关闭帧(例如发生了可能底层的传输连接突然丢失的情况),那么WebSocket 关闭状态码被默认为1006。注:两个终端可能没有就WebSocket 关闭状态码的值达成一致。例如:如果远端发送一个关闭帧,但是本地应用没有从它的 socket 缓冲区中读到关闭帧的数据,同时本地应用单独的决定关闭连接并且发送了一个关闭帧,那么两个终端都发送了并且会收到一个关闭帧,同时不会发送更多的关闭帧。每一个终端会看到另一个终端发送过来的WebSocket 关闭状态码的状态码。像这样的,在这个示例里面,有可能两个终端都没有协商过WebSocket 关闭状态码,两个终端都几乎在同一时间单独开始 WebSocket 关闭握手。7.1.6 WebSocket 连接关闭原因像第 5.5.1 节和第 7.4 节中定义的一样,一个关闭帧可能包含一个用于关闭的表示原因的状态码,然后是 UTF-8 编码的数据,数据的解析方式是留给终端来解释,而不在这个协议中定义。一个正在关闭中的 WebSocket 连接可能是同时从另一端开始的。WebSocket 连接关闭原因是实现了该协议的应用收到的紧跟在状态码(第 7.4 节)之后的包含在第一个关闭控制帧中的 UTF-8 编码数据。如果在关闭控制帧中没有这些数据,那么WebSocket 连接关闭原因的值就是一个空字符串。注:和在第 7.1.5 中被提到的逻辑一样,两个终端可能没有协商过WebSocket 连接关闭原因。7.1.7 WebSocket 连接失效某些算法和规范要求终端有WebSocket 连接失效。为了实现这些,客户端必须关闭 WebSocket 连接,并且可以用一个合适的方式向用户上报相关问题(尤其是对开发者有帮助的内容)。相似的,为了实现这个,服务端必须关闭 WebSocket 连接,并且应该用日志记录这个问题。如果在此之前WebSocket 已经建立连接,此时终端需要让WebSocket 连接失效,那么在进行关闭 WebSocket 连接之前,终端需要发送一个包含恰当的状态码(第 7.4 节)。终端在确认另一端没有能力接收或者处理关闭帧时,可能会选择省略发送关闭帧,从而在一开始就进入正常错误流程导致 WebSocket 连接关闭。终端在接到WebSocket 连接失效的指令后,不能继续尝试处理来自另一端的数据(包括响应的关闭帧)。除了上面说到的场景和应用层指定的场景(例如:脚本使用了 WebSocket 的 API)外,客户端不应该关闭连接。7.2 异常关闭7.2.1 客户端主动关闭在开始握手中的某些特定算法,需要客户端让WebSocket 连接失效。为了实现这些,客户端必须像第 7.1.7 节中定义的一样让WebSocket 连接失败。如果任意一端底层的传输连接意外丢失,客户端必须让WebSocket 连接失败。除了上面指定的情况和应用层的约束(例如,脚本使用了 WebSocket 的 API)外,客户端不应该关闭连接。7.2.2 服务端主动关闭在开始监建立连接握手时,有些算法要求或者推荐服务端终端 WebSocket 连接。为了实现这些,服务端必须关闭 WebSocket 连接(第 7.1.1 节)。7.2.3 从异常关闭中恢复导致异常关闭的原因有很多。例如是由于一个临时的错误导致的关闭,在这种情况下能够恢复就能够带来一个稳定的连接,恢复正常的操作。有些问题也有可能是一个非临时的问题导致的,在这种情况下如果每个客户端都遇到了异常的关闭,客户端立刻重试连接并且不间断情况下,服务端可能会收到由于大量客户端重新连接带来的拒绝服务攻击。最终的结果就是这个方案可能会导致服务没有办法及时的恢复,或者让服务恢复变得困难的多。为了避免这个问题,客户端应该在异常终端尝试恢复连接时,使用在这一节中定义的一些备选策略。第一次尝试恢复连接应该在一个随机长度时间后。随机事件的参数如何选择,这个交给客户端来决定;选择 0 到 5 秒之间的随机值是一个合理的初始延时,但是客户端可以根据自己的经验和特定的应用来选择不同长度的时间延时。如果第一次重试连接失败,接下来的连接的延时应该变大,使用如截断二进制指数退避方法(译者注:解决以太网碰撞算法,见截断二进制质数退避算法)等来进行设置这个延时。7.3 连接正常关闭服务端可以在任意需要时关闭 WebSocket 连接。客户端不应该任意关闭 WebSocket 连接。在任一情况中,终端要发起关闭都必须遵循开始 WebSocket 连接关闭的步骤。7.4 状态码当关闭一个连接时(如:在开始握手已经完成后,发送一个关闭帧),终端可能会说明关闭的原因。终端的这个原因的描述和终端应该采取的行动,在这个文档中都没有说明。这个文档提前定义了一些可能用于扩展、框架和终端应用的状态码和状态码范围。这些状态码和任何有关联的的文本消息在关闭帧中都是可选的。7.4.1 定义状态码在发送一个关闭帧时,终端可以提前定义如下的状态码。10001000 表示一个正常的关闭,意味着连接建立的目标已经完成了。10011001 表示终端已经“走开”,例如服务器停机了或者在浏览器中离开了这个页面。10021002 表示终端由于协议错误中止了连接。10031003 表示终端由于收到了一个不支持的数据类型的数据(如终端只能怪理解文本数据,但是收到了一个二进制数据)从而关闭连接。1004保留字段。这意味着这个状态码可能会在将来被定义。10051005 是一个保留值并且不能被终端当做一个关闭帧的状态码。这个状态码是为了给上层应用表示当前没有状态码。10061006 是一个保留值并且不能被终端当做一个关闭帧的状态码。这个状态码是为了给上层应用表示连接被异常关闭如没有发送或者接受一个关闭帧这种场景的使用而设计的。10071007 表示终端因为收到了类型不连续的消息(如非 UTF-8 编码的文本消息)导致的连接关闭。10081008 表示终端是因为收到了一个违反政策的消息导致的连接关闭。这是一个通用的状态码,可以在没有什么合适的状态码(如 1003 或者 1009)时或者可能需要隐藏关于政策的具体信息时返回。10091009 表示终端由于收到了一个太大的消息无法进行处理从而关闭连接。10101010 表示终端(客户端)因为预期与服务端协商一个或者多个扩展,但是服务端在 WebSocket 握手中没有响应这个导致的关闭。需要的扩展清单应该出现在关闭帧的原因(reason)字段中。10011001 表示服务端因为遇到了一个意外的条件阻止它完成这个请求从而导致连接关闭。10151015 是一个保留值,不能被终端设置到关闭帧的状态码中。这个状态码是用于上层应用来表示连接失败是因为 TLS 握手失败(如服务端证书没有被验证过)导致的关闭的。7.4.2 保留状态码范围0-9990-999 的状态码都没有被使用。1000-29991000-2999 的状态码是在这个文档、将来的修订和扩展中定义的保留字段,用于永久的可用的公共文档。3000-39993000-3999 的状态码是保留给库、框架和应用使用的。这些状态码被IANA直接注册了。这些状态码在这篇文档中没有进行解释。4000-499940000-4999 的状态码是保留下来私用的,因此这些状态码不能被注册。这些状态码可以使用在 WebSocket 应用之前的协议上。这些状态码在这篇文档中没有进行解释。 ...

February 19, 2019 · 2 min · jiezi