socket.io 原理详解

栏目: 后端 · 发布时间: 7年前

内容简介:两者的依赖关系可参考:

socket.io 是基于Websocket 的Client-Server 实时通信库。 socket.io 底层使用 engine.io 封装了一层协议。

两者的依赖关系可参考: package.json

2. WebSocket 简介

Websocket 定义 参考规范 rfc6455

规范解释 Websocket 是一种提供客户端(提供不可靠秘钥)与服务端(校验通过该秘钥)进行 双向通信 的协议。

在没有 websocket 协议之前,要提供客户端与服务端实时 双向推送 消息,就会使用 polling 技术,客户端通过 xhr 或 jsonp 发送 消息给服务端,并通过事件回调来 接收 服务端消息。

这种技术虽然也能保证双向通信,但是有一个不可避免的问题,就是 性能问题 。客户端不断向服务端发送请求,如果客户端并发数过大,无疑导致服务端压力剧增。因此, websocket 就是解决这一痛点而诞生的。

这里再延伸一些名词:

  • 长轮询 客户端向服务端发送 xhr 请求,服务端接收并 hold 该请求,直到有新消息 push 到客户端,才会主动断开该连接。然后,客户端处理该 response 后再向服务端发起新的请求。以此类推。

HTTP1.1 默认使用长连接,使用长连接的 HTTP 协议,会在响应头中加入下面这行信息: Connection:keep-alive

  • 短轮询 :

客户端不管是否收到服务端的 response 数据,都会定时想服务端发送请求,查询是否有数据更新。

  • 长连接 指在一个 TCP 连接上可以发送多个数据包,在 TCP 连接保持期间,如果没有数据包发送,则双方就需要发送 心跳包 来维持此连接。

连接过程: 建立连接——数据传输——...——(发送心跳包,维持连接)——...——数据传输——关闭连接

  • 短连接 指通信双方有数据交互时,建立一个 TCP 连接,数据发送完成之后,则断开此连接。

连接过程: 建立连接——数据传输——断开连接——...——建立连接——数据传输——断开连接

Tips

这里有一个 误解 ,长连接和短连接的概念本质上指的是传输层的 TCP 连接,因为 HTTP1.1 协议以后,连接默认都是长连接。没有短连接说法( HTTP1.0 默认使用短连接),网上大多数指的长短连接实质上说的就是 TCP 连接。 http 使用长连接的好处: 当我们请求一个网页资源的时候,会带有很多 js 、 css 等资源文件,如果使用的时短连接的话,就会打开很多 tcp 连接,如果客户端请求数过大,导致 tcp 连接数量过多,对服务端造成压力也就可想而知了。

  • 单工 数据传输的方向唯一,只能由发送方向接收方的单一固定方向传输数据。
  • 半双工 即通信双方既是接收方也是发送方,不过,在某一时刻只能允许向一个方向传输数据。
  • 全双工 : 即通信双方既是接收方也是发送方,两端设备可以同时发送和接收数据。

Tips

单工、 半双工 和 全双工 这三者都是建立在 TCP 协议(传输层上)的概念,不要与应用层进行混淆。

3. 什么是Websocket

Websocket 协议也是基于 TCP 协议的,是一种双全工通信技术、复用 HTTP 握手通道。

Websocket 默认使用请求协议为: ws:// ,默认端口: 80 。对 TLS 加密请求协议为: wss:// ,端口: 443 。

3.1 特点

  • 支持浏览器/Nodejs环境
  • 支持双向通信
  • API简单易用
  • 支持二进制传输
  • 减少传输数据量

3.2 建立连接过程

Websocket 复用了 HTTP 的握手通道。指的是,客户端发送 HTTP 请求,并在请求头中带上 Connection: Upgrade 、 Upgrade: websocket ,服务端识别该header之后,进行协议升级,使用 Websocket 协议进行数据通信。

socket.io 原理详解

参数说明

Request URL
Request Method
Status Code

RFC 7231 规范定义

