网络知识 娱乐 【Linux从青铜到王者】第二十四篇:Linux网络基础第四篇之WebSocket协议

【Linux从青铜到王者】第二十四篇:Linux网络基础第四篇之WebSocket协议

在这里插入图片描述

系列文章目录


文章目录

  • 系列文章目录
  • 前言
  • 一、WebSocket简介
  • 二、WebSocket产生背景
  • 三、WebSocket实现原理
  • 四、WebSocket协议举例
  • 五、WebSocket使用
      • 1.WebSocket 介绍
      • 2.WebSocket API
      • 3.WebSocket事件
        • 1.open
        • 2.Message
        • 3.Error
        • 4.Close
      • 4.WebSocket 方法
        • 1.send()
        • 2.close()
      • 5.WebSocket 属性
        • 1.readyState
        • 2.bufferedAmount
        • 3.protocol
  • 六、WebSocket语言支持
  • 七、WebSocket通信
      • 1.连接握手
        • 1.请求
        • 2.应答
      • 2.数据传输(双工)
      • 3.关闭请求
  • 八、WebSocket前世今生
      • 1.WebSocket前世
        • 1.Http keep-alive
        • 2.轮询(Polling)
        • 3.长轮询(long polling)
      • 2.WebSocket今生
        • 1.什么是Websocket?它的特点又有哪些?
        • 2.WebSocket的通讯流程和协议解析
      • 3.总结
  • 九、WebSocket协议进一步理解
      • 1.WebSocket是什么
      • 2.WebSocket出现之前的实时技术
      • 3.WebSocket应用场景
      • 4.WebSocket协议栈
      • 5.WebSocket与HTTP的区别
      • 6.WebSocket握手过程
      • 7.WebSocket帧格式
      • 8.WebSocket分片传输
      • 9.WebSocket相关扩展
  • 十、WebSocket 能解决什么问题
  • 十一、WebSocket工作原理
  • 十二、进一步解析什么是WebSocket协议(附代码)
      • 1.websocket背景
      • 2.websocket概念
      • 3.websocket特点
        • 1.websocket优点
        • 2.websocket缺点
      • 4.websocket协议通信过程
        • 1.handshake
          • 1.客户端
          • 2.服务端
          • 3.data transfer
      • 5.epoll反应堆模型下实现http协议
        • 1.客户端结构体
        • 2.int http_response(struct qsevent *ev)
        • 3.int http_response(struct qsevent *ev)
        • 4.总代码
      • 6.epoll反应堆模型下实现websocket协议
      • 7.C1000K reactor模型,epoll实现,连接并回发一段数据,测试正常
  • 总结


前言

在这里插入图片描述

WebSocket协议是基于TCP的一种新的网络协议。它实现了浏览器与服务器全双工(full-duplex)通信——允许服务器主动发送信息给客户端。

WebSocket通信协议于2011年被IETF定为标准RFC 6455,并被RFC7936所补充规范。


在这里插入图片描述

一、WebSocket简介

  • webSocket是什么:
  • 1、WebSocket是一种在单个TCP连接上进行全双工通信的协议
  • 2、WebSocket使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据
  • 3、在WebSocket API中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输
  • 4、需要安装第三方包:cmd中:go get -u -v github.com/gorilla/websocket

WebSocket 是一种标准协议,用于在客户端和服务端之间进行双向数据传输。但它跟 HTTP 没什么关系,它是一种基于 TCP 的一种独立实现。

以前客户端想知道服务端的处理进度,要不停地使用 Ajax 进行轮询,让浏览器隔个几秒就向服务器发一次请求,这对服务器压力较高。另外一种轮询就是采用 long poll 的方式,这就跟打电话差不多,没收到消息就一直不挂电话,也就是说,客户端发起连接后,如果没消息,就一直不返回 Response 给客户端,连接阶段一直是阻塞的。

而 WebSocket 解决了 HTTP 的这几个难题。首先,当服务器完成协议升级后( HTTP -> WebSocket ),服务端可以主动推送信息给客户端,解决了轮询造成的同步延迟问题。由于 WebSocket 只需要一次 HTTP 握手,服务端就能一直与客户端保持通讯,直到关闭连接,这样就解决了服务器需要反复解析 HTTP 协议,减少了资源的开销。

