Socket.io 深入理解

3,792 阅读3分钟

最近在做项目优化工作时,用到了Socket.io , Socket.io 文档比较少, 结合官网介绍以及自己在项目开发中的摸索,总结如下内容;

Socket.io将Websocket和轮询 (Polling)机制以及其它的实时通信方式封装成了通用的接口,并且在服务端实现了这些实时机制的相应代码。也就是说,Websocket仅仅是Socket.io实现实时通信的一个子集;

Socket.io 支持如下方式的通信方式,根据浏览器的支持程度,自动选择使用哪种技术进行通信:

  • WebSocket
  • Flash Socket
  • AJAX long-polling
  • AJAX multipart streaming
  • Forever IFrame
  • JSONP polling

Socket.io 底层是 Engine.io; Engine.io作为Socket.io的服务器和浏览器之间交换的数据的传输层,实现了跨平台的双向通信。但是它不会取代Socket.io,它只是抽象出固有的复杂性,支持多种浏览器,设备和网络的实时数据交换。

Engine.io使用了 WebSocket 和 XMLHttprequest(或JSONP) 封装了一套自己的 Socket 协议(暂时叫 EIO Socket),在低版本浏览器里面使用长轮询替代 WebSocket。一个完整的 EIO Socket 包括多个 XHR 和 WebSocket 连接.

下面从前后端的实现原理上来说明;

前端

EIO Socket 通过一个 XHR (XMLHttprequest) 握手。前端发送一个 XHR,告诉服务端我要开始 XHR 长轮询了。后端返回的数据里面包括一个 open 标志(数字 0 表示), 以及一个 sid 、 upgrades 、pingInterval、pingTimeout四个字段;

sid

sid 是本次 EIO Socket 的会话 ID,因为一次 EIO Socket 包含了多个请求,而后端又会同时连接多个 EIO Socket,sid 的作用就相当于 SESSION ID;

upgrades

会话升级得字符串,正常情况下是 ['websocket'],表示可以把连接方式从长轮询升级到 WebSocket.

pingInterval

设定每隔在一定时间发送一个ping包,可以用于心跳包的设置。默认为25000

pingTimeout

Server配置的ping超时时间,默认60000

前端在发送第一个 XHR 的时候就开始了 XHR 长轮询,这个时候如果有收发数据的需求,是通过长轮询实现的。所谓长轮询,是指前端发送一个 request,服务端会等到有数据需要返回时再 response. 前端收到 response 后马上发送下一次 request。这样就可以实现双向通信。

前端收到握手的 upgrades 后,EIO 会检测浏览器是否支持 WebSocket,如果支持,就会启动一个 WebSocket 连接,然后通过这个 WebSocket 往服务器发一条内容为 probe, 类型为 ping 的数据。如果这时服务器返回了内容为 probe, 类型为 pong 的数据,前端就会把前面建立的 HTTP 长轮询停掉,后面只使用 WebSocket 通道进行收发数据。

EIO Socket 生命周期内,会间隔一段时间 (默认25000毫秒)ping - pong 一次,用来测试网络是否正常;

下图是WebSocket 帧的结构

绿色是发送,白色是接收。前面的数字是数据包类型,2 是 ping, 3 是 pong, 4 是 message.

服务端

服务端使用 ws 库实现 WebSocket 协议。Socket.io服务启动时,会先启动一个 ws 服务。Socket.io 会监听 HTTP 服务器的 upgrade 和 request 事件。当 upgrade 事件触发时,说明可能是 WebSocket 握手,先简单校验下,然后把请求交给 ws 服务进行处理,拿到 WebSocket 对象。当 request 事件触发时,根据 url 路径判断是不是 Socket.io的 XHR 请求,拿到 res 和 res 对象。这样就可以正确接收和返回客户端数据了,具体处理过程和前端部分是对应的。