WebSocket协议解析

news2024/11/19 5:32:28

文章目录

    • 概要
    • 一、WS原理
        • 1.1、帧格式
    • 二、WS实战
        • 2.1、客户端发起协议升级请求
        • 2.2、服务端响应协议升级
        • 2.3、核心事件
        • 2.4、心跳保活
    • 三、总结

概要

项目中的IM系统是基于WebSocket做的,所以这里聊一下。
说到WS,不得不提HTTP,HTTP是基于TCP,面向文本的,无状态的,半双工通信协议。由于HTTP1.0的缺陷,后面版本做相应的优化:

  • 每次请求都要建立TCP连接,并且请求一个文档需要两倍RTT(一个是TCP握手,一个是请求和接收文档)。针对这种情况HTTP1.1引入了keep-alive机制,实现了建立一次TCP连接,可发送多次请求。
  • HTTP是明文传输,所以有一个中间人攻击的漏洞。针对这种情况引入了HTTPS(HTTP+TLS),实现了防窃听(数据加密)、防冒充(身份验证)、防篡改(完整性校验)。
  • HTTP的请求一般是非流水线模式【收到上一个请求的响应后才能发下一个请求】(注意现代浏览器并不启用流水线模式,实现复杂,问题多),这里有队头阻塞问题,所以有了HTTP2。
  • HTTP2只是解决了应用层的队头阻塞问题,但是TCP有丢包重传等机制,所以传输层也有队头阻塞问题,这个时候就要HTTP3登场了,引入 QUIC 协议,基于UDP,可以完美解决队头阻塞问题。

通过上面分析HTTP迭代的原因,那么WS的出现是为了解决HTTP什么问题呢?

针对HTTP请求-响应这种半双工通信模式,既要实现全双工通信

为什么要这样呢?主要是因为在没有WS之前,HTTP要实现IM,服务端事件推送等实时场景是很麻烦的,常见的有短轮询,长轮询,SSE(Server Send Event),针对实时通讯的场景方案都不是很优秀,所以有了WS,它最初是在 HTML5 中引入的。经过多年发展后,该协议慢慢被多个浏览器支持,RFC 在 2011 年就把该协议作为一个国际标准,叫 rfc6455。

也许是为了修复HTTP缺陷而诞生的,所以WS的建立是依赖HTTP的,同样规定默认使用80,443端口,当然其错误码是与HTTP不重合的。

一、WS原理

WebSocket 是一种基于TCP长连接,面向报文(二进制),支持全双工通信的网络协议。其与HTTP是平级的,相比HTTP1.1,优点如下:

  • 支持全双工通信,实时性更强;
  • 客户端与服务端通信的最小单位是帧(frame),由1个或多个帧组成一条完整的消息。即发送端将消息切割成多个帧,并发送给接收端;接收端接收消息帧,并将关联的帧重新组装成完整的消息;
  • 长连接,有状态;
  • 较少的控制开销。连接创建后,WS客户端、服务端进行数据交换时,协议控制的数据包头部较小,不像HTTP每次请求都要携带那么多头;
  • 支持扩展。ws协议定义了扩展,用户可以扩展协议,或者实现自定义的子协议(比如支持自定义压缩算法等)。

1.1、帧格式

