<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>WebSocket归档 - 枫阿雨&#039;s blog</title>
	<atom:link href="https://www.crazyfay.com/tag/websocket/feed/" rel="self" type="application/rss+xml" />
	<link>https://www.crazyfay.com/tag/websocket/</link>
	<description>CrazyFay</description>
	<lastBuildDate>Tue, 04 Apr 2023 02:47:36 +0000</lastBuildDate>
	<language>zh-CN</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=6.5.2</generator>

<image>
	<url>https://www.crazyfay.com/wp-content/uploads/2023/04/cropped-DockerGopher-32x32.png</url>
	<title>WebSocket归档 - 枫阿雨&#039;s blog</title>
	<link>https://www.crazyfay.com/tag/websocket/</link>
	<width>32</width>
	<height>32</height>
</image> 
	<item>
		<title>速通WebSocket协议 &#8211; 建立在HTTP之上的应用层&#8221;TCP&#8221;协议</title>
		<link>https://www.crazyfay.com/2022/10/11/%e9%80%9f%e9%80%9awebsocket%e5%8d%8f%e8%ae%ae-%e5%bb%ba%e7%ab%8b%e5%9c%a8http%e4%b9%8b%e4%b8%8a%e7%9a%84%e5%ba%94%e7%94%a8%e5%b1%82tcp%e5%8d%8f%e8%ae%ae/</link>
					<comments>https://www.crazyfay.com/2022/10/11/%e9%80%9f%e9%80%9awebsocket%e5%8d%8f%e8%ae%ae-%e5%bb%ba%e7%ab%8b%e5%9c%a8http%e4%b9%8b%e4%b8%8a%e7%9a%84%e5%ba%94%e7%94%a8%e5%b1%82tcp%e5%8d%8f%e8%ae%ae/#respond</comments>
		
		<dc:creator><![CDATA[crazyfay]]></dc:creator>
		<pubDate>Tue, 11 Oct 2022 15:09:30 +0000</pubDate>
				<category><![CDATA[学习笔记]]></category>
		<category><![CDATA[WebSocket]]></category>
		<category><![CDATA[网络]]></category>
		<guid isPermaLink="false">http://net.crazyfay.xyz/?p=142</guid>

					<description><![CDATA[<p>由于最近在看一些IM系统相关的内容，学习到了webSocket协议，感觉很有意思，但是感觉感觉网上没有找到很清 [&#8230;]</p>
<p><a rel="nofollow" href="https://www.crazyfay.com/2022/10/11/%e9%80%9f%e9%80%9awebsocket%e5%8d%8f%e8%ae%ae-%e5%bb%ba%e7%ab%8b%e5%9c%a8http%e4%b9%8b%e4%b8%8a%e7%9a%84%e5%ba%94%e7%94%a8%e5%b1%82tcp%e5%8d%8f%e8%ae%ae/">速通WebSocket协议 &#8211; 建立在HTTP之上的应用层&#8221;TCP&#8221;协议</a>最先出现在<a rel="nofollow" href="https://www.crazyfay.com">枫阿雨&#039;s blog</a>。</p>
]]></description>
										<content:encoded><![CDATA[<p>由于最近在看一些IM系统相关的内容，学习到了webSocket协议，感觉很有意思，但是感觉感觉网上没有找到很清晰描述webSocket的文章，故有此篇，文章整理了网络上的一些资料并加上我的个人理解，如有错误，欢迎指出</p>
<h1>WebSocket</h1>
<p><strong>WebSocket是一种网络传输协议</strong>，可在单个TCP连接上进行全双工通信，WebSocket使得客户端和服务器之间的数据交换变得更加简单，允许服务端主动向客户端推送数据。在WebSocket API中，浏览器和服务器只需要完成一次握手，两者之间就可以建立持久性的连接，并进行双向数据传输。WebSocket是为了在浏览器中使用长连接而定义的协议。因此，它是在http超文本传输协议的基础上升级而来，<code>Http和WebSocket协议都是基于TCP协议的</code>。</p>
<p>WebSocket 与 HTTP/2 一样，都是为了解决 HTTP 某方面的缺陷而诞生的。HTTP/2 针对的是“队头阻塞”，而 WebSocket 针对的是“请求 - 应答”通信模式。</p>
<p><strong>那么，“请求 - 应答”有什么不好的地方呢？</strong></p>
<p>“请求 - 应答”是一种“半双工”的通信模式，虽然可以双向收发数据，但同一时刻只能一个方向上有动作，传输效率低。更关键的一点，它是一种“被动”通信模式，服务器只能“被动”响应客户端的请求，无法主动向客户端发送数据。</p>
<p>虽然后来的 HTTP/2、HTTP/3 新增了 Stream、Server Push 等特性，但“请求 - 应答”依然是主要的工作方式。这就导致 HTTP 难以应用在动态页面、即时消息、网络游戏等要求“实时通信”的领域。</p>
<p>在 WebSocket 出现之前，在浏览器环境里用 JavaScript 开发实时 Web 应用很麻烦。因为浏览器是一个“受限的沙盒”，不能用 TCP，只有 HTTP 协议可用，所以就出现了很多“变通”的技术，“轮询”（polling）就是比较常用的的一种。</p>
<p>简单地说，轮询就是不停地向服务器发送 HTTP 请求，问有没有数据，有数据的话服务器就用响应报文回应。如果轮询的频率比较高，那么就可以近似地实现“实时通信”的效果。</p>
<p>但轮询的缺点也很明显，反复发送无效查询请求耗费了大量的带宽和 CPU 资源，非常不经济。</p>
<p>所以，为了克服 HTTP“请求 - 应答”模式的缺点，WebSocket 就“应运而生”了。它原来是 HTML5 的一部分，后来“自立门户”，形成了一个单独的标准，RFC 文档编号是 6455。</p>
<p>WebSocket 采用了二进制帧结构，语法、语义与 HTTP 完全不兼容，但因为它的主要运行环境是浏览器，为了便于推广和应用，就不得不“搭便车”，在使用习惯上尽量向 HTTP 靠拢，这就是它名字里“Web”的含义。</p>
<p>服务发现方面，WebSocket 没有使用 TCP 的“IP 地址 + 端口号”，而是延用了 HTTP 的 URI 格式，但开头的协议名不是“http”，引入的是两个新的名字：“ws”和“wss”，分别表示明文和加密的 WebSocket 协议。</p>
<p>WebSocket 的默认端口也选择了 80 和 443，因为现在互联网上的防火墙屏蔽了绝大多数的端口，只对 HTTP 的 80、443 端口“放行”，所以 WebSocket 就可以“伪装”成 HTTP 协议，比较容易地“穿透”防火墙，与服务器建立连接。</p>
<h2>WebSocket 协议格式</h2>
<pre><code class="language-shell"> 0                   1                   2                   3
 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-------+-+-------------+-------------------------------+
|F|R|R|R| opcode|M| Payload len |    Extended payload length    |
|I|S|S|S|  (4)  |A|     (7)     |             (16/64)           |
|N|V|V|V|       |S|             |   (if payload len==126/127)   |
| |1|2|3|       |K|             |                               |
+-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
|     Extended payload length continued, if payload len == 127  |
+ - - - - - - - - - - - - - - - +-------------------------------+
|                               |Masking-key, if MASK set to 1  |
+-------------------------------+-------------------------------+
| Masking-key (continued)       |          Payload Data         |
+-------------------------------- - - - - - - - - - - - - - - - +
:                     Payload Data continued ...                :
+ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
|                     Payload Data continued ...                |
+---------------------------------------------------------------+
</code></pre>
<p>WebSocket 和 HTTP/2 的关注点不同，WebSocket 更侧重于“实时通信”，而 HTTP/2 更侧重于提高传输效率，所以两者的帧结构也有很大的区别。</p>
<p>WebSocket 虽然有“帧”，但却没有像 HTTP/2 那样定义“流”，也就不存在“多路复用”“优先级”等复杂的特性，而它自身就是“全双工”的，也就不需要“服务器推送”。所以综合起来，WebSocket 的帧学习起来会简单一些。</p>
<p>上面就是 WebSocket 的帧结构定义，长度不固定，最少 2 个字节，最多 14 字节，看着好像很复杂，实际非常简单。</p>
<p>开头的两个字节是必须的，也是最关键的。</p>
<p>第一个字节的第一位“<strong>FIN</strong>”是消息结束的标志位，相当于 HTTP/2 里的“END_STREAM”，表示数据发送完毕。一个消息可以拆成多个帧，接收方看到“FIN”后，就可以把前面的帧拼起来，组成完整的消息。</p>
<p>“FIN”后面的三个位是保留位，目前没有任何意义，但必须是 0。</p>
<p>第一个字节的后 4 位很重要，叫“Opcode”，操作码，其实就是帧类型，比如 1 表示帧内容是纯文本，2 表示帧内容是二进制数据，8 是关闭连接，9 和 10 分别是连接保活的 PING 和 PONG。</p>
<p>第二个字节第一位是掩码标志位“MASK”，表示帧内容是否使用异或操作（xor）做简单的加密。目前的 WebSocket 标准规定，客户端发送数据必须使用掩码，而服务器发送则必须不使用掩码。</p>
<p>第二个字节后 7 位是“Payload len”，表示帧内容的长度。它是另一种变长编码，最少 7 位，最多是 7+64 位，也就是额外增加 8 个字节，所以一个 WebSocket 帧最大是 2^64。</p>
<p>长度字段后面是“Masking-key”，掩码密钥，它是由上面的标志位“MASK”决定的，如果使用掩码就是 4 个字节的随机数，否则就不存在。</p>
<p>这么分析下来，其实 WebSocket 的帧头就四个部分：“结束标志位 + 操作码 + 帧长度 + 掩码”，只是使用了变长编码的“小花招”，不像 HTTP/2 定长报文头那么简单明了。</p>
<h2>WebSocket 握手</h2>
<p>和 TCP、TLS 一样，WebSocket 也要有一个握手过程，然后才能正式收发数据。</p>
<p>这里它还是搭上了 HTTP 的“便车”，利用了 HTTP 本身的“协议升级”特性，“伪装”成 HTTP，这样就能绕过浏览器沙盒、网络防火墙等等限制，这也是 WebSocket 与 HTTP 的另一个重要关联点。</p>
<p>WebSocket 的握手是一个标准的 HTTP GET 请求，但要带上两个协议升级的专用头字段：</p>
<ul>
<li>“Connection: Upgrade”，表示要求协议“升级”；</li>
<li>“Upgrade: websocket”，表示要“升级”成 WebSocket 协议。</li>
</ul>
<p>另外，为了防止普通的 HTTP 消息被“意外”识别成 WebSocket，握手消息还增加了两个额外的认证用头字段（所谓的“挑战”，Challenge）：</p>
<ul>
<li>Sec-WebSocket-Key：一个 Base64 编码的 16 字节随机数，作为简单的认证密钥；</li>
<li>Sec-WebSocket-Version：协议的版本号，当前必须是 13。</li>
</ul>
<p><strong>来自客户端的握手如下所示</strong>：</p>
<pre><code class="language-http">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</code></pre>
<p>服务器收到 HTTP 请求报文，看到上面的四个字段，就知道这不是一个普通的 GET 请求，而是 WebSocket 的升级请求，于是就不走普通的 HTTP 处理流程，而是构造一个特殊的“101 Switching Protocols”响应报文，通知客户端，接下来就不用 HTTP 了，全改用 WebSocket 协议通信。（有点像 TLS 的“Change Cipher Spec”）</p>
<p>WebSocket 的握手响应报文也是有特殊格式的，要用字段“Sec-WebSocket-Accept”验证客户端请求报文，同样也是为了防止误连接。</p>
<p>具体的做法是把请求头里“Sec-WebSocket-Key”的值，加上一个专用的 UUID “258EAFA5-E914-47DA-95CA-C5AB0DC85B11”，再计算 SHA-1 摘要。</p>
<pre><code class="language-shell">encode_base64(sha1(Sec-WebSocket-Key + &#039;258EAFA5-E914-47DA-95CA-C5AB0DC85B11&#039; ))</code></pre>
<p>客户端收到响应报文，就可以用同样的算法，比对值是否相等，如果相等，就说明返回的报文确实是刚才握手时连接的服务器，认证成功。返回握手信息。</p>
<p>握手完成，后续传输的数据就不再是 HTTP 报文，而是 WebSocket 格式的二进制帧了。</p>
<p><strong>来自服务器的握手如下所示</strong>:</p>
<pre><code class="language-http">HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
Sec-WebSocket-Protocol: chat</code></pre>
<p>大致意思就是告诉服务器，我要升级为websocket长连，如果服务器同意，就必须返回101状态，告诉客户端，升级成功。</p>
<blockquote>
<p>服务器不可返回200状态码</p>
</blockquote>
<h2>总结</h2>
<p>浏览器是一个“沙盒”环境，有很多的限制，不允许建立 TCP 连接收发数据，而有了 WebSocket，我们就可以在浏览器里与服务器直接建立“TCP 连接”，获得更多的自由。</p>
<p>不过自由也是有代价的，WebSocket 虽然是在应用层，但使用方式却与“TCP Socket”差不多，过于“原始”，用户必须自己管理连接、缓存、状态，开发上比 HTTP 复杂的多，所以是否要在项目中引入 WebSocket 必须慎重考虑。</p>
<p><strong>WebSocket 特点</strong>：</p>
<ol>
<li>全双工通信，相当于对 TCP 做了一层“薄薄的包装”，让它运行在浏览器环境里。</li>
<li>WebSocket 使用二进制帧，结构比较简单，特殊的地方是有个“掩码”操作，客户端发数据必须掩码，服务器则不用。</li>
<li>Websocket与HTTP和HTTPS使用相同的TCP端口，可以绕过大多数防火墙的限制。</li>
<li>Websocket协议使用80端口；运行在TLS之上时，默认使用443端口。</li>
<li>协议头大小可变，节省空间。</li>
<li>opcode：自带心跳ping/pong协议码。</li>
</ol>
<p><strong>相比TCP缺点</strong>：</p>
<ol>
<li>增加了协议头，对流量有一点影响。</li>
<li>客户端发送数据载体时必须做一个编码(MASK=1)，这会增加cpu负载。</li>
<li>服务端解析HTTP Upgrade头会增加消耗。</li>
</ol>
<blockquote>
<p>tips:</p>
<ol>
<li>WebSocket 标准诞生于2011年，HTTP/2 诞生于2015年</li>
<li>WebSocket 不兼容URI后面的 <code>#</code> 标识，需要编码为 <code>%23</code></li>
<li>WebSocket 强制要求客户端发送数据使用掩码，是为了提供最基本的安全防护，让每次发送的消息都是随机、不可预测的，抵御”缓存中毒“攻击。但如果运行在SSL/TLS上，采用加密通信，那么掩码就没有必要了</li>
<li>WebSocket 协议里的 <code>PING</code>、<code>PONG</code> 帧，对于保持长连接很重要，可以让链路上总有数据在传输，防止被服务器、路由、网关认为是”无效连接“而意外关闭</li>
</ol>
</blockquote>
<p><a rel="nofollow" href="https://www.crazyfay.com/2022/10/11/%e9%80%9f%e9%80%9awebsocket%e5%8d%8f%e8%ae%ae-%e5%bb%ba%e7%ab%8b%e5%9c%a8http%e4%b9%8b%e4%b8%8a%e7%9a%84%e5%ba%94%e7%94%a8%e5%b1%82tcp%e5%8d%8f%e8%ae%ae/">速通WebSocket协议 &#8211; 建立在HTTP之上的应用层&#8221;TCP&#8221;协议</a>最先出现在<a rel="nofollow" href="https://www.crazyfay.com">枫阿雨&#039;s blog</a>。</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.crazyfay.com/2022/10/11/%e9%80%9f%e9%80%9awebsocket%e5%8d%8f%e8%ae%ae-%e5%bb%ba%e7%ab%8b%e5%9c%a8http%e4%b9%8b%e4%b8%8a%e7%9a%84%e5%ba%94%e7%94%a8%e5%b1%82tcp%e5%8d%8f%e8%ae%ae/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
	</channel>
</rss>
