websocket协议原理

        在WebSocket API尚未被众多浏览器实现和发布的时期,开发者在开发需要接收来自服务器的实时通知应用程序时,不得不使用ajax长轮询、long pull或者长连接的方式。

  • ajax轮询的原理非常简单,让浏览器隔个几秒就发送一次请求,询问服务器是否有新信息。长轮询主要是发出一个HTTP请求到服务器,然后保持连接打开以允许服务器在稍后的时间响应;
  • long poll 其实原理跟 ajax轮询 差不多,都是采用轮询的方式,不过采取的是阻塞模型,也就是说,客户端发起连接后,如果没消息,就一直不返回Response给客户端。直到有消息才返回,返回完之后,客户端再次建立连接,周而复始;
  • HTTP1.0需要在request中增加“Connection: keep-alive” header才能够支持长连接,而HTTP1.1默认支持。

        虽然这三种方式都可以解决这一问题,但它们会耗费更多的资源,如CPU、内存和带宽等,并且存在较长的延时问题,导致实时性和效率都比较差,要想很好的解决实时通信问题就需要设计和发布一种新的协议。

        WebSocket就是在这个背景下产生的,它是伴随HTML5发布的一种新协议,WebSocket通信协议于2011年被IETF定为标准RFC6455它实现了浏览器与服务器全双工通信(full-duplex)。全双工(Full Duplex)是通讯传输的一个术语。双方在通信时允许数据在两个方向上同时传输,它在能力上相当于两个单工通信方式的结合。全双工可以同时进行信号的双向传输。指A→B的同时B→A,就像是双向车道。WebSocket最初建立连接时需要借助于现有的HTTP协议,其他时候直接基于TCP完成通信。

        目前主流浏览器对WebSocket都支持的比较好,历史版本可能有问题。

WebSocket使用

        WebSocket使用起来比较简单。在客户端,通过new WebSocket实例化一个WebSocket对象,然后基于这个对象发送消息到服务器或接收服务器消息。WebSocket有两个统一资源标志符,四种事件和两个方法。

        我们先看一个示例:

var socket;
socket = new WebSocket("ws://test-live.meditrusthealth.com/live-wss?liveId=274");
socket.onmessage = function(event){
	var ta = document.getElementById('responseText');
	ta.value += event.data;
};
socket.onopen = function(event){
	var ta = document.getElementById('responseText');
	ta.value = "Netty-WebSocket服务器。。。。。。连接";
};
socket.onclose = function(event){
	var ta = document.getElementById('responseText');
	ta.value = "Netty-WebSocket服务器。。。。。。关闭";
};

function send(message){
	if(!window.WebSocket){return;}
	if(socket.readyState == WebSocket.OPEN){
		socket.send(message);
	}else{
		alert("WebSocket 连接没有建立成功!");
	}

}

WebSocket URIs
        WebSocket目前支持两种统一资源标志符ws和wss,类似于HTTP和HTTPS,如果是wss,那么在连接时必须进行一次TLS握手。URI端口号是非必填,ws默认80端口,wss默认443端口。

ws-URI = "ws:" "//" host [ ":" port ] path [ "?" query ]
wss-URI = "wss:" "//" host [ ":" port ] path [ "?" query ]

WebSocket事件

  • onopen当在客户端和服务器建立连接,就会从Web Socket实例触发open事件。它被称为客户端和服务器之间的初始握手;
  • onmessage通常在服务器发送一些数据时发生消息事件。服务器发送给客户端的消息可以包括纯文本消息,二进制数据或图像。无论何时发送数据,都会触发onmessage函数;
  • onclose关闭事件标志着服务器和客户端之间通信的结束。在onclose事件的帮助下,可以关闭连接。在onclose事件的帮助下标记通信结束后,服务器和客户端之间无法进一步传输消息。关闭事件也可能由于连接不良而发生;
  • onerror某些错误的错误标记,在通信期间发生。它是在onerror事件的帮助下标记的。在错误之后总是会终止连接。

WebSocket方法

  • send() WebSocket在客户端和服务器之间建立全双工双向连接后,就可以在连接打开时调用send()方法发送消息;
  • close() 可以关闭WebSocket连接或者终止连接尝试。如果连接已经关闭,该方法就什么都不做。

