了解WebSocket及Socket.io

6,097 阅读22分钟

之前在项目中简单的使用过,但是追究使用它的缘由、优点以及原理,在这之前笔者也是模糊不清,所以在这期间,做了比较系统的了解后,在此记录一番。

话不多说,切入正题。可能在了解到这个协议的时候,大多数人都不知道它是做什么,或者说不知道为什么需要这个协议,那么我们就从基础开始,一点点的了解。

还是现在这里粘一个下面代码的github地址。

一、WebSocket基本知识

1.1 WebSocket简单介绍

WebSocket是一种 网络传输协议(说到网络协议,大家可能会立马想到HTTP协议,下面会有两者的对比),可在单个TCP连接上进行全双工通信,位于OSI模型的应用层。

WebSocket使得客户端和服务器之间的数据交换变得更加简单,允许服务端向客户端推送数据。在WebSocket API中,浏览器和服务器只需要完成一次握手,两者之间就可以创建持久性的链接,并进行双向数据传输

WebSocket协议规范将 ws(WebSocket)和 wss(WebSocket Secure)定义为两个新的统一资源标识符,分别对应明文和加密连接。

1.2 为什么需要WebSocket

WebSocket最大特点就是,服务器可以主动向客户端推送信息,客户端也可以主动向服务器发送信息。

在这个协议之前,很多网站为了实现 推送技术,所用的技术都是轮询。轮询是在特定的时间间隔,由浏览器对服务器发出HTTP请求,然后由服务器返回最新的数据给客户端的浏览器。这种传统的模式带来很明显的缺点,即浏览器需要不断的向服务器发出请求,然而HTTP请求可能包含较长的头部,其中真正有效的数据可能只是很小的一部分,显然这样会消耗很多的带宽资源。

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

1.3 WebSocket特点

  • 建立在 TCP 协议之上,服务器端的实现比较容易。
  • 与 HTTP 协议有着良好的兼容性。默认端口也是80和443,并且握手阶段采用 HTTP 协议,因此握手时不容易屏蔽,能通过各种 HTTP 代理服务器。
  • 数据格式比较轻量,性能开销小,通信高效。
  • 可以发送文本,也可以发送二进制数据。
  • 没有同源限制,客户端可以与任意服务器通信。
  • 协议标识符是ws(如果加密,则为wss),服务器网址就是 URL。

1.4 WebSocket优点

在上面简单的介绍WebSocket之后,想必大家也都可以总结出一些WebSocket的优点,下面相较于HTTP再做进一步的总结

  • 较少的控制开销:在连接创建后,服务器和客户端之间交换数据时,用于协议控制的数据包头部相对较小。在不包含扩展的情况下,对于服务器到客户端的内容,此头部大小只有2至10字节(和数据包长度有关);对于客户端到服务器的内容,此头部还需要加上额外的4字节的掩码。相对于HTTP请求每次都要携带完整的头部,此项开销显著减少了。

  • 更强的实时性:由于协议是全双工的,所以服务器可以随时主动给客户端下发数据。相对于HTTP请求需要等待客户端发起请求服务端才能响应,延迟明显更少

  • 保持连接状态:Websocket需要先创建连接,这就使得其成为一种有状态的协议,之后通信时可以省略部分状态信息。而HTTP请求可能需要在每个请求都携带状态信息(如身份认证等)。

  • 更好的二进制支持:Websocket定义了二进制帧,相对HTTP,可以更轻松地处理二进制内容

二、WebSocket进阶知识

简单来讲,WebSocket协议由两部分组成:建立连接过程(握手)数据传输

2.1 建立连接(握手)

在第一部分的介绍中,我们提到,WebSocket在创建持久性连接之前,需要进行一次握手,而且为了兼容性考虑,WebSocket复用了HTTP的握手通道。具体指的是,客户端通过HTTP请求与WebSocket服务端协商升级协议。协议升级完成之后,后续的数据交换则遵照WebSocket的协议。

客户端:升级协议版本

首先,客户端发起协议升级请求,从下图可以看到,采用的是标准的HTTP报文格式,且只支持 GET方法。

