HTML5 之 WebSocket

HTML5 之 WebSocket

2013 年 6 月 14 日

什么是 WebSocket

WebSocket 是 HTML5 提出的一个协议规范, 参考rfc6455. 不过目前还都是在草案, 没有成为标准, 毕竟 HTML5 还在路上.

WebSocket 约定了一个通信的规范, 通过一个握手的机制, 客户端(浏览器)和服务器(WebServer)之间能建立一个类似 TCP 的连接, 从而方便 CS 之间的通信.

在 WebSocket 出现之前, web 交互一般是基于 HTTP 协议的短连接或者长连接. 短连接的过程大概有下面几个步骤:

  • 建立 TCP 连接
  • 浏览器发出 HTTP 请求
  • WebServer 应答
  • 断开 TCP 连接.

优点是简洁明了, 缺点也很明显, 每一次的交互中, 建立和断开 TCP 连接带来了比较大的开销, 而且 HTTP 协议头比较长, 也会带来带宽的浪费.

通过设置 HTTP 头中的keep-alive域可以实现 HTTP 长连接, 避免了建立和断开连接的开销, 但是 HTTP 协议头的问题仍然无法解决.

除此之外, WebServer 主动向浏览器推送数据的处理会比较麻烦, 要么是浏览器发起轮询(毫无疑问, 这是一个低效的方式), 或者利用comet技术), 比较复杂, 而且这已经不是在协议层上解决问题了.

而 WebSocket 的出现, 解决了上面描述的问题.

WebSocket 握手协议

WebSocket 是一种全新的协议, 不属于 HTTP 无状态协议, 协议名为"ws", 这意味着一个 WebSocket 连接地址会是这样的写法:ws://**.

WebSocket 协议本质上是一个基于 TCP 的协议. 建立连接需要握手, 客户端(浏览器)首先向服务器(WebServer) 发起一条特殊的 HTTP 请求, WebServer 解析后生成应答到浏览器, 这样子一个 WebSocket 连接就建立了, 直到某一方关闭连接.

由于是草案的原因, 前前后后就出现了多个版本的握手协议, 分情况说明一下.

基于 flash 的握手协议

使用场景是IE的多数版本, 因为 IE 的多数版本不都不支持 WebSocket 协议, 以及 FF、Chrome 等浏览器的低版本, 还没有原生的支持 WebSocket. 此处, server 唯一要做的, 就是准备一个 WebSocket-Location 域给 client, 没有加密, 可靠性很差.

#客户端请求:
GET /ls HTTP/1.1
Upgrade: WebSocket
Connection: Upgrade
Host: www.xx.com
Origin: http://www.xx.com

#服务器返回:
HTTP/1.1 101 Web Socket Protocol Handshake
Upgrade: WebSocket
Connection: Upgrade
WebSocket-Origin: http://www.xx.com
WebSocket-Location: ws://www.xx.com/ls

基于md5加密方式的握手协议

# 客户端请求:
GET /demo HTTP/1.1
Host: example.com
Connection: Upgrade
Sec-WebSocket-Key2: **
Upgrade: WebSocket
Sec-WebSocket-Key1: **
Origin: http://example.com
[8-byte security key]

# 服务端返回:
HTTP/1.1 101 WebSocket Protocol Handshake
Upgrade: WebSocket
Connection: Upgrade
WebSocket-Origin: http://example.com
WebSocket-Location: ws://example.com/demo
[16-byte hash response]

其中 Sec-WebSocket-Key1, Sec-WebSocket-Key2 和 [8-byte security key] 这几个头信息是web server用来生成应答信息的来源, 依据 draft-hixie-thewebsocketprotocol-76 草案的定义, web server基于以下的算法来产生正确的应答信息:

  • 逐个字符读取 Sec-WebSocket-Key1 头信息中的值, 将数值型字符连接到一起放到一个临时字符串里, 同时统计所有空格的数量.
  • 将在第(1)步里生成的数字字符串转换成一个整型数字, 然后除以第(1)步里统计出来的空格数量, 将得到的浮点数转换成整数型.
  • 将第(2)步里生成的整型值转换为符合网络传输的网络字节数组.
  • 对 Sec-WebSocket-Key2 头信息同样进行第(1)到第(3)步的操作, 得到另外一个网络字节数组.
  • 将 [8-byte security key] 和在第(3)、(4)步里生成的网络字节数组合并成一个16字节的数组.
  • 对第(5)步生成的字节数组使用MD5算法生成一个哈希值, 这个哈希值就作为安全密钥返回给客户端, 以表明服务器端获取了客户端的请求, 同意创建 WebSocket 连接.

