zoukankan      html  css  js  c++  java
  • RFC-6455 The WebSocket Protocol 浅读

    什么是WebSokcet?

    WebSocket是一种协议,并且是各大主流浏览器作为客户端支持的协议。它的目标就是用来替代基于 XMLHTTPRequest和长轮询的解决方案。应用在时时弹幕,消息推送,棋牌游戏等需要及时通讯的业务场景。

    握手

    WebSocket连接有两个阶段:握手(handshake)和数据传输(data transfer)。此握手非TCP三次握手,但是目的差不多,就是客户端告诉浏览器我想要使用WebSocket协议进行通讯。客户端需要发送如下请求,它是一个 HTTP Upgrade 请求:

     GET /chat HTTP/1.1
     Host: server.example.com
     Upgrade: websocket
     Connection: Upgrade
     Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
     Origin: http://example.com
     Sec-WebSocket-Protocol: chat, superchat
     Sec-WebSocket-Version: 13
    

    那么如果握手成功的话,服务器响应:

     HTTP/1.1 101 Switching Protocols
     Upgrade: websocket
     Connection: Upgrade
     Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
     Sec-WebSocket-Protocol: chat
    

    客户端发送握手请求

    1. Uri要满足如下格式:
    ws-URI = "ws:" "//" host [ ":" port ] path [ "?" query ]
    wss-URI = "wss:" "//" host [ ":" port ] path [ "?" query ]
    
    1. 在与服务端建立连接时,客户端必须方发送握手请求,请求是一个HTTP的升级协议(Upgrade)请求。并且该请求必须满足
      • 握手请求必须是一个正常的HTTP请求。
      • 请求方法必须为GET,并且HTTP协议最低为1.1
      • 请求头必须包含Host
      • 请求头必须包含Upgrade,并且值为websocket
      • 请求头必须包含Connection,并且值为Upgrade
      • 请求头必须包含Sec-WebSocket-Key,值为经过Base64转换的长度为16字节的一组数据
      • 请求头必须包含Origin,如果客户端是浏览器这个值肯定是有的,如果非浏览器的客户端,这个值可以随意改。
      • 请求头必须包含Sec-WebSocket-Version,并且值为13
      • 请求头可以带一个Sec-WebSocket-Protocol,这个值告诉服务端客户端想用的子协议,多个用逗号分开
      • 请求头可以带一个Sec-WebSocket-Extensions,这个值告诉服务端客户端支持的协议级别的扩展。
      • 请求头可以带一个和权限校验相关的头,例如Cookie,Authentication等

    当客户端将握手请求发出去之后,就要等待服务端的响应了。当服务端成功响应之后,客户端还需要做如下校验:

    1. 返回的响应码非101,例如401,500,403,503 等等,客户端连接失败
    2. 返回的响应头部不包含Upgrade或者Upgrade的值不是websocket,客户端连接失败
    3. 返回的响应头部不包含Connection或者Connection的值不是Upgrade,客户端连接失败
    4. 返回的响应头部不包含Sec-WebSocket-Accept或者Sec-WebSocket-Accept的值并不是Base64(SHA1(Sec-WebSocket-Key+"258EAFA5-E914-47DA-95CA-C5AB0DC85B11")),客户端连接失败
    5. 返回的响应头部Sec-WebSocket-Extensions中的值并不是客户端发送的Sec-WebSocket-Extensions中的值,客户端连接失败
    6. 返回的响应头部Sec-WebSocket-Protocol中的值并不是客户端发送的Sec-WebSocket-Protocol中的值,客户端连接失败

    服务端接收握手请求
    如果服务端在处理请求过程中不满足一下任何一点,服务端都会终止处理该请求

    1. 必须是HTTP1.1+的GET请求
    2. 包含Host请求头
    3. 包含Upgrade值为WebSocket的请求头
    4. 包含Connection值为Upgrade的请求头
    5. 包含Sec-WebSocket-Key值为16字节长度的Base64字符串
    6. 包含Sec-WebSocket-Version值为13的请求头
    7. 非必须:Origin
    8. 非必须:Sec-WebSocket-Protocol
    9. 非必须:Sec-WebSocket-Extensions

    当服务端确定这是一个正常的握手请求并且愿意处理此请求,那么服务端需要回应一个HTTP响应:

    1. 状态码必须为 101 Switching Protocol
    2. Upgrade:WebSocket
    3. Connection:Upgrade
    4. Sec-WebSocket-Accept,如上文所说,值为:Base64(SHA1(Sec-WebSocket-Key+"258EAFA5-E914-47DA-95CA-C5AB0DC85B11"))
    5. Sec-WebSocket-Protocol,根据客户端传的值
    6. Sec-WebSocket-Extensions,根据客户端传的值

    至此,握手结束。连接状态由CONNECTING进入OPEN状态

    协议帧

    WebSocket的协议帧格式如下:
    WebSocket协议

    • FIN 1bit
      包结束标志,1 代表最后一个消息包,0代表某一段消息包
    • RSV1, RSV2, RSV3: 每个1bit,共3bit
      值为0,除非协议扩展(Extensions)声明了非0的值的含义。如果服务端收到非0的值,并且没有相应的定义,那么服务端将直接终止连接。
    • Opcode 4bit
      x0 后续帧
      x1 文本帧
      x2 二进制帧
      x3-X7 非控制帧预留
      x8 关闭连接
      x9 PING
      xA PONG
      xB-xF 控制帧预留
    • Mask 1 bit 是否掩码。客户端向服务器发送,必须掩码。服务端向客户端发送不需掩码
    • PayLoad Length 7bits,7+16bits,7+64bits,如果值为 0-125,则数据包长度为0-125.如果值为126,则后2个字节为数据包长度:16bit。如果值为127,则后8个字节为数据包长度:64bit。
    • Masking-Key, 0-4bits.是否有值取决于 Mask 标识位是否为1.
    • Extension data X bytes 如果在握手时协商了扩展,会有值,否则为0
    • Application data y bytes 剩余消息包
    • PayLoad data (x+y)bytes 总消息包=Extension data + Application data.如果有掩码,解码公式如下:
    body[i] = body[i] ^ body[i % 4]
    

    代码解析

    下面我用tio网络通讯框架代码来解释一下上文中的内容,不必纠结具体代码,只要大概理解代码功能即可。
    WebSocket协议
    具体协议升级代码如下:
    WebSocket协议
    以上就是握手部分Http协议升级过程的代码部分。没有什么难理解的地方,只要对着文档要求去实现即可。不过要注意的是,这里是升级协议的过程,如果有其他业务处理,比如访问权限校验失败等,可以直接返回 HttpStatusCode 401.

    协议帧解析:
    WebSocket协议
    WebSocket协议

    总结

    大致过了一遍RFC-6455文档,发现还是官方文档中解释的更详细的也更清楚一些,但是苦于英语水平不过关,有些部分理解起来比较困难。

    参考资料

    RFC-6455

  • 相关阅读:
    堆、栈及静态数据区详解
    新浪云上传代码包
    主机屋MySQL数据库链接
    Doctype作用?严格模式与混杂模式如何区分?它们有何意义?
    height 与 min-height 的继承
    @media 照成的问题
    img 在chrome和Firefox下的兼容性
    Ionic
    setInterval()和setTimeout()可以接收更多的参数
    angularJs 模拟jQuery中的this
  • 原文地址:https://www.cnblogs.com/panzi/p/10973619.html
Copyright © 2011-2022 走看看