WebSocket 是一种基于 TCP 连接的全双工通信的协议,其工作在应用层,建立连接的时候通过复用 Http 握手通道,完成 Http 协议的切换升级,即切换到 WebSocket 协议,协议切换成功后,将不再需要客户端发起请求,服务端就可以直接主动向客户端发送数据,实现双向通信。
和 Http 相比,WebSocket有以下优点:
首先由客户端发起协议升级请求, 根据WebSocket协议规范, 请求头必须包含如下的内容:
GET / HTTP/1.1
Host: localhost:8080
Origin: http://127.0.0.1:3000
Connection: Upgrade
Upgrade: websocket
Sec-WebSocket-Version: 13
Sec-WebSocket-Key: w4v7O6xFTi36lq3RNcgctw==
服务器返回的响应头必须包含如下的内容:
HTTP/1.1 101 Switching Protocols
Connection:Upgrade
Upgrade: websocket
Sec-WebSocket-Accept: Oy4NRAQ13jhfONC7bP8dTKb4PTU=
Sec-WebSocket-Key 值由一个随机生成的16字节的随机数通过 base64(见 RFC4648 的第四章)编码得到的。例如, 随机选择的16个字节为:
// 十六进制 数字1~16
0x01 0x02 0x03 0x04 0x05 0x06 0x07 0x08 0x09 0x0a 0x0b 0x0c 0x0d 0x0e 0x0f 0x10
测试代码如下:
const list = Array.from({ length: 16 }, (v, index) => ++index)
const key = Buffer.from(list)
console.log(key.toString('base64'))
// AQIDBAUGBwgJCgsMDQ4PEA==
而 Sec-WebSocket-Accept 值的计算方式为:
将 Sec-Websocket-Key 的值和 258EAFA5-E914-47DA-95CA-C5AB0DC85B11 拼接 通过 SHA1 计算出摘要, 并转成 base64 字符串。
此处不需要纠结神奇字符串 258EAFA5-E914-47DA-95CA-C5AB0DC85B11, 它就是一个 GUID, 没准儿是写 RFC 的时候随机生成的。
测试代码如下:
const crypto = require('crypto')
function hashWebSocketKey (key) {
const GUID = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'
return crypto.createHash('sha1')
.update(key + GUID)
.digest('base64')
}
console.log(hashWebSocketKey('w4v7O6xFTi36lq3RNcgctw=='))
// Oy4NRAQ13jhfONC7bP8dTKb4PTU=
前面简单提到他的作用为: 提供基础的防护, 减少恶意连接, 进一步阐述如下:
wget https://nodejs.org/dist/v14.16.0/node-v14.16.0-linux-x64.tar.xz
tar -xJvf node-v14.16.0-linux-x64.tar.xz
ln -s /root/node-v14.16.0-linux-x64/bin/node /usr/local/bin/node
ln -s /root/node-v14.16.0-linux-x64/bin/npm /usr/local/bin/npm
npm install ws
本次实验后端服务 Http 和 Websocket 使用相同的 80 和 443 端口。在实际应用中有个好处,如果原先是提供的是 Http 服务,后来新增了 Websocket 服务,不需要暴露新的端口,也不需要修改防火墙规则。
把 WebSocketServer 和 Http 绑定到同一个端口的关键代码是先获取创建的 http.Server 的引用,再根据 http.Server 创建 WebSocketServer。
不管是 Http 还是 Websocket,客户端发送的都是标准的 Http 请求,都会先将请求交给 http.Server 处理。WebSocketServer 会首先判断请求是不是 Websocket 请求,如果是,它将处理该请求,如果不是,该请求仍由 http.Server 处理。
服务器代码:
// app.js 文件
// 导入相关模块
const WebSocket = require('ws');
const http = require('http');
// 使用 http 模块创建的 http.Server
httpserver = http.createServer(function (request, response) {
// 发送 HTTP 头部
// HTTP 状态值: 200 : OK
// 内容类型: text/plain
response.writeHead(200, {'Content-Type': 'text/plain'});
// 发送响应数据 "Hello World"
response.end('Http Message: Hello World\n');
}).listen(80); // 监听 80 端口, 根据 http.Server 创建 WebSocketServer
//创建 WebSocketServer
const WebSocketServer = WebSocket.Server;
const wss = new WebSocketServer({
server: httpserver //根据 http.Server 创建 WebSocketServer
});
wss.on('connection', function (ws) {
ws.send("Websocket Send: Hello World") //客户端连接成功后立即向客户端发送一条消息
console.log(`WebSocket connection()`);
ws.on('message', function (message) { //收到客户端的消息
console.log(`Websocket Received: ${message}`);
})
});
console.log('WebSocket and Http Server started at port 80...');
[root@ws1 ws-http-server]# node app.js
WebSocket and Http Server started at port 80...
分别使用客户端验证 Http 和 Websocket 服务,后端服务器的地址为 192.168.1.141:
# curl -i http://192.168.1.141
HTTP/1.1 200 OK
Content-Type: text/plain
Date: Thu, 25 Mar 2021 08:00:40 GMT
Connection: keep-alive
Keep-Alive: timeout=5
Transfer-Encoding: chunked
Http Message: Hello World
# 方式一:使用 wscat(客户端 npm install wscat 安装)
# wscat --connect ws://192.168.1.141
Connected (press CTRL+C to quit)
< Websocket Send: Hello World #接收到服务器的消息
> send hello #向服务器发送消息
# 方式二:使用 curl
curl -i \
--header "Upgrade: websocket" \
--header "Sec-WebSocket-Key: AQIDBAUGBwgJCgsMDQ4PEA==" \
--header "Sec-WebSocket-Version: 13" \
--header "Connection: upgrade" \ #直接访问后端服务的 Websocket 需要带上该头部
http://192.168.1.141
抓包查看交互报文,可以看到 Websocket 复用了 HTTP 的握手通道, 客户端通过 HTTP 请求与 WebSocket 服务器协商升级协议, 协议升级完成后, 后续的数据交换则遵照 WebSocket协议。
在后端服务器上抓包:
tcpdump -i any host 192.168.1.141 and port 80 -nn -w ws.pcap
通过 Wireshark 软件打开查看:
https 证书我们都在 CA 站点申请,并由 CA 机构颁发,本次实验使用 openssl 生成自签名 https 证书。
[root@nginx-plus1 certs]# openssl req -newkey rsa:2048 -nodes -keyout server.key -x509 -days 365 -out server.crt
# 以下信息自行添加,可以随意
Generating a 2048 bit RSA private key
....................+++
......+++
writing new private key to 'server.key'
-----
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [XX]:CN
State or Province Name (full name) []:Shanghai
Locality Name (eg, city) [Default City]:Shanghai
Organization Name (eg, company) [Default Company Ltd]:Ect
Organizational Unit Name (eg, section) []:Ect
Common Name (eg, your name or your server's hostname) []:chengzw
Email Address []:chengzw258@163.com
Nginx 监听 80 端口用于 Http 和 ws 服务,监听 443 端口用于 Https 和 wss 服务。wss 就是加密的 ws 服务。
events{}
http {
upstream websocket {
server 192.168.1.141:80; #后端服务器地址
}
server {
listen 443 ssl;
# ssl 相关配置
ssl_protocols TLSv1 TLSv1.1 TLSv1.2 SSLv3 SSlv2;
ssl_certificate_key /usr/local/nginx/certs/server.key;
ssl_certificate /usr/local/nginx/certs/server.crt;
location / {
proxy_pass http://websocket;
# 添加 WebSocket 协议升级 Http 头部
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}
server {
listen 80;
location / {
proxy_pass http://websocket;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}
}
/usr/local/nginx/sbin/nginx #根据自己安装 nginx 的路径
# Http 连接
# curl -i http://192.168.1.134
HTTP/1.1 200 OK
Server: nginx/1.14.2
Date: Thu, 25 Mar 2021 08:16:59 GMT
Content-Type: text/plain
Transfer-Encoding: chunked
Connection: keep-alive
Http Message: Hello World
# Https 连接
# curl -i https://192.168.1.134 -k
HTTP/1.1 200 OK
Server: nginx/1.14.2
Date: Thu, 25 Mar 2021 08:17:07 GMT
Content-Type: text/plain
Transfer-Encoding: chunked
Connection: keep-alive
Http Message: Hello World
# 方式一:使用wscat
# ws 连接
# wscat --connect ws://192.168.1.134
Connected (press CTRL+C to quit)
< Websocket Send: Hello World
> send hello
# wss 连接,由于是自签名证书需要 -n 参数,表示不检验证书 # wscat --connect wss://192.168.1.134 -n
Connected (press CTRL+C to quit)
< Websocket Send: Hello World
> send hello
# 方式二:使用curl
# ws 连接
curl -i \
--header "Upgrade: websocket" \
--header "Sec-WebSocket-Key: MlRAR6bQZi07587UD4H8oA==" \
--header "Sec-WebSocket-Version: 13" \
http://192.168.1.134
HTTP/1.1 101 Switching Protocols
Server: nginx/1.14.2
Date: Thu, 25 Mar 2021 08:18:48 GMT
Connection: upgrade
Upgrade: websocket
Sec-WebSocket-Accept: iURIl3uIT+tsPMmZ0x1IVH7EL98=
# wss 连接,由于是自签名证书需要 -k 参数,表示不检验证书
curl -i \
--header "Upgrade: websocket" \
--header "Sec-WebSocket-Key: MlRAR6bQZi07587UD4H8oA==" \
--header "Sec-WebSocket-Version: 13" \
https://192.168.1.134 -k
HTTP/1.1 101 Switching Protocols
Server: nginx/1.14.2
Date: Thu, 25 Mar 2021 08:20:20 GMT
Connection: upgrade
Upgrade: websocket
Sec-WebSocket-Accept: iURIl3uIT+tsPMmZ0x1IVH7EL98=
扫码关注腾讯云开发者
领取腾讯云代金券
Copyright © 2013 - 2025 Tencent Cloud. All Rights Reserved. 腾讯云 版权所有
深圳市腾讯计算机系统有限公司 ICP备案/许可证号:粤B2-20090059 深公网安备号 44030502008569
腾讯云计算(北京)有限责任公司 京ICP证150476号 | 京ICP备11018762号 | 京公网安备号11010802020287
Copyright © 2013 - 2025 Tencent Cloud.
All Rights Reserved. 腾讯云 版权所有