基于sha加密方式的握手协议

也是目前见的最多的一种方式, 这里的版本号目前是需要13以上的版本.

# 客户端请求:
GET /ls HTTP/1.1
Upgrade: websocket
Connection: Upgrade
Host: www.xx.com
Sec-WebSocket-Origin: http://www.xx.com
Sec-WebSocket-Key: 2SCVXUeP9cTjV+0mWB8J6A==
Sec-WebSocket-Version: 13

# 服务器返回:
HTTP/1.1 101 Switching Protocols
Upgrade: :websocket
Connection: Upgrade
Sec-WebSocket-Accept: mLDKNeBNWz6T9SxU+o0Fy/HgeSw=

它的原理, 是把客户端上报的 key 拼上一段 GUID “258EAFA5-E914-47DA-95CA-C5AB0DC85B11″, 拿这个字符串做 SHA-1 hash 计算, 然后再把得到的结果通过 base64 加密, 最后在返回给客户端.

WebSocket 数据帧

WebScoket 协议中, 数据以帧序列的形式传输, 具体的协议标准可以参考rfc6455

(1) 客户端向服务器传输的数据帧必须进行掩码处理:服务器若接收到未经过掩码处理的数据帧, 则必须主动关闭连接.

(2) 服务器向客户端传输的数据帧一定不能进行掩码处理. 客户端若接收到经过掩码处理的数据帧, 则必须主动关闭连接.

针对上情况, 发现错误的一方可向对方发送 close 帧(状态码是1002, 表示协议错误), 以关闭连接.

FIN:1位

表示这是消息的最后一帧(结束帧), 一个消息由一个或多个数据帧构成. 若消息由一帧构成, 起始帧即结束帧.

RSV1, RSV2, RSV3:各1位

MUST be 0 unless an extension is negotiated that defines meanings for non-zero values. If a nonzero value is received and none of the negotiated extensions defines the meaning of such a nonzero value,  the receiving endpoint MUST Fail the WebSocket Connection.

如果未定义扩展, 各位是0;如果定义了扩展, 即为非0值. 如果接收的帧此处非0, 扩展中却没有该值的定义, 那么关闭连接.

OPCODE:4位

解释 Payload Data, 如果接收到未知的 opcode, 接收端必须关闭连接.
- 0x0 表示附加数据帧
- 0x1 表示文本数据帧
- 0x2 表示二进制数据帧
- 0x3-7 暂时无定义, 为以后的非控制帧保留
- 0x8 表示连接关闭
- 0x9 表示 ping
- 0xA 表示 pong
- 0xB-F 暂时无定义, 为以后的控制帧保留

MASK:1位

用于标识 Payload Data 是否经过掩码处理. 如果是1, Masking-key 域的数据即是掩码密钥, 用于解码 Payload Data. 客户端发出的数据帧需要进行掩码处理, 所以此位是1.

Payload length:7位, 7+16位, 7+64位, Payload Data 的长度(以字节为单位)

- 如果其值在0-125, 则是payload的真实长度.
- 如果值是126, 则后面2个字节形成的16位无符号整型数的值是payload的真实长度. 注意, 网络字节序, 需要转换.
- 如果值是127, 则后面8个字节形成的64位无符号整型数的值是payload的真实长度. 注意, 网络字节序, 需要转换.
- 长度表示遵循一个原则, 用最少的字节表示长度(我理解是尽量减少不必要的传输). 举例说, payload真实长度是124, 在0-125之间, 必须用前7位表示;不允许长度1是126或127, 然后长度2是124, 这样违反原则.

分片

这种情况就比较复杂, 具体可以参考 rfc, 一般在日常中用到的应该会比较少.

客户端API

WebSocket 的客户端 API 可以参考Tutorial