WebSocket协议支持(在受控环境中运行不受信任的代码的)客户端与(选择加入该代码的通信的)远程主机之间进行全双工通信。用于此的安全模型是Web浏览器常用的基于原始的安全模式。 协议包括一个开放的握手以及随后的TCP层上的消息帧。 该技术的目标是为基于浏览器的、需要和服务器进行双向通信的(服务器不能依赖于打开多个HTTP连接(例如,使用XMLHttpRequest或和长轮询))应用程序提供一种通信机制。
在这里插入图片描述
websocket 是一个基于应用层的网络协议,建立在tcp 协议之上,和 http 协议可以说是兄弟的关系,但是这个兄弟有点依赖 http ,为什么这么说呢?我们都知道 HTTP 实现了三次握手来建立通信连接,实际上 websocket 的创始人很聪明,他不想重复的去造轮子,反正我兄弟已经实现了握手了,我干嘛还要重写一套呢?先让它去冲锋陷阵呢,我坐收渔翁之利不是更香 吗,所以一般来说,我们会先用 HTTP 先进行三次握手,再向服务器请求升级为websocket 协议,这就好比说,嘿兄弟你先去给我排个队占个坑位建个小房子,到时候我在把这房子改造成摩天大楼。而且一般来说 80 和 443 端口一般 web 服务端都会外放出去,这样可以有效的避免防火墙的限制。当然,你创建的 websocket 服务端进程的端口也需要外放出去。

很多人会想问,web开发 使用 HTTP 协议不是已经差不多够用了吗?为什么还要我再多学一种呢?这不是搞事情嘛,仔细想想,一门新技术的产生必然有原因的,如果没有需求,我们干嘛那么蛋疼去写那么多东西,就是因为 HTTP 这个协议有些业务需求支持太过于鸡肋了,从 HTTP 0.9 到现在的 HTTP3.0 ,HTTP协议可以说说是在普通的web开发领域已经是十分完善且高效的了,说这个协议养活了全球半数的公司也不为过吧,像 2.0 服务器推送技术,3.0 采用了 UDP 而放弃了原来的 TCP ,这些改动都是为了进一步提升协议的性能,然而大家现在还是基本使用的 HTTP 1.1 这个最为经典的协议, 也是让开发者挺尴尬的。

绝大多数的web开发都是应用层开发者,大多数都是基于已有的应用层去开发应用,可以说我们最熟悉、日常打交道最多的就是应用层协议了,底下 TCP/IP 协议我们基本很少会去处理,当然大厂可能就不一样了,自己弄一套协议也是正常的,这大概也是程序员和码农的区别吧,搬砖还是创新,差别还是很大的。网络这种分层协议的好处我在之前的文章也说过了,这种隔离性很方便就可以让我们基于原来的基础去拓展,具有较好的兼容性。

总的来说,它就是一种依赖HTTP协议的,支持全双工通信的一种应用层网络协议。

二、WebSocket产生背景

简单的说,WebSocket协议之前,双工通信是通过多个http链接来实现,这导致了效率低下。WebSocket解决了这个问题。下面是标准RFC6455中的产生背景概述。

长久以来, 创建实现客户端和用户端之间双工通讯的web app都会造成HTTP轮询的滥用: 客户端向主机不断发送不同的HTTP呼叫来进行询问。

  • 这会导致一系列的问题:

  • 1.服务器被迫为每个客户端使用许多不同的底层TCP连接:一个用于向客户端发送信息,其它用于接收每个传入消息。

  • 2.有些协议有很高的开销,每一个客户端和服务器之间都有HTTP头。

  • 3.客户端脚本被迫维护从传出连接到传入连接的映射来追踪回复。

一个更简单的解决方案是使用单个TCP连接双向通信。 这就是WebSocket协议所提供的功能。 结合WebSocket API ,WebSocket协议提供了一个用来替代HTTP轮询实现网页到远程主机的双向通信的方法。

WebSocket协议被设计来取代用HTTP作为传输层的双向通讯技术,这些技术只能牺牲效率和可依赖性其中一方来提高另一方,因为HTTP最初的目的不是为了双向通讯。

三、WebSocket实现原理