重点说明一下上面四处的意义:

  • Connection:Upgrade:表示要升级协议
  • Upgrade:WebSocket:表示要升级到WebSocket协议
  • Sec-WebSocket-Key与后面服务器端响应首部的Sec-WebSocket-Accept是配套的,提供基本的防护,比如恶意的链接,或者无意的连接
  • Sec-WebSocket-Version: 13:表示WebSocket的版本。如果服务器不支持该版本,需要返回一个Sec-WebSocket-Versionheader,里面包含服务端支持的版本号

注意,上面请求省略了部分非重点请求首部。由于是标准的HTTP请求,类似Host、Origin、Cookie等请求首部会照常发送。在握手阶段,可以通过相关请求首部进行 安全限制、权限校验等。

服务端:响应协议升级

服务端返回内容如下,状态码 101表示协议切换。到此完成协议升级,后序的数据交互都按照新的协议进行。

Sec-WebSocket-Accept的计算

Sec-WebSocket-Accept根据客户端请求首部的Sec-WebSocket-Key计算出来。

计算公式为:

Sec-WebSocket-Key258EAFA5-E914-47DA-95CA-C5AB0DC85B11拼接。 通过SHA1计算出摘要,并转成base64字符串。 伪代码如下:

>toBase64( sha1( Sec-WebSocket-Key + 258EAFA5-E914-47DA-95CA-C5AB0DC85B11 )  )

2.2 数据传递

一旦WebSocket客户端、服务端建立连接后,后续的操作都是基于数据帧的传递。

因为此处涉及到了数据帧的知识,所以可以先查看2.3 数据帧格式的部分。

1、数据分片

WebSocket的每条消息可能被切分成多个数据帧。当WebSocket的接收方收到一个数据帧时,会根据FIN的值来判断,是否已经收到消息的最后一个数据帧。

FIN=1表示当前数据帧为消息的最后一个数据帧,此时接收方已经收到完整的消息,可以对消息进行处理。FIN=0,则接收方还需要继续监听接收其余的数据帧。

此外,opcode在数据交换的场景下,表示的是数据的类型。0x01表示文本,0x02表示二进制。而0x00比较特殊,表示延续帧(continuation frame),顾名思义,就是完整消息对应的数据帧还没接收完。

2、数据分片例子

直接看例子更形象些。下面例子来自MDN,可以很好地演示数据的分片。客户端向服务端两次发送消息,服务端收到消息后回应客户端,这里主要看客户端往服务端发送的消息。

第一条消息

FIN=1, 表示是当前消息的最后一个数据帧。服务端收到当前数据帧后,可以处理消息。opcode=0x1,表示客户端发送的是文本类型。

第二条消息

  • FIN=0,opcode=0x1,表示发送的是文本类型,且消息还没发送完成,还有后续的数据帧。

  • FIN=0,opcode=0x0,表示消息还没发送完成,还有后续的数据帧,当前的数据帧需要接在上一条数据帧之后。

  • FIN=1,opcode=0x0,表示消息已经发送完成,没有后续的数据帧,当前的数据帧需要接在上一条数据帧之后。服务端可以将关联的数据帧组装成完整的消息。

Client: FIN=1, opcode=0x1, msg="hello"
Server: (process complete message immediately) Hi.
Client: FIN=0, opcode=0x1, msg="and a"
Server: (listening, new message containing text started)
Client: FIN=0, opcode=0x0, msg="happy new"
Server: (listening, payload concatenated to previous message)
Client: FIN=1, opcode=0x0, msg="year!"
Server: (process complete message) Happy new year to you too!

2.3 数据帧格式

客户端、服务端数据的交换,离不开数据帧格式的定义。所以我们在这里看一看WebSocket的数据帧格式。

客户端、服务端数据的交换,离不开数据帧格式的定义。因此,在实际讲解数据交换之前,我们先来看下WebSocket的数据帧格式。

WebSocket客户端、服务端通信的最小单位是帧(frame),由1个或多个帧组成一条完整的消息(message)。

  • 发送端:将消息切割成多个帧,并发送给服务端;

  • 接收端:接收消息帧,并将关联的帧重新组装成完整的消息;

本节的重点,就是讲解数据帧的格式。

1、数据帧格式概览