WebSocket和Http、TCP的关系

  • http、WebSocket为应用层协议,作用在于定义数据内容的格式;
  • TCP是传输层协议,作用在于定义传输方面的东西,譬如如何传输,三次握手的报文格式与方式等;
  • WebSocket 是独立的、创建在 TCP 上的协议。一个WebSocket连接是建立在TCP连接之上的连接,如果要建立WebSocket连接,必须先建立TCP连接。如果TCP连接失败,那么WebSocket连接失败。如果要关闭WebSocket链接,也是先关闭TCP链接,然后再关闭WebSocket链接。实际上,在WebSocket链接关闭后,还会再关闭一次TCP链接,在RFC6455中,称之为TCP的彻底关闭。
  • Websocket 通过HTTP/1.1 协议的101状态码进行握手。TCP三次握手建立后,才能发送http请求;而WebSocket之所以与http请求有一定交叉,是因为等TCP建立好后,需要用http请求一次服务器,告诉服务器这次请求是WebSocket协议,之后再是发送基于WebSocket协议定义的数据格式。

WebSocket协议特点

  • 建立在 TCP 协议之上,属于应用层协议;
  • 与 HTTP 协议有着良好的兼容性:
  • 数据格式比较轻量,性能开销小:
  • WebSocket数据帧格式相对简单,用于协议控制的数据包头部相对较小;
  • 数据实时性较高:
  • 由于WebSocket是全双工的,所以服务端可以随时主动给客户端下发数据,相对于HTTP请求需要待客户端发起请求服务端才能响应,延迟明显更少,用户体验更高。
  • 可以发送的载荷数据可以是文本数据,也可以是二进制数据;
  • WebSocket协议的数据载荷量相对较大时,还可以分片多帧进行数据发送与接收。

WebSocket Hand Shake解析
        为了实现WebSocket通信,首先需要客户端发起一次普通HTTP请求。其中HTTP头部字段Upgrade: websocket(区分大小写)和Connection: Upgrade很重要,告诉服务器通信协议将发生改变,需要将请求转为WebSocket协议。支持WebSocket的服务器端在确认以上请求后,返回状态码为101 Switching Protocols的响应。

  • 请求header
GET /chat HTTP/1.1
Host: example.com:8000
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13
  •  返回header
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=

握手示意图

GET /chat HTTP/1.1

        标准请求行,遵循标准格式,可以拆成三个部分:GET请求类型、chat协议、1.1版本的HTTP请求。WebSocket请求握手请求必须是GET,并且版本最低是HTTP1.1。

Connection

        必须,且值为Upgrade;

Upgrade

        必须,且值为websocket,区分大小写;

Host

        必须,如果不是默认端口号,需要在末尾加”:端口号”;

Origin

        可以防止使用脚本来跨域访问服务器

Sec-WebSocket-Key

        一个 Base64 encode 的值,这个是浏览器随机生成的;

Sec-WebSocket-Version

        在WebSocket起草阶段,浏览器厂商使用的协议不尽相同,支持的不太好,于是定义这个header来约定版本。目前已经默认使用13标准版本。WebSocket 版本注册表(下图);

HTTP/1.1 101 Switching Protocols

        返回状态行,遵循状态行标准格式。必须是101,其它code都表示连接没有成功。

Sec-WebSocket-Accept

        它是根据客户端请求中的Sec-WebSocket-Key来生成的,它表示服务器端愿意初始化和客户端的连接。具体而言是将客户端发送的Sec-WebSocket-Key 和 字符串GUID(258EAFA5-E914-47DA-95CA-C5AB0DC85B11)进行连接。然后使用SHA1算法求得其hash值。最后将hash值进行base64编码。只有当请求头参数Sec-WebSocket-Key字段的值经过固定算法加密后的数据和响应头里的Sec-WebSocket-Accept的值保持一致,该连接才会被认可建立;

Sec-WebSocket-Protocol

        它从客户端发送到服务器并返回从服务器到客户端,以确认联系这使脚本既可以选择子协议,也可以确保服务器同意为该子协议提供服务。目前13版本被官方定义为标准版本,13之前的都是草案或者保留版本。而各大浏览器最新的也都默认使用13版本。

WebSocket数据帧

        RFC5466对分帧的说明是这样的,为了避免服务器缓存超长度数据,所以将数据分帧。RFC 6455: The WebSocket Protocol

帧和消息的关系我们可以这样理解

帧:最小的通信单位,包含可变长度的帧首部和净荷部分,净荷可能包含完整或部分应用消息;

消息:一系列帧,与应用消息对等。

帧的结构图如下

FIN

        是否为最后一个分片(占1比特位)。如果是1,表示这是消息的最后一个分片(fragment);如果是0,表示不是是消息的最后一个分片fragment。有的时候第一个也可能是最后一个,具体看怎么拆分;

