整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

HTML5(十二)-一文读懂 WebSocket 原

HTML5(十二)-一文读懂 WebSocket 原理

、WebSocket 由来

WebSocket 是一个持久化的协议,通过第一次 HTTP Request 建立连接之后,再把通信协议升级成 websocket,保持连接状态,后续的数据交换不需要再重复请求。websocket 可以看成一种类似 TCP/IP 的 socke t技术,在 web 应用中实现、并获得同 TCP/IP 通信一样的双向通信功能,因此客户端既和服务器可以发送消息也可以接收消息,同时还支持多路复用的功能,由于它借用了 HTTP 协议的一些概念,所以被称为 WebSocket。

webSocket API定义了web应用和服务器进行通信的公共接口,具体的构造函数创建对象、对象的属性、方法、事件及它的意义,在上一篇《HTML5(十一)——WebSocket 基础教程》文章中已详细介绍。

二、WebSocket 通信过程

WebSocket 协议可分为两部分:握手阶段和数据通信阶段。

WebSocket 为应用层协议,定义在 TCP/IP 协议栈之上,连接服务器的 url 是以 ws 或 wss 开头的。ws 开头的默认TCP端口为80,wss 开头的默认端口为443。

ws(websocket)是不安全的,容易被窃听,只要别人知道你的ip和端口号,任何人都可以去连接通讯。

wss(web socket secure)是websocket的加密版本。

2.1、建立连接

客户端去与服务器建立 TCP 连接,客户端生成 websocket 对象,然后使用 API 建立连接,代码如下:

let ws=new WebSocket('ws://localhost:8888')
ws.onopen=function(){
 console.log("连接")
}

2.2、握手阶段

客户端与服务器建立连接之后,客户端发送握手请求,随后服务器发送握手响应即完成握手阶段。

客户端握手请求如下:

'GET / HTTP/1.1',
 'Host: localhost:8888',
 'Connection: Upgrade',
 'Pragma: no-cache',
 'Cache-Control: no-cache',
 'Upgrade: websocket',
 'Origin: file://',
 'Sec-WebSocket-Version: 13',
 'Accept-Encoding: gzip, deflate, br',
 'Accept-Language: zh-CN,zh;q=0.9',
 'Sec-WebSocket-Key: In1aAp/ya9Lkv+tsUtXLXQ==',
 'Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits',

服务器握手响应如下:

Status Code: 101 Switching Protocols
Connection: Upgrade
sec-websocket-Accept: HBMDBbZMiS59r3aAITpGtJ64Mfc=Upgrade: websocket

2.3、数据通讯

WebSocket 握手连接成功之后。可以使用 send 进行发送数据,onmessage 接收数据,如下发送“你好”:

let ws=new WebSocket('ws://localhost:8888')
ws.onopen=function(){
 console.log("连接成功")
 ws.send("你好")
}
ws.onmessage=function(res){
 console.log('接收到的消息',res)
}

服务器打印接收到的数据,如:<Buffer 81 86 af 87 53 b4 4b 3a f3 51 0a 3a>。

websocket 在发送数据时,被组织为一串数据帧,然后进行发送。传送的帧包含两部分:数据帧和控制帧。数据帧可以携带文本数据或者二进制数据,控制帧包含关闭帧和 Ping/Pong 帧。


  • FIN :1bit ,表示是消息的最后一帧,如果消息只有一帧那么第一帧也就是最后一帧。
  • RSV1,RSV2,RSV3:每个1bit,必须是0,除非扩展定义为非零。如果接受到的是非零值但是扩展没有定义,则需要关闭连接。
  • Opcode:4bit,解释Payload数据,规定有以下不同的状态,如果是未知的,接收方必须马上关闭连接。状态如下:0x0(附加数据帧) 0x1(文本数据帧) 0x2(二进制数据帧) 0x3-7(保留为之后非控制帧使用) 0xB-F(保留为后面的控制帧使用) 0x8(关闭连接帧) 0x9(ping) 0xA(pong)
  • Mask:1bit,掩码,定义payload数据是否进行了掩码处理,如果是1表示进行了掩码处理。Masking-key域的数据即是掩码密钥,用于解码PayloadData。客户端发出的数据帧需要进行掩码处理,所以此位是1。
  • Payload length:7位,7 + 16位,7+64位,payload数据的长度,如果是0-125,就是真实的payload长度,如果是126,那么接着后面的2个字节对应的16位无符号整数就是payload数据长度;如果是127,那么接着后面的8个字节对应的64位无符号整数就是payload数据的长度。
  • Masking-key:0到4字节,如果MASK位设为1则有4个字节的掩码解密密钥,否则就没有。
  • Payload data:任意长度数据。包含有扩展定义数据和应用数据,如果没有定义扩展则没有此项,仅含有应用数据。