下面给出了WebSocket数据帧的统一格式。

从左到右,单位是比特。比如FIN、RSV1各占据1比特,opcode占据4比特。 内容包括了标识、操作代码、掩码、数据、数据长度等。

  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 ...                |
 +---------------------------------------------------------------+

2、数据帧格式详解

针对前面的格式概览图,这里逐个字段进行讲解,可以参考协议规范。

FIN:1个比特。

如果是1,表示这是消息(message)的最后一个分片(fragment),如果是0,表示不是是消息(message)的最后一个分片(fragment)。

RSV1, RSV2, RSV3:各占1个比特。

一般情况下全为0。当客户端、服务端协商采用WebSocket扩展时,这三个标志位可以非0,且值的含义由扩展进行定义。如果出现非零的值,且并没有采用WebSocket扩展,连接出错。

Opcode: 4个比特。

操作代码,Opcode的值决定了应该如何解析后续的数据载荷(data payload)。如果操作代码是不认识的,那么接收端应该断开连接(fail the connection)。可选的操作代码如下:

  • %x0:表示一个延续帧。当Opcode为0时,表示本次数据传输采用了数据分片,当前收到的数据帧为其中一个数据分片。

  • %x1:表示这是一个文本帧(frame)

  • %x2:表示这是一个二进制帧(frame)

  • %x3-7:保留的操作代码,用于后续定义的非控制帧。

  • %x8:表示连接断开。

  • %x9:表示这是一个ping操作。

  • %xA:表示这是一个pong操作。

  • %xB-F:保留的操作代码,用于后续定义的控制帧。

Mask: 1个比特

表示是否要对数据载荷进行掩码操作。从客户端向服务端发送数据时,需要对数据进行掩码操作;从服务端向客户端发送数据时,不需要对数据进行掩码操作。

如果服务端接收到的数据没有进行过掩码操作,服务端需要断开连接。

如果Mask是1,那么在Masking-key中会定义一个掩码键(masking key),并用这个掩码键来对数据载荷进行反掩码。所有客户端发送到服务端的数据帧,Mask都是1。

Payload length:数据载荷的长度,单位是字节。为7位,或7+16位,或1+64位

假设数Payload length === x,如果

  • x为0~126:数据的长度为x字节。

  • x为126:后续2个字节代表一个16位的无符号整数,该无符号整数的值为数据的长度。

  • x为127:后续8个字节代表一个64位的无符号整数(最高位为0),该无符号整数的值为数据的长度。

此外,如果payload length占用了多个字节的话,payload length的二进制表达采用网络序(big endian,重要的位在前)。

Masking-key:0或4字节(32位)

所有从客户端传送到服务端的数据帧,数据载荷都进行了掩码操作,Mask为1,且携带了4字节的Masking-key。如果Mask为0,则没有Masking-key

备注:载荷数据的长度,不包括mask key的长度。

Payload data:(x+y) 字节

载荷数据:包括了扩展数据、应用数据。其中,扩展数据x字节,应用数据y字节。

  • 扩展数据:如果没有协商使用扩展的话,扩展数据数据为0字节。所有的扩展都必须声明扩展数据的长度,或者可以如何计算出扩展数据的长度。此外,扩展如何使用必须在握手阶段就协商好。如果扩展数据存在,那么载荷数据长度必须将扩展数据的长度包含在内。

  • 应用数据:任意的应用数据,在扩展数据之后(如果存在扩展数据),占据了数据帧剩余的位置。载荷数据长度 减去 扩展数据长度,就得到应用数据的长度。

3、掩码算法 掩码键(Masking-key)是由客户端挑选出来的32位的随机数。掩码操作不会影响数据载荷的长度。掩码、反掩码操作都采用如下算法:

首先,假设:

  • original-octet-i:为原始数据的第i字节。

  • transformed-octet-i:为转换后的数据的第i字节。

  • j:为i mod 4的结果。

  • masking-key-octet-j:为mask key第j字节。

算法描述为: original-octet-imasking-key-octet-j异或后,得到 transformed-octet-i

j = i MOD 4
transformed-octet-i = original-octet-i XOR masking-key-octet-j

2.4 连接保持(保持长连接)