WebSocket主要是解决HTTP无法实时通信的问题,所以没有HTTP2 的多路复用,优先级等复杂功能,所以二进制帧格式](https://www.rfc-editor.org/rfc/rfc6455.html#page-27)简单:
ws-datagram
可以看到帧头是16bit,2字节:

  • 第一个字节:
    前四位是标志位,第一个标志位是FIN,表示报文结束,因为一个消息可能被拆分成多帧,当接收方收到携带FIN标志位的帧时,就会把前面的帧拼起来组成完整的消息。后三位是保留标志位,必须设置为0,除非想要自定义扩展。
    后四位是opcode,也就是帧类型,比如0表示连续帧,1表示帧内容是文本,2表示帧内容是二进制,8表示关闭连接,9和10表示ping和pong等
  • 第二个字节:
    它的第一位是掩码标志位 MASK,表示帧内容是否使用异或操作(xor)做简单的加密,当该位被设置为 1 时表示加密,设置为 0 时表示不加密。如果加密了,那么必须解密才能得到正确内容。目前的 WebSocket 标准规定,客户端发送数据必须使用掩码加密,而服务器发送则不使用掩码加密。
    后面7位表示Payload Length,也就是有效负载或者说有效业务消息的长度,并且采用的是大端存储。但是7位大表示127,如果payload data大于127字节咋办??
    所以就引入了 Extended payload length 来表示真是的Payload Length:
    Payload Length ==127,则 后面8字节表示 Extended payload length;
    Payload Length ==126,则 后面2字节表示 Extended payload length;
    Payload Length < 126,则 没有 Extended payload length;

可知最大payload data长度 可以用8字节表示。

二、WS实战

  • WS是基于HTTP协议实现的。
  • WSS是基于HTTPS协议实现的,即完成tls握手后再进行协议升级。

WS建立复用了 HTTP 的握手请求过程。
客户端通过 HTTP 请求与 WebSocket 服务端协商升级协议。协议完成后,后续的数据交互则遵循 WebSocket 的协议。

2.1、客户端发起协议升级请求

GET /websocket HTTP/1.1
Host: 127.0.0.1:8080
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/115.0
Accept: */*
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate, br
Sec-WebSocket-Version: 13 #表示WS的版本。如果服务端不支持该版本,需要返回一个Sec-WebSocket-Versionheader头,里面包含服务端支持的版本号
Origin: http://www.jsons.cn
Sec-WebSocket-Extensions: permessage-deflate
Sec-WebSocket-Key: 9zQPEx8m0sqMkV1vanwJIA== #与服务端响应首部的Sec-WebSocket-Accept是配套的,提供基本的防护,比如恶意的连接,或者无意的连接(比如特殊的HTTP请求被意外识别成WS)
Connection: keep-alive, Upgrade   #告诉服务端要升级协议了
Sec-Fetch-Dest: websocket
Sec-Fetch-Mode: websocket
Sec-Fetch-Site: cross-site
Pragma: no-cache
Cache-Control: no-cache
Upgrade: websocket             #告诉服务端要升级成WS协议

2.2、服务端响应协议升级

HTTP/1.1 101 Switching Protocols  #状态码 101 表示协议切换成功
Upgrade: websocket  #表示升级到WS协议
Connection: Upgrade  #表示协议升级
Sec-WebSocket-Accept: MzzLu4vmstovah28VVOvEgveq8o=  #根据客户端请求首部的 Sec-WebSocket-Key 计算出来
#将 Sec-WebSocket-Key 跟 258EAFA5-E914-47DA-95CA-C5AB0DC85B11 拼接。
#通过 SHA1 计算出摘要,并转成 base64 字符串。计算公式如下:
# Base64(sha1(Sec-WebSocket-Key + 258EAFA5-E914-47DA-95CA-C5AB0DC85B11))

协议升级完成后就完全遵循WS协议发送接收数据了。

2.3、核心事件

WebSocket协议也是事件驱动的,客户应用程序不需要轮序服务来得到更新的数据,消息和事件将在服务器发送它们的时候异步到达。

  1. open 连接建立时触发
  2. message 接收/发送消息时触发
  3. error 通信发生错误时触发
  4. close 连接关闭时触发

2.4、心跳保活

这个其实在1.1小节已经说过了,就是opcode类型,通过ping和pong来实现,不需要自己再写保活接口了,只需要使用ping/pong即可。
以go gorilla/websocket为例:

func WebSocket(c *gin.Context) {
   //支持协议升级
	conn, err := (&websocket.Upgrader{CheckOrigin: func(r *http.Request) bool { return true }}).Upgrade(c.Writer,      	c.Request, nil)
	if err != nil {
		http.NotFound(c.Writer, c.Request)
		return
	}
	//添加保活机制
	conn.SetPongHandler(func(appData string) error {
		library.ZapDebug(appData)
		//check something
		return nil
	})
	//deal websocket connect
	for {
	  //接收消息
		mt, msg, err := conn.ReadMessage()
		if err != nil { //错误,打Trace,因为可能是主动或者网络问题
			library.ZapDebug("ReadMessage err:%v", err)
			continue
		}
		//这里可以go func(){}() 包一下解决并发问题
		{
			//业务处理
			library.ZapDebug("mt=%v,msg=%v", mt, string(msg))
			//响应消息
			err = conn.WriteMessage(mt, []byte(fmt.Sprintf("hello:%s", msg)))
			if err != nil {
				library.ZapDebug("WriteMessage err:%v", err)
			}
		}
	}
}

三、总结

经过上述内容,可以知道WS可以看做是对HTTP打的补丁,主要是为了解决HTTP半双工通信弊端,侧重处理数据实时通信场景。

这里再说下WS为什么没有替代HTTP,反而后面出了一样支持长连接的HTTP2 ?

  1. WS协议简单,可以说只是在TCP之上提供了数据拆包组包的功能。所以对于静态资源的请求效率是低下的,而不像HTTP2提供了多路复用,请求优先级等特性来提高传输效率,增强页面渲染。
  2. 长连接,全双工通信是WS相比于HTTP的一大优势,其实HTTP2也有,但奈何浏览器为了提高页面渲染效率往往还是同时开多个TCP连接。可以这样说,WS与HTTP各有优势,在页面渲染和短请求场景下选HTTP,在实时通信,如IM等场景下选WS,二者结合使用最好。
  3. 长连接也并不是银弹,随着在线用户增加,相同资源下,其最大在线用户数量理论上不如HTTP。
  4. 长连接的负载均衡不如短连接灵活,简单。

可以这样说,如果不是浏览器是个沙河环境,不允许用户使用TCP,说不定WS压根不会诞生。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/813418.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Mycat分片函数详解

Mycat新一代Mysql分布式集群,大数据处理中间件,中国第一开源软件 Checkout项目 可以用eclipse的svn插件来进行项目检出,也可以用Tortoise SVN等工具检出,由于maven(M2)中的buildnumber-maven-plugin 中的SVNkit最高支持1.7的SVN仓库,因此当你用Tortoise SVN 1.8的工具或版…

聊聊原子弹之父:奥本海默

最近诺兰的电影奥本海默即将热映,其改编自Kai Bird和 Martin J. Sherwin的 2005 年Pulitzer Prize 获奖小说:“American Prometheus: The Triumph and Tragedy of J. Robert Oppenheimer”。这本小说作者研究奥本海默25年,才得以成形,可见奥神本人身上的故事曲折和传奇。 …

MP的开发流程-2

RESTful的实现等级 0级&#xff1a;传统的RPC&#xff0c;基于SOAP的WS&#xff0c;调用的服务名&#xff0c;参数放在HTTP协议的body里面&#xff0c;同时必须以POST方式提交&#xff0c;问题在于你必须清楚的知道所有服务&#xff0c;子服务&#xff0c;及其参数的信息&…

SpringBoot环境标识设置及nacos匹配配置

本地环境标识设置 本地父类maven配置 可以看到相关的分类&#xff0c;设置环境标识主要需要用到profiles; <profiles><profile><id>dev</id><properties><!-- 环境标识&#xff0c;需要与配置文件的名称相对应 --><profiles.active&…

AX7A200教程(8): HDMI输入和输出显示1080p视频

文章目录 本章节主要将hdmi输入的1080p视频通过ddr3缓存&#xff0c;然后通过hdmi输出口输出到显示屏上显示 一&#xff0c; 突发读写命令 设置读写突发长度为64 //parameter defineparameter WRITE_LENGTH 64;parameter READ_LENGTH 64;parameter IDLE 3d0; …

SSM面试题-Spring容器的启动流程

解答: 1. BeanDefinitionReader读取配置文件(xml yml properties),创建BeanDefinition(存储bean的定义信息) 2. 配置文件读取成功后&#xff0c;将相应的配置转换成 BeanDefinition 的对象实例保存在DefaultListableBeanFactory#beanDefinitionMap 中 3. 根据配置的 BeanFacto…

fastadmin采坑之固定表格某一列

// 初始化表格table.bootstrapTable({url: $.fn.bootstrapTable.defaults.extend.index_url,pk: id,sortName: id,fixedColumns: true,fixedRightNumber: 1,columns: [[{checkbox: true},{field: id, title: __(Id)},{field: proposal_title, title: __(Proposal_title), opera…

Modbus Poll 软件----下载和安装

Modbus Poll 下载 modbus tools 官网地址&#xff1a;https://www.modbustools.com/ 步骤1 点击进入官网&#xff0c;然后点击 DOWNLOAD&#xff0c;进入下载界面。 步骤2 在下载界面&#xff0c;点击 Download 64bit &#xff0c;下载 Modbus Poll。 步骤3 下载完成 Mo…

如何生成丰富的啸叫样本?

前段时间有个公众号的朋友问我如何生成丰富的啸叫类型&#xff0c;当时回答比较简单&#xff0c;只是把啸叫产生的条件说了一下&#xff0c;后来在写AI降噪的N种数据扩增方法时候也简单提了一下使用冲激响应(Impluse Respose, IR)和增益产生啸叫&#xff0c;今天我们把这个坑填…

RabbitMQ 教程 | 第3章 客户端开发向导

&#x1f468;&#x1f3fb;‍&#x1f4bb; 热爱摄影的程序员 &#x1f468;&#x1f3fb;‍&#x1f3a8; 喜欢编码的设计师 &#x1f9d5;&#x1f3fb; 擅长设计的剪辑师 &#x1f9d1;&#x1f3fb;‍&#x1f3eb; 一位高冷无情的编码爱好者 大家好&#xff0c;我是 DevO…

第1章 JavaScript简史

JavaScript的起源 JavaScript是Netscape公司与Sun公司合作开发的在JavaScript诞生之前游览器就是显示超文本文档的简单的软件&#xff0c;JavaScript为此增加了交互行为ECMAScript是JavaScript的标准化&#xff0c;本质上是同一个语言JavaScript是一门脚本语言通常只能运行在游…

VCS ICO - Intelligent Coverage Optimization

ico是vcs提供的用于优化覆盖率的feature&#xff1b;一般用户通过dist solver bofore等约束了变量的随机概率&#xff0c;而ico会在用户约束的基础上&#xff0c;做一些自动“修正”&#xff0c;以此来优化随机激励&#xff0c;提高随机多样性&#xff0c;加速覆盖率收敛&#…

【腾讯云 Cloud Studio 实战训练营】通过云IDE构建Web3项目

文章目录 背景一、 前言二、 Cloud Studio 主要功能三、Cloud Studio 实验前期准备3.1. 注册平台 四、构建Web3项目项目中技术栈 五、其他功能演示六、常见问题及注意事项七、总结八、相关链接 ​ Cloud Studio 是基于浏览器的集成式开发环境&#xff08;IDE&#xff09;&#…

《吐血整理》进阶系列教程-拿捏Fiddler抓包教程(12)-Fiddler设置IOS手机抓包,你知多少???

1.简介 Fiddler不但能截获各种浏览器发出的 HTTP 请求&#xff0c;也可以截获各种智能手机发出的HTTP/ HTTPS 请求。 Fiddler 能捕获Android 和 Windows Phone 等设备发出的 HTTP/HTTPS 请求。同理也可以截获iOS设备发出的请求&#xff0c;比如 iPhone、iPad 和 MacBook 等苹…

每日一题——只出现一次的数字

只出现一次的数字 题目链接 思路 要求为线性时间复杂度&#xff0c;即时间复杂度为O(n)&#xff0c;那么我们就不能用简单的两层循环来解决问题 要求只能使用常量额外空间&#xff0c;即空间复杂度为O(1)&#xff0c;那么我们就不能额外开辟一个数组来记录每个元素出现的次数…

Cpp学习——通过日期类来了解Cpp中的运算符重载

目录 一&#xff0c;日期类 二&#xff0c;运算符重载 运算符重载1(比较&#xff09; 1.< 2. 复用 3.> 4.! 5.< 6.> 运算符重载2(日期加减&#xff09; 0.准备条件------计算每月的日期函数 1. 2. 3.- 4.- 5.前置 6.后置 7前置-- 6.后置-- 7.计…

「BLIP 微调指南」以 Image-Text Captioning 任务为例

前言&#xff1a;近日需要用到 BLIP 微调下游任务&#xff0c;搜索发觉如今并无 BLIP 微调教程&#xff0c;下面就以 Image-Text Captioning 任务为例&#xff0c;演示如何完成 BLIP 模型在自己数据集上的微调。 目录 1. BLIP 介绍2. 关键代码定位3. 关键参数赋值4. 模型定义&a…

Scratch 教程 之 如何四舍五入保留一个小数到指定的数位

有些时候&#xff0c;我们需要四舍五入一个多位小数到指定的位&#xff0c;但scratch并没有这个积木&#xff0c;怎么做呢&#xff1f;我来教你&#xff5e; 我们创建一个函数&#xff0c;需要时调用就行了&#xff5e; 如图&#xff0c;创建一个带参函数&#xff0c;勾选"…

《GreenPlum系列-部署维护》GreenPlum数据库Standby故障处理

一、Standby故障 1.检查监控中心数据库状态 2.查看master节点数据库状态 su - gpadmin gpstate -f二、重启数据库 1.快速关闭数据库 [gpadminmdw pg_log]$ gpstop -M fast ... Continue with Greenplum instance shutdown Yy|Nn (defaultN): > y ...2.开启数据库 [gpad…

[SSM]Spring对事务的支持

目录 十六、Spring对事务的支持 16.1事务概述 16.2引入事务场景 16.3Spring对事务的支持 Spring实现事务的两种方式 Spring事务管理API 声明式事务之注解实现方式 事务属性 事务的全注解式开发 声明式事务之XML实现方式 十六、Spring对事务的支持 16.1事务概述 什么是…