把接收到的buffer十六进制数据转成二进制数据,控制帧与上述各个类型帧进行对比解析其意义。

2.4、关闭连接

任何一端可以关闭连接。客户端关闭连接如下:

ws.close()

然后发送关闭帧给对方,通常会带有关闭连接的状态码,常见的状态码如下:

  • 1000 连接正常关闭
  • 1001 端点离线,例如服务器down,或者浏览器已经离开此页面
  • 1002 端点因为协议错误而中断连接
  • 1003 端点因为受到不能接受的数据类型而中断连接
  • 1004 保留
  • 1005 保留, 用于提示应用未收到连接关闭的状态码
  • 1006 端点异常关闭
  • 1007 端点收到的数据帧类型不一致而导致连接关闭
  • 1008 数据违例而关闭连接
  • 1009 收到的消息数据太大而关闭连接
  • 1010 客户端因为服务器未协商扩展而关闭
  • 1011 服务器因为遭遇异常而关闭连接
  • 1015 TLS握手失败关闭连接

三、websocket 实例

3.1、客户端创建websocket对象,并建立连接之后发送数据。其中 ws 地址根据后台服务端口对应。

<!DOCTYPE html>
<html lang="en">
<head>
 <meta charset="UTF-8">
 <meta http-equiv="X-UA-Compatible" content="IE=edge">
 <meta name="viewport" content="width=device-width, initial-scale=1.0">
 <title>Document</title>
</head>
<body>
 <script>
  let ws=new WebSocket('ws://localhost:8888')
  ws.onopen=function(){
   console.log("连接")
   ws.send("你好")
   }
   ws.onmessage=function(res){
    console.log('res',res)
  }
 </script>
</body>
</html>

3.2、使用 node.js 创建一个 websocket 服务,如创建一个serve.js文件,代码如下:

const http=require("http")
const net=require("net") //原生的websocket
const crypto=require('crypto') // 安全性校验
let serve=net.createServer(sock=>{
 //只握手一次
 sock.once('data',(data)=>{
  console.log("hand shake start") // 开始握手
  let str=data.toString();
  let lines=str.split('\r\n')
  //舍弃第一行和最后两行
  lines=lines.slice(1,lines.length-2)
  let headers={}
  lines.forEach(line=>{
   let [key,val]=line.split(': ')
   headers[key.toLowerCase()]=val
  })
  if( headers['upgrade']!='websocket' ){
    console.log("其他协议")
    sock.end()
  }else if(headers['sec-websocket-version']!=13){
   console.log("版本不对")
   sock.end()
  }else{
   let key=headers['sec-websocket-key']
   let mask='258EAFA5-E914-47DA-95CA-C5AB0DC85B11'
   //sha1(key+mask) -> base64=>client
   let hash=crypto.createHash('sha1')
   hash.update(key+mask)
   let key2=hash.digest('base64')
   sock.write(`HTTP/1.1 101 Switching Protocols\r\nUpgrade:websocket\r\nConnection:Upgrade\r\nsec-websocket-Accept:${key2}\r\n\r\n` )
   console.log("hand shake end") // 握手结束
   //真正的数据
   sock.on('data',res=>{
   console.log("真正接收数据",res)
   //数据解析
   let FIN=res[0]&0x001;
   let opcode=data[0]&0x0F0;
   let msak=data[1]&0x001;
   let payload=data[1]&0x0FE;          
  })
  }
 })
 //断开
 sock.on('end',()=>{
  console.log("连接已断开")
 })
})
serve.listen("8888")

使用命令 node serve.js 或node serve 启动服务,服务启动成功之后可以使用localhost:8888访问服务。

启动服务之后,访问前边创建的html文件访问websocket服务。

四、websocket的优点

  • 第一次通过http建立连接之后,数据交互不用发送http请求,节省了带宽资源。
  • websocket连接是双向通信,服务器和客户端既可接受也可发送消息。
  • websocket多路复用,几个不同url可以复用一个websocket服务。
  • 是HTML5的技术之一,有巨大应用前景。

ebSocket 是 HTML5 开始提供的一种在单个 TCP 连接上进行全双工通讯的协议。

WebSocket 使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。在 WebSocket API 中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。可以说WebSocket的出现,使得浏览器具备了实时双向通信的能力

