socket.io 原理详解

上一篇文章中,咱们了解到 socket.io 是 基于engine.io 进行封装的库。 因此对engine.io不清楚的童鞋能够点击进行了解: engine.io 详解javascript

1.概述


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

二者的依赖关系可参考: package.jsonhtml

2. WebSocket 简介


Websocket 定义 参考规范 rfc6455java

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

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

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

这里再延伸一些名词:redis

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

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

  • 短轮询:

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

  • 长链接 指在一个TCP链接上能够发送多个数据包,在TCP链接保持期间,若是没有数据包发送,则双方就须要发送心跳包来维持此链接。

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

  • 短链接 指通讯双方有数据交互时,创建一个TCP链接,数据发送完成以后,则断开此链接。

链接过程: 创建链接——数据传输——断开链接——...——创建链接——数据传输——断开链接

Tips

这里有一个误解,长链接和短链接的概念本质上指的是传输层的TCP链接,由于HTTP1.1协议之后,链接默认都是长链接。没有短链接说法(HTTP1.0默认使用短链接),网上大多数指的长短链接实质上说的就是TCP链接。 http使用长链接的好处: 当咱们请求一个网页资源的时候,会带有不少jscss等资源文件,若是使用的时短链接的话,就会打开不少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: UpgradeUpgrade: websocket,服务端识别该header以后,进行协议升级,使用Websocket协议进行数据通讯。

在这里插入图片描述

参数说明

  • Request URL 请求服务端地址
  • Request Method 请求方式 (支持get/post/option)
  • Status Code 101 Switching Protocols

RFC 7231 规范定义

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

  • Connection 设置upgrade header,通知服务端,该request类型须要进行升级为websocketupgrade_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.ioengine.io的一大区别在于,socket.io并不直接提供链接功能,而是在engine.io层提供。

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

在这里插入图片描述

根据流程图,能够看出:

  • 建立长链接的方式有三种: websocketxhrjsonp。其中,后两种使用长轮询的方式进行模拟。
  • 所谓的长轮询是指,客户端发送一次request,当服务端有消息推送时会push一条response给客户端。客户端收到response后,会再次发送request,重复上述过程,直到其中一端主动断开链接为止。
// 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:UpgradeUpgrade:websocket)等信息,告诉服务端准备创建链接,此时,后端返回的response数据。 数据格式以下:

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

当客户端收到响应以后,scoket.io会根据当前客户端环境是否支持Websocket。若是支持,则创建一个websocket链接,不然使用polling(xhrjsonp)长轮询进行双向数据通讯

6. socket.io 协议解析

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

在这里插入图片描述

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 upgradeengine.io切换传输以前,它会测试服务器和客户端是否能够经过此传输进行通讯。若是此测试成功,客户端将发送升级数据包,请求服务器刷新旧传输上的缓存并切换到新传输。
  • 6 noop noop packet。主要用于在收到传入的websocket链接时强制轮询周期。
    1. 客户端经过新的传输链接
    2. 客户端发送 2send
    3. 服务端接收并发送 3probe
    4. 客户端结束并发送 5
    5. 服务端刷新并关闭旧的传输链接并切换到新传输链接

7. socket.io 全家桶


在这里插入图片描述

区别

相关文章
相关标签/搜索