一 复习和目标

1 复习

  • 上一节使用wireshark抓包分析了WebSocket流量
  • 包含连接的建立:HTTP协议升级WebSocket协议
  • 使用建立完成的WebSocket协议发送数据

2 目标

  • 协议对比

  • 初始握手和计算响应键值

  • 消息格式

  • 关闭握手

注:WebSocket服务器使用《HTML5 WebSocket权威指南》3.4节中使用nodejs实现,WebSocket客户端使用Chrome浏览器实现。

二 协议对比

特性 TCP HTTP WebSocket
寻址 IP地址和端口 URL URL
并发传输 全双工 半双工 全双工
内容 字节流 MIME消息 文本和二进制消息
消息定界
连接定向

注:

  • TCP传送字节流,消息定界由高层协议来表现。
  • WebSocket中,多字节的消息作为整体、按照顺序到达。.

三 初始握手

1 HTTP请求升级协议和协议升级成功响应

  • HTTP请求
GET ws://localhost:9999/echo HTTP/1.1
Host: localhost:9999
Connection: Upgrade
Upgrade: websocket
Sec-WebSocket-Version: 13
Sec-WebSocket-Key: UjxPJpGjxC4JH5+0znrYBg==
Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits
  • HTTP响应
HTTP/1.1 101 Web Socket Protocol Handshake
Upgrade: WebSocket
Connection: Upgrade
sec-websocket-accept: NTeDlW+9/P48+pMOtotMmM1m/J0=

注:响应不带Sec-WebSocket-Extensions代表该服务器不支持请求中的拓展

2 计算响应键值

(1)概述

响应中的sec-websocket-accept等于base64(sha1(请求中的Sec-WebSocket-Key + 258EAFA5-E914-47DA-95CA-C5AB0DC85B11))

(2)nodejs版本实现

var KEY_SUFFIX = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";

function(key){
var sha1 = crypto.createHash('sha1');
sha1.update(key+KEY_SUFFIX,'ascii');
return sha1.digest('base64');
}

(3)java版本

public class MessageDigestUtils {

    private final static String KEY_SUFFIX = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";

    public static String generateFinalKey(String key) {
String seckey = key.trim() + KEY_SUFFIX;
MessageDigest sha1;
try {
sha1 = MessageDigest.getInstance( "SHA1" );
} catch ( NoSuchAlgorithmException e ) {
throw new IllegalStateException( e );
}
return Base64.getEncoder().encodeToString(sha1.digest(seckey.getBytes()));
}
}

(4)其他首部

首部字段 描述
Sec-WebSocket-Key 用于初始握手,避免跨协议攻击。
Sec-WebSocket-Accept 用于初始握手,服务器确认WebSocket协议。
Sec-WebSocket-Extensions 用于初始握手,服务器确认客户端的拓展。
Sec-WebSocket-Protocol 用于初始握手,服务器子协议选择。
Sec-WebSocket-Version 用于初始握手,对于RFC 6455对应为13。

四 消息格式

1 帧和消息

  • 帧:最小的通信单位,包含可变长度的帧首部和净荷部分,净荷可能包含完整或部分应用消息。
  • 消息:一系列帧,与应用消息对等。

2 帧格式

  • FIN:表示当前帧是否为消息的最后一帧;可能一条消息就只有一帧。
  • 操作码(4位):表示被传输帧的类型
    • 1:文本
    • 2:二进制
    • 8:关闭连接
    • 9:呼叫,ping
    • 10:回应,pong
  • 掩码位:净荷是否有掩码(只适用客户端发送给服务器的消息)
  • 净荷长度:
    • 0~125:表示长度
    • 126:接下来2个字节的16位无符号整数才是该帧的长度
    • 127:接下来8个字节的64位无符号整数才是该帧的长度,高位必须为0。
  • 掩码键:包含32位,用于给净荷加掩护
  • 净荷包含应用数据,如果客户端和服务器在建立连接时协商过,也可以包含自定义的扩展数据。

注:WebSocket的队首阻塞:如果一个大消息被分成多个WebSocket 帧,就会阻塞其他消息的帧。

3 数据抓包

3.1 基础信息

  • 客户端:

    • IP:192.168.1.10
    • Port:3263
  • 服务器:
    • IP:192.168.1.10
    • Port:9999

注:如果使用localhost,wireshark无法抓包,因为流量走的时loop back接口。

# 管理员执行route add 本机IP地址 mask 掩码 网关IP地址
route add 192.168.1.10 mask 255.255.255.255 192.168.1.1