在 WebSocket API 中,浏览器和服务器只需要做一个握手的动作,然后,浏览器和服务器之间就形成了一条快速通道。两者之间就直接可以数据互相传送。

原来为了实现推送,很多公司用的是Ajax 轮询,即按照特定的时间间隔,由浏览器对服务器发出HTTP请求,然后由服务器返回最新的数据给客户端的浏览器。这种传统的模式带来很明显的缺点,即浏览器需要不断的向服务器发出请求,然而HTTP请求可能包含较长的头部,其中真正有效的数据可能只是很小的一部分,显然这样会浪费很多的带宽等资源。而websocket就可以解决这些问题。websocker有如下优点

  1. 支持双向通信,实时性更强。
  2. 更好的二进制支持。
  3. 较少的控制开销。连接创建后,ws客户端、服务端进行数据交换时,协议控制的数据包头部较小。在不包含头部的情况下,服务端到客户端的包头只有2~10字节(取决于数据包长度),客户端到服务端的的话,需要加上额外的4字节的掩码。而HTTP协议每次通信都需要携带完整的头部。
  4. 支持扩展。ws协议定义了扩展,用户可以扩展协议,或者实现自定义的子协议。(比如支持自定义压缩算法等)

HTML5 定义的 WebSocket 协议,能更好的节省服务器资源和带宽,并且能够更实时地进行通讯。

websocket在越来越多的公司开始开始使用,如推送,客服聊天之类的,今天推荐一个在线调试工具,使用起来超级方便,如果有更好的工具欢迎大家在下方留言,地址为:http://www.websocket-test.com/

还有并发压力测试工具等一系列实用功能

ebSocket 和 Socket 的区别就像Java和JavaScript,并没有什么太大的关系,但又不能说完全没关系。可以这么说:

  • 命名方面,Socket是一个深入人心的概念,WebSocket借用了这一概念;
  • 使用方面,完全两个东西。

Java和JavaScript的关系

当我们探讨两件事物的区别和联系时,我们想探讨些什么?

对这个问题最直接的解决方法应该是去了解Socket和WebSocket的来源和用法,那么它们的区别和联系就不言自明了。

Socket

Socket可以有很多意思,和IT较相关的本意大致是指在端到端的一个连接中,这两个端叫做Socket。对于IT从业者来说,它往往指的是TCP/IP网络环境中的两个连接端,大多数的API提供者(如操作系统,JDK)往往会提供基于这种概念的接口,所以对于开发者来说也往往是在说一种编程概念。同时,操作系统中进程间通信也有Socket的概念,但这个Socket就不是基于网络传输层的协议了。

Unix 中的 Socket

操作系统中也有使用到Socket这个概念用来进行进程间通信,它和通常说的基于TCP/IP的Socket概念十分相似,代表了在操作系统中传输数据的两方,只是它不再基于网络协议,而是操作系统本身的文件系统。

网络中的 Socket

通常所说的Socket API,是指操作系统中(也可能不是操作系统)提供的对于传输层(TCP/UDP)抽象的接口。现行的Socket API大致都是遵循了BSD Socket规范(包括Windows)。这里称规范其实不太准确,规范其实是POSIX,但BSD Unix中对于Socket的实现被广为使用,所以成为了实际的规范。如果你要使用HTTP来构建服务,那么就不需要关心Socket,如果你想基于TCP/IP来构建服务,那么Socket可能就是你会接触到的API。

在TCP/IP网络中HTTP的位置

从上图中可以看到,HTTP是基于传输层的TCP协议的,而Socket API也是,所以只是从使用上说,可以认为Socket和HTTP类似(但一个是成文的互联网协议,一个是一直沿用的一种编程概念),是对于传输层协议的另一种直接使用,因为按照设计,网络对用户的接口都应该在应用层。

Socket 名称的由来

和很多其他Internet上的事物一样,Socket这个名称来自于大名鼎鼎的ARPANET(Advanced Research Projects Agency),早期ARPANET中的Socket指的是一个源或者目的地址——大致就是今天我们所说的IP地址和端口号。最早的时候一个Socket指的是一个40位的数字(RFC33中说明了此用法,但在RFC36中并没有明确地说使用40位数字来标识一个地址),其中前32为指向的地址(socket number,大致相当于IP),后8位为发送数据的源(link,大致相当于端口号)。对他们的叫法有很多的版本,这里列举的并不严谨。

领取C++音视频开发学习资料:点击→音视频开发(资料文档+视频教程+面试题)(FFmpeg+WebRTC+RTMP+RTSP+HLS+RTP)

端口号的野史

