Websock简要介绍+Nginx WS代理设置

AI 摘要: Websocket是一种网络传输协议,在TCP连接上进行全双工通信,在应用层,解决C/S之间的数据交互问题,浏览器支持广泛。它涵盖了传输和协议两个部分,可以在80和443端口进行数据传输。本文介绍了Websocket的概念、握手过程和相关问题,包括并发连接、应用集成、负载均衡和数据同步。值得参考的阅读内容有ably服务商的文档、ably ws聊天示例以及其他参考资料。

1. 概述

Websocket是一种网络传输协议,可以在TCP连接上进行全双工通信,位于OSI的应用层,协议版本要求13以上。

Websocket是从Ajax、Comet(长轮询)逐步衍进的技术,解决C/S之间的数据交互、数据互推(全双工)问题,浏览器支持方面目前基本都已经支持,而且在物联网方面也有应用;

Websocket涵盖了传输+协议两个部分,传输指的是WS可以在应用层数据封装(组装成协议MQTT、AMPQ、SOAP等)传输数据,实际上还是基于TCP上,因此是握手过程是是在TCP三次握手后,再基于Connection: UpgradeUpgrade: websocket升级到websocket协议,具体内容可以看阅读文档.

针对数据加密方面,ws是基于80端口进行数据传输,wss是基于443端口进行数据传输(tls保障数据传输安全)

实例参考 (tips:通过chrome webdevelop工具查看)

2. 握手过程

2.1. 客户端请求

涉及ConnetionUpgrade头字段,告知服务端采用websocket进行数据通信,并基于Sec-WebSocket-Key指定客户标识,基于Sec-WebSocket-Version告知版本为13

1
2
3
4
5
6
7
GET / HTTP/1.1
Upgrade: websocket
Connection: Upgrade
Host: example.com
Origin: http://example.com
Sec-WebSocket-Key: sN9cRrP/n9NdMgdcy2VJFQ==
Sec-WebSocket-Version: 13

2.2. 服务器回应

1
2
3
4
5
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: fFBooB7FAkLlXgRSz0BT3v4hq5s=
Sec-WebSocket-Location: ws://example.com/

涉及101协议选择响应,告知客户端服务端将升级至websocket,并告知客户端接受的Sec-WebSocket-Accept,改值是通过客户端发送过来的Sec-WebSocket-Key加上一个特殊字符串258EAFA5-E914-47DA-95CA-C5AB0DC85B11,然后计算SHA-1摘要,之后进行BASE-64编码。

js生成的代码为:

1
2
3
4
5
6
7
8
const crypto = require('crypto');

function generateAcceptValue (acceptKey) {
  return crypto
    .createHash('sha1')
    .update(acceptKey + '258EAFA5-E914-47DA-95CA-C5AB0DC85B11', 'binary')
    .digest('base64');
}

3. Nginx作为WS代理

WebSocket协议与HTTP协议不同,但是WebSocket握手与HTTP兼容,使用HTTP升级工具将连接从HTTP升级到WebSocket。这使WebSocket应用程序可以更轻松地适合现有基础架构。例如,WebSocket应用程序可以使用标准的HTTP端口80和443,从而允许使用现有的防火墙规则。

WebSocket应用程序使客户端和服务器之间的长期连接保持打开状态,从而促进了实时应用程序的开发。

用于将连接从HTTP升级到WebSocket的HTTP升级机制使用UpgradeConnection标头。

反向代理服务器在支持WebSocket方面面临一些挑战:

  1. WebSocket是逐跳协议(hop‑by‑hop protocol),因此,当代理服务器截获来自客户端的升级请求时,它需要将其自身的升级请求(包括适当的标头)发送到后端服务器。
  2. 由于WebSocket连接的寿命长,而不是HTTP使用的典型的短连接,因此反向代理需要允许这些连接保持打开状态,而不能因为它们是空闲就关闭。

3.1. 代理配置

NGINX侦听端口8020,并将请求代理到后端WebSocket服务器,proxy_set_header指令使NGINX妥善处理WebSocket协议。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
http {
    map $http_upgrade $connection_upgrade {
        default upgrade;
        ''      close;
    }
    upstream wsbackend {
        server localhost:8010;
    }
    server {
        listen 8020;
        # 以传递给到wsbackend,http版本需要是1.1版本,同时需要将HTTP请求头升级
        location / {
            proxy_pass http://wsbackend;
            proxy_http_version 1.1;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection $connection_upgrade;
        }
    }
}

3.2. node配置一个ws服务

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// 初始化目录
$ mkdir ws-srv && cd ws-srv

// 安装ws依赖
$ npm init -y .
$ npm npm install ws wscat

// 全局安装wscat
$ npm install -g wscat
/usr/local/node/bin/wscat -> /usr/local/node/lib/node_modules/wscat/bin/wscat

// 配置一个nodejs的ws,用于接收消息并返回消息给客户端
$ cat > ws-server.js<<EOF
console.log("Server started");
var Msg = '';
var WebSocketServer = require('ws').Server
    , wss = new WebSocketServer({port: 8010});
    wss.on('connection', function(ws) {
        ws.on('message', function(message) {
        console.log('Received from client: %s', message);
        ws.send('Server received from client: ' + message);
    });
 });
EOF

// 启动ws服务
$ node ws-server.js
Server started

3.3. 启动另一个终端作为ws客户端进行ws调试

注意将/usr/local/node/bin/,加入到PATH目录,这样可以直接执行wscat命令:

1
2
3
4
5
$ wscat -c ws://localhost:8020
Connected (press CTRL+C to quit)
> Hey,Man!
< Server received from client: Hey,Man!
Disconnected (code: 1006, reason: "")

3.4. 利用wscat调试

可以发现,WS协议经过一次HTTP协议升级后,完成了WS握手过程,就开始进行数据收发了,内部过程即客户端wscat通过Upgrade: websocketConnection: Upgrade,外加版本和Key信息发送,服务端响应了HTTP/1.1 101 Switching Protocols协议切换到websocket响应。

另外,注意到ws后续的收发过程中,是没有HTTP协议的相关头信息的,即ws等同于应用层的传输协议,可以自定义相关的子协议了,因此其数据传输的效率相比HTTP协议的轮询方式要高出很多!

3.5. 利用chrome调试工具调试

打开Chrome的Console控制台,执行下述JS脚本,注意需要同源!

1
2
3
4
// 需要和ws同源!
var ws_srv = new WebSocket('ws://10.40.2.181:8020');
ws_srv.onmessage = function(msg){console.log(msg.data)};
ws.send("hey,man!")

4. 问题讨论

  1. 并发连接:大多数体面的WebSocket服务器可以支持数千个并发连接,但一旦WebSocket服务器进程处理了实际数据的接收,处理和响应消息所需的工作量是多少?
  2. 应用集成:在各种潜在的问题,例如与数据库之间的读取和写入,与游戏服务器的集成,每个客户端的资源分配和管理等等。
  3. 负载均衡:一台机器无法处理工作负载时,您需要开始添加其他服务器,这意味着现在您需要开始考虑负载平衡
  4. 数据同步:连接到不同服务器的客户端之间的消息同步

相关问题:服务器的并发连接支持问题,C10K、10M连接支持(IO多路复用相关),同时还需要保障业务处理过程中的效率问题

5. 阅读内容

5.1. ably服务商的文档

文档较为详细讲解了websocket的基本知识,下面还有几篇附带的链接也可以读一下!

https://www.ably.io/concepts/websockets

5.2. ably ws聊天示例,可以直接通过浏览器查看ws的连接创建过程

https://www.ably.io/design-patterns/read-receipts

5.3. 其他参考