WebSocket协议入门简介

前言

以前的网站为了实现推送功能,使用的方法都是轮询。所谓的轮询就是在特定的时间间隔(例如1秒),由浏览器向服务器发出一个 Http request ,然后服务器返回最新的数据给客户端浏览器,从而给出一种服务端实时推送的假象。由于 Http RequestHeader(请求头)很长,而传输的数据可能很短就只占一点点,每次请求消耗的带宽大部分都消耗在 Header 上。从网上资料得知后来还有改进的轮询方法叫做 Comet ,使用 Ajax 。但这种技术虽然可达到双向通信,但依然需要发出请求,而且在 Comet 中,普遍采用了长轮询,这也会大量消耗服务器带宽资源

正文

所以 HTML5 定义了WebSocket 协议,以及相关的编程 API ,能更好的实现双向通信且节省服务器资源带宽

WebSocket原理

注意WebSocket 实际上指的是一种协议,与我们熟知的 Http 协议是同等协议栈的一个网络协议。用网络模型结构来解释的话, WebSocketHttp 协议都属于 应用层协议,两者都基于传输层协议 TCP协议。

WebSocket与HTTP的联系

简述WebSocketHttp 一样,都是基于都 TCP,属于应用层的协议。

WebSocket并不是 HTTP 协议,WebSocket协议只是基于 HTTP 协议在客户端和服务器通过握手建立连接,连接建立以后就通过 TCP 协议发送和接收报文,与 HTTP 协议无关了。

WebSocket 协议和 HTTP 协议是两种不同的东西,它们的联系如下:

  1. 客户端开始建立 WebSocket 连接时要发送一个 header 标记了 UpgradeHTTP 请求,表示请求协议升级
  2. 服务器端做出响应的是,直接在现有的 HTTP 服务器和现有的 HTTP 端口上实现 WebSocket 协议,重用 HTTP 握手建立连接这一功能(比如解析和认证这个 HTTP 请求。如果在 TCP 协议上实现,这两个功能就要重新实现),然后再回一个状态码为 101HTTP 响应完成握手,再往后发送数据时就没 HTTP 的事了。

WebSocket组件

WebSocket通信架构

HTML5 WebSockets规范定义了一个API,它允许web页面使用websocket协议与远程主机双向沟通。介绍了WebSocket接口,并定义了一个全双工的通信通道,通过一个套接字在网络上运行。相比不断客户端轮询的请求方式,HTML5 WebSockets长连接方式极大的减少了不必要的网络流量和延迟。

HTML5 WebSocket简化架构

HTML5 WebSocket

HTML5 WebSocket 规范定义了 WebSocket API , 允许用户在 Browser 使用 。WebSocket 协议为全双工通信,远程主机。基本原理是通过引入 WebSocket Endpoint, 定义一个 全双工 的通信通道, 通过一个 套接字 在网络上运行。HTML5 WebSocket 通过单个套接字长连接有效地降低网络上的开销。相比原有的的轮询和长轮询(Comet)方式来说,极大的减少了不必要的 网络流量延迟, 通常用于 推送实时数据 到客户端, 甚至可以通过维护两个HTTP连接来模拟 全双工连接

Proxy Server

通常,代理服务器建立于内网公网之间。代理服务器可以监控流量, 通过超时机制断开连接HTTP 代理服务器——原本为文档转移——可以选择关闭或闲置 WebSocket 连接, 它会试图不断去call一个反应迟钝的 HTTP 服务器。对于长连接, 比如网络套接字, 这种行为是不合理的。另外, 代理服务器可能会缓存未加密的 HTTP 响应报文, 从而会给 HTTP 响应流带来巨大的延迟。

HTML5 WebSocket and Proxy Servers

让我们看看 HTML5 WebSockes 是如何与代理服务器通信的。WebSocket 连接使用标准的HTTP端口 (80和443)。因此, HTML5 WebSocket 不需要新的硬件设备, 或新开放其他的网络端口。没有任何代理服务器 (代理或反向代理服务器、防火墙、负载平衡路由器等等), 浏览器和 WebSocket 服务器之间通信非常简单, 只要服务器和客户端都支持 WebSocket 协议。然而, 在生产环境中, 大量的网络通信都会穿透防火墙、代理服务器等。

HTML5 WebSocket and Proxy Servers之间的网络拓扑图

生产环境网络拓扑

与常规的HTTP请求/响应,不断建立断开连接的方式相比, WebSocket 连接可以保持很长一段时间。代理服务器可能会对这种长连接的方式进行合理的处理, 但也可能带来不可预料的问题。

WebSocket示例