随着ARPANET的发展,后来(RFC433,Socket Number List)socket number被明确地定义为一个40位的数字,其中后8位被用来制定某个特定的应用使用(比如1是Telnet)。这8位数有很多名字:link、socket name、AEN(another eight number,看到这个名字我也是醉了),工程师逗逼起来也是挺拼的。

后来在Internet的规范制定中,才真正的用起了port number这个词。至于为什么端口号是16位的,我想可能有两个原因,一是对于当时的工程师来说,如果每个端口号来标识一个程序,65535个端口号也差不多够用了。二可能是为了对齐吧,^_^!!。


Socket 原本的意思

在上边提到的历史中使用到的Socket,包括TCP文档中使用到的Socket,其实指的是网络传输中的一端,是一个虚拟化的概念。


WebSocket

上边简单叙述了Socket的意义,由于年代久远,很多事情也搞不了那么清楚。但WebSocket是一个很晚近的东西,可以让我们看到它是如何成为现在我们看到的这个样子的。


WHATWG(Web Hypertext Application Technology Working Group)

关于HTML5的故事很多人都是知道的,w3c放弃了HTML,然后有一群人(也有说是这些人供职的公司,不过官方的文档上是说的个人)创立了WHATWG组织来推动HTML语言的继续发展,同时,他们还发展了很多关于Web的技术标准,这些标准不断地被官方所接受。WebSocket就属于WHATWG发布的Web Application的一部分(即HTML5)的产物。


为什么会有 WebSocket

大约在08年的时候,WG的工程师在讨论网络环境中需要一种全双工的连接形式,刚开始一直叫做「TCPConnection」,并讨论了这种协议需要支持的功能,大致已经和我们今天看到的WebSocket差不多了。他们认为基于现有的HTTP之上的一些技术(如长轮询、Comet)并满足不了这种需求,有必要定义一个全新的协议。


名称的由来

在很多的关于HTML5或者WebSocket的文档中,都能看到一个名字,Hixie(Ian Hickson),他是WHATWG组织的发言人,曾供职于Netscape、Opera、Google,看工作的公司就知道这个人的背景了。

08年6月18日,一群WHATWG的工程师在讨论一些技术问题,一个工程师提到说「我们之前讨论的那个东西,不要叫TCPConnection 了,还是起个别的名字吧 」,接着几个名字被提及,DuplexConnection,TCPSocket,SocketConnection ,一个叫mcarter(Michael Carter )的工程师说他马上要写一篇关于Comet的文章,如果可以确定这个名称,想在文章中引用这个名字。

Socket一直以来都被人用来表示网络中一个连接的两端,考虑到怎么让工程师更容易接受,后来Hixie说了一句「我看WebSocket这个名字就很适合嘛(Hixie briefly pops back online to record that “WebSocket” would probably be a good new name for the TCPConnection object)」,大家都没有异议,紧接着mcarter在Comet Daily中发表了文章Independence Day: HTML5 WebSocket Liberates Comet From Hacks,后来随着各大浏览器对WebSocket的支持,它变成了实际的标准,IETF也沿用了这个名字。

下边是在WHATWG文档中对WebSocket接口的定义

    enum BinaryType { "blob", "arraybuffer" };

    [Constructor(USVString url, optional (DOMString or sequence<DOMString>) protocols=[]), Exposed=(Window,Worker)]

    interface WebSocket : EventTarget {

      readonly attribute USVString url;

     

      // ready state

      const unsigned short CONNECTING=0;

      const unsigned short OPEN=1;

      const unsigned short CLOSING=2;

      const unsigned short CLOSED=3;

      readonly attribute unsigned short readyState;

      readonly attribute unsigned long long bufferedAmount;

     

      // networking

      attribute EventHandler onopen;

      attribute EventHandler onerror;

      attribute EventHandler onclose;

      readonly attribute DOMString extensions;

      readonly attribute DOMString protocol;

      void close([Clamp] optional unsigned short code, optional USVString reason);

     

      // messaging

      attribute EventHandler onmessage;

      attribute BinaryType binaryType;

      void send(USVString data);

      void send(Blob data);

      void send(ArrayBuffer data);

      void send(ArrayBufferView data);

    };

内容的确定

大多数新技术的出现都是建立在已有技术的铺垫之上的,WebSocket内容的确定也是如此,其中就有Comet看不到的贡献,Comet是一个很有趣的技术,有兴趣可以看看这里


结论

可以把WebSocket想象成HTTP,HTTP和Socket什么关系,WebSocket和Socket就是什么关系。