在这里插入图片描述
在实现websocket连线过程中,需要通过浏览器发出websocket连线请求,然后服务器发出回应,这个过程通常称为“握手” 。在 WebSocket API,浏览器和服务器只需要做一个握手的动作,然后,浏览器和服务器之间就形成了一条快速通道。两者之间就直接可以数据互相传送。在此WebSocket 协议中,为我们实现即时服务带来了两大好处:

  • 1. Header:互相沟通的Header是很小的-大概只有 2 Bytes。
  • 2. Server Push:服务器的推送,服务器不再被动的接收到浏览器的请求之后才返回数据,而是在有新数据时就主动推送给浏览器。

四、WebSocket协议举例

在这里插入图片描述

  • 浏览器请求:
  • GET /webfin/websocket/ HTTP/1.1。
  • Host: localhost。
  • Upgrade: websocket。
  • Connection: Upgrade。
  • Sec-WebSocket-Key: xqBt3ImNzJbYqRINxEFlkg==。
  • Origin: http://服务器地址。
  • Sec-WebSocket-Version: 13。
  • 服务器回应:
  • HTTP/1.1 101 Switching Protocols。
  • Upgrade: websocket。
  • Connection: Upgrade。
  • Sec-WebSocket-Accept: K7DJLdLooIwIG/MOpvWFB3y3FE8=。
  • WebSocket借用http请求进行握手,相比正常的http请求,多了一些内容。其中:
  • Upgrade: websocket。
  • Connection: Upgrade。
    表示希望将http协议升级到Websocket协议。Sec-WebSocket-Key是浏览器随机生成的base64 encode的值,用来询问服务器是否是支持WebSocket。
  • 服务器返回:
  • Upgrade: websocket。
  • Connection: Upgrade。
    告诉浏览器即将升级的是Websocket协议

Sec-WebSocket-Accept是将请求包“Sec-WebSocket-Key”的值,与”258EAFA5-E914-47DA-95CA-C5AB0DC85B11″这个字符串进行拼接,然后对拼接后的字符串进行sha-1运算,再进行base64编码得到的。用来说明自己是WebSocket助理服务器。

Sec-WebSocket-Version是WebSocket协议版本号。RFC6455要求使用的版本是13,之前草案的版本均应当被弃用。

五、WebSocket使用

1.WebSocket 介绍

WebSocket 发起单个请求,服务端不需要等待客服端,客户端在任何时候也能发消息到服务端,减少了轮询时候的延迟.经历一次连接后,服务器能给客户端发多次。下图是轮询与WebSocket的区别。
在这里插入图片描述
基于http的实时消息是相当的复杂,在无状态的请求中维持回话的状态增加了复杂度,跨域也很麻烦,使用ajax处理请求有序请求需要考虑更多。通过ajax进行交流也不简单。每一个延伸http功能的目的不是增加他的复杂度。websocket 可以大大简化实时通信应用中的链接。

Websocket是一种底层网络协议,可以让你在这个基础上建立别的标准协议。比如在WebSocket的客户端的基础上使用XMPP登录不同的聊天服务器,因为所有的XMPP服务理解相同的标准协议。WebSocket是web应用的一种创新。

为了与其他平台竞争,WebSocket是H5应用提供的一部分先进功能。每个操作系统都需要网络功能,能够让应用使用Sockets与别的主机进行通信,是每个大平台的核心功能。在很多方面,让Web应用表现的像操作系统平台是html5的趋势。像socket这样底层的网络协议APIs不会符合原始的安全模型,也不会有web api那样的设计风格。WebSocket给H5应用提供TCP的方式不会消弱网络安全且有现代的Api。

WebSocket是Html5平台的一个重要组件也是开发者强有力的工具。简单的说,你需要WebSocket创建世界级的web应用。它弥补了http不适合实时通信的重大缺陷。异步、双向通信模式,通过传输层协议使WebSocket具有普遍灵活性。想象一下你能用WebSocket创建正真实实时应用的所有方式。比如聊天、协作文档编辑、大规模多人在线游戏(MMO),股票交易应用等等。

WebSocket是一个协议,但也有一个WebSocket API,这让你的应用去控制WebSocket的协议去响应被服务端触发的事件。API是W3C开发,协议是IETE制定。现代浏览器支持WebSocket API,这包括使用全双工和双向链接的方法和特性。让你执行像打开关闭链接、发送接收消息、监听服务端事件等必要操作。

