Nodejs教程20:WebSocket之⼆:⽤原⽣实现WebSocket应⽤阅读更多系列⽂章请访问我的,⽰例代码请访问。
原⽣实现WebSocket应⽤
上⼀节使⽤了Socket.io实现WebSocket,也是开发中常⽤的⽅式。
但这样不利于了解其原理,这⼀节使⽤Nodejs的Net模块和Web端的WebSocket API实现WebSocket服务器。
⽰例代码:/lesson20/server.js,/lesson20/index.html
1. 服务端创建⼀个Net服务器
// 引⼊net模块
const net = require('net')
// 使⽤net模块创建服务器,返回的是⼀个原始的socket对象,与Socket.io的socket对象不同。
const server = ateServer((socket) => {
})
server.listen(8080)
2. Web端创建⼀个WebSocket链接
创建⼀个WebSocket连接,此时控制台的Network模块可以看到⼀个处于pending状态的HTTP连接。
这个连接是⼀个HTTP请求,与普通HTTP请求的请求你头相⽐,增加了以下内容:
Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits // 扩展信息
Sec-WebSocket-Key: O3PKSb95qaSB7/+XfaTg7Q== // 发送⼀个Key到服务端,⽤于校验服务端是否⽀持WebSocket Sec-WebSocket-Version: 13 // WebSocket版本
Upgrade: websocket // 告知服务器通信协议将会升级到WebSocket若服务器⽀持则继续下⼀步
const ws = new WebSocket('ws://localhost:8080/')
3. 服务端使⽤,触发⼀次data事件处理HTTP请求头数据
<('data', (buffer) => {
// 接收到HTTP请求头数据
const str = String()
console.log(str)
})
打印结果如下:
GET / HTTP/1.1
Host: localhost:8080
Connection: Upgrade
Pragma: no-cache
Cache-Control: no-cache
Upgrade: websocket
Origin: file://
Sec-WebSocket-Version: 13
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML,
like Gecko) Chrome/72.0.3626.121 Safari/537.36
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9
Cookie: _ga=GA1.1.1892261700.1545540050; _gid=GA1.1.774798563.1552221410; io=7X0VY8jhwRTdRHBfAAAB
Sec-WebSocket-Key: JStOineTIKaQskxefzer7Q==
Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits
将回车符转换为\r\n显⽰,结果如下:
GET / HTTP/1.1\r\nHost: localhost:8080\r\nConnection: Upgrade\r\nPragma: no-cache\r\nCache-Control: no-cache\r\nUpgrade: websocket\r\nOrigin: file://\ r\nSec-WebSocket-Version: 13\r\nUser-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.1 21 Safari/537.36\r\nAccept-Encoding: gzip, deflate, br\r\nAccept-Language: zh-CN,zh;q=0.9\r\nCookie: _ga=GA1.1.1892261700.1545540050; _gid=GA1.1. 774798563.1552221410; io=7X0VY8jhwRTdRHBfAAAB\r\nSec-WebSocket-Key: dRB1xDJ/vV+IAGnG7TscNQ==\r\nSec-WebSocket-Extensions: permessa ge-deflate; client_max_window_bits\r\n\r\n
通过观察请求头数据,可以发现数据是以key: value的形式显⽰,可以通过字符串切割,将其转换为对象格式。
4. 将请求头字符串转换为对象
创建⼀个parseHeader⽅法处理请求头。
function parseHeader(str) {
// 将请求头数据按回车符切割为数组,得到每⼀⾏数据
let arr = str.split('\r\n').filter(item => item)
// 第⼀⾏数据为GET / HTTP/1.1,可以丢弃。
arr.shift()
console.log(arr)
/*
处理结果为:
[ 'Host: localhost:8080',
'Connection: Upgrade',
'Pragma: no-cache',
'Cache-Control: no-cache',
'Upgrade: websocket',
'Origin: file://',
'Sec-WebSocket-Version: 13',
'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36',
'Accept-Encoding: gzip, deflate, br',
'Accept-Language: zh-CN,zh;q=0.9',
'Cookie: _ga=GA1.1.1892261700.1545540050; _gid=GA1.1.774798563.1552221410; io=7X0VY8jhwRTdRHBfAAAB',
'Sec-WebSocket-Key: jqxd7P0Xx9TGkdMfogptRw==',
'Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits' ]
*/
let headers = {} // 存储最终处理的数据
arr.forEach((item) => {
// 需要⽤":"将数组切割成key和value
let [name, value] = item.split(':')
// 去除⽆⽤的空格,将属性名转为⼩写
name = place(/^\s|\s+$/g, '').toLowerCase()
value = place(/^\s|\s+$/g, '')
// 获取所有的请求头属性
headers[name] = value
})
return headers
}
打印结果如下:
{ host: 'localhost',
connection: 'Upgrade',
pragma: 'no-cache',
'cache-control': 'no-cache',
upgrade: 'websocket',
origin: 'file',
'sec-websocket-version': '13',
'user-agent':
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36',
'accept-encoding': 'gzip, deflate, br',
'accept-language': 'zh-CN,zh;q=0.9',
cookie:
'_ga=GA1.1.1892261700.1545540050; _gid=GA1.1.585339125.1552405260',
'sec-websocket-key': 'TipyPZNW+KNvV3fePNpriw==',
'sec-websocket-extensions': 'permessage-deflate; client_max_window_bits' }
5. 根据请求头参数,判断是否WebSocket请求
根据headers[‘upgrade’] !== ‘websocket’,判断该HTTP连接是否可升级为WebSocket,若可以升级,表⽰为WebSocket请求。
根据headers[‘sec-websocket-version’] !== ‘13’,判断WebSocket的版本是否为13,以免因为版本不同出现兼容问题。
<('data', (buffer) => {
// 接收到HTTP请求头数据
const str = String()
console.log(str)
// 4. 将请求头数据转为对象
const headers = parseHeader(str)
console.log(headers)
// 5. 判断请求是否为WebSocket连接
if (headers['upgrade'] !== 'websocket') {
// 若当前请求不是WebSocket连接,则关闭连接
console.log('⾮WebSocket连接')
} else if (headers['sec-websocket-version'] !== '13') {
// 判断WebSocket版本是否为13,防⽌是其他版本,造成兼容错误
console.log('WebSocket版本错误')
} else {
// 请求为WebSocket连接时,进⼀步处理
}
})
6. 校验Sec-WebSocket-Key,完成连接
根据协议规定的⽅式,向前端返回⼀个请求头,完成建⽴WebSocket连接的过程。
若客户端校验结果正确,在控制台的Network模块可以看到HTTP请求的状态码变为101 Switching Protocols,同时客户端的ws.onopen 事件被触发。
<('data', (buffer) => {
// 接收到HTTP请求头数据
const str = String()
console.log(str)
// 4. 将请求头数据转为对象
const headers = parseHeader(str)
console.log(headers)
// 5. 判断请求是否为WebSocket连接
前端websocket怎么用if (headers['upgrade'] !== 'websocket') {
// 若当前请求不是WebSocket连接,则关闭连接
console.log('⾮WebSocket连接')
} else if (headers['sec-websocket-version'] !== '13') {
// 判断WebSocket版本是否为13,防⽌是其他版本,造成兼容错误
console.log('WebSocket版本错误')
} else {
// 6. 校验Sec-WebSocket-Key,完成连接
/*
协议中规定的校验⽤GUID,可参考如下链接:
/html/rfc6455#section-5.5.2
stackoverflow/questions/13456017/what-does-258eafa5-e914-47da-95ca-c5ab0dc85b11-means-in-websocket-protocol
*/
const GUID = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'
const key = headers['sec-websocket-key']
const hash = ateHash('sha1') // 创建⼀个签名算法为sha1的哈希对象
hash.update(`${key}${GUID}`) // 将key和GUID连接后,更新到hash
const result = hash.digest('base64') // ⽣成base64字符串
const header = `HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-Websocket-Accept: ${result}\r\n\r\n` // ⽣成供前端校验⽤的请求头
socket.write(header) // 返回HTTP头,告知客户端校验结果,HTTP状态码101表⽰切换协议:httpstatuses/101。
// 若客户端校验结果正确,在控制台的Network模块可以看到HTTP请求的状态码变为101 Switching Protocols,同时客户端的ws.onopen事件被触发。
console.log(header)
// 处理聊天数据
}
})
7. 建⽴连接后,通过data事件接收客户端的数据并处理
连接开始后,可以在控制台的Network模块看到,该连接会⼀直保留在pending状态,直到连接断开。
此时可以通过data事件处理客户端的数据,但此时双⽅通信的数据为⼆进制,需要按照其格式进⾏处理后才可以正常使⽤。
格式如下:
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论