- WebSocket 是 HTML5 开始提供的一种在单个 TCP 连接上进行全双工通讯的协议。
- WebSocket 使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。在 WebSocket API 中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。
- 首先来看前端代码
<!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">
<title>Document</title>
</head>
<body>
<input type="text" id="input" value="test message">
<input type="button" value="发送消息" id="send">
<button id="start">开始连接</button>
<button id="check">查看状态</button>
<h3>接收到的消息: </h3>
<p></p>
<input type="button" value="关闭聊天" id="close">
<script>
var pp = document.querySelector('p')
var start = document.querySelector('#start')
var check = document.querySelector('#check')
var ws;
function wsStart () {
if ('WebSocket' in window) {
console.log('浏览器支持webSocket!')
// 创建一个连接
ws = new WebSocket('ws://localhost:9999/')
// 连接建立时触发
ws.onopen = function (res) {
console.log('链接已建立',res);
}
// 客户端接收到消息时触发
ws.onmessage = function (response) {
console.log(response);
pp.innerHTML = response.data
}
// 连接关闭时触发
ws.onclose = function () {
console.log('连接已关闭');
pp.innerHTML = '链接已关闭'
}
// 通信出错时触发
ws.onerror = function() {
console.log('连接出错');
}
console.log(ws.readyState)
} else {
console.log('浏览器不支持WebSocket!!!')
}
}
// 开始连接
start.onclick = function () {
wsStart()
}
// 查看链接状态
check.onclick = function () {
console.log(ws.readyState)
}
// 点击发送消息
document.querySelector('#send').addEventListener('click', function () {
var send = document.querySelector('#input').value
// 发送消息
ws.send(send)
})
// 关闭连接
document.querySelector('#close').addEventListener('click', function () {
ws.close()
})
</script>
</body>
</html>
- 想要验证WebScoket是否能够成功连接,这时候就需要后台支持了, 这里提供一个简单的node后台服务
// 引入net模块
const net = require('net')
// 引入 crypto 模块
const crypto = require('crypto')
// 将处理请求头
function parseHeader (str) {
let arr = str.split('\r\n').filter(item => item )
// 第一行数据为GET /HTTP/1.1 可以丢弃
arr.shift()
// console.log(arr);
// 结果
/*[
'Host: localhost:9999',
'Connection: Upgrade',
'Pragma: no-cache',
'Cache-Control: no-cache',
'User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.87 Safari/537.36',
'Upgrade: websocket',
'Origin: file://',
'Sec-WebSocket-Version: 13',
'Accept-Encoding: gzip, deflate, br',
'Accept-Language: zh-CN,zh;q=0.9',
'Sec-WebSocket-Key: hxaMHw5rY9scIiS7fHLhUg==',
'Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits'
]*/
let head = {}
arr.forEach((item) => {
// 需要用 ":" 将数据切割成 key 和 value
let [name, value] = item.split(':')
// 去除无用的空格, 将属性名 转为小写
name = name.replace(/^\s|\s+$/g, '').toLowerCase()
value = value.replace(/^\s|\s+$/g, '')
// 组合对象数据
head[name] = value
})
return head
}
// 使用net模块创建服务器, 返回的是一个原始的socket 对象, 与socket对象不同
const server = net.createServer((socket) => {
// 当有客户端链接到 服务器时, 代码线运行到此处, 但此时WebSocket连接还未建立, 客户端的ws.onopen 不会触发
console.log('--socket--');
console.log(socket);
// 第一次接收到WebSocket 的 http 连接是, 进行处理
socket.once('data', (buffer) => {
// 接收到 http 请求头数据
const str = buffer.toString()
// console.log('--str--1');
// console.log(str);
// 将请求头数据转为对象
const headers = parseHeader(str)
console.log(headers);
// 判断请求是否为WebSocket 连接
if(headers['upgrade'] != 'websocket') {
// 当前请求不是WebSocket 关闭连接
console.log('--------非WebSocket连接----------');
socket.end()
} else if (headers['sec-websocket-version'] != '13') {
// 判断WebSocket版本是否为13, 防止 其他版本, 造成兼容错误
console.log('---------WebSocket版本错误------------------');
socket.end()
} else {
// 校验 sec-websocket-version 完成连接
const GUID = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'
const key = headers['sec-websocket-key']
const hash = crypto.createHash('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 表示切换协议 http://httpstatuses.com/101
// 若客户端校验结果正确, 在控制台 的NetWork 模块可以看到 http 请求状态码变成101 Switching Protocols, 同时客户端的ws.onopen 触发
console.log(header);
socket.on('data', (buffer) => {
const data = decodeWsFrame(buffer)
console.log(data);
console.log(data.payloadData && data.payloadData.toString());
if (data.opcode == 8) {
socket.end()
} else {
// 接收到客户端数据是的处理, 此处默认为返回接收到的数据
socket.write(encodeWsFrame({payloadData: `服务端接收到的消息为: ${data.payloadData?data.payloadData.toString(): '' }`}))
}
})
}
})
socket.on('end', () => {
console.log('连接断开!');
})
})
// 处理收到的数据
function decodeWsFrame(data) {
let start = 0;
let frame = {
isFinal: (data[start] & 0x80) === 0x80,
opcode: data[start++] & 0xF,
masked: (data[start] & 0x80) === 0x80,
payloadLen: data[start++] & 0x7F,
maskingKey: '',
payloadData: null
};
if (frame.payloadLen === 126) {
frame.payloadLen = (data[start++] << 8) + data[start++];
} else if (frame.payloadLen === 127) {
frame.payloadLen = 0;
for (let i = 7; i >= 0; --i) {
frame.payloadLen += (data[start++] << (i * 8));
}
}
if (frame.payloadLen) {
if (frame.masked) {
const maskingKey = [
data[start++],
data[start++],
data[start++],
data[start++]
];
frame.maskingKey = maskingKey;
frame.payloadData = data
.slice(start, start + frame.payloadLen)
.map((byte, idx) => byte ^ maskingKey[idx % 4]);
} else {
frame.payloadData = data.slice(start, start + frame.payloadLen);
}
}
console.dir(frame)
return frame;
}
// 处理发出的数据
function encodeWsFrame(data) {
const isFinal = data.isFinal !== undefined ? data.isFinal : true,
opcode = data.opcode !== undefined ? data.opcode : 1,
payloadData = data.payloadData ? Buffer.from(data.payloadData) : null,
payloadLen = payloadData ? payloadData.length : 0;
let frame = [];
if (isFinal) frame.push((1 << 7) + opcode);
else frame.push(opcode);
if (payloadLen < 126) {
frame.push(payloadLen);
} else if (payloadLen < 65536) {
frame.push(126, payloadLen >> 8, payloadLen & 0xFF);
} else {
frame.push(127);
for (let i = 7; i >= 0; --i) {
frame.push((payloadLen & (0xFF << (i * 8))) >> (i * 8));
}
}
frame = payloadData ? Buffer.concat([Buffer.from(frame), payloadData]) : Buffer.from(frame);
console.dir(decodeWsFrame(frame));
return frame;
}
// 监听服务器端口
server.listen(9999, () => {
console.log('--服务器启动了,访问 http://localhost:9999');
})
使用node运行这个代码, 然后就可以正确连接了, 觉得有用就点个赞吧