WebSocket 协议详解

409 阅读8分钟

我报名参加金石计划1期挑战——瓜分10万奖池,这是我的第20篇文章,点击查看活动详情


WebSocket 协议详解

WebSocket的由来

(1)HTTP协议是符合请求响应模型基本特征的。即它的生命周期是一个request一个response。 在HTTP1.0中,当有了一个request和一个response 后这次 HTTP请求就结束了。如再请求就需 要再建立连接。这个问题在HTTP1.1中进行了改进,连接定义为keep—alive在一个HTTP连接中, 可以发送多个request,接收多个response。但一个request也只能有一个response,不存在一个request 对应多个response的情况,反之亦然。

(2)HTTP协议的服务器端不能主动给客户端发送请求,一次连接的建立只能由客户端发起。 所以HTTP协议对于服务器端来说是被动发起请求的。

(3)HTTP 协议的无状态性,请求与请求间是没有关联的。通俗地说,服务器是个健忘鬼, 你一挂电话,它就把你的东西全忘光了,把你的东西全丢掉了。你第二次还得将之前的信息再告 诉服务器一遍,它才能识别你的身份。

随着推送通知等功能对请求传输需求的日益更新,HTTP 因为这些缺点已经无法满足开发需 求。或许你会说,可以用HTTP long poll或者Ajax轮询实现实时信息传递。但这又会产生另外两 个问题,要说明这些我们必须先掌握HTTP long poll及Ajax轮询的原理是什么。

HTTP long poll的实现原理如下。

long poll是指客户端发起连接后,如果没消息,就一直不返回response给客户端。直到有消 息才返回,返回之后,客户端再次建立连接,周而复始。

传输场景再现。

客户端:哈喽,服务亲,有没有新信息,没有的话就等有了再返回给我吧(request)

服务端:没有消息不理你,等到有新消息的时候再给你(response)

客户端:哈喽,服务亲,有没有新信息,没有的话就等有了再返回给我吧(request)

服务端:没有消息不理你,等到有新消息的时候再给你(response)

客户端:哈喽,服务亲,有没有新信息,没有的话就等有了再返回给我吧(request)-loop

可以看出long poll其实是很消耗服务器资源的,需要长时间的响应和处理并发的能力。 ajax轮询的实现原理如下。

ajax轮询就是让浏览器隔个几秒就发送一次请求,询问服务器是否有新信息。 传输场景再现。

客户端:哈喽,服务亲,有没有新信息(request) 

服务端:没有(response)

客户端:哈喽,服务亲,有没有新信息(request)

服务端:没有(response)

客户端:哈喽,服务亲,有没有新信息(request)

服务端:你怎么那么烦都说了没有,没有啊(response)

客户端:哈喽,服务亲,有没有新消息(request)

服务端:好啦好啦,有啦给你。(response)

客户端:哈喽,服务亲,有没有新消息(request) 

服务端:又来了吐血中,没有10086(response)-loop

从以上我们可以看出,ajax轮询需要服务器有很快的处理速度和资源。而long poll需要有很高 的并发,也就是说同时接待客户的能力。那么问题来了,怎么解决这种过度消耗资源的问题呢?人 类的智慧是伟大的,随着HTML5的推出一种崭新的协议出现在世人面前。它就是WebSocket协议。

在HTML5规范中,定义了WebSocket API。WebSocket API是下一代客户端-服务器的异步通 信方法。该通信取代了单个的TCP套接字,使用ws或wss协议,可用于任意的客户端和服务器 程序。这个新的 API提供了一个方法,从客户端使用简单的语法有效地推动消息到服务器。 WebSocket API最伟大之处在于服务器和客户端可以在给定的时间范围内的任意时刻,相互推送信 息。WebSocket并不限于以Ajax(或XHR)方式通信,因为Ajax技术需要客户端发起请求,而WebSocket 服务器和客户端可以彼此相互推送信息;XHR受到域的限制,而WebSocket允许跨域通信。

WebSocket的属性

