一、概述
  
  上一篇文章《浅析一次HTTP请求》我们分析了简单的一次 HTTP 请求具体是怎么样完成的,分析了 HTTP 协议的数据结构,如何连接,如何断开,又是如何多路复用的,那么今天我们来聊聊另外一个协议,WebSocket。由于 WebSocket 的协议的内容非常多,本文只会取其冰山一角进行简单阐述,不会铺开详细说。
  
  二、什么是 WebSocket
  
  2.1 WebSocket 产生的背景
  
  在 WebSocket 协议出现以前,创建一个和服务端进双通道通信的 web 应用,需要依赖HTTP协议,进行不停的轮询,这会导致一些问题:
  
  服务端被迫维持来自每个客户端的大量不同的连接
  
  大量的轮询请求会造成高开销,比如会带上多余的header,造成了无用的数据传输。
  
  所以,为了解决这些问题,WebSocket 协议应运而生。
  
  2.2 WebSocket 的定义
  
  WebSocket 是一种在单个TCP连接上进行全双工通信的协议。 WebSocket 使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。
  
  在 WebSocket API 中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接, 并进行双向数据传输。(维基百科)
  
  三、WebSocket 的基础帧结构分析
  
  下图是我参考 RFC6455 5.2章节画的websocket 基础帧的数据结构图,接下里我们重点解析下数据结构图。
  
  FIN:占用1 bit,表示这是消息的最后一个片段。第一个片段也有可能是最后一个片段。
  
  RSV1,RSV2,RSV3: 每个1 bit
  
  必须设置为0,除非扩展了非0值含义的扩展。如果收到了一个非0值但是没有扩展任何非0值的含义,接收终端必须断开WebSocket连接。
  
  Opcode: 4 bit,操作码,如果收到一个未知的操作码,接收终端必须断开WebSocket连接。
  
  %x0 表示一个持续帧
  
  ​ %x1 表示一个文本帧
  
  ​ %x2 表示一个二进制帧
  
  ​ %x3-7 预留给以后的非控制帧
  
  ​ %x8 表示一个连接关闭包
  
  ​ %x9 表示一个ping包
  
  ​ %xA 表示一个pong包
  
  ​ %xB-F 预留给以后的控制帧
  
  Mask: 1 bit,mask标志位,定义“有效负载数据”是否添加掩码。如果设置为1,那么掩码的键值存在于Masking-Key中。
  
  Payload length: 7 bits, 7+16 bits, or 7+64 bits,以字节为单位的“有效负载数据”长度。
  
  Masking-Key: 0 or 4 bytes,
  
  ​ 所有从客户端发往服务端的数据帧都已经与一个包含在这一帧中的32 bit的掩码进行过了运算。如果mask标志位(1 bit)为1,那么这个字段存在,如果标志位为0,那么这个字段不存在。 备注:载荷数据的长度,不包括mask key的长度。。
  
  Payload data: 有效负载数据
  
  为什么需要掩码?
  
  为了安全,但并不是为了防止数据泄密,而是为了防止早期版本的协议中存在的代理缓存污染攻击(proxy cache poisoning attacks)等问题。
  
  四、 抓包分析
  
  4.1 DEMO展示及分析
  
  我写了一个DMEMO用来抓包分析 websocket,源代码会放在文章末尾的链接。DEMO效果如下:
  
  页面提供连接与断开功能,输入自己的名字发送,服务端返回Hello,名字!功能很简单,我们先看看页面的请求和响应。
  
  请求:
  
  响应:
  
  这里的请求与响应就是反应了 WebSocket 的一次握手,我们根据上图可以简单抽象一下 WebSocket 的请求和响应格式: 客户端握手请求格式:
  
  GET /chat HTTP/1.1
  
  Host: server.example.com
  
  Upgrade: websocket
  
  Connection: Upgrade
  
  Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==