规范解释: 当收到101请求状态码时,表明服务端理解并同意客户端请求,更改 Upgrade header字段。服务端也必须在 response 中,生成对应的 Upgrade 值。

  • Connection 设置 upgrade header,通知服务端,该 request 类型需要进行升级为 websocket 。 upgrade_mechanism 规范

  • Host 服务端 hostname

  • Origin 客户端 hostname:port

  • Sec-WebSocket-Extensions 客户端向服务端发起请求扩展列表(list),供服务端选择并在响应中返回

  • Sec-WebSocket-Key 秘钥的值是通过规范中定义的算法进行计算得出,因此是不安全的,但是可以阻止一些误操作的websocket请求。

  • Sec-WebSocket-Accept 计算公式: 1. 获取客户端请求header的值: Sec-WebSocket-Key 2. 使用魔数magic = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11' 3. 通过 SHA1 进行加密计算, sha1(Sec-WebSocket-Key + magic) 4. 将值转换为base64

  • Sec-WebSocket-Protocol 指定有限使用的Websocket协议,可以是一个协议列表(list)。服务端在 response 中返回列表中支持的第一个值。

  • Sec-WebSocket-Version 指定通信时使用的Websocket协议版本。最新版本:13,历史版本

  • Upgrade 通知服务端,指定升级协议类型为 websocket

3.3 数据帧格式

数据格式定义参考:规范 RFC6455

0                   1                   2                   3
  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
 +-+-+-+-+-------+-+-------------+-------------------------------+
 |F|R|R|R| opcode|M| Payload len |    Extended payload length    |
 |I|S|S|S|  (4)  |A|     (7)     |             (16/64)           |
 |N|V|V|V|       |S|             |   (if payload len==126/127)   |
 | |1|2|3|       |K|             |                               |
 +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
 |     Extended payload length continued, if payload len == 127  |
 + - - - - - - - - - - - - - - - +-------------------------------+
 |                               |Masking-key, if MASK set to 1  |
 +-------------------------------+-------------------------------+
 | Masking-key (continued)       |          Payload Data         |
 +-------------------------------- - - - - - - - - - - - - - - - +
 :                     Payload Data continued ...                :
 + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
 |                     Payload Data continued ...                |
 +---------------------------------------------------------------+
复制代码
  • FIN : 1 bit 如果该位值为1,表示这是 message 的最终片段( fragment ),如果为0,表示这是一个 message 的第一个片段。
  • RSV1, RSV2, RSV3 : 各占1 bit 一般默认值是0,除非协商扩展,为非零值进行定义,否则收到非零值,并且没有进行协商扩展定义,则 websocket 连接失败。
  • Opcode : 4 bits 根据操作码( Opcode ),解析有效载荷数据( Payload data ).如果接受到未定义操作码,则应该断开 websocket 连接。
  • Mask : 1 bit 定义是否需要的载荷数据(``Payload data),进行掩码操作。如果设置值为1,那么在 Masking-key 中会定义一个掩码key,并用这个key对载荷数据进行反掩码( unmask )操作。所有从客户端发送到服务端的数据帧( frame ),mask都被设置为1.
  • Payload length : 7 bits, 7+16 bits, or 7+64 bits 载荷数据的长度。
  • Masking-key : 0 or 4 bytes 所有从客户端传送到服务端的数据帧,数据载荷都进行了掩码操作,Mask为1,且携带了4字节的Masking-key。如果Mask为0,则没有Masking-key。
  • Payload data : (x+y) bytes

3.4 心跳检测

为了确保客户端与服务端的长连接正常,有时即使客户端连接中断,但是服务端未触发 onclose 事件,这就有可能导致无效连接占用。所以需要一种机制,确保两端的连接处于正常状态, 心跳检测 就是这种机制。客户端每隔一段时间,会向服务端发送 心跳 (数据包),服务端也会返回 response 进行反馈连接正常。

4. socket.io 简介

socket.io 与 engine.io 的一大区别在于, socket.io 并不直接提供连接功能,而是在 engine.io 层提供。

socket.io 提供了一个 房间 ( Namespace )概念。当客户端创建一个新的长连接时,就会分配一个新的 Namespace 进行区分。