既然 WebSocket如此好,那客户端如何通知服务器端它需要使用WebSocket协议呢? 首先WebSocket是基于HTTP协议的,它借用HTTP的协议来完成一部分握手工作。 我们来看一个典型的WebSocket握手。

image.png

这个就是WebSocket的核心,它告诉Apache、Nginx等服务器:注意啦,我发起的是WebSocket 协议,快点帮我找到对应的助理处理,不是那个老土的HTTP。

image.png

  • Sec-websocket-Key

是一个Base64 encode的值,这个是浏览器随机生成的。它就像要告诉服务器:我要验证你 是不是真的是 WebSocket助理。

  • Sec-websocket-Protocol

是一个用户定义的字符串,用来区分同一个URL下不同的服务所需要的协议。

  • ·Sec-websocket-Version

是告诉服务器所使用的WebSocket Draft(协议版本)。

然后服务器会返回以下响应,表示已经接收到请求,成功建立WebSocket。

image.png

这里开始就是HTTP最后负责的区域了,告诉客户端,我已经成功切换协议啦。

  • ·Upgrade:WebSocket Connection

image.png Upgrade 依然是固定的,告诉客户端即将升级的是WebSocket 协议,而不是 mozillasocket,

\

lurnarsocket 或者 shitsocket。 Sec-websocket-Accept

是经过服务器确认,并且加密后的 Sec—WebSocket—Key。类似于服务器说:好啦好啦,知道 啦,给你看我的IDCARD来证明行了吧。

·Sec-WebSocket-Protocol 表示最终使用的协议。

至此,HTTP已经完成它所有的工作了,接下来就是完全按照WebSocket协议进行了。

WebSocket的原理

WebSocket 解决了HTTP的缺点。当服务器完成协议升级后(HTTP->websocket),服务器端 就可以主动推送信息给客户端啦。

WebSocket实现原理场景如下。

客户端:哈喽,服务亲,我要建立WebSocket协议,需要的服务:chat,WebSocket协议版本: 17(HTTP Request)

服务端:ok,确认,已升级为WebSocket 协议(HTTP Protocols Switched) 

客户端:亲,麻烦你有信息的时候推送给我噢

服务端:ok,有的时候会告诉你的 

服务端:消息1

服务端:消息2 

服务端:消息3

服务端:消息n

只需要经过一次HTTP请求,就可以做到源源不断的信息传送。(在程序设计中,这种设计叫作回调,即你有信息了再来通知我,而不是我傻乎乎地每次跑来问你。)

这样的协议也同时解决了 langloop 以及 Ajax轮询的同步有延迟非常消耗资源的问题。我们所用的程序是要经过两层代理的,即HTTP 协议在Nginx等服务器的解析下,然后再传送给相应的Handler来处理。简单地说,我们有一个非常快速的接线员(Nginx),它负责把问题转交给相应的客服(Handler)。

接线员基本上速度是足够的,但是每次都卡在客服(Handler),老有客服处理速度太慢,导致客服不够。WebSocket 就解决了这样一个难题,建立后,可以直接跟接线员建立持久连接。有信息的时候客服想办法通知接线员,然后接线员再统一转交给客户。这样就可以解决客服处理速度过慢的问题了。

同时,在传统的方式上,要不断地建立、关闭HTTP协议。由于HTTP是无状态性的,每次都要重新传输 identity info 告诉服务器端你是谁。虽然接线员很快速,但是每次都要听这么一堆,效率也会有所下降,同时还得不断把这些信息转交给客服,不但浪费客服的处理时间,而且还会在网络传输中消耗过多的流量/时间。但是WebSocket只需要一次HTTP握手,所以说整个通信过程是建立在一次连接/状态中,也就避免了HTTP的无状态性。服务端会一直知道你的信息,直到你关闭请求,这样就解决了接线员要反复解析HTTP协议,还要查看identity info的信息的问题。

同时由客户主动询问,转换为服务器(推送)有信息的时候就发送(当然客户端还是等主动发送信息过来的),没有信息的时候就交给接线员(Nginx),不需要占用本身速度就慢的客服(Handler)。