一个简单的代码参考:

<!DOCTYPE HTML>
<html>
<head>
    <script type="text/javascript">
    function WebSocketTest()
    {
        if ("WebSocket" in window)
        {
            alert("WebSocket is supported by your Browser!");
            // Let us open a web socket
            var ws = new WebSocket("ws://localhost:9998/echo");
            ws.onopen = function()
            {
                // Web Socket is connected,  send data using send()
                ws.send("Message to send");
                alert("Message is sent...");
            };
            ws.onmessage = function (evt)
            {
                var received_msg = evt.data;
                alert("Message is received...");
            };
            ws.onclose = function()
            {
                // websocket is closed.
                alert("Connection is closed...");
            };
        }
        else
        {
            // The browser doesn't support WebSocket
            alert("WebSocket NOT supported by your Browser!");
        }
    }
    </script>
</head>
<body>
    <div id="sse">
        <a href="javascript:WebSocketTest()">Run WebSocket</a>
    </div>
</body>
</html>

WebSocket服务器

目前开源的 WebSocket server 的代码还是不少的.

自己也整理过一份C的, 支持上面三种握手情况, 并嵌在 reactor 框架下, 使用chrome测试过: gbase-wsconn

目前主流的浏览器对 WebSocket 的支持:

浏览器

支持性

Chrome

Supported in version 4+

Firefox

Supported in version 4+

IE

Supported in version 10+

Opera

Supported in version 10+

Safari

Supported in version 5+

参考文章

  1. WIKI
  2. 使用 HTML5 WebSocket 构建实时 Web 应用
  3. WebSocket不同版本的三种握手方式以及一个Netty实现JAVA类
  4. WebSocket协议分析

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏美团技术团队

Node.js Stream - 进阶篇

在构建较复杂的系统时,通常将其拆解为功能独立的若干部分。这些部分的接口遵循一定的规范,通过某种方式相连,以共同完成较复杂的任务。譬如,shell通过管道|连接各...

45140
来自专栏DT乱“码”

oracle 里面定时执行任务设置

DECLARE    job_no_ NUMBER;    BEGIN       DBMS_JOB.SUBMIT(job_no_,         ...

21680
来自专栏从零开始学自动化测试

python接口自动化21-规范的API接口文档示例

前言 接口文档到底长啥样?做接口测试最大的障碍在于没有接口文档,很多公司不注重接口文档的编写,导致测试小伙伴没见过接口文档。 运气好一点的测试小伙伴可能厚着脸皮...

1.5K80
来自专栏GopherCoder

『Go 语言学习专栏』-- 第十期

17230
来自专栏前端小吉米

如何写出一手好的小程序之多端架构篇

为了大家能更好的开发出一些高质量、高性能的小程序,这里带大家理解一下小程序在不同端上架构体系的区分,更好的让大家理解小程序一些特有的代码写作方式。

20930
来自专栏小曾

.Net Web开发技术栈

有很多朋友有的因为兴趣,有的因为生计而走向了.Net中,有很多朋友想学,但是又不知道怎么学,学什么,怎么系统的学,为此我以我微薄之力总结归纳写了一篇.Net w...

47130
来自专栏程序员的SOD蜜

单数据库,多数据库,单实例,多实例不同情况下的数据访问效率测试

最近公司的项目准备优化一下系统的性能,希望在数据库方面看有没有提升的空间,目前压力测试发现数据库服务器压力还不够大,Web服务器压力也不是很大的情况下,前台页面...

270100
来自专栏杨建荣的学习笔记

Oracle Data Guard延迟的原因(r11笔记第69天)

Oracle Data Guard中很可能出现延迟的情况,而数据一旦出现延迟就意味着丢数据。退一步来说丢数据总比数据乱了好,但是回过头来,能不丢数据但...

40950
来自专栏慎独

AVPlayer初体验之边下边播与视频缓存

2.7K50
来自专栏互联网杂技

内存卡存储原理,你知道吗?

1、 简介: SD卡(Secure Digital Memory Card)是一种为满足安全性、容量、性能和使用环境等各方面的需求而设计的一种新型存储器...

51060

扫码关注云+社区

领取腾讯云代金券