2.WebSocket API

WebSocket API其实就是一个使用WebSocket协议的接口,通过它来建立全双工通道来收发消息,简单易学,要连接远程服务器,只需要创建一个WebSocket对象实体,并传入一个服务端的URL。在客户端和服务端一开始握手的期间,http协议升级到WebSocket协议就建立了连接,底层都是TCP协议。一旦建立连接,通过WebSocket接口可以反复的发送消息。在你的代码里面,你可以使用异步事件监听连接生命周期的每个阶段。

WebSocket API是纯事件驱动,一旦建立全双工连接,当服务端给客户端发送数据或者资源,它能自动发送状态改变的数据和通知。所以你不需要为了状态的更新而去轮训Server,在客户端监听即可。

首先,我们需要通过调用WebSocket构造函数来创建一个WebSocket连接,构造函数会返回一个WebSocket实例,可以用来监听事件。这些事件会告诉你什么时候连接建立,什么时候消息到达,什么时候连接关闭了,以及什么时候发生了错误。WebSocket协议定义了两种URL方案,WS和WSS分别代表了客户端和服务端之间未加密和加密的通信。WS(WebSocket)类似于Http URL,而WSS(WebSocket Security)URL 表示连接是基于安全传输层(TLS/SSL)和https的连接是同样的安全机制。

WebSocket的构造函数需要一个URL参数和一个可选的协议参数(一个或者多个协议的名字),协议的参数例如XMPP(Extensible Messaging and Presence Protocol)、SOAP(Simple Object Access Protocol)或者自定义协议。而URL参数需要以WS://或者WSS://开头,例如:ws://www.websocket.org,如果URL有语法错误,构造函数会抛出异常。

// Create new WebSocket connection
var ws = new WebSocket("ws://www.websocket.org");
//测试了下链接不上。

在这里插入图片描述
第二个参数是协议名称,是可选的,服务端和客服端使用的协议必须一致,这样收发消息彼此才能理解,你可以定义一个或多个客户端使用的协议,服务端会选择一个来使用,一个客服端和一个服务端之间只能有一个协议。当然都得基于WebSocket,WebSocket的重大好处之一就是基于WebSocket协议的广泛使用,让你的Web能够拥有传统桌面程序那样的能力。

言归正传,我们回到构造函数,在第一次握手之后,和协议的名称一起,客户端会发送一个Sec-WebSocket-Protocol 头,服务端会选择0个或一个协议,响应会带上同样的Sec-WebSocket-Protocol 头,否则会关闭连接。通过协议协商(Protocol negotiation ),我们可以知道给定的WebSocket服务器所支持的协议和版本,然后应用选择协议使用。

// Connecting to the server with one protocol called myProtocol
var ws = new WebSocket("ws://echo.websocket.org", "myProtocol");
//myProtocol 是假设的一个定义好的且符合标准的协议。

你可以传递一个协议的数组。

var echoSocket = new WebSocket("ws://echo.websocket.org", ["com.kaazing.echo","example.imaginary.protocol"])
//服务端会选择其中一个使用
echoSocket.onopen = function(e) {
// Check the protocol chosen by the server
console.log(echoSocket.protocol);
}

输出:com.kaazing.echo

协议这个参数有三种。

  • 1.注册协议:根据RFC6455(WebSocket 协议)和IANA被官方注册的标准协议。例如 微软的SOAP。
    详情可以参考:http://www.iana.org/assignments/websocket/websocket.xml
    在这里插入图片描述
    看到两个华为的:
  • 2.开放协议:被广泛使用的标注协议,例如XMPP和STOMP。但没有被正式注册。
  • 3.自定义协议:自己编写和使用的WebSocket的协议。
    协议会再后续章节给出详细介绍,下面先看事件、对象和方法以及实例。

3.WebSocket事件

WebSocket API是纯事件驱动,通过监听事件可以处理到来的数据和改变的链接状态。客户端不需要为了更新数据而轮训服务器。服务端发送数据后,消息和事件会异步到达。WebSocket编程遵循一个异步编程模型,只需要对WebSocket对象增加回调函数就可以监听事件。你也可以使用addEventListener()方法来监听。而一个WebSocket对象分四类不同事件。

1.open