RSV1、2、3

        RSV实际是英文单词Re扩展字段(共占3比特位)。一般情况下全为0。当客户端、服务端协商采用WebSocket扩展时,这三个标志位可以非0,且值的含义由扩展进行定义。对于 Reserved 字段,这里不做详细说明,如有扩展需求,可自行查阅相关国际标准RFC6455

opcode

        操作码(占4比特位)。Opcode的值决定了应该如何解析后续的载荷数据payload data;opcode注册表可以参照RCF6455#section-11.8。比如说0x0表示正常帧数据,0x8表示关闭链接操作,0xA表示一个pong操作。这里使用了16进行,0xA实际等于十进制10。

MASK

        表示是否要对数据载荷进行掩码操作。从客户端向服务端发送数据时,需要对数据进行掩码操作;从服务端向客户端发送数据时,不需要对数据进行掩码操作。其实这是RFC6455的规定,主要是为网络安全考虑,防止出现“缓存污染攻击”,即防止HTTP代理服务器被攻击。假如给代理服务器发送一条伪造的破坏消息,这时候服务器正常不会接收,但是代理服务器会将消息暂时放到缓存中。在设置掩码的条件下,消息是乱七八糟的字符,HTTP代理服务器就不会缓存下来尝试解码转发,这样就防止了恶意攻击。我的理解是,掩码和分帧的初衷其实是一样的,分帧是为了避免缓存客户端大数据,遇到恶意的请求,顶多缓存一个分帧;而掩码对帧进行一个类似加密的操作,不让服务器识别,这样就连这一个分帧的数据都不会缓存。

Payload length

        主要表示数据载荷的长度,包括扩展数据和应用数据之和。扩展数据可能为0,这时候就表示整个应用数据的长度。在RCF6455#section-5.2是这样定义的(如图);官方文档里给了三种形式的定义,我是没看懂,知道的朋友可以在评论里留言,感谢。

Mask-key

        所有从客户端传送到服务端的数据帧,数据载荷都进行了掩码操作(Mask值为1),会携带4字节的Masking-key。对于 Masking-key 掩码算法,可查阅相关国际标准RFC6455

Payload Data

        任意的应用数据,在扩展数据之后(如果存在扩展数据),占据了数据帧剩余的位置。

WebSocket数据发送

  • 必须连接是OPEN状态;
  • 必须将数据压缩成帧的格式,一个或者多个;
  • opcode必须规范定义;
  • 最后一个帧的FIN必须为1;
  • 如果数据从客户端发往服务器,必须做掩码。

WebSocket数据接收

        客户端监听底层网络就可以接收数据;接收到数据之后,需要将这些数据按照帧的定义进行解析;如果是控制帧,比如说关闭、ping或者pong,还需要做相应的操作,控制帧定义RFC 6455: The WebSocket Protocol

WebSocket连接关闭

        WebSocket连接关闭有很多种原因,有正常,有非正常的。WebSocket断开时,会触发closeEvent,而这个对象在onClose()方法中可以直接得到;

  • CloseEvent.code: code是错误码,是整数类型,它在RCF6455#section-7.4.1中定义;
  • CloseEvent.reason: reason是断开原因,是字符串,是对上面code的描述;
  • CloseEvent.wasClean: wasClean表示是否正常断开,是布尔值。一般异常断开时,该值为false。

WebSocket认证

        WebSocket没有指定特别的身份认证机制。事实上服务器可以使用任何通用HTTP服务器的身份验证机制,例如cookie、HTTP身份验证或TLS身份验证。

WebSocket和WireShark

        如果需要抓包查看WebSocket帧,可以使用WireShark抓包WebSocket,示例如下

小结

        不难看出,WebSocket(又称“网套”)是一种全双工通信,它使得客户端和服务器之间的数据交换变得更加简单,且允许服务端主动向客户端推送数据。WebSocket显然解决了传统的“轮询”模式带来的缺点,因为HTTP请求每次都要携带完整的首部。建立WebSocket连接后交换数据,能够显著减少用于协议控制的数据包首部。在WebSocket中,浏览器和服务器只需要一次握手,就直接可以创建持久性的连接,并进行双向的数据传输。由于协议是全双工的,服务器可以随时主动给客户端发送数据,相比HTTP的请求-响应模式,这种方式的延迟明显更少,更适合用于对实时性要求高的应用场景。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

xinqing5130

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值