zoukankan      html  css  js  c++  java
  • [转载]websocket最新协议的握手实现


    现在能找到的实现握手协议的代码基本上是76草案的,76草案已经过期,Firefox在强制升级到6.0以后,不再支持76草案,而且WebSocket对象也不存在了,转而使用自家的对象:MozWebSocket,所以需要修改你的javascript代码:

    var support = "MozWebSocket" in window ? 'MozWebSocket' : ( "WebSocket" in window ? 'WebSocket' : null ) ;  if( support ) {         ws = new window[support]('ws://localhost:8080', 'my-custom-chat-protocol'); }else{         alert("Your browser doesn't support websocket!"); }

    虽然Firefox在和Chrome血拼版本号,谁敢说Firefox疯狂升级版本号而没却有什么更新?看看Firefox:把WebSocket升级为MozWebSocket也算是一大超级创新!因为你刚刚发布好了程序,和朋友们出去吃着火锅还唱着歌,突然客户打电话来说:原来跑的好好的代码不能运行了!然后翻遍 mozilla.org ,终于在一个小角落了发现了声明:6.0以后没有了WebSocket对象,取而代之的是MozWebSocket,就算你可以随便修改名称,但是你是不是得保留原有的?

    好了,这篇文章主要是记录不同语言的握手协议实现,当然了,没有涵盖到的语言大同小异,对这修改一下就可以了。

    因为我平常主要使用 ruby、php、javascript,而 nodejs基本上用来做测试的比较多。

    如下,我获取到的一次请求头部,最新版本的websocket协议没有了key1和key2这两个罗嗦的玩意,看来html5小组也在精简实现规则,这里不是全部的头部,比如应该有 cookie和User-Agent、Accept等,但都与实现无关,所以不贴出来了。

     

    GET /pub/chat?q=me HTTP/1.1 Host: localhost:8080 Connection: keep-alive, Upgrade Sec-WebSocket-Version: 7 Sec-WebSocket-Origin: null Sec-WebSocket-Protocol: my-custom-chat-protocol Sec-WebSocket-Key: /4VCUCTU2R4ycJl99yQWXw== Pragma: no-cache Cache-Control: no-cache Upgrade: websocket

     

    好,没有了Sec-WebSocket-Key1 和 Sec-WebSocket-Key2,只有一个Sec-WebSocket-Key,对于整天跟编码打交到的程序员,一眼就可以看出来:这个是一个经过base64编码后的数据,不过你不需要解码该数据,需要把这个字符串连接上一个固定的字符串:

     

    258EAFA5-E914-47DA-95CA-C5AB0DC85B11

     

    至于为什么是上面这个一堆,我没有深入研究,实际上websocket的草案我也是大致的浏览,因为平常很忙,没有细心去钻研。

    把Sec-WebSocket-Key:后的字符串,即:/4VCUCTU2R4ycJl99yQWXw== 连接上那一串固定字符串,生成一个这样:

     

    /4VCUCTU2R4ycJl99yQWXw==258EAFA5-E914-47DA-95CA-C5AB0DC85B11

     

    假设该字符串存储在变量 key 中:

    对该字符串先用 sha1安全散列算法计算出二进制的值,然后用base64对其进行编码,即可以得到握手后的字符串:

    Ruby计算握手字符串:

     

    require 'digest/sha1' require 'base64' # 注意:这里要用 strict_encode64 方法 response_key = Base64.strict_encode64(Digest::SHA1.digest( key ))

    Nodejs计算:

     

     

    var crypto = require('crypto'); response_key = crypto.createHash('sha1').update( key ).digest('base64');

     

    PHP计算:

     

    // sha1函数第二个参数为 true,sha1返回的为二进制格式数据 $response_key = base64_encode(sha1($key, true))

     

    最终的结果应该是:

     

    i/yxBvO+uGlGAOVqFUhEdVQS8mM=

     

    对照实现看看是否一样,

    生成后,返回给客户端:

     

    HTTP/1.1 101 Switching Protocols\r\n  Upgrade: websocket\r\n  Connection: Upgrade\r\n  Sec-WebSocket-Accept: i/yxBvO+uGlGAOVqFUhEdVQS8mM=\r\n\r\n

     

    好,用你的socket输出给浏览器,就可以完成握手了。

    下篇介绍如何用C/C++来实现。

     

    websocket通讯协议(10版本)简介

    工作中用到了websocket 协议10版本的,英文的协议请看这里:

     

    http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-10

     

    这篇文章相当于工作的总结吧。

     

    首先, 你需要简单了解一下为什么会诞生websocket通讯协议,web上的通讯一般都是基于HTTP(超文本传输协议)的通讯,故而没有建立长时间的网络连接的方法,一般的通讯都是这样子的:

     

    请求

    浏览器———————>服务器

    <———————–

    响应

     

    这种连接都是客户端发起的,服务器回复数据后关闭连接。

    就好像你用浏览器访问百度输入www.baidu.com后,浏览器发起请求,百度的服务器将该页面的html超文本传给你的浏览器后关闭连接。

     

    这种连接时间很短的, 而且服务器无法主动传送数据。

     

    举几个例子:

    优酷, 土豆这些网站可以在网上播放电影,播放电影需要持续传送数据的,故只能内嵌flash播放器,用flash中的flash socket持续传送数据。

     

    用html, js等编写一个及时聊天软件是很困难的,关键就在于不能建立持续的连接,服务器不能主动传送数据给客户端。只能隔一段时间客户端发起请求主动询问服务器有无数据?

     

    websocket可以建立稳定的连接,能解决上述的问题。

     

    先说下原理,稍后会把代码文件穿上来给大家下载。

     

    websocket通讯过程:

     

    1.客户端发起连接请求

    websocket客户端首先发起一个连接请求,发送的数据格式如下:

     

     

    GET /10.15.1.218:12345/chat?key=value\r\n

    HTTP/1.1\r\n
    Upgrade: websocket\r\n
    Connection: Upgrade\r\n
    Host: 10.15.1.218:12345\r\n
    Sec-WebSocket-Origin: null\r\n
    Sec-WebSocket-Key: 4tAjitqO9So2Wu8lkrsq3w==\r\n
    Sec-WebSocket-Version: 8\r\n\r\n

     

    这是类似于HTTP的头,注意每行数据结尾结束符是”\r\n”, 最后的结束符是”\r\n\r\n”。

     

    请求头第1行详解:

    “GET /”后面是服务器的IP和端口(10.15.1.218:12345)必须有。’/'的后面是你自己字符串,(chat),随便你传什么,这部分是可选的。字符串 ‘?’后面是一些参数(key=value),是什么你自己定义, 这部分也是可选的。像下面这三种都是合法的:

     

    GET /10.15.1.218:12345\r\n

    或者

    GET /10.15.1.218:12345/chat\r\n

    或者

    GET /10.15.1.218:12345/chat?key=value\r\n

     

    第2, 3, 4, 5, 6行:

     

    HTTP/1.1\r\n
    Upgrade: websocket\r\n
    Connection: Upgrade\r\n
    Host: 10.15.1.218:12345\r\n
    Sec-WebSocket-Origin: null\r\n

     

    这些都基本是固定的格式与内容,Host: 后面是服务器(被连接者)的IP和Port。

     

    第7行:

     

    Sec-WebSocket-Key: 4tAjitqO9So2Wu8lkrsq3w==\r\n

    Sec-WebSocket-Key后面的那一串东西,那一串长度为24的字符串是客户端随机生成的,我们暂时叫他cli_key,服务器必须用它经过一定的运算规则生成服务器端的key,暂时叫做ser_key,然后把ser_key发回去,客户端验证正确后,握手成功!

     

    第8行:

     

    Sec-WebSocket-Version: 8\r\n\r\n

     

    之所以版本为8的原因,我不太清楚。10版本的通讯协议中客户端发出的都是8。

     

    chrome 14浏览器中实现了websocket客户端,不用自己实现。可以去下载一个,当websocket客户端用。

     

    2.制作服务端的密钥

    我们的服务器将key1(长度24)截取出来

     

    4tAjitqO9So2Wu8lkrsq3w==

     

    用它和自定义的一个字符串(长度36):

     

    258EAFA5-E914-47DA-95CA-C5AB0DC85B11

    连接起来,像这样:

     

    4tAjitqO9So2Wu8lkrsq3w==258EAFA5-E914-47DA-95CA-C5AB0DC85B11

     

    然后把这一长串经过SHA-1算法加密,得到长度为20字节的二进制数据,

    再将这些数据经过Base64编码,最终得到服务端的密钥,也就是ser_key:

     

    bEVeGLZrb9fS3Rj8WzExJdCsedg=

     

    3.服务端返回密钥

     

    然后需要把密钥返回给客户端,完成握手,发送的数据格式如下:

     

    HTTP/1.1 101 Switching Protocols\r\n

    Upgrade: websocket\r\n

    Connection: Upgrade\r\n

    Sec-WebSocket-Accept: bEVeGLZrb9fS3Rj8WzExJdCsedg=\r\n\r\n

     

    至此,算是握手成功了!

     

    4.传输数据(简单的介绍数据长度小于126的数据传输,传输大于等于126字节的数据头部(head)可就不止2个字节了,去看英文文档中介绍头部的部分)

     

    必须有掩码

    客户端———————–>服务器

    <———————–

    掩码(可选)

     

    协议中规定客户端发向服务器的数据必须有掩码,比如需要发送一个字符串“Hello”,

     

    “Hello“的ascii码:

    H        e              l            l            o

    十六进制         0×48     0×65        0x6c     0x6c     0x6f

    十进制             72        101          108      108       111

     

     

     

    但是实际发出的数据是这样的:

     

    ——————head—–掩码0—-1——-2——-3—–H—-e—–l——l——-o————–

    0×81 0×85   0×37  0xfa   0×21    0x3d 0x7f 0x9f 0x4d 0×51 0×58

     

    head头部我不在这里说了,需要很多文字才能说明,自己去英文协议中相关地方查看一下吧,head后是4字节的掩码,随机生成的,再后面是数据了,你可能发现数据变了,这些数据是 hello的ascii码和掩码做异或运算算出来的。

     

    运算规则是这样的, 第零个字符’H'和第零个掩码异或, 第一个字符’e'和第一个掩码异或……

     

     

    其实就是用c语言表示就是res = str[n]^mask[n%4]

     

     

    服务器发出数据可以有掩码, 也可以没有掩码

     

    发出一个“Hello”字符串可以发出和客户端一样的数据,也可以发出像下面的无掩码的:

     

    0×81 0×05 0×48 0×65 0x6c 0x6c 0x6f
    H       e       l        l       o

     

    这就是头部信息加上原始数据啦。

     

    5. 关闭连接

     

    这部分很简单,或许你可以去英文协议中找到它看一看。

     

     

    基于Websocket草案10协议的升级及基于Netty的握手实现

    最近发现,WEBWW在chrome14及FF6.5中没法与后台建立连接了,后面经过查找原因,是chrome14中使用最新的websocket协议草案,而chrome12中使用的websocket协议标准还是草案7.5、7.6的标准;现在草案的最新版本是草案10,草案的链接地址为:http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-10,本次协议变更比较大,主要体现在安全性和可扩展性上:

    1、握手的标准:

    1)、最老的websocket草案标准中是没有安全key,草案7.5、7.6中有两个安全key,而现在的草案10中只有一个安全key,即将7.5、7.6中http头中的”Sec-WebSocket-Key1"与"Sec-WebSocket-Key2"合并为了一个"Sec-WebSocket-Key"

    2)、把http头中Upgrade的值由”WebSocket“修改为了”websocket”;

    3)、把http头中的”-Origin”修改为了”Sec-WebSocket-Origin”;

    4)、增加了http头”Sec-WebSocket-Accept”,用来返回原来草案7.5、7.6服务器返回给客户端的握手验证,原来是以内容的形式返回,现在是放到了http头中;另外服务器返回客户端的验证方式也变了,后面会有介绍。

    2、数据传输的格式:

    以下是一个格式标准图:

    FIN:1位,用来表明这是一个消息的最后的消息片断,当然第一个消息片断也可能是最后的一个消息片断;

    RSV1, RSV2, RSV3: 分别都是1位,如果双方之间没有约定自定义协议,那么这几位的值都必须为0,否则必须断掉WebSocket连接;

    Opcode:4位操作码,定义有效负载数据,如果收到了一个未知的操作码,连接也必须断掉,以下是定义的操作码:
    *  %x0 表示连续消息片断
    *  %x1 表示文本消息片断
    *  %x2 表未二进制消息片断
    *  %x3-7 为将来的非控制消息片断保留的操作码
    *  %x8 表示连接关闭
    *  %x9 表示心跳检查的ping
    *  %xA 表示心跳检查的pong
    *  %xB-F 为将来的控制消息片断的保留操作码

    Mask:1位,定义传输的数据是否有加掩码,如果设置为1,掩码键必须放在masking-key区域,客户端发送给服务端的所有消息,此位的值都是1;

    Payload length: 传输数据的长度,以字节的形式表示:7位、7+16位、或者7+64位。如果这个值以字节表示是0-125这个范围,那这个值就表示传输数据的长度;如果这个值是126,则随后的两个字节表示的是一个16进制无符号数,用来表示传输数据的长度;如果这个值是127,则随后的是8个字节表示的一个64位无符合数,这个数用来表示传输数据的长度。多字节长度的数量是以网络字节的顺序表示。负载数据的长度为扩展数据及应用数据之和,扩展数据的长度可能为0,因而此时负载数据的长度就为应用数据的长度。

    Masking-key:0或4个字节,客户端发送给服务端的数据,都是通过内嵌的一个32位值作为掩码的;掩码键只有在掩码位设置为1的时候存在。
    Payload data:  (x+y)位,负载数据为扩展数据及应用数据长度之和。
    Extension data:x位,如果客户端与服务端之间没有特殊约定,那么扩展数据的长度始终为0,任何的扩展都必须指定扩展数据的长度,或者长度的计算方式,以及在握手时如何确定正确的握手方式。如果存在扩展数据,则扩展数据就会包括在负载数据的长度之内。
    Application data:y位,任意的应用数据,放在扩展数据之后,应用数据的长度=负载数据的长度-扩展数据的长度。

    数据帧协议是按照扩展的巴科斯范式(ANBF:Augmented Backus-Naur Form RFC5234)组成的:

    ws-frame = frame-fin
    frame-rsv1
    frame-rsv2
    frame-rsv3
    frame-opcode
    frame-masked
    frame-payload-length
    [ frame-masking-key ]
    frame-payload-data

    frame-fin = %x0 ; 表示这不是当前消息的最后一帧,后面还有消息
    / %x1 ; 表示这是当前消息的最后一帧

    frame-rsv1 = %x0
    ; 1 bit, 如果没有扩展约定,该值必须为0

    frame-rsv2 = %x0
    ; 1 bit, 如果没有扩展约定,该值必须为0

    frame-rsv3 = %x0
    ; 1 bit, 如果没有扩展约定,该值必须为0

    frame-opcode = %x0 ; 表示这是一个连续帧消息
    / %x1 ; 表示文本消息
    / %x2 ; 表示二进制消息
    / %x3-7 ; 保留
    / %x8 ; 表示客户端发起的关闭
    / %x9 ; ping(用于心跳)
    / %xA ; pong(用于心跳)
    / %xB-F ; 保留

    frame-masked = %x0 ; 数据帧没有加掩码,后面没有掩码key
    / %x1 ; 数据帧加了掩码,后面有掩码key

    frame-payload-length = %x00-7D
    / %x7E frame-payload-length-16
    / %x7F frame-payload-length-63
    ; 表示数据帧的长度

    frame-payload-length-16 = %x0000-FFFF
    ; 表示数据帧的长度

    frame-payload-length-63 = %x0000000000000000-7FFFFFFFFFFFFFFF
    ; 表示数据帧的长度

    frame-masking-key = 4( %0×00-FF ) ; 掩码key,只有当掩码位为1时出现

    frame-payload-data = (frame-masked-extension-data
    frame-masked-application-data) ; 当掩码位为1时,这里的数据为带掩码的数据,扩展数据及应用数据都带掩码
    / (frame-unmasked-extension-data
    frame-unmasked-application-data) ; 当掩码位为0时,这里的数据为不带掩码的数据,扩展数据及应用数据都不带掩码

    frame-masked-extension-data = *( %x00-FF ) ; 目前保留,以后定义

    frame-masked-application-data = *( %x00-FF )

    frame-unmasked-extension-data = *( %x00-FF ) ; 目前保留,以后定义

    frame-unmasked-application-data = *( %x00-FF )

  • 相关阅读:
    CMD窗口正确显示UTF-8字符
    《剑指offer》 链表中倒数第k个节点
    《剑指offer》 调整数组顺序使得奇数在偶数前面
    《剑指offer》 大数递增
    《剑指offer》 数值的整数次方
    《剑指offer》 二进制中1的个数
    《剑指offer》 跳台阶
    《剑指offer》斐波那契数列
    《剑指offer》旋转数组中的最小数字
    刷《剑指offer》笔记
  • 原文地址:https://www.cnblogs.com/fx2008/p/2738798.html
Copyright © 2011-2022 走看看