一旦服务端响应WebSocket连接请求,就会触发open事件。响应的回调函数称为onopen。

// Event handler for the WebSocket connection opening
ws.onopen = function(e) {
console.log("Connection open...");
};

open事件触发的时候,意味着协议握手结束,WebSocket已经准备好收发数据。如果你的应用收到open事件,就可以确定服务端已经处理了建立连接的请求,且同意和你的应用通信。

2.Message

当消息被接受会触发消息事件,响应的回调函数叫做onmessage。如下:

// 接受文本消息的事件处理实例:
ws.onmessage = function(e) {
if(typeof e.data === "string"){
console.log("String message received", e, e.data);
} else {
console.log("Other message received", e, e.data);
}
};

除了文本消息,WebSocket消息机制还能处理二进制数据,有Blob和ArrayBuffer两种类型,在读取到数据之前需要决定好数据的类型。

// 设置二进制数据类型为blob(默认类型)
ws.binaryType = "blob";
// Event handler for receiving Blob messages
ws.onmessage = function(e) {
if(e.data instanceof Blob){
console.log("Blob message received", e.data);
var blob = new Blob(e.data);
}
};

//ArrayBuffer
ws.binaryType = "arraybuffer";
ws.onmessage = function(e) {
if(e.data instanceof ArrayBuffer){
console.log("ArrayBuffer Message Received", + e.data);
// e.data即ArrayBuffer类型
var a = new Uint8Array(e.data);
}
};

3.Error

如果发生意外的失败会触发error事件,相应的函数称为onerror,错误会导致连接关闭。如果你收到一个错误事件,那么你很快会收到一个关闭事件,在关闭事件中也许会告诉你错误的原因。而对错误事件的处理比较适合做重连的逻辑。

//异常处理
ws.onerror = function(e) {
console.log("WebSocket Error: " , e);
//Custom function for handling errors
handleErrors(e);
};

4.Close

不言而喻,当连接关闭的时候回触发这个事件,对应onclose方法,连接关闭之后,服务端和客户端就不能再收发消息。

WebSocket的规范其实还定义了ping和pong 架构(frames),可以用来做keep-alive,心跳,网络状态查询,latency instrumentation(延迟仪表?),但是目前 WebSocket API还没有公布这些特性,尽管浏览器支持了ping,但不会触发ping事件,相反,浏览器会自动响应pong,第八章会将更多关于ping和pong的细节。

当然你可以调用close方法断开与服务端的链接来触发onclose事件:

ws.onclose = function(e) {
console.log("Connection closed", e);
};

连接失败和成功的关闭握手都会触发关闭事件,WebSocket的对象的readyState属性就代表连接的状态(2代表正在关闭,3代表已经关闭)。关闭事件有三个属性可以用来做异常处理和重获: wasClean,code和reason。wasClean是一个bool值,代表连接是否干净的关闭。 如果是响应服务端的close事件,这个值为true,如果是别的原因,比如因为是底层TCP连接关闭,wasClean为false。code和reason代表关闭连接时服务端发送的状态,这两个属性和给入close方法的code和reason参数是对应的,稍后会描述细节。

4.WebSocket 方法

WebSocket 对象有两个方法:send()和close()。

1.send()

一旦在服务端和客户端建立了全双工的双向连接,可以使用send方法去发送消息。

//发送一个文本消息
ws.send("Hello WebSocket!");

当连接是open的时候send()方法传送数据,当连接关闭或获取不到的时候回抛出异常。一个通常的错误是人们喜欢在连接open之前发送消息。如下所示:

// 这将不会工作
var ws = new WebSocket("ws://echo.websocket.org")
ws.send("Initial data");

正确的姿势如下,应该等待open事件触发后再发送消息。

var ws = new WebSocket("ws://echo.websocket.org")
ws.onopen = function(e) {
ws.send("Initial data");
}

如果想通过响应别的事件去发送消息,可以检查readyState属性的值为open的时候来实现。

function myEventHandler(data) {
if (ws.readyState === WebSocket.OPEN) {
//open的时候即可发送
ws.send(data);
} else {
// Do something else in this case.
//Possibly ignore the data or enqueue it.
}
}

发送二进制数据:

// Send a Blob
var blob = new Blob("blob contents");
ws.send(blob);
// Send an ArrayBuffer
var a = new Uint8Array([8,6,7,5,3,0,9]);
ws.send(a.buffer);

