WebSocket原理与实践(二)---WebSocket协议

WebSocket原理与实践(二)---WebSocket协议javascript

    WebSocket协议是为了解决web即时应用中服务器与客户端浏览器全双工通讯问题而设计的。协议定义ws和wss协议,分别为普通请求和基于SSL的安全传输, ws端口是80,wss的端口为443.html

WebSocket协议由两部分组成,握手和数据传输。java

2-1 握手
WS的握手使用HTTP来实现的。客户端的握手消息是一个普通的,带有Upgrade头的,HTTP Request的消息。
先来看看以下代码:node

<!DOCTYPE html>
<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <title>websocket</title>
    <meta name="viewport" content="width=device-width, initial-scale=1"/>
  </head>
  <body>
    <script type="text/javascript">
      var wsUrl = "wss://echo.websocket.org";
      var ws = new WebSocket(wsUrl);
      ws.onopen = function() {
        console.log('open');
      };
      ws.onmessage = function(msg) {
        console.log(msg.data);
      }
      ws.onclose = function() {
        console.log('已经被关闭了');
      }
    </script>
  </body>
</html>

页面运行后,咱们能够看到连接到 wss://echo.websocket.org 期间记录的一个握手协议。先来看看客户端发送http的请求头:git

GET /chat HTTP/1.1
Host:echo.websocket.org
Upgrade:websocket
Connection:Upgrade
Sec-WebSocket-Key:ALS2AoBJtUup67heKDgzFg==
Origin:file://
Sec-WebSocket-Version:13

服务器响应的头字段github

Connection:Upgrade
Sec-WebSocket-Accept:qyzx/EgbRK15QNmr5PhpMQrPZMM=
Server: Kaazing Gateway
Upgrade:websocket

下面是请求和响应头字段的含义:
Upgrade: websocket, 告诉服务器这个HTTP连接是升级的WebSocket协议。
Connection:Upgrade 告知服务器当前请求连接是升级的。
Origin: 该字段是用来防止客户端浏览器使用脚本进行未受权的跨源攻击,服务器要根据这个字段是否接受客户端的socket连接。
能够返回一个HTTP错误状态码来拒绝链接。web

Sec-WebSocket-Key: ALS2AoBJtUup67heKDgzFg==
Sec-WebSocket-Accept: qyzx/EgbRK15QNmr5PhpMQrPZMM=算法

Sec-WebSocket-Key 的值是一串长度为24的字符串是客户端随机生成的base64编码的字符串,它发送给服务器,服务器须要使用它通过必定的运算规则生成服务器的key,而后把服务器的key发到客户端去,客户端验证正确后,握手成功。浏览器

握手的具体原理:当咱们客户端执行 new WebSocket(''wss://echo.websocket.org')的时候,客户端就会发起请求报文进行握手申请,报文中有一个key就是
Sec-WebSocket-Key,服务器获取到key,会将这个key与字符串258EAFA5-E914-47DA-95CA-C5AB0DC85B11相连,对新的字符串经过sha1安全散列算法计算出结果后,再进行Base64编码,而且将结果放在请求头的"Sec-WebSocket-Accept",最后返回给客户端,
客户端进行验证后,握手成功。握手成功后就能够开始数据传输了。安全

下面是实现一个简单的握手协议的demo,代码以下:

### 目录结构以下:

demo
  |--- hands.html
  |--- hands.js

hands.html 代码以下:

<html>
<head>
  <title>WebSocket Demo</title>
</head>
<body>
  <script type="text/javascript">
    var ws = new WebSocket("ws://127.0.0.1:8000");
    ws.onerror = function(e) {
      console.log(e);
    };
    ws.onopen = function() {
      console.log('握手成功');
    }
  </script>
</body>
</html>

hands.js 代码以下:

var crypto = require('crypto');

var WS = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11';

require('net').createServer(function(o) {
  var key;
  o.on('data', function(e) {
    if (!key) {
      console.log(e);

      key = e.toString().match(/Sec-WebSocket-Key: (.+)/)[1];
      console.log(key);
      
      // WS的字符串 加上 key, 变成新的字符串后作一次sha1运算,最后转换成Base64
      key = crypto.createHash('sha1').update(key+WS).digest('base64');
      console.log(key);

      // 输出字段数据,返回到客户端,
      o.write('HTTP/1.1 101 Switching Protocol\r\n');
      o.write('Upgrade: websocket\r\n');
      o.write('Connection: Upgrade\r\n');
      o.write('Sec-WebSocket-Accept:' +key+'\r\n');
      // 输出空行,使HTTP头结束
      o.write('\r\n');
    } else {
      // 数据处理
    }
  })
}).listen(8000);

首先在命令行中 进入相对应项目目录后,运行 node hands.js, 而后打开 hands.html 运行一下便可看到 命令行中打印出来以下信息:

$ node hands.js
<Buffer 47 45 54 20 2f 20 48 54 54 50 2f 31 2e 31 0d 0a 48 6f 73 74 3a 20 31 32 37 2e 30 2e 30 2e 31 3a 38 30 30 30 0d 0a 43 6f 6e 6e 65 63 74 69 6f 6e 3a 20 ... >
+iHlfGTolBaWYpnyTIw22g==
W7IEsdQtwv8EP2204kssK/6pg+c=

而后在浏览器中查看请求头以下信息:

Request Headers:

Connection:Upgrade
Host:127.0.0.1:8000
Origin:file://
Sec-WebSocket-Extensions:permessage-deflate; client_max_window_bits
Sec-WebSocket-Key:+iHlfGTolBaWYpnyTIw22g==
Sec-WebSocket-Version:13
Upgrade:websocket

响应头以下信息:

Response Headers:

Connection:Upgrade
Sec-WebSocket-Accept:W7IEsdQtwv8EP2204kssK/6pg+c=
Upgrade:websocket

如上信息能够看到,获取报文中的key代码:
key = e.toString().match(/Sec-WebSocket-Key: (.+)/)[1];
console.log(key); // 打印 +iHlfGTolBaWYpnyTIw22g==

和 Request Headers:中的 Sec-WebSocket-Key 值是同样的,该值是浏览器自动生成的,而后获取该值后,与 '258EAFA5-E914-47DA-95CA-C5AB0DC85B11',相连,对新的字符串经过sha1安全散列算法计算出结果后,再进行Base64编码,
而且将结果放在请求头的"Sec-WebSocket-Accept",最后返回给客户端,客户端进行验证后,握手成功。在浏览器中能够看到打印出 握手成功了。

github上查看demo