HTTP:协议升级机制(WebSocket)
HTTP协议提供了⼀种特殊的机制,这⼀机制允许将⼀个已建⽴的连接升级成新的、不相容的协议。
通常来说这⼀机制总是由客户端发起的(不过也有例外,⽐如说可以由服务端发起升级到传输层安全协议(TLS)),服务端可以选择是否要升级到新协议。借助这⼀技术,连接可以以常⽤的协议启动(⽐如HTTP/1.1),随后再升级到HTTP2甚⾄是WebSockets.
注意:HTTP/2 明确禁⽌使⽤此机制,这个机制只属于HTTP/1.1
升级HTTP/1.1的链接
协议升级请求总是由客户端发起的;暂时没有服务端请求协议更改的机制。当客户端试图升级到⼀个新的协议时,可以先发送⼀个普通的请求如GET、POST等,不过这个请求需要进⾏特殊配置以包含升级请求。
这个请求需要添加两项额外的header:
Connection: Upgrade:设置 Connection 头的值为 “Upgrade” 来指⽰这是⼀个升级请求.
Upgrade: protocols:Upgrade 头指定⼀项或多项协议名,按优先级排序,以逗号分隔。
⼀个典型的包含升级请求的例⼦差不多是这样的:
GET /index.html HTTP/1.1
Host: ample
Connection: upgrade
Upgrade: example/1, foo/2
如果服务器没有升级这次连接,它会忽略客户端发送的 Upgrade 头部字段,返回⼀个常规的响应(例如⼀个200 OK)
如果服务器决定升级这次连接,就会返回⼀个101 Switching Protocols 响应状态码,和⼀个要切换到的协议的头部字段Upgrade。
HTTP/1.1101 Switching Protocols
Upgrade: foo/2
Connection: Upgrade
服务在发送 101 状态码之后,就可以使⽤新的协议,并可以根据需要执⾏任何其他协议指定的握⼿。实际上,⼀旦这次升级完成了,连接就变成了双向管道。并且可以通过新协议完成启动升级的请求。
由HTTP/1.1请求建⽴的连接可以升级为HTTP/2协议的连接,但是反过来不可以。事实上HTTP/2已经不再⽀持101状态码了,也不再⽀持任何连接升级机制。
WebSocket
在TCP/IP协议栈的时候有关TCP Socket,它实际上是⼀种功能接⼝,通过这些接⼝就可以使⽤TCP/IP协议栈在传输层收发数据。
⽽WebSocket中,“Web”指的就是HTTP,“Socket”是在套接字调⽤,WebSocket就是运⾏在Web,也就是HTTP上的Socket通信规范,提供与TCP Socket类似的功能,使⽤它可以像TCP Socket⼀样调⽤下层协议栈,任意的收发数据
更准确的说,WebSocket是⼀种基于TCP的轻量级⽹络通信协议,在地位上与HTTP是平级的
为什么要有WebSocket
不过,已经有了HTTP协议,为什么要有WebSocket呢?
其实,WebSocket与HTTP/2⼀样,都是为了解决HTTP某⽅⾯的缺陷⽽诞⽣的。
HTTP/2针对的是队头阻塞
WebSocket针对的是请求-应答通信模式
那么,“请求-应答”有什么不好的地⽅吗?
“请求-应答”是⼀种半双⼯的通信模式,虽然可以双向收发数据,但是同⼀时刻只能⼀个⽅向上有动作,传输效率低。更关键的⼀点是,它是⼀种被动通信模式,服务器只能被动响应客户端的请求,⽆法主动向客户端发送数据
虽然后来的HTTP/2、HTTP/3新增了stream、server push等特性,但“请求-应答”依然是主要的⼯作模式。这就导致HTTP难以应⽤在动态页⾯、即时消息、⽹络游戏等要求“实时通信”的领域
在WebSocket出现之前,在浏览器环境⾥使⽤JavaScript开发实时Web应⽤很⿇烦。因为浏览器是⼀个“受限的沙盒”,不能⽤TCP,只有HTTP协议可⽤,所以就出现了很多“变通”的技术,轮询(polling)就是⽐较常⽤的⼀种
简单的说,轮询就是不停地向服务器发送HTTP请求,问有没有数据,有数据的话服务器就⽤响应报⽂回应。如果轮询的频率⽐较⾼,就可以近似实现“实时通信”效果
但轮询的缺点也很明显,反复发送⽆效查询请求耗费了⼤量的带宽和CPU资源,⾮常不经济
所以,为了克服 HTTP“请求 - 应答”模式的缺点,WebSocket 就“应运⽽⽣”了。它原来是 HTML5 的⼀部分,后来“⾃⽴门户”,形成了⼀个单独的标准,RFC ⽂档编号是6455。
WebSocket的特点
WebSocket是⼀个真正的全双⼯的通信协议,与TCP⼀样,客户端和服务器都可以随时向对⽅发送数据。因此,⼀旦后台有新的数据,服务器就可以将之推送到客户端,不需要客户端轮询,“实时通信”的概率也就提⾼了。
WebSocket采⽤了⼆进制帧结构,语法、语义与HTTP完全不兼容,但因为它的主要运⾏环境是浏览器,为了便于推⼴和应⽤,就不得不“搭便车”,在使⽤习惯上尽量向HTTP考虑,这就是它名字⾥的“web”的含义
服务发现⽅⾯,WebSocket没有使⽤TCP的“IP地址+端⼝号”,⽽是延⽤了HTTP的URI格式,但开头的协议名不是“http”,引⼊的是两个新的名字:“ws”和“wss”,分别表⽰明⽂和加密的WebSocket协议
WebSocket的默认断开也选择了80和443,因为现在互联⽹上的防⽕墙屏蔽了绝⼤多数端⼝,只对HTTP的80、443端⼝“放⾏”,所以WebSocket就可以“伪装”成HTTP协议,⽐较容易的“穿透”防⽕墙,与
服务器建⽴连接。
WebSocket的帧结构
WebSocket⽤的和HTTP/2⼀样,⽤的也是⼆进制帧。但WebSocket更侧重于“实时通信”,HTTP/2更侧重于提⾼传输效率,所以两者的帧结构也有很⼤的区别。
WebSocket虽然有帧,但却没有像HTTP/2那样定义“流”,也就不存在“多路复⽤”、“优先级”等复杂的特性,⽽它⾃⾝就是“全双⼯”的,也就不需要“服务器推送”。所以,WebSocket的帧简单⼀些。
下图就是WebSocket的帧结果定义,长度不固定,最少2个字节,最⼤14个字节。看起来复杂,实际上很简单。
开头的两个字节是必须的,也是最关键的
第⼀个字节FIN是消息结束的标志位,相当于HTTP/2⾥⾯的END_STREAM,表⽰数据发送完毕,⼀个消息可以拆成多个帧,接收⽅看到FIN之后,就可以把前⾯的帧拼起来,组成完成的消息
FIN后⾯的三位是保留位,⽬前没有任何意义,但是必须是0
第⼀个字节后的后4位很重要,叫做Opcode,操作吗,其实就是帧类型,⽐如1表⽰帧内容是纯⽂本,2表⽰帧内容是⼆进制数据,8是关闭连接,9和10分别是连接保活的PING和PONG
第⼆个字节的第⼀位是掩码标志位MASK,表⽰帧内容是否使⽤异或操作(xor)做简单的加密。⽬前的WebSocket标准规定,客户端发送数据必须使⽤掩码,⽽服务器发送则必须不使⽤掩码
第⼆个字节后 7 位是Payload len,表⽰帧内容的长度。它是另⼀种变长编码,最少 7位,最多是 7+64 位,也就是额外增加 8 个字节,所以⼀个 WebSocket 帧最⼤是2^64。
长度字段后⾯是“Masking-key”,掩码密钥,它是由上⾯的标志位“MASK”决定的,如果使⽤掩码就是 4 个字节的随机数,否则就不存在。
这么分析下来,其实 WebSocket 的帧头就四个部分:“结束标志位 + 操作码 + 帧长度 +掩码”,只是使⽤了变长编码的“⼩花招”,不像 HTTP/2 定长报⽂头那么简单明了。
WebSocket的握⼿
和TCP、TLS⼀样,WebSocket也要有⼀个握⼿的过程,然后才能正式收发数据。
这⾥它还是搭上了 HTTP 的“便车”,利⽤HTTP本⾝的“协议升级”特性,“伪装”成HTTP,这样就能绕过浏览器沙盒、⽹络防⽕墙等限制,这也是 WebSocket 与HTTP 的另⼀个重要关联点。
如果想要⾃⼰重头实现WebSocket 连接,就必须要处理握⼿和升级过程。
WebSocket 的握⼿是⼀个标准的 HTTP GET 请求,但要带上两个协议升级的专⽤头字段:
“Connection: Upgrade”,表⽰要求协议“升级”;
“Upgrade: websocket”,表⽰要“升级”成 WebSocket 协议。
在创建初始HTTP/1.1会话之后你需要发送另⼀个HTTP GET,但在headers中要带上Upgrade (en-US) and Connection,也就是:
Connection: Upgrade
Upgrade: websocket
另外,为了防⽌普通的 HTTP 消息被“意外”识别成 WebSocket,握⼿消息还增加了两个额外的认证⽤头字段(所谓的“挑
战”,Challenge):
Sec-WebSocket-Key:⼀个 Base64 编码的 16 字节随机数,作为简单的认证密钥;从本质上说,它表⽰“是的,我真的想打开⼀个WebSocket连接。”
Sec-WebSocket-Version:协议的版本号,当前必须是 13。
服务器收到 HTTP 请求报⽂,看到上⾯的四个字段,就知道这不是⼀个普通的 GET 请求,⽽是 WebSocket 的升级请求,于是就不⾛普通的 HTTP 处理流程,⽽是构造⼀个特殊的“101 Switching Protocols”响应报⽂,通知客户端,接下来就不⽤ HTTP 了,全改⽤WebSocket 协议通信。
WebSocket 的握⼿响应报⽂也是有特殊格式的,要⽤字段“Sec-WebSocket-Accept”验证客户端请求报⽂,同样也是为了防⽌误连接。
当服务器愿意初始化WebSocket连接时,在开始握⼿过程中,服务器的响应消息中包含该消息。它将在响应头中只出现⼀次。
Sec-WebSocket-Accept: hashwebsocket和socket
具体的做法是把请求头⾥“Sec-WebSocket-Key”的值,加上⼀个专⽤的 UUID“258EAFA5-E914-47DA-95CA-
C5AB0DC85B11”,再计算 SHA-1 摘要。
客户端收到响应报⽂,就可以⽤同样的算法,⽐对值是否相等,如果相等,就说明返回的报⽂确实是刚才握⼿时连接的服务器,认证成功。
握⼿完成,后续传输的数据就不再是 HTTP 报⽂,⽽是 WebSocket 格式的⼆进制帧了。
WebSocket ⼀些的 headers
Sec-WebSocket-Extensions
⽤于指定⼀个或者多个请求服务器使⽤的协议级websocket扩展。允许在⼀个请求中使⽤多个Sec-WebSocket-Extension头,它会⾃动合并到⼀个头⽂件。
Sec-WebSocket-Extensions: extensions
extensions:指需要(或⽀持)的扩展的逗号分隔列表。这些值来⾃。带参数的扩展使⽤分号表⽰。⽐如:
Sec-WebSocket-Extensions: superspeed, colormode; depth=16
Sec-WebSocket-Protocol
按优先级排序指定了⼀个或多个你想要使⽤的WebSocket协议。服务器将在响应中包含的aSec-WebSocket-Protocol中选择并返回服务器⽀持的第⼀个协议。你也可以在头⽂件中多次使⽤它;结果与在单个头⽂件中使⽤逗号分隔的⼦协议标识符列表相同。
Sec-WebSocket-Protocol: subprotocols
subprotocols:以逗号分隔的⼦协议名称列表,按优先顺序排列。⼦协议可以从IANA WebSocket⼦协议名称注册表或者可以是客户机和服务器共同理解的⾃定义名称。
Sec-WebSocket-Version
请求头中
指定客户端希望使⽤的WebSocket协议版本,以便服务器可以确认其端部是否⽀持该版本。
Sec-WebSocket-Version: version
version:客户端在与服务器通信时希望使⽤的WebSocket协议版本。此编号应该是中列出的最新版本IANA WebSocket版本号注册表. WebSocket协议的最新最终版本是版本13。
响应头中
如果服务器不能使⽤指定版本的WebSocket协议进⾏通信,它将响应⼀个错误(⽐如426 Upgrade Required),该错误包含在其报头Sec-WebSocket-Version中,并以逗号分隔⽀持的协议版本列表。
如果服务器确实⽀持请求的协议版本,响应中将包含Sec-WebSocket-Version头。
Sec-WebSocket-Version: supportedVersions
supportedVersions:服务器⽀持的WebSocket协议版本的逗号分隔列表。
总结
浏览器是⼀个“沙盒”环境,有很多限制,不允许建⽴TCP连接收发数据,⽽有了WebSocket,我们就可以在浏览器与服务器直接建⽴TCP连接,获取更多的⾃由。
不过⾃由也是有代价的,WebSocket虽然是在应⽤层,但是使⽤⽅式与TCP Socket差不多,过于“原始”,⽤户必须⾃⼰管理连接、缓存、状态,开发上⽐HTTP复杂得多,所以是否要在项⽬中引⼊WebSocket必须慎重考虑
HTTP 的“请求 - 应答”模式不适合开发“实时通信”应⽤,效率低,难以实现动态页⾯,所以出现了 WebSocket;
WebSocket是⼀个“全双⼯”的通信协议,相当于对TCP做了⼀层“薄薄的封装”,让它运⾏在浏览器环境中
WebSocket使⽤兼容HTTP的URI来发现服务,但是定义了新的协议名“ws”和“wss”,端⼝号也沿⽤了80和443
WebSocket使⽤了⼆进制帧,结果⽐较简单,特殊的地⽅是有个“掩码”操作,客户端必须掩码,服务器则不⽤
WebSocket利⽤HTTP协议实现连接握⼿,发送GET请求要求“协议升级”,握⼿过程中有个⾮常简单的认证机制,⽬的是防⽌误连接
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论