Blob对象和JavaScript File API一起使用的时候相当有用,可以发送或接受文件,大部分的多媒体文件,图像,视频和音频文件。这一章末尾会结合File API提供读取文件内容来发送WebSocket消息的实例代码。

2.close()

使用close方法来关闭连接,如果连接以及关闭,这方法将什么也不做。调用close方法只后,将不能发送数据。

ws.close();

close方法可以传入两个可选的参数,code(numerical)和reason(string),以告诉服务端为什么终止连接。第三章讲到关闭握手的时候再详细讨论这两个参数。

// 成功结束会话
ws.close(1000, "Closing normally");
//1000是状态码,代表正常结束。

5.WebSocket 属性

WebSocket对象有三个属性,readyState,bufferedAmount和Protocol。

1.readyState

WebSocket对象通过只读属性readyState来传达连接状态,它会更加连接状态自动改变。下表展示了readyState属性的四个不同的值。
在这里插入图片描述
了解当前连接的状态有助于我们调试。

2.bufferedAmount

有时候需要检查传输数据的大小,尤其是客户端传输大量数据的时候。虽然send()方法会马上执行,但数据并不是马上传输。浏览器会缓存应用流出的数据,你可以使用bufferedAmount属性检查已经进入队列但还未被传输的数据大小。这个值不包含协议框架、操作系统缓存和网络软件的开销。

下面这个例子展示了如何使用bufferedAmount属性每秒更新发送。如果网络不能处理这个频率,它会自适应。

// 10k
var THRESHOLD = 10240;
//建立连接
var ws = new WebSocket("ws://echo.websocket.org");
// Listen for the opening event
ws.onopen = function () {
setInterval( function() {
//缓存未满的时候发送
if (ws.bufferedAmount < THRESHOLD) {
ws.send(getApplicationState());
}
}, 1000);
};
//使用bufferedAmount属性发送数据可以避免网络饱和。

3.protocol

在构造函数中,protocol参数让服务端知道客户端使用的WebSocket协议。而WebSocket对象的这个属性就是指的最终服务端确定下来的协议名称,当服务端没有选择客户端提供的协议或者在连接握手结束之前,这个属性都是空的。

完整实例:

现在我们已经过了一遍WebSocket的构造函数、事件、属性和方法,接下来通过一个完整的实例来学习WebSocket API。实例使用“Echo”服务器:ws://echo.websocket.org,它能够接受和返回发过去的数据。这样有助于理解WebSocket API是如何和服务器交互的。

首先,我们先建立连接,让页面展示客户端连接服务端的信息,然后发送、接受消息,最后关闭连接。

<h2>Websocket Echo Client</h2>
<div id="output"></div>

// 初始化连接和事件
        function setup() {
            output = document.getElementById("output");
            ws = new WebSocket("ws://echo.websocket.org/echo");
            // 监听open
            ws.onopen = function (e) {
                log("Connected");
                sendMessage("Hello WebSocket!");
            }
            // 监听close
            ws.onclose = function (e) {
                log("Disconnected: " + e.reason);
            }
            //监听errors
            ws.onerror = function (e) {
                log("Error ");
            }
            // 监听 messages 
            ws.onmessage = function (e) {
                log("Message received: " + e.data);
                //收到消息后关闭
                ws.close();
            }
        }
        // 发送消息
        function sendMessage(msg) {
            ws.send(msg);
            log("Message sent");
        }
        // logging
        function log(s) {
            var p = document.createElement("p");
            p.style.wordWrap = "break-word";
            p.textContent = s;
            output.appendChild(p);
            // Also log information on the javascript console
            console.log(s);
        }
        // Start 
        setup();

在这里插入图片描述
判断浏览器是否支持:

if (window.WebSocket){
console.log("This browser supports WebSocket!");
} else {
console.log("This browser does not support WebSocket.");
}

在这里插入图片描述

六、WebSocket语言支持

