注册 登录  
 加关注
   显示下一条  |  关闭
温馨提示!由于新浪微博认证机制调整,您的新浪微博帐号绑定已过期,请重新绑定!立即重新绑定新浪微博》  |  关闭

小葫芦君(汉斯的博客)

博客迁移到新博客:https://blog.ssxingshou.com

 
 
 

日志

 
 
关于我

小小葫芦商城,为您提供高品质的商品,一流的产品,一流的包装服务,一流的物流服务,放心购买

网易考拉推荐

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

2014-04-13 12:12:02|  分类: 默认分类 |  标签: |举报 |字号 订阅

  下载LOFTER 我的照片书  |

最近发现,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)组成的:       

  1. ws-frame                = frame-fin  
  2.                           frame-rsv1  
  3.                           frame-rsv2  
  4.                           frame-rsv3  
  5.                           frame-opcode  
  6.                           frame-masked  
  7.                           frame-payload-length  
  8.                           [ frame-masking-key ]  
  9.                           frame-payload-data  
  10.   
  11. frame-fin               = %x0 ; 表示这不是当前消息的最后一帧,后面还有消息  
  12.                         / %x1 ; 表示这是当前消息的最后一帧  
  13.   
  14. frame-rsv1              = %x0  
  15.                           ; 1 bit, 如果没有扩展约定,该值必须为0  
  16.   
  17. frame-rsv2              = %x0  
  18.                           ; 1 bit, 如果没有扩展约定,该值必须为0  
  19.   
  20. frame-rsv3              = %x0  
  21.                           ; 1 bit, 如果没有扩展约定,该值必须为0  
  22.   
  23. frame-opcode            = %x0 ; 表示这是一个连续帧消息  
  24.                         / %x1 ; 表示文本消息  
  25.                         / %x2 ; 表示二进制消息  
  26.                         / %x3-7 ; 保留  
  27.                         / %x8 ; 表示客户端发起的关闭  
  28.                         / %x9 ; ping(用于心跳)  
  29.                         / %xA ; pong(用于心跳)  
  30.                         / %xB-F ; 保留  
  31.   
  32. frame-masked            = %x0 ; 数据帧没有加掩码,后面没有掩码key  
  33.                         / %x1 ; 数据帧加了掩码,后面有掩码key  
  34.   
  35. frame-payload-length    = %x00-7D  
  36.                         / %x7E frame-payload-length-16  
  37.                         / %x7F frame-payload-length-63  
  38.    ; 表示数据帧的长度  
  39.   
  40. frame-payload-length-16 = %x0000-FFFF  
  41.    ; 表示数据帧的长度  
  42.   
  43. frame-payload-length-63 = %x0000000000000000-7FFFFFFFFFFFFFFF  
  44.    ; 表示数据帧的长度  
  45.   
  46. frame-masking-key       = 4( %0x00-FF ) ; 掩码key,只有当掩码位为1时出现  
  47.   
  48. frame-payload-data      = (frame-masked-extension-data  
  49.                            frame-masked-application-data)   ; 当掩码位为1时,这里的数据为带掩码的数据,扩展数据及应用数据都带掩码  
  50.                         / (frame-unmasked-extension-data  
  51.                            frame-unmasked-application-data) ; 当掩码位为0时,这里的数据为不带掩码的数据,扩展数据及应用数据都不带掩码  
  52.   
  53. frame-masked-extension-data     = *( %x00-FF ) ; 目前保留,以后定义  
  54.   
  55. frame-masked-application-data   = *( %x00-FF )  
  56.   
  57. frame-unmasked-extension-data   = *( %x00-FF ) ; 目前保留,以后定义  
  58.   
  59. frame-unmasked-application-data = *( %x00-FF )  

    还有一些其它的变更,详细的可以查看查案文档了,主要的应该就是上面提到的两大块。

    后台Server使用的是Netty NIO框架,目前Netty官方还没有对websocket草案10的实现,只支持到草案7.6,自己根据草案标准实现netty的encode及decoder也不难,不过在非官方还是找到了Netty支持最新草案10的实现:https://github.com/joewalnes/webbit/tree/0356ba12f5c21f8a297a5afb433215bb2f738008/src/main/java/org/webbitserver/netty,呵,有现成的就用现成的了。

    在后台只需要判断是最新的草案10,将Encoder及Decoder分别换成Hybi10WebSocketFrameDecoder()及Hybi10WebSocketFrameEncoder(),判断是否最新草案10的websocket协议,只需要判断请求头中是否同时包括了这两个请求头:Sec-WebSocket-Origin及Sec-WebSocket-Key,如果包含了那说明是草案10标准,如果不是再判断是否草案76或最老的草案标准,进行相应的处理。

    握手的实现,首先要获取到请求头中的Sec-WebSocket-Key的值,再把这一段GUID "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"加到获取到的Sec-WebSocket-Key的值的后面,然后拿这个字符串做SHA-1 hash计算,然后再把得到的结果通过base64加密,就得到了返回给客户端的Sec-WebSocket-Accept的http响应头的值,以下是基于Netty的JAVA实现:    

  1.    private HttpResponse buildWebSocketRes(HttpRequest req, boolean isWebSocketProtocolAfterDraft7) {  
  2.        String reasonPhrase = "";  
  3.        // websocket协议草案7后面的格式,可以参看wikipedia上面的说明,比较前后版本的不同:http://en.wikipedia.org/wiki/WebSocket  
  4.        reasonPhrase = "Switching Protocols";  
  5.        HttpResponse res = new DefaultHttpResponse(HttpVersion.HTTP_1_1, new HttpResponseStatus(101, reasonPhrase));  
  6.        res.addHeader(HttpHeaders.Names.UPGRADE, "websocket");  
  7.        res.addHeader(HttpHeaders.Names.CONNECTION, HttpHeaders.Values.UPGRADE);  
  8.     String protocol = req.getHeader(Names.SEC_WEBSOCKET_PROTOCOL);  
  9.     if (protocol != null) {  
  10.         res.addHeader(Names.SEC_WEBSOCKET_PROTOCOL, protocol);  
  11.     }  
  12.     res.addHeader(SEC_WEBSOCKET_ACCEPT, getSecWebSocketAccept(req));  
  13.        return res;  
  14.    }  
  15. private String getSecWebSocketAccept(HttpRequest req) {  
  16.        // CHROME WEBSOCKET VERSION 8中定义的GUID,详细文档地址:http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-10  
  17.        String guid = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";  
  18.        String key = "";  
  19.        key = req.getHeader(SEC_WEBSOCKET_KEY);  
  20.        key += guid;  
  21.        try {  
  22.            MessageDigest md = MessageDigest.getInstance("SHA-1");  
  23.            md.update(key.getBytes("iso-8859-1"), 0, key.length());  
  24.            byte[] sha1Hash = md.digest();  
  25.            key = base64Encode(sha1Hash);  
  26.        } catch (NoSuchAlgorithmException e) {  
  27.            if (logger.isErrorEnabled()) {  
  28.                logger.error("NoSuchAlgorithmException:" + e.getMessage(), e);  
  29.            }  
  30.        } catch (UnsupportedEncodingException e) {  
  31.            if (logger.isErrorEnabled()) {  
  32.                logger.error("UnsupportedEncodingException:" + e.getMessage(), e);  
  33.            }  
  34.        }  
  35.        return key;  
  36.    }  
  37. /** 
  38.     * 将输入字节数组进行base64编码,再返回编码后的字符串 
  39.     *  
  40.     * @param input 
  41.     * @return 
  42.     */  
  43.    public static String base64Encode(byte[] input) {  
  44.        BASE64Encoder encoder = new BASE64Encoder();  
  45.        String base64 = encoder.encode(input);  
  46.        return base64;  
  47.    }  
  评论这张
 
阅读(668)| 评论(0)
推荐 转载

历史上的今天

在LOFTER的更多文章

评论

<#--最新日志,群博日志--> <#--推荐日志--> <#--引用记录--> <#--博主推荐--> <#--随机阅读--> <#--首页推荐--> <#--历史上的今天--> <#--被推荐日志--> <#--上一篇,下一篇--> <#-- 热度 --> <#-- 网易新闻广告 --> <#--右边模块结构--> <#--评论模块结构--> <#--引用模块结构--> <#--博主发起的投票-->
 
 
 
 
 
 
 
 
 
 
 
 
 
 

页脚

网易公司版权所有 ©1997-2017