WebSocket为了保持客户端、服务端的实时双向通信,需要确保客户端、服务端之间的TCP通道保持连接没有断开。然而,对于长时间没有数据往来的连接,如果依旧长时间保持着,可能会浪费包括的连接资源。

但不排除有些场景,客户端、服务端虽然长时间没有数据往来,但仍需要保持连接。这个时候,可以采用心跳来实现。

  • 发送方->接收方:ping 接收方->发送方:`pong` `ping`、`pong的操作`,对应的是WebSocket的两个控制帧,`opcode分别是0x90xA`。

在这一部分最后,在说明两个知识点(不做详细说明)

1.Sec-WebSocket-Key/Accept的作用:主要作用在于提供基础的防护,减少恶意连接、意外连接。 作用大致归纳如下:

  • 避免服务端收到非法的websocket连接(比如http客户端不小心请求连接websocket服务,此时服务端可以直接拒绝连接)

  • 确保服务端理解websocket连接。因为ws握手阶段采用的是http协议,因此可能ws连接是被一个http服务器处理并返回的,此时客户端可以通过Sec-WebSocket-Key来确保服务端认识ws协议。(并非百分百保险,比如总是存在那么些无聊的http服务器,光处理Sec-WebSocket-Key,但并没有实现ws协议。。。)

  • 用浏览器里发起ajax请求,设置header时,Sec-WebSocket-Key以及其他相关的header是被禁止的。这样可以避免客户端发送ajax请求时,意外请求协议升级(websocket upgrade

  • 可以防止反向代理(不理解ws协议)返回错误的数据。比如反向代理前后收到两次ws连接的升级请求,反向代理把第一次请求的返回给cache住,然后第二次请求到来时直接把cache住的请求给返回(无意义的返回)。

  • Sec-WebSocket-Key主要目的并不是确保数据的安全性,因为Sec-WebSocket-Key、Sec-WebSocket-Accept的转换计算公式是公开的,而且非常简单,最主要的作用是预防一些常见的意外情况(非故意的)。

2. 数据掩码的作用:

WebSocket协议中,数据掩码的作用是增强协议的安全性(并不是为了防止数据泄密,而是为了防止早期版本的协议中存在的代理缓存污染攻击(proxy cache poisoning attacks)等问题。)。但数据掩码并不是为了保护数据本身,因为算法本身是公开的,运算也不复杂。除了加密通道本身,似乎没有太多有效的保护通信安全的办法。

三、 WebSocket实例

3.1 客户端代码示例

  <input type="text" id="sendTxt">
  <button id="sendBtn">发送</button>

  <div id="recv"></div>
  
  <script>
    /**
     * WebSocket对象作为一个构造函数,用于新建WebSocket实例
     * 执行下面的语句之后,客户端就会个服务器进行连接
     */
    let webSocket = new WebSocket("wss://echo.websocket.org");

    /**
     * 下面结合实际讲一下WebSocket实例对象的属性和方法
     * 1. 属性
     * 1.1 webSocket.readyState(属性返回实例对象的当前状态)
     *     . CONNECTING:值为0,表示正在连接。
     *     . OPEN:值为1,表示连接成功,可以通信了。
     *     . CLOSING:值为2,表示连接正在关闭。
     *     . CLOSED:值为3,表示连接已经关闭,或者打开连接失败。
     */


    /**
     * 1.2 webSocket.onopen(用于指定连接成功后的回调函数)
     */
    webSocket.onopen = function () {
      console.log("webSocket open");
      document.getElementById('recv').innerHTML = "Connected";
    };

    /**
     * 1.3 webSocket.onclose(用于指定连接关闭之后的回调函数)
     */
    webSocket.onclose = function () {
      console.log("webSocket close");
    }

    /**
     * 1.4 webSocket.onmessage(用于指定收到服务器数据后的回调函数)
     */
    webSocket.onmessage = function (e) {
      console.log(e.data);
      document.getElementById('recv').innerHTML = e.data;
    }

    //发送信息
    document.getElementById('sendBtn').onclick = function () {
      var text = document.getElementById('sendTxt').value;
      /**
       * 2. 方法
       * 2.1 webSocket.send() (用于向服务器发送数据)
       */
      webSocket.send(text);
    }
  </script>

客户端的API上面的代码中有简单的介绍以及使用,如果想要查看更加具体的文档说明,可以在MDN进行查看。

对比于服务端的实现,客户端的使用略显简单,那么接下来我们继续实现服务端的WebSocket。

3.2 服务端的实现

因为笔者目前局限于JS,所以服务端的实现是使用的Node,常用的Node实现有以下三种:

  • µWebSockets

  • Socket.IO

  • WebSocket-Node

因为在项目中使用的是Socket.IO,所以在这里笔者就结合自己的亲身经历去讲解以下,其他的实现方式应该也是差不多的,有兴趣的话是可以自己实现一些的。

Socket.IO想实现双向通信,当然WebSocket是必不可少的技术了,不过Socket.IO不仅仅是WebSocket的封装,在不支持WebSocket的环境中,Socket.IO还有多种轮询解决方案,确保它能够正常运行。

既然用到了Socket.IO,那我们就要扒一扒有关于它的介绍,基本使用等等内容,先在这里贴一个官方文档。因为官方文档为全英,这里笔者找到了一个中文文档,建议两者对比着看,有能力的当然还是看全英的比较好,内容更加准确。

Socket.IO

Socket.Io主要由两个部分组成:

  • socket.io模块,集成到Node.jshttp模块的服务器

  • socket.io-client,在浏览器中运行的客户端 Socket.Io支持多种传输机制,例如WebSocket、Adobe Flash Sockets、XHR轮询、JsonP轮询,它们被隔离在统一的接口之下,这意味着任何浏览器都可以作为客户端。

标准的WebSocket服务器并不能和Socket.Io客户端进行直接通信,需要注意这一点。

1.1 介绍

Socket.io是一个WebSocket库,包括了客户端的js和服务器端的nodejs,它的目标是构建可以在不同浏览器和移动设备上使用的实时应用。它会自动根据浏览器从WebSocketAJAX长轮询、Iframe流等等各种方式中选择最佳的方式来实现网络实时应用,非常方便和人性化,而且支持的浏览器最低达IE5.5。

1.2 使用

现在Node.js的框架非常的多,譬如:Express、ThinkJS、Koa、Egg.js等,每一个框架有可能进一步对Socket.io进行了封装,比如笔者使用过的Egg.js框架就提供了 egg-socket.io插件,使用这些插件就要遵循框架的一些约束,所以对于框架中的使用,还是需要读者根据文档要求使用,因为这个因素,所以读者只在这里介绍在不使用任何框架的情况下的使用。

安装

$ npm install socket.io

使用 Node http 服务器

先直接上代码(最基础),之后会根据官方文档讲解其他内容。

//  index.html
    <script src="./node_modules/socket.io-client/dist/socket.io.js"></script>
    <script>
        let socket = io('http://localhost');
        socket.on('news', (data) => {   //监听'news'事件,有结果后输出
            console.log(data);
            socket.emit('my other event', {  //触发'my other event'事件
                my: 'data'
            })
        })
    </script>
    
    
    
// app.js
    let app = require('http').createServer(handler);//使用Node创建一个Http服务
    let io = require('socket.io')(app); //此处为绑定上面创建的服务器
    let fs = require('fs');

    app.listen(80);

    var handler = (req, res) => {
        fs.readFile(__dirname + './index.html', (err, data) => {
            if (err) {
               res.writeHead(500);
               return res.end('Error loading index.html');
            }
            res.writeHead(200);
            res.end(data);
        })
    }
    io.on('connection', (socket) => {
        socket.emit('news', {  //触发'news'事件
            hello: 'world'
        });
        socket.on('my other event', (data) => {  //监听'my other event'事件,有结果后输出
            console.log(data);
    })
})

上面代码完成后,运行 node app.js,之后打开 index.html,之后再打开浏览器的控制台,会发现浏览器的控制台上先打印出{hello: "world"},之后编辑器的控制台上打印出{my: "data"},注意两个是有先后顺序的,这个看代码就明白了,不多说。

1.3 emit和on

emiton 是最重要的两个api,分别对应 发送监听 事件。

  • socket.emit(eventName[, ...args]):发射(触发)一个事件

  • socket.on(eventName, callback):监听一个emit发射的事件

我们可以非常自由的在服务端定义并发送一个事件emit,然后在客户端监听 on,反过来也一样。 发送的内容格式也非常自由,既可以是基本数据类型 Number,String,Boolean等,也可以是Object,Array 类型,甚至还可以是函数。而用回调函数的方式则可以进行更便携的交互。

1.2 部分的示例代码就是这两个api的使用,这里就不多说了。

1.4 广播(broadcast)

broadcast默认是向所有的socket连接进行广播,但是不包括发送者自身。

注意:socket连接要确保是同一个命名空间下的

代码解释:

io.on('connection', (socket) => {
    //发送给除自己以外的其他客户端
    socket.broadcast.emit('news', {
        hello: 'world'
    })
})

此时,要想查看效果,可以在创建一个HTML页面,代码一样即可,之后在浏览器上同时打开两个页面,刷新一个页面时(刷新一次页面就相当于触发一次事件),本页面控制台没有输出任何内容,另一个页面的控制台则会输出内容(可以创建更多页面查看效果)。

如果想要自身也可以收到消息,此时可以

io.on('connection', (socket) => {
    //发送给自己
    socket.emit('news', {
        hello: 'world'
    })
})

1.5 命名空间(namespace)

所谓的命名空间,就是指在不同的域当中发消息只能给当前的域的socket收到。

作用:可以最大限度地减少资源(TCP连接)的数量,,并为应用提供频道划分功能。(这样多个应用模块可以共享单个TCP连接)

如果想隔离作用域,或者划分业务模块,这时候就可以使用命名空间,命名空间相当于建立新的频道,使你可以在一个socket.io服务上隔离不同的连接,时间和中间件。

默认的命名空间是/,Socket.IO 客户端默认连接到这个命名空间,服务端默认监听的也是这个命名空间。

自定义命名空间

重要提示:命名空间是 Socket.IO 协议的一个实现细节, 与底层传输的实际 URL 无关。底层传输的实际 URL 默认是/socket.io/…

使用命名空间的方式一:直接在链接后面加子域名,这种方式其实还是用同一个socket服务进程---软隔离

服务端代码:

io
    .of('my-nsp')
    .on('connection', (socket) => {
        // 发送给除自己以外的其他客户端
        socket.broadcast.emit('news', {
            hello: 'world'
        })
        //发送给自己
        socket.emit('news', {
            hello: 'world'
        })
    })

客户端需要修改的代码:

let socket = io('http://localhost:3000/my-nsp');

使用命名空间的方式二path参数,这种方式就是真正的重新开启了一个socket服务。

1.6 Room

这里说一下,namespaceroomsocket的关系

socket会属于某一个 room,如果没有指定,那么会有一个默认的room。这个room又会属于某个namespace,如果没有指定,那么就是默认的/。(一个命名空间下可以有多个room)

客户端连接时指定自己属于哪一个 namespace,服务端看到namespace就会把这个socket加入到指定的namespace中,如果客户端没有指定具体的room,则服务端会放入默认的room,或者服务端通过代码socket.join(bar)放入barroom中。

默认情况下,每一个id便自成一个房间,房间名是 socket.id(指定命名空间之后,前面会带上命名空间,socket会自动加入到以此ID来标识的房间);自定义房间之后,原先的默认控件仍然存在;房间为一个对象,包含当前进入房间的sockets以及长度。

代码示例:

io
    .on('connection', (socket) => {
        //在服务端将一个socket加入到一个房间中
        socket.join('manannan', () => {
            console.log(socket.rooms);
        });
        //进入到该房间中,之后的事件发布仅仅在这个房间
        io.to('manannan').emit('news', {
            hello: 'world'
        })
        //离开房间
        socket.leave('mananan')
    })

以上内容就是基本的使用,然而在实际项目中,肯定会比这些更加复杂,这里就不一一赘述,当我们用到我们之前没有用过的东西时,一定要善于查看官方文档以及百度。

所以关于客户端API服务端API等更多的内容,需要时查看官方文档就可以了。