在这里插入图片描述

  • 所有主流浏览器都支持RFC6455。但是具体的WebSocket版本有区别。
  • php jetty netty ruby Kaazing nginx python Tomcat Django erlang
  • WebSocket浏览器支持
  • WebSocket浏览器支持
  • netty .net等语言均可以用来实现支持WebSocket的服务器。
  • websocket api在浏览器端的广泛实现似乎只是一个时间问题了, 值得注意的是服务器端没有标准的api, 各个实现都有自己的一套api, 并且tcp也没有类似的提案, 所以使用websocket开发服务器端有一定的风险.可能会被锁定在某个平台上或者将来被迫升级。

WebSocket是HTML5出的东西(协议),也就是说HTTP协议没有变化,或者说没关系,但HTTP是不支持持久连接的(长连接,循环连接的不算)。

  • 首先HTTP有1.1和1.0之说,也就是所谓的keep-alive,把多个HTTP请求合并为一个,但是Websocket其实是一个新协议,跟HTTP协议基本没有关系,只是为了兼容现有浏览器的握手规范而已,也就是说它是HTTP协议上的一种补充可以通过这样一张图理解:
    在这里插入图片描述
  • 有交集,但是并不是全部。另外Html5是指的一系列新的API,或- 者说新规范,新技术。Http协议本身只有1.0和1.1,而且跟Html本身没有直接关系。
  • 通俗来说,你可以用HTTP 协议 传输非Html 数据 ,就是这样=。=
  • 再简单来说, 层级不一样 。

七、WebSocket通信

1.连接握手

连接握手分为两个步骤:请求和应答。WebSocket利用了HTTP协议来建立连接,使用的是HTTP的协议升级机制。

1.请求

一个标准的HTTP请求,格式如下:
在这里插入图片描述
请求头的具体格式定义参见Request-Line格式

请求header中的字段解析:
在这里插入图片描述

  • 协议升级机制

  • Origin
    所有浏览器将会发送一个 Origin请求头。 你可以将这个请求头用于安全方面(检查是否是同一个域,白名单/ 黑名单等),如果你不喜欢这个请求发起源,你可以发送一个403 Forbidden。需要注意的是非浏览器只能发送一个模拟的 Origin。大多数应用会拒绝不含这个请求头的请求。

  • Sec-WebSocket-Key
    由客户端随机生成的,提供基本的防护,防止恶意或者无意的连接。

2.应答

返回字段解析:
在这里插入图片描述

  • Connection
    可以参见 rfc7230 6.1rfc7230 6.7

  • Sec-WebSocket-Accept
    它通过客户端发送的Sec-WebSocket-Key 计算出来。计算方法:

  1. 将 Sec-WebSocket-Key 跟 258EAFA5-E914-47DA-95CA-C5AB0DC85B11 拼接;
  2. 通过 SHA1 计算出摘要,并转成 base64 字符串。
    Sec-WebSocket-Key / Sec-WebSocket-Accept 的主要作用还是为了避免一些网络通信过程中,一些非期待的数据包,”乱入“进来,导致一些错误的响应,并不能用于实现登录认证和数据安全,这些功能还需要应用层自己实现。

2.数据传输(双工)

此段落包含的内容,来自于此链接