Copy下面的代码,保存为 websocket.html。然后在浏览器中打开它。页面将自动连接到 ws://echo.websocket.org/ 服务器,发送一个消息,显示反应,关闭连接。参考链接:http://www.websocket.org/echo.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
<!DOCTYPE html>
<meta charset="utf-8" />
<title>WebSocket Test</title>
<script language="javascript" type="text/javascript">

var wsUri = "ws://echo.websocket.org/";
var output;

function init()
{
output = document.getElementById("output");
testWebSocket();
}

function testWebSocket()
{
websocket = new WebSocket(wsUri);
websocket.onopen = function(evt) { onOpen(evt) };
websocket.onclose = function(evt) { onClose(evt) };
websocket.onmessage = function(evt) { onMessage(evt) };
websocket.onerror = function(evt) { onError(evt) };
}

function onOpen(evt)
{
writeToScreen("CONNECTED");
doSend("WebSocket rocks");
}

function onClose(evt)
{
writeToScreen("DISCONNECTED");
}

function onMessage(evt)
{
writeToScreen('<span style="color: blue;">RESPONSE: ' + evt.data+'</span>');
websocket.close();
}

function onError(evt)
{
writeToScreen('<span style="color: red;">ERROR:</span> ' + evt.data);
}

function doSend(message)
{
writeToScreen("SENT: " + message);
websocket.send(message);
}

function writeToScreen(message)
{
var pre = document.createElement("p");
pre.style.wordWrap = "break-word";
pre.innerHTML = message;
output.appendChild(pre);
}

window.addEventListener("load", init, false);

</script>

<h2>WebSocket Test</h2>

<div id="output"></div>

解读

创建一个 WebSocket 对象,参数是需要连接的服务器端的地址,同 http 协议使用http://开头 一样,WebSocket 协议的URL使用ws://开头,另外安全的WebSocket 协议使用wss://开头。。

1
2
var wsUri ="ws://echo.websocket.org/";
websocket = new WebSocket(wsUri);

WebSocket 对象一共支持 onopen , onmessage , oncloseonerror四个消息事件。

当Browser和 WebSocketServer 连接成功后,会触发 onopen 消息;

1
2
websocket.onopen = function(evt) {
};

如果连接失败,发送、接收数据失败或者处理数据出现错误,browser会触发 onerror 消息;

1
2
websocket.onerror = function(evt) {
};

当Browser接收到 WebSocketServer 发送过来的数据时,就会触发 onmessage 消息,参数evt中包含server传输过来的数据;

1
2
websocket.onmessage = function(evt) {
};

当Browser接收到 WebSocketServer 端发送的关闭连接请求时,就会触发 onclose 消息。

1
2
websocket.onclose = function(evt) {
};

WebSocket报文

下面给出 WebSocket 通过 HTTP 握手建立连接时发送的 request 和接收的 repsonse报文。

注意:下面的请求报文与响应报文中的内容不是完整的报文,而是 WebSocket 基于 Http 请求(响应)报文添加的内容。

客户端向 WebSocket 服务器发送 HTTP 请求,在请求头中加入Upgrade请求头,要求把连接从 HTTP 升级到 WebSocket,示例请求报文:

1
2
3
4
5
6
7
8
GET ws://echo.websocket.org/?encoding=text HTTP/1.1
Origin: http://websocket.org
Cookie: __utma=99as
Connection: Upgrade
Host: echo.websocket.org
Sec-WebSocket-Key: uRovscZjNol/umbTt5uKmw==
Upgrade: websocket
Sec-WebSocket-Version: 13

服务器发现客户端的请求是 WebSocket 协议,通过在在响应报文中添加Upgrade协议将连接从 HTTP 升级为 WebSocket,示例响应报文:

1
2
3
4
5
6
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: fFBooB7FAkLlXgRSz0BT3v4hq5s=
Sec-WebSocket-Origin: null
Sec-WebSocket-Location: ws://example.com/

接下来,HTTP 连接断开连接,被底层同样依赖于 TCP/IPWebSocket连接所替换。对于WebSocket协议,默认情况下,同样是使用的 HTTP (80) 端口和 HTTPS (443) 端口。

WebSocket报文格式

WebSocket报文格式


欢迎关注技术公众号: 零壹技术栈

零壹技术栈

本帐号将持续分享后端技术干货,包括虚拟机基础,多线程编程,高性能框架,异步、缓存和消息中间件,分布式和微服务,架构学习和进阶等学习资料和文章。

文章作者: Chen Vainlgory
文章链接: https://geek.vainlgory.top/2017/02/23/WebSocket协议入门简介/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 零壹技術棧 | VainlgoryのBlog
微信打赏
支付宝打赏