// lookup 源码
var parsed = url(uri)
var source = parsed.source
var id = parsed.id
var path = parsed.path
// 查找相同房间
var sameNamespace = cache[id] && path in cache[id].nsps
// 如果房间号已存在,创建新连接
var newConnection = sameNamespace
// ...
复制代码

socket.io 也提供支持多路复用( built-in multiplexing )方式,这表明每一个数据包( Packet )都始终属于给定的 namespace ,并有 path 进行标识(例如: /xxxx )

socket.io 可以在 open 之前, emit 消息,并且该消息会在 open 之后发出。而 engine.io 必须等到 open 之后,才能 send 消息。

socket.io 也支持断网重连( reconnection )功能。

5. socket.io 工作流程

当使用 socket.io 创建一个长连接时,到底发生了什么呢?下面我们就来进入本文的正体:

const socket = io('http://localhost', {
  path: '/myownpath'
});
复制代码

首先, socket.io 通过一个 http 请求,并且该请求头中带有升级协议( Connection:Upgrade 、 Upgrade:websocket )等信息,告诉服务端准备建立连接,此时,后端返回的 response 数据。 数据格式如下 :

0{"sid":"ab4507c4-d947-4deb-92e4-8a9e34a9f0b2","upgrades":["websocket"],"pingInterval":25000,"pingTimeout":60000}
复制代码
  • 0 : 代表 open 标识
  • sid : session id
  • upgrades : 升级协议类型
  • pingInterval : ping 的间隔时长
  • pingTimeout : 判断连接超时时长

当客户端收到响应之后, scoket.io 会根据当前客户端环境是否支持 Websocket 。如果支持,则建立一个 websocket 连接,否则使用 polling ( xhr 、 jsonp )长轮询进行 双向数据通信 。

6. socket.io 协议解析

socket.io 协议中定义的数据格式称之为 Pakcet ,每一个 Packet 都含有 nsp 的对象值。

socket.io 原理详解

Packet

编码包可以是 UTF8 或 二进制 数据,编码格式如下:

< 包类型id >[< data >]

例如:

2probe

包类型id(packet type id)是一个整型,具体含义如下:

  • 0 open 当打开一个新传输时,服务端检测并发送
  • 1 close 请求关闭传输,但不是主动断开连接
  • 2 ping 客户端发出,服务端应该返回包含相同数据的 pong packet进行应答
  • 3 pong 服务端发出,用以响应客户端的 ping packet
  • 4 message 真实数据,客户端和服务端应该调用回调中的 data
// 服务端发送 
send('4HelloWorld')
// 客户端接收数据并调用回调 
socket.on('message', function (data) { console.log(data); });
// 客户端发送 
send('4HelloWorld')
// 服务端接收数据并调用回调 
socket.on('message', function (data) { console.log(data); })
复制代码
  • 5 upgrade 在 engine.io 切换传输之前,它会测试服务器和客户端是否可以通过此传输进行通信。如果此测试成功,客户端将发送升级数据包,请求服务器刷新旧传输上的缓存并切换到新传输。
  • 6 noop noop packet。主要用于在收到传入的 websocket 连接时强制轮询周期。
    2send
    3probe
    5
    

以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。

有的放矢

有的放矢

Nathan Furr、Paul Ahlstrom / 七印部落 / 华中科技大学出版社 / 2014-4-20 / 38.00元

创业需要大笔资金吗?自信的人适合创业吗?好点子究竟来自哪里?《有的放矢:NISI创业指南》的两位作者拥有多年创业与投资经验,收集了大量的一手案例和资料,提出有的放矢创业流程,帮助创业者规避创业风险,提高创业成功率。一起来看看 《有的放矢》 这本书的介绍吧!

CSS 压缩/解压工具
CSS 压缩/解压工具

在线压缩/解压 CSS 代码

JSON 在线解析
JSON 在线解析

在线 JSON 格式化工具

HTML 编码/解码
HTML 编码/解码

HTML 编码/解码