摘要:用实现简单协议从浏览器说起浏览器提供的非常简洁。创建连接连接建立时的回调收到消息时的回调连接出错时的回调连接终止时的回调发送消息告诉我们也就是说,在创建对象时,浏览器尝试与服务端建立连接发送请求建立个服务端一旦收到数据,就会触发。
用 Node 实现简单 WebSocket 协议 从浏览器 WebSocket API 说起
浏览器提供的 WebSocket API 非常简洁。
let ws = new WebSocket("ws://localhost:8124") // 创建连接
ws.onopen = function(e){...} // 连接建立时的回调
ws.onmessage = function(e){...} // 收到消息时的回调
ws.onerror = function(e){...} // 连接出错时的回调
ws.onclose = function(e){...} // 连接终止时的回调
ws.send("Hello Server!") // 发送消息
MDN告诉我们
In order to communicate using the WebSocket protocol, you need to create a WebSocket object; this will automatically attempt to open the connection to the server.
也就是说,在 let ws = new WebSocket("ws://localhost:8124") 创建 WebSocket 对象时,浏览器尝试与服务端建立连接(发送请求)
建立个服务端const net = require("net") const server = net.createServer(s => { s.on("data", d => { console.log(d.toString()) }) }) server.listen(8124, () => { console.log("listening on 8124...") })
一旦收到数据,就会触发 data。
启动服务端,监听8124端口;在浏览器中打开控制台,输入let ws = new WebSocket("ws://localhost:8124");得到结果如下
通过这次请求,浏览器告诉服务端,要升级协议为 websocket
服务端收到这个请求后,向浏览器发出响应,这个过程就叫做握手(handshaking)
服务端向浏览器发出同意升级协议的响应后会触发浏览器端 websocket.onopen 的回调
服务端的响应Obtain the value of Sec-WebSocket-Key request header without any leading and trailing whitespace
Link it with "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
Compute SHA-1 and Base64 code of it
Write it back as value of Sec-WebSocket-Accept response header as part of a HTTP response.
根据MDN的指示,修改服务端代码
const net = require("net") const crypto = require("crypto") const server = net.createServer(s => { s.on("data", d => { let req = d.toString() // Obtain the value of Sec-WebSocket-Key request header without any leading and trailing whitespace let secWebsocketKey = /Sec-WebSocket-Key:s(.*)/.exec(req)[1] // Link it with "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" let key = secWebsocketKey + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" // Compute SHA-1 and Base64 code of it let secWebSocketAccept = crypto.createHash("sha1").update(key).digest("base64") // Write it back as value of Sec-WebSocket-Accept response header as part of a HTTP response. let res = "HTTP/1.1 101 Switching Protocols Connection: Upgrade Upgrade: websocket Sec-WebSocket-Accept: " + secWebSocketAccept + " " s.write(res) }) }) server.listen(8124, () => { console.log("listening on 8124...") })
同时为了方便测试,写个html文件
socketClient socketClient
启动服务端,用浏览器打开html文件
至此之后,浏览器和服务端之间就可以更加愉快地聊天了,之前的服务端很矜持,浏览器问一句,服务端答一句;而握手之后,服务端也会主动向浏览器发送消息,这样就会触发浏览器端 websocket.onmessage 的回调
服务端主动发送消息修改服务端代码
... s.on("data", d => { let req = d.toString() console.log(req) // Obtain the value of Sec-WebSocket-Key request header without any leading and trailing whitespace let secWebsocketKey = /Sec-WebSocket-Key:s(.*)/.exec(req)[1] // Link it with "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" let key = secWebsocketKey + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" // Compute SHA-1 and Base64 code of it let secWebSocketAccept = crypto.createHash("sha1").update(key).digest("base64") // Write it back as value of Sec-WebSocket-Accept response header as part of a HTTP response. let res = "HTTP/1.1 101 Switching Protocols Connection: Upgrade Upgrade: websocket Sec-WebSocket-Accept: " + secWebSocketAccept + " " console.log(res) s.write(res) // 服务端主动发消息 Hi, Client! l"m Server. let dataBuffer = new Buffer(`Hi, Client! l"m Server.`) let payload_len = dataBuffer.length let assistData = [] assistData.push(129) assistData.push(payload_len) let assistBuffer = new Buffer(assistData) let message = Buffer.concat([assistBuffer, dataBuffer]) console.log(message) s.write(message) }) }) ...
代码中
// 服务端主动发消息 Hi, Client! l"m Server. let dataBuffer = new Buffer(`Hi, Client! l"m Server.`) let payload_len = dataBuffer.length let assistData = [] assistData.push(129) assistData.push(payload_len) let assistBuffer = new Buffer(assistData) let message = Buffer.concat([assistBuffer, dataBuffer]) console.log(message) s.write(message)
实际上是将数据编码成数据帧的过程,其具体细节稍后再说。
修改html
websocket.send()浏览器发送消息很简单
修改html
修改服务端代码
const net = require("net") const crypto = require("crypto") const server = net.createServer(s => { s.handshaking = false s.on("data", d => { if (!s.handshaking) { let req = d.toString() console.log(req) // Obtain the value of Sec-WebSocket-Key request header without any leading and trailing whitespace let secWebsocketKey = /Sec-WebSocket-Key:s(.*)/.exec(req)[1] // Link it with "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" let key = secWebsocketKey + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" // Compute SHA-1 and Base64 code of it let secWebSocketAccept = crypto.createHash("sha1").update(key).digest("base64") // Write it back as value of Sec-WebSocket-Accept response header as part of a HTTP response. let res = "HTTP/1.1 101 Switching Protocols Connection: Upgrade Upgrade: websocket Sec-WebSocket-Accept: " + secWebSocketAccept + " " console.log(res) s.write(res) // 服务端主动发消息 Hi, Client! l"m Server. let dataBuffer = new Buffer(`Hi, Client! l"m Server.`) let payload_len = dataBuffer.length let assistData = [] assistData.push(129) assistData.push(payload_len) let assistBuffer = new Buffer(assistData) let message = Buffer.concat([assistBuffer, dataBuffer]) console.log(message) s.write(message) s.handshaking = true } else { //解析浏览器发送消息 let fin = d[0] >> 7 let opcode = d[0] & parseInt(1111, 2) // 1 表示文本数据帧 let mask = d[1] >> 7 // 标示是否进行掩码处理,客户端发送给服务端时为1,服务端发送给客户端时为0 let payload_len = d[1] & parseInt(1111111, 2) // 这里假设发送的数据长度小于 125 let masking_key = d.slice(2, 6) let payload_data = new Buffer(payload_len) for (let i = 0; i < payload_len; i++) { let j = i % 4 payload_data[i] = d[6 + i] ^ masking_key[j] } console.log(payload_data.toString()) } }) }) server.listen(8124, () => { console.log("listening on 8124...") })编码解码数据帧
解码Hi, Server! l"m Client.
let fin = d[0] >> 7 let opcode = d[0] & parseInt(1111, 2) // 1 表示文本数据帧 let mask = d[1] >> 7 // 标示是否进行掩码处理,客户端发送给服务端时为1,服务端发送给客户端时为0 let payload_len = d[1] & parseInt(1111111, 2) // 这里假设发送的数据长度小于 125 let masking_key = d.slice(2, 6) let payload_data = new Buffer(payload_len) for (let i = 0; i < payload_len; i++) { let j = i % 4 payload_data[i] = d[6 + i] ^ masking_key[j] } console.log(payload_data.toString())
编码Hi, Client! l"m Server.
let dataBuffer = new Buffer(`Hi, Client! l"m Server.`) let payload_len = dataBuffer.length let assistData = [] assistData.push(129) assistData.push(payload_len) let assistBuffer = new Buffer(assistData) let message = Buffer.concat([assistBuffer, dataBuffer]) console.log(message)
解码编码依据Base Framing Protocol
主要用到 nodeAPI 中 Buffer 的模块以及位运算
其实我主要看的是《深入浅出NodeJS》第七章websocket的那部分
小结
握手之后才可以通信
从请求头获取websocketKey
设置响应头
通信
编码信息
解码信息
位运算/Buffer/正则
let socketArr = [] const server = net.createServer(s => { s.handShaking === false s.name = `Client_${socketArr.length}` socketArr.push(s) s.on("data", d => {}) ... })
将每个socket对象标记并保存,从而实现对象之间的通信
参考资料WebSocket
Writing WebSocket client applicaitons
Writing a WebSocket server in Java
《深入浅出NodeJS》第六章、第七章部分
The WebSocket Protocol
用Node实现WebSocket协议
基于node实现websocket协议
nodejs实现websocket的数据接收发送
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/90464.html
摘要:预备工作序最近正在研究相关的知识,想着如何能自己实现协议。监听事件就是协议的抽象,直接在上面监听已有的事件和事件这两个事件。表示当前数据帧为消息的最后一个数据帧,此时接收方已经收到完整的消息,可以对消息进行处理。 A、预备工作 1、序 最近正在研究 Websocket 相关的知识,想着如何能自己实现 Websocket 协议。到网上搜罗了一番资料后用 Node.js 实现该协议,倒也没...
摘要:接口用于接收服务器发送的事件。因此,是目前来说最佳的选择。最大特点就是,服务器可以主动向客户端推送消息,客户端也可以主动向服务器发送信息,是一种不受限的全双工通信。若是,则交给的回调函数处理,否则,还是走正常的回调的路子。 使用 WebSocket 的理由 传统的http协议有一个根本性的缺陷,那就是请求只能由客户端向服务器发起,服务器接收到请求后再进行响应,把数据返回给客户端。也就是...
摘要:服务器推迟响应,直到发生更改更新或超时。旨在取代现有的双向通信技术。只要我们对套接字事件和有了充分的了解,理解和实现就非常简单。 翻译:疯狂的技术宅 原文:blog.logrocket.com/websockets-… showImg(https://user-gold-cdn.xitu.io/2019/5/21/16ada008fc1f81d7); Web 为了支持客户端和服务器之间的全...
阅读 704·2021-11-22 13:54
阅读 3064·2021-09-26 10:16
阅读 3486·2021-09-08 09:35
阅读 1575·2019-08-30 15:55
阅读 3428·2019-08-30 15:54
阅读 2074·2019-08-30 10:57
阅读 496·2019-08-29 16:25
阅读 876·2019-08-29 16:15