WebSocket 以 frame 为单位传输数据, frame 是客户端和服务端数据传输的最小单元。当一条消息过长时, 通信方可以将该消息拆分成多个 frame 发送, 接收方收到以后重新拼接、解码从而还原出完整的消息, 在 WebSocket 中, frame 有多种类型, frame 的类型由 frame 头部的 Opcode 字段指示, WebSocket frame 的结构如下所示:
在这里插入图片描述

  • 该结构的字段语义如下:
  • FIN, 长度为 1 比特, 该标志位用于指示当前的 frame 是消息的最后一个分段, 因为 WebSocket 支持将长消息切分为若干个 frame 发送, 切分以后, 除了最后一个 frame, 前面的 frame 的 FIN 字段都为 0, 最后一个 frame 的 FIN 字段为 1, 当然, 若消息没有分段, 那么一个 frame 便包含了完成的消息, 此时其 FIN 字段值为 1。
  • RSV 1 ~ 3, 这三个字段为保留字段, 只有在 WebSocket 扩展时用, 若不启用扩展, 则该三个字段应置为 1, 若接收方收到 RSV 1 ~ 3 不全为 0 的 frame, 并且双方没有协商使用 WebSocket 协议扩展, 则接收方应立即终止 WebSocket 连接。
  • Opcode, 长度为 4 比特, 该字段将指示 frame 的类型, RFC 6455 定义的 Opcode 共有如下几种:
  1. 0x0, 代表当前是一个 continuation frame,既被切分的长消息的每个分片frame
  2. 0x1, 代表当前是一个 text frame
  3. 0x2, 代表当前是一个 binary frame
  4. 0x3 ~ 7, 目前保留, 以后将用作更多的非控制类 frame
  5. 0x8, 代表当前是一个 connection close, 用于关闭 WebSocket 连接
  6. 0x9, 代表当前是一个 ping frame
  7. 0xA, 代表当前是一个 pong frame
  8. 0xB ~ F, 目前保留, 以后将用作更多的控制类 frame
  • Mask, 长度为 1 比特, 该字段是一个标志位, 用于指示 frame 的数据 (Payload) 是否使用掩码掩盖, RFC 6455 规定当且仅当由客户端向服务端发送的 frame, 需要使用掩码覆盖, 掩码覆盖主要为了解决代理缓存污染攻击 (更多细节见 RFC 6455 Section 10.3)。
  • Payload Len, 以字节为单位指示 frame Payload 的长度, 该字段的长度可变, 可能为 7 比特, 也可能为 7 + 16 比特, 也可能为 7 + 64 比特. 具体来说, 当 Payload 的实际长度在 [0, 125] 时, 则 Payload Len 字段的长度为 7 比特, 它的值直接代表了 Payload 的实际长度; 当 Payload 的实际长度为 126 时, 则 Payload Len 后跟随的 16 位将被解释为 16-bit 的无符号整数, 该整数的值指示 Payload 的实际长度; 当 Payload 的实际长度为 127 时, 其后的 64 比特将被解释为 64-bit 的无符号整数, 该整数的值指示 Payload 的实际长度。
  • Masking-key, 该字段为可选字段, 当 Mask 标志位为 1 时, 代表这是一个掩码覆盖的 frame, 此时 Masking-key 字段存在, 其长度为 32 位, RFC 6455 规定所有由客户端发往服务端的 frame 都必须使用掩码覆盖, 即对于所有由客户端发往服务端的 frame, 该字段都必须存在, 该字段的值是由客户端使用熵值足够大的随机数发生器生成, 关于掩码覆盖, 将下面讨论, 若 Mask 标识位 0, 则 frame 中将设置该字段 (注意是不设置该字段, 而不仅仅是不给该字段赋值)。
  • Payload, 该字段的长度是任意的, 该字段即为 frame 的数据部分, 若通信双方协商使用了 WebSocket 扩展, 则该扩展数据 (Extension data) 也将存放在此处, 扩展数据 + 应用数据, 它们的长度和便为 Payload Len 字段指示的值。
    以下是一个客户端和服务端相互传递文本消息的示例(内容来此于此链接)。
    在这里插入图片描述

其中模拟了长消息被切分为多个帧(continuation frame)的例子。

3.关闭请求

关闭相对简单,由客户端或服务端发送关闭帧,即可完成关闭。
在这里插入图片描述

八、WebSocket前世今生

1.WebSocket前世

  • Websocket出现之前,浏览器是如何和服务端保持”长连接“的?
  • 在协议层引入了Http keep-alive,重用已有的TCP连接,从而避免重复建立TCP连接造成的性能损失。
  • 在使用方式,引入短轮询和长轮询方式,模拟”长连接“。

1.Http keep-alive

在Http 1.0协议中,引入了keep-alive 请求头参数(http 1.1后,此参数默认开启),可以通过这个参数控制在一次tcp 连接中可以持续发送多份数据而不会断开连接,这样就能复用tcp 连接,减少tcp连接建立的次数。

注:TCP也一个keepAlive的功能,这个后续如果用的到会再详细解读。

这篇文章里详细的描述了Http keep-alive和TCP keepAlive,以及客户端和服务端具体实现方式,可以作为参考
在这里插入图片描述

2.轮询(Polling)

一种定期轮询的方式,是从服务器获取新信息的最简单方法。也就是说,定期向服务器请求:“你有什么信息要告诉我吗?”例如,每10秒一次。 作为响应,服务器首先通知自己客户端在线,然后发送它在那一刻收到的消息包。 和长轮询相比,服务端不需要做特殊的处理(例如:等待一段时