3.2 客户端 -> 服务器(长度小于125的小包)

(1)wireshark抓包
WebSocket
1... .... = Fin: True
.000 .... = Reserved: 0x0
.... 0001 = Opcode: Text (1)
1... .... = Mask: True
.000 0101 = Payload length: 5
# [Extended Payload length (16 bits): 40200] 如果length超过125
Masking-Key: 0b4b5535
Masked payload
63 2e 39 59 64
(2)掩码解析:nodejs
// maskBytes为0b4b5535  data为632e395964
// 结果为:68656c6c6f ==> hello
var unmask = function (maskBytes, data) {
var payload = new Buffer(data.length);
for (var i = 0; i < data.length; i++) {
payload[i] = maskBytes[i % 4] ^ data[i];
}
return payload;
}
(3)掩码解析:java
 public static String unmask(byte[] maskBytes,byte[] data){
byte[] payload = new byte[data.length];
for (int i = 0; i < data.length; i++) {
payload[i] = (byte)(maskBytes[i % 4] ^ data[i]);
}
return new String(payload);
}
(4)数据解析:nodejs
WebSocketConnection.prototype._processBuffer = function () {
var buf = this.buffer; if (buf.length < 2) return; var b1 = buf.readUInt8(0);
var fin = b1 & 0x80;
var opcode = b1 & 0x0f; var b2 = buf.readUInt8(1);
var mask = b2 & 0x80;
var length = b2 & 0x7f;
var idx = 2; // 索引 if (length > 125) {
if (buf.length < 8) return; if (length == 126) {
length = buf.readUInt16BE(2);
idx += 2;
} else if (length == 127) {
var highBits = buf.readUInt32BE(2);
if (highBits != 0) this.close(1009, "");// 高位必须为0
length = buf.readUInt32BE(6);
idx += 8;
}
} // 4个字节的掩码
if (buf.length < idx + 4 + length) {
return;
} maskBytes = buf.slice(idx, idx + 4);
idx += 4; var payload = buf.slice(idx, idx + length);
payload = unmask(maskBytes, payload); this._handleFrame(opcode, payload);
this.buffer = buf.slice(idx + length); // buffer置空 return true;
}

注:java版本的数据解析太麻烦,后期考虑补上。

3.3 服务器 -> 客户端 (长度小于125的小包)

  • 服务器发给客户端不需要掩码,直接发送即可。
WebSocket
1... .... = Fin: True
.000 .... = Reserved: 0x0
.... 0001 = Opcode: Text (1)
0... .... = Mask: False
.000 0101 = Payload length: 5
Payload
hello

五 关闭握手

1 关闭握手异常代号

代号 描述 使用场景
1000 正常关闭 会话正常完成时
1001 离开 应用离开且不期望后续连接的尝试而关闭连接时
1002 协议错误 因协议错误而关闭连接时
1003 不可接受的数据类型 非二进制或文本类型时
1007 无效数据 文本格式错误,如编码错误
1008 消息违反政策 当应用程序由于其他代号不包含的原因时
1009 消息过大 当接收的消息太大,应用程序无法处理时(帧的载荷最大为64字节)
1010 需要拓展
1011 意外情况

2 其他代号

代号 描述 使用情况
0~999 禁止
1000~2999 保留
3000~3999 需要注册 用于程序库、框架和应用程序
4000~4999 私有 应用程序自由使用

3 抓包分析

(1)客户端发起关闭

WebSocket
1... .... = Fin: True
.000 .... = Reserved: 0x0
.... 1000 = Opcode: Connection Close (8)
1... .... = Mask: True
.000 0000 = Payload length: 0
Masking-Key: 461086e0

(2)服务器响应关闭

WebSocket
1... .... = Fin: True
.000 .... = Reserved: 0x0
.... 1000 = Opcode: Connection Close (8)
0... .... = Mask: False
.000 0000 = Payload length: 0

参考:

  • 《Web性能权威指南》
  • 《HTML5 WebSocket权威指南》
  • RFC 6455