@Controller
@RequestMapping("/user"www.honghgjpt.com)
public class UserController {
@RequestMapping("/login"www.meiwanyule.cn)
public String login(Model model,@Validated SysUser user,BindingResult result) {
if(result.hasErrors(www.michenggw.com)) {
List<ObjectError> allErrors = result.getAllErrors();
for (ObjectError objectError : allErrors) {
System.out.println(objectError.getDefaultMessage());
model.addAttribute("errors", allErrors);
return "login";
  
root@docker-jenkins ~]# mv tomcat-java-demo tomcat-java-demo.bak
[root@docker-jenkins ~]# git clone https://www.365soke.com github.com/dingkai163/tomcat-java-demo.git
[root@docker-jenkins tomcat-java-demo]# cat .git/config
[core]
repositoryformatversion www.fengshen157.com= 0
filemode = true
bare = false
logallrefupdates = true
[remote "origin"]
url = git@10.0.0.99:/home/git/tomcat-java-demo.git
fetch = +refs/heads/*www.qcaphb.com/ :refs/remotes/origin/*
[branch "master"]
remote = origin
merge = refs/heads/master
[root@docker-jenkins tomcat-java-demo]# git add .
[root@docker-jenkins tomcat-java-demo]# git status
# On branch master
nothing to commit, working directory clean
[root@docker-jenkins tomcat-java-demo]# git commit -m "all"
# On branch master
nothing to commit, working directory clean
[root@docker-jenkins tomcat-java-demo]# git push origin master
git@10.0.0.99's password:
Counting objects: 229, done.
Compressing objects: 100% (185/185), done.
Writing objects: 100% (229/229), 4.52 MiB | 0 bytes/s, done.
Total 229 (delta 25), reused 229 (delta 25)
To git@10.0.0.99:/home/git/tomcat-java-demo.git
* [new branch] master -> master
[root@docker-jenkins tomcat-java-demo]#

  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
  
  我们重点说明下结果请求字段:
  
  Upgrade:表示HTTP协议升级为webSocket
  
  connection:Upgrade 请求升级。
  
  Sec-WebSocket-Key: 用于服务端进行标识认证,生成全局唯一id,GUID。
  
  Sec-WebSocket-Version: 版本
  
  Sec-WebSocket-Protocol: 请求服务端使用指定的子协议。如果指定了这个字段,服务器需要包含相同的字段,并且从子协议的之中选择一个值作为建立连接的响应。
  
  Sec-WebSocket-Extensions: WebSocket的扩展。
  
  Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo= 生成的全局唯一id,GUID。
  
  GUID的生成算法
  
  算法思想:通过 Sec-WebSocket-Key 传入的 值,dGhlIHNhbXBsZSBub25jZQ==,连接服务端生成的字符串,拼接格式如下
  
  dGhlIHNhbXBsZSBub25jZQ==258EAFA5-E914-47DA-95CA-
  
  C5AB0DC85B11
  
  , 然后采用SHA-1哈希算法,然后用base64编码生成最终的 Sec-WebSocket-Accept的值,生成的值就是
  
  s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
  
  (注意,这里SHA1哈希算法生成的结果必须是二进制的哈希结果,比如
  
  Python代码中的
  
  h = hashlib.sha1("dGhlIHNhbXBsZSBub25jZQ==258EAFA5-E914-47DA-95CA-C5AB0DC85B11")
  
  .digest()
  
  ,如果用在线处理工具生成,生成的Hash是16进制的哈希,用 Base64就会生成错误结果)。
  
  4.2 抓包
  
  我在DEMO中的操作流程如下:
  
  连接WebSocket
  
  发送“LUOZHOU”
  
  断开连接
  
  用 Wireshark 抓包如下:
  
  我们结合浏览器截图和抓包截图,发现在真正开启 websocket 之前,浏览器会有两次http请求,分别是:
  
  A请求 GET /gs-guide-websocket/info?t=1551252237372 HTTP/1.1
  
  B请求 GET /gs-guide-websocket/690/pdsz5x1q/websocket HTTP/1.1
  
  根据 RFC6455 协议规定 WebSocket 只需要一次握手就可以完成,所以我们只需要分析第二次的http 握手请求,A请求应该是使用的框架层面自己实现。
  
  我们根据截图可以知道,B请求对应的响应是序号 192 的数据,返回码是101,根据 HTTP 返回码我们可以知道,服务器已经理解了客户端的请求,并将通过Upgrade 消息头通知客户端采用不同的协议来完成这个请求。在发送完这个响应最后的空行后,服务器将会切换到在 Upgrade 消息头中定义的那些协议,也就是升级为 WebSocket 协议。所以接着193的包已经变成了 WebSocket 协议了。到这里,WebSocket 的握手连接就已经完成了。
  
  接下来我们分析下发送消息的流程,这里大家肯定会疑惑,就发送了一条消息,为啥会有这么多 WebSocket 的包呢?其实这里多余的包是框架层面进行发送的,比如要进行订阅与发布的注册等等操作。所以真正使我们操作的包就只有断开连接的相关包和发送“LUOZHOU”的包
  
  根据上图我们发现 序号229的包是一个文本类型的包,opcode:1,然后采用了掩码处理,同时是最后一个处理包。我们仔细发现所有客户端发送服务端的包都会有[MASKED]标记,服务端返回的没有,这就说明了从客户端向服务端发送数据时,需要对数据进行掩码操作;从服务端向客户端发送数据时,不需要对数据进行掩码操作。
  
  五、总结
  
  WebSocket 是为了在 web 应用上进行双通道通信而产生的协议,相比于轮询HTTP请求的方式,WebSocket 有节省服务器资源,效率高等优点。
  
  WebSocket 中的掩码是为了防止早期版本中存在中间缓存污染攻击等问题而设置的,客户端向服务端发送数据需要掩码,服务端向客户端发送数据不需要掩码。
  
  WebSocket 中 Sec-WebSocket-Key 的生成算法是拼接服务端和客户端生成的字符串,进行SHA1哈希算法,再用base64编码。
  
  WebSocket 协议握手是依靠 HTTP 协议的,依靠于 HTTP 响应101进行协议升级转换。
  
  DEMO代码
  
  六、参考
  
  [1]RFC6455
  
  [2]WebSocket 协议 RFC 文档(全中文翻译)
  
  [3]WebSocket协议:5分钟从入门到精通

简单聊聊WebSocket的更多相关文章

  1. 简单聊聊Storm的流分组策略

    简单聊聊Storm的流分组策略 首先我要强调的是,Storm的分组策略对结果有着直接的影响,不同的分组的结果一定是不一样的.其次,不同的分组策略对资源的利用也是有着非常大的不同,本文主要讲一讲loca ...

  2. 用 Go 编写一个简单的 WebSocket 推送服务

    用 Go 编写一个简单的 WebSocket 推送服务 本文中代码可以在 github.com/alfred-zhong/wserver 获取. 背景 最近拿到需求要在网页上展示报警信息.以往报警信息 ...

  3. 简单聊聊java中的final关键字

    简单聊聊java中的final关键字 日常代码中,final关键字也算常用的.其主要应用在三个方面: 1)修饰类(暂时见过,但是还没用过); 2)修饰方法(见过,没写过); 3)修饰数据. 那么,我们 ...

  4. 简单聊聊SOA和微服务

    转自:https://juejin.im/post/592f87feb123db0064e5ef7c  (2017-06) 简单聊聊SOA和微服务 架构设计中的朴素主义 前两天和一个朋友聊天,他向我咨 ...

  5. 【node+小程序+web端】简单的websocket通讯

    [node+小程序+web端]简单的websocket通讯 websoket是用来做什么的? 聊天室 消息列表 拼多多 即时通讯,推送, 实时交互 websoket是什么 websocket是一个全新 ...

  6. 转 简单聊聊IT软件项目的风险及应对

    https://www.jianshu.com/p/b347adca87a6 前言 上段时间在一家演讲俱乐部做即兴演讲主持人,聊的就是风险管理,与会的小伙伴分享了不同行业的风险问题,令人受益匪浅,今天 ...

  7. springboot搭建一个简单的websocket的实时推送应用

    说一下实用springboot搭建一个简单的websocket 的实时推送应用 websocket是什么 WebSocket是一种在单个TCP连接上进行全双工通信的协议 我们以前用的http协议只能单 ...

  8. 简单聊聊CSS中的3D技术之“立方体”

    简单聊聊CSS中的3D技术之“立方体” 大家好,我是今天的男一号,我叫小博主. 今天来聊一下我在前端“逆战班”学习中遇到的颇为有趣的3D知识.前端学习3周,见识稀疏,在下面的分享中如有不对的地方请大家 ...

  9. 【DNS】简单聊聊DNS如何工作

    随便聊聊 我们知道,网络上传输的数据包是一层一层的包起来的,典型的是mac地址层,ip层,tcp/udp层,应用层数据 这么几个层,那用户在浏览器中打开www.baidu.com数据包如何传到baid ...

随机推荐

  1. Java工具类——UUIDUtils

    借用一下百度百科的解释,来看一下UUID是什么. UUID含义是通用唯一识别码 (Universally Unique Identifier),这 是一个软件建构的标准,也是被开源软件基金会 (Ope ...

  2. JMeter中返回Json数据的处理方法(转)

    Json 作为一种数据交换格式在网络开发,特别是 Ajax 与 Restful 架构中应用的越来越广泛.而 Apache 的 JMeter 也是较受欢迎的压力测试工具之一,但是它本身没有提供对于 Js ...

  3. 源码追踪,解决Could not locate executable null\bin\winutils.exe in the Hadoop binaries.问题

    在windows系统本地运行spark的wordcount程序,会出现一个异常,但不影响现有程序运行. >>提君博客原创  http://www.cnblogs.com/tijun/  & ...

  4. Android——Service介绍与例子

    官方定义:Service(服务)是一个没有用户界面的在后台运行执行耗时操作的应用组件.其他应用组件能够启动Service,并且当用户切换到另外的应用场景,Service将持续在后台运行.另外,一个组件 ...

  5. Python2.7从入门到精通

    快速入门 1.程序输出print语句 (1)使用print语句可查看对象的值:在交互式解释器使用对象本身则输出此对象的字符串表示: (2)使用print语句调用str()显示对象:在交互式解释器使用对 ...

  6. dataTable之自定义按钮实现全表 复制 打印 导出 重载

    //本文对常用表格插件datatable 的自定义按钮功能键进行详细解释//其中 15-78行是定义表单//16 18 19 三行定义自定义功能按钮 实现对全表的 复制 打印 导出(csv即excel ...

  7. Java连接RabbitMQ之创建连接

    依赖包: <dependencies> <dependency> <groupId>junit</groupId> <artifactId> ...

  8. HDU 5025 Saving Tang Monk

    Problem Description <Journey to the West>(also <Monkey>) is one of the Four Great Classi ...

  9. LOADING Redis is loading the dataset in memory Redis javaAPI实例

    今天在实现Redis客户端API操作Jedis的八种调用方式详解中,遇到了LOADING Redis is loading the dataset in memory错误,经过多番查找资料,找到了解决 ...

  10. CentOS7 搭建影梭服务器

    安装Python包管理工具 yum install python-setuptools && easy_install pip 安装Shadowsocks pip install sh ...