WebSocket协议探究(一)的更多相关文章

  1. WebSocket协议探究(三):MQTT子协议

    一 复习和目标 1 复习 Nodejs实现WebSocket服务器 Netty实现WebSocket服务器(附带了源码分析) Js api实现WebSocket客户端 注:Nodejs使用的Socke ...

  2. WebSocket协议探究(序章)

    一 WebSocket协议基于HTTP和TCP协议 与往常一样,进入WebSocket协议学习之前,先进行WebSocket协议抓包,来一个第一印象. WebSocket能实现客户端和服务器间双向.基 ...

  3. WebSocket协议探究(二)

    一 复习和目标 1 复习 协议概述: WebSocket内置消息定界并且全双工通信 WebSocket使用HTTP进行协议协商,协商成功使用TCP连接进行传输数据 WebScoket数据格式支持二进制 ...

  4. Websocket 协议解析

    WebSocket protocol 是HTML5一种新的协议.它是实现了浏览器与服务器全双工通信(full-duplex).          现 很多网站为了实现即时通讯,所用的技术都是轮询(po ...

  5. WebSocket协议开发

    一直以来,网络在很大程度上都是围绕着HTTP的请求/响应模式而构建的.客户端加载一个网页,然后直到用户点击下一页之前,什么都不会发生.在2005年左右,Ajax开始让网络变得更加动态了.但所有的HTT ...

  6. 初识WebSocket协议

    1.什么是WebSocket协议 RFC6455文档的表述如下: The WebSocket Protocol enables two-way communication between a clie ...

  7. Websocket协议的学习、调研和实现

    本文章同时发在 cpper.info. 1. websocket是什么 Websocket是html5提出的一个协议规范,参考rfc6455. websocket约定了一个通信的规范,通过一个握手的机 ...

  8. python测试基于websocket协议的即时通讯接口

    随着html5的广泛应用,基于websocket协议的即时通讯有了越来越多的使用场景,本文使用python中的websocket-client模块来做相关的接口测试 import webclient ...

  9. Websocket协议之php实现

    前面学习了HTML5中websocket的握手协议.打开和关闭连接等基础内容,最近用php实现了与浏览器websocket的双向通信.在学习概念的时候觉得看懂了的内容,真正在实践过程中还是会遇到各种问 ...

随机推荐

  1. Oracle的大表,小表与全表扫描

    大小表区分按照数据量的大小区分: 通常对于小表,Oracle建议通过全表扫描进行数据访问,对于大表则应该通过索引以加快数据查询,当然如果查询要求返回表中大部分或者全部数据,那么全表扫描可能仍然是最好的 ...

  2. Python-文件读写及修改

    文件的读写有三种形式:读.写和追加. 一.读模式 r 和读写模式 r+ 1.读模式 r 读模式r特点:(1)只能读,不能写:(2)文件不存在时会报错. (1)例:读取当前目录下的books.txt文件 ...

  3. C#中 Dictionary<>的使用及注意事项

    1,如果在主体代码中使用,直接在初始化中生成就行 2如果在其他层,比如逻辑层,要注意在事件内部定义,在外部的话,重复调用就会提示“”“已经定义了相同的KEY”,见例子 (例子是转的) Dictiona ...

  4. python 设计模式之策略模式

    这几天太忙了,都没空写,所以持续了好几天. 1.策略模式的定义: 策略模式定义了算法族,分别封装起来,让他们之间可以互相替换,此模式让算法的变化独立于使用算法的客户. 通俗的讲,也就是将那些使用的方法 ...

  5. java udp通信

    package net.kkxm.kms;  import java.net.DatagramPacket; import java.net.DatagramSocket; import java.n ...

  6. OpenNebula概述

    OpenNebula概述 OpenNebula是专门为云计算打造的开源系统,用户可以使用Xen.KVM.VMware等虚拟化软件一起打造企业云.利用OpenNebula可以轻松构建私有云.混合云.公开 ...

  7. jar启动脚本shell

    #!/bin/bash#这里可替换为你自己的执行程序,其他代码无需更改 APP_NAME=/opt/server/msp/health-api/health-2.0.2.jar#使用说明,用来提示输入 ...

  8. HashSet的实现原理,简单易懂

    HashSet的实现原理,简单易懂   答: HashSet实际上是一个HashMap实例,都是一个存放链表的数组.它不保证存储元素的迭代顺序:此类允许使用null元素.HashSet中不允许有重复元 ...

  9. APP 抓包-fiddler

    App抓包原理 客户端向服务器发起HTTPS请求 抓包工具拦截客户端的请求,伪装成客户端向服务器进行请求 服务器向客户端(实际上是抓包工具)返回服务器的CA证书 抓包工具拦截服务器的响应,获取服务器证 ...

  10. LeetCode_67. Add Binary

    67. Add Binary Easy Given two binary strings, return their sum (also a binary string). The input str ...