Socket.io
Socket.io(Socket.IO)是一个流行的 JavaScript 库,用于实现实时、双向通信的应用程序,内部引擎采用 Engine.IO (负责建立服务器和客户端之间的底层连接)。Socket.IO 通过 Engine.IO 连接提供了一些附加功能,例如自动重新连接、广播、房间管理等。
Socket.IO 不是 WebSocket 实现,无法与 WebSocket 服务器建立连接,即使 transports: ["websocket"]
。
服务器
安装
npm install socket.io
其他软件包
默认情况下,Socket.IO 使用 ws 包提供的 WebSocket 服务器。
有 2 个可选包可以与此包一起安装。这些软件包是改进某些操作的二进制附加组件。预构建的二进制文件可用于最流行的平台,因此您不一定需要在计算机上安装 C++ 编译器。
bufferutil
:允许有效地执行操作,例如屏蔽和取消屏蔽 WebSocket 帧的数据负载。utf-8-validate
:允许有效地检查消息是否包含规范要求的有效 UTF-8。
npm install bufferutil utf-8-validate
初始化
使用 Express
const express = require('express')
const { createServer } = require('http')
const { Server } = require('socket.io')
const app = express()
const httpServer = createServer(app)
const io = new Server(httpServer, {
/* options */
})
io.on('connection', (socket) => {
// ...
})
httpServer.listen(3000)
options 选项
- Socket.IO:
- path: 在服务器端捕获的路径的名称,默认值
/socket.io/
- serveClient: 是否提供客户端文件,默认值
true
- adapter: 要使用的“适配器”,默认值
require("socket.io-adapter")
(内置适配器),还支持 Redis(Redis Streams)、MongoDB 等适配器 - parser: 要使用的“解析器”,默认值
socket.io-parser
- connectTimeout: 连接超时时间,单位:毫秒,默认值 1000*45
- Engine.IO:
- pingTimeout: 服务器定期检查与客户端之间的连接是否仍处于活动状态(以毫秒为单位),默认值 1000*20
- pingInterval:客户端定期检查与服务器之间的连接是否仍处于活动状态(以毫秒为单位),默认值 1000*25
- upgradeTimeout:取消未完成的传输升级之前的延迟时间(以毫秒为单位)
- maxHttpBufferSize:在关闭套接字之前单个消息可以包含多少字节,默认值 1M
- allowRequest(req, callback):用于自定义连接请求的处理逻辑,以确定是否允许客户端建立 WebSocket 连接
- transports:服务器端允许的低级别传输
- allowUpgrades:用于控制是否允许在 WebSocket 连接建立之后进行升级到其他传输方式(如 HTTP 长轮询)的操作,默认为 true
- perMessageDeflate:是否为 WebSocket 传输启用 permessage-deflate 扩展启用或配置消息的压缩,在性能和内存消耗方面增加大量开销,默认为 false
- httpCompression:是否为 HTTP 长轮询传输启用压缩,默认值 true
- wsEngine:用于指定 WebSocket 引擎,默认值
require("ws").Server
,可选值:eiows 引擎 - cors:用于解决跨域资源共享,配置详见 Express cors 中间件
- cookie:用于发送 cookie 数据(从 Socket.IO v3 开始,默认情况下不再发送 cookie),配置详见 Express 响应对象 APIs
TIP
HTTP 协议和 WebSocket 协议可以共存,并且经常一起使用在同一个应用程序中。WebSocket 协议通常是在 HTTP 协议之上创建的,因此它们可以共享相同的网络端口(通常是 80 或 443),并在同一应用程序中使用。
WebSocket 握手通常是在 HTTP 请求上完成的,但这个过程是由 socket.io
库内部处理的,通常无需手动干预。当你调用 io.listen
时,socket.io
库会自动创建一个 HTTP 服务器,并将 WebSocket 握手功能嵌入其中。因此,你可以通过 server.listen 来启动整个服务器,包括 HTTP 和 WebSocket 部分。
服务器实例
服务器实例(通常使用 io
在代码示例中调用)具有一些可能在应用程序中使用的属性,是对底层 Engine.IO 服务器的引用。
APIs
- 构造器
new Server(httpServer[, options])
: 通过传递一个现有的 HTTP 服务器实例,创建 Socket.IO 服务器的实例与现有的 Web 服务器集成在一起new Server(port[, options])
:通过传递端口号,创建一个独立的 Socket.IO 服务器实例,它会监听指定的端口,处理来自客户端的 WebSocket 连接请求new Server(options)
:通过传递配置对象,创建 Socket.IO 服务器实例,根据配置来监听端口或将其集成到现有的 HTTP 服务器中
- 广播
server.emit(event, data)
:向所有连接到 Socket.IO 服务器的客户端发送特定事件server.local.emit(event, data)
:向当前 Socket.IO 服务器实例的所有连接客户端发送特定事件
- 监听
server.on(eventName, callback)
: 用于监听 Socket.IO 服务器实例的事件。可以使用这个方法来监听服务器级别的事件,例如服务器启动、关闭、错误等
- 房间
server.to(room)
: 用于向特定房间(Room)中的所有客户端发送事件消息。这个方法通常用于实现将事件消息广播给房间内的所有客户端的场景server.socketsJoin(rooms)
: 使匹配的 Socket 实例加入指定的房间server.socketsLeave(rooms)
:使匹配的 Socket 实例离开指定房间
- 命名空间
server.of(nsp)
: 创建一个命名空间,每个命名空间可以有自己的事件监听器、自定义事件,以及与客户端的 Socket 连接。
- 属性
server.sockets
: 主命名空间/
的别名server.engine
: 访问底层的 Socket.IO 引擎实例,用于进行底层的 WebSocket 连接管理和控制。通常情况下,不需要直接操作引擎,而是使用更高级别的 Socket.IO API
- 方法
server.disconnectSockets([close])
: 使匹配的 Socket 实例断开连接server.fetchSockets()
: 返回匹配的 Socket 实例server.serverSideEmit(eventName[, ...args][, ack])
: 在多服务器设置中向集群的其他 Socket.IO 服务器发出事件server.use()
: 注册一个服务器实例中间件(执行中间件时,Socket 实例实际上并未连接,这意味着 disconnect 如果连接最终失败,则不会发出任何事件)
与 Express 中间件的兼容性
大多数现有的 Express 中间件模块应该与 Socket.IO 兼容,只需要一个小包装函数来使方法签名匹配:
const wrap = (middleware) => (socket, next) =>
middleware(socket.request, {}, next)
以 express-session 为例:
const session = require('express-session')
io.use(wrap(session({ secret: 'cats' })))
io.on('connection', (socket) => {
const session = socket.request.session
})
事件
connection
: 在来自客户端的连接上触发connect
: 同 connectionnew_namespace
: 创建新命名空间时触发
Room 房间
Socket.IO 中的每一个 socket 都由一个随机的、不可猜测的、唯一的标识符 Socket#id。为了您的方便,每个 socket 都会自动加入一个由其自己的 id 标识的房间。
“房间”功能由我们称为适配器的东西实现。该适配器是一个服务器端组件,负责:
- 存储 Socket 实例和房间之间的关系
- 向所有(或部分)客户端广播事件
基本上,它包含两个 ES6 Maps:
- sids:
Map<SocketId, Set<Room>>
- rooms:
Map<Room, Set<SocketId>>
调用 socket.join("the-room")将导致:
- 在 sids Map 中,将“the-room”添加到由 Socket ID 标识的 Set
- 在 rooms Map 中,将 Socket ID 添加到由字符串“the-room”标识的 Set 中
然后在广播时使用这两个地图:
- 对所有套接字的广播(io.emit())循环通过 sidsMap,并将数据包发送到所有 sockets
- 对给定房间的广播 ( io.to("room21").emit())循环通过 roomsMap 中的 Set,并将数据包发送到所有匹配的 sockets
注意
在多服务器设置中,rooms 和 sids 对象不会在 Socket.IO 服务器之间共享(房间可能只“存在”在一个服务器上而不是另一个服务器上)。
事件
create-room
:创建一个新的房间delete-room
:删除一个现有的房间join-room
:客户端或用户加入特定的房间`leave-room``:客户端或用户离开特定的房间
命名空间
命名空间是一种通信通道,允许通过单个共享连接(也称为“多路复用”)拆分应用程序的逻辑。在同一个 Socket.IO 服务器实例下创建多个命名空间,每个命名空间都有自己的事件处理程序、房间、中间件以及与客户端的 Socket 连接。
注意
命名空间之间是相互隔离的,客户端可以连接到特定的命名空间,而不受其他命名空间的影响。
主命名空间
主命名空间 /
是默认的命名空间,通常称为根命名空间。这个命名空间是在创建 Socket.IO 服务器实例时自动创建的,它是服务器实例的一部分,不需要显式创建。
主命名空间用于处理来自客户端的连接请求和全局事件,它是服务器上所有命名空间的根节点。所有未分配到特定命名空间的 Socket 连接都属于主命名空间。
服务器实例 io
继承了主命名空间 /
的所有 API 。
io.on('connection', (socket) => {})
io.use((socket, next) => {
next()
})
io.emit('hello')
//相当于
io.of('/').on('connection', (socket) => {})
io.of('/').use((socket, next) => {
next()
})
io.of('/').emit('hello')
自定义命名空间
要设置自定义命名空间,您可以 of 在服务器端调用该函数:
const nsp = io.of('/my-namespace')
nsp.on('connection', (socket) => {
console.log('someone connected')
})
nsp.emit('hi', 'everyone!')
动态命名空间
动态命名空间是指可以根据特定规则动态创建命名空间的能力。这允许你在运行时根据需要创建命名空间,以满足特定的应用场景或业务逻辑。通常,动态命名空间使用正则表达式或函数来匹配特定的命名空间名称。
- 正则表达式
const dynamicNamespace = io.of(/^\/dynamic-\d+$/)
- 函数
const dynamicNamespace = io.of(/^\/dynamic-\d+$/).on('connection', (socket) => {
const namespace = socket.nsp
})
TIP
- 在
connection
事件中可以访问新的命名空间:
const dynamicNamespace = io.of(/^\/dynamic-\d+$/).on('connection', (socket) => {
const namespace = socket.nsp
})
of()
方法返回值称为父命名空间,可以从中进行:
- 注册中间件
const parentNamespace = io.of(/^\/dynamic-\d+$/)
parentNamespace.use((socket, next) => {
next()
})
- 广播事件
const parentNamespace = io.of(/^\/dynamic-\d+$/)
parentNamespace.emit('hello') // will be sent to users in /dynamic-1, /dynamic-2, ...
WARNING
现有命名空间优先于动态命名空间。
// 创建 "dynamic-101" 命名空间
const dynamicNamespace = io.of('/dynamic-101')
// 创建动态命名空间
const dynamicNamespace = io.of(/^\/dynamic-\d+$/) // 创建 "dynamic-101" 命名空间
dynamicNamespace.on('connection', (socket) => {
// 这里将不会调用 "dynamic-101" 命名空间上的连接
})
API
namespace.name
: 返回命名空间的名称namespace.sockets
: 返回一个对象,其中包含了当前连接到该命名空间的所有 Socket 实例namespace.adapter
: 返回命名空间使用的适配器(例如,Redis 适配器)namespace.to(room)
: 返回一个命名空间对象,用于将消息发送给限制指定的房间namespace.in(room)
: 返回一个命名空间对象,用于将消息发送给加入指定的房间namespace.except(rooms)
: 返回一个命名空间对象,用于排除指定房间,即发送消息给除了指定房间之外的所有房间namespace.emit(eventName[, ...args])
:向该命名空间的所有连接客户端发送指定事件和数据namespace.timeout(value)
: 设置该命名空间上的超时时限,超过该时限将自动断开连接namespace.allSockets()
: 返回一个包含所有连接到该命名空间的 Socket 实例 ID 的 Set 对象namespace.use(fn)
: 添加中间件函数,用于在命名空间级别处理连接namespace.socketsJoin(rooms)
: 将所有连接到该命名空间的 Socket 实例加入指定的房间namespace.socketsLeave(rooms)
: 将所有连接到该命名空间的 Socket 实例离开指定的房间namespace.disconnectSockets([close])
: 断开该命名空间上的所有连接。close 参数用于指定是否关闭连接namespace.fetchSockets()
: 返回一个包含所有连接到该命名空间的 Socket 实例的数组namespace.serverSideEmit(eventName[, ...args][, ack])
: 用于服务器端发射事件到该命名空间,不同于 emit 方法,该方法不需要来自客户端的请求
事件
connection
: 在来自客户端的连接上触发connect
: 类似connection
修饰符
volatile
: 为后续事件发出设置一个修饰符,如果客户端未准备好接收消息(由于网络速度缓慢或其他问题,或者因为它们通过长轮询连接并且处于请求-响应周期的中间),事件数据可能会丢失local
: 为后续事件发生设置一个修饰符,即事件数据将仅广播到当前节点(当扩展到多个节点时)
Socket 实例
Socket 是与客户端交互的基础类。它继承了 Node.jsEventEmitter 的所有方法,例如 emit, on, once 或 removeListener 。
APIs
- 发送:
socket.emit(eventName[, ...args][, ack])
: 用于向当前连接发送一个指定事件(event)和相关数据(data)socket.volatile.emit
: 用于标记一个消息是否是 "volatile" 消息,如果消息无法立即送达,它可能会被忽略,不会被重新发送
- 广播:
socket.broadcast.emit(event, data)
: 向除当前 socket 以外的所有连接到 Socket.IO 服务器的客户端发送特定事件。也就是向所有其他客户端广播事件
- 监听:
socket.on(eventName, callback)
: 用于监听当前连接接收到的特定事件,并指定回调函数来处理事件的数据socket.once(eventName, callback)
: 用于监听一次当前连接接收到的特定事件,并指定回调函数来处理事件的数据socket.removeListener(eventName, listener)
: 从客户端 Socket 实例中移除特定事件的特定监听器,以停止监听该事件socket.removeAllListeners([eventName])
: 客户端 Socket 实例中移除所有事件的监听器,或者如果提供了事件名称,则只移除指定事件的监听器socket.onAny(callback)
: 添加一个通用事件监听器,用于捕获和处理所有事件,不论事件名称是什么socket.prependAny(callback)
: 添加一个通用事件监听器,但将其插入到事件监听器列表的最前面,以便它首先处理事件通知
- 房间:
socket.rooms
: 用于获取当前连接加入的所有房间(Rooms)的名称,返回一个包含房间名称的 Set 对象socket.join(room)
: 用于将当前连接加入指定的房间(room)socket.leave(room)
: 用于将当前连接从指定的房间中移除socket.to(room)
: 用于将当前连接限制在指定的房间(room)内,以便只有在该房间中的连接才能接收到该房间的消息socket.in(room)
: 用于将连接加入到特定房间,以便连接可以接收该房间内的消息
- 常用:
socket.id
: 每个新连接都分配有一个随机的 20 个字符的唯一标识符(不能覆盖此标识符),此标识符与客户端的值同步socket.use(fn)
:注册一个 Socket 中间件socket.timeout(value)
: 配置客户端连接的超时时间socket.handshake
: 提供与当前连接的握手(handshake)相关的信息和数据。握手数据包括了连接时客户端发送的一些元信息,如查询参数、HTTP 请求头等
服务器实例与 Socket 实例关系
- Socket.IO 服务器实例(io 实例):
- Socket.IO 服务器实例是整个 Socket.IO 服务器的核心,它负责管理所有的连接、事件处理、广播消息等。
- 一个 Socket.IO 服务器实例可以管理多个连接,它是服务器级别的对象,负责整个 Socket.IO 应用程序的管理和控制。
- 通常,你会在 Node.js 服务器上创建一个 Socket.IO 服务器实例,然后通过该实例来配置和管理 Socket.IO 服务器的行为。
- Socket 实例:
- Socket 实例代表一个与服务器建立的 WebSocket 连接。每当客户端与服务器建立连接时,都会创建一个对应的 Socket 实例。
- 每个 Socket 实例都与一个特定的客户端连接相关联,它负责处理该连接的事件、消息传递等。
- 可以将 Socket 实例视为连接的句柄,用于与特定客户端进行双向通信。
【关系总结:】
- 一个 Socket.IO 服务器实例(io 实例)可以管理多个 Socket 实例,每个 Socket 实例代表一个客户端连接。
- Socket.IO 服务器实例是服务器级别的,用于管理全局的连接和事件。
- Socket 实例是连接级别的,每个连接都有一个对应的 Socket 实例,用于处理该连接的事件和消息。
事件
disconnect
: 断开连接时触发
io.on('connection', (socket) => {
socket.on('disconnect', (reason) => {
// ...
})
})
disconnecting
:当客户端将要断开连接时触发(但尚未离开 rooms )
io.on('connection', (socket) => {
socket.on('disconnecting', (reason) => {
console.log(socket.rooms) // Set { ... }
})
})
修饰符
broadcast
:为后续事件发生设置一个修饰符,即事件数据将仅广播到除了事件发起者之外的所有连接客户端volatile
: 为后续事件发出设置一个修饰符,如果客户端未准备好接收消息(由于网络速度缓慢或其他问题,或者因为它们通过长轮询连接并且处于请求-响应周期的中间),事件数据可能会丢失
客户端
安装
- 独立构建:默认情况下,Socket.IO 服务器在
/socket.io/socket.io.js
<script src="/socket.io/socket.io.js"></script>
<script>
const socket = io();
</script>
- 使用 NPM:已内置 TypeScript 类型,不再需要来自 @types/socket.io-client 的类型
npm install socket.io-client
初始化
- 使用
<script>
引入
<script src="/socket.io/socket.io.js"></script>
TIP
socket.io.js
是由 Socket.io 服务器根据服务器端配置和需要自动生成的。当你启动 Socket.io 服务器时,它会自动提供 /socket.io/socket.io.js
路径(默认路径),该路径将映射到服务器上的 Socket.io 客户端脚本。
这意味着你不需要手动创建或引入 socket.io.js
文件,只需在 HTML 中引入 <script src="/socket.io/socket.io.js"></script>
,当客户端加载该脚本时,它将自动连接到 Socket.io 服务器并与之通信。
- 使用 NPM
const { io } = require('socket.io-client')
连接同一域
如果您的前端与您的服务器在同一个域上提供服务,服务器 URL 将从 window.location 对象中推导出来。
const socket = io([opts])
连接不同的域
如果您的前端不是来自与服务器相同的域,则必须传递服务器的 URL。
const socket = io('https://server-domain.com'[, opts])
注意
在这种情况下,请确保在服务器上启用 跨域资源共享 (CORS)。
连接自定义命名空间
默认情况下,客户端将连接到主命名空间 /
。
// same origin version
const socket = io('/admin'[, opts])
// cross origin version
const socket = io('https://server-domain.com/admin'[, opts])
opts 选项
- Socket.io
forceNew: 是否创建新的 Manager 实例,Manager 实例负责与服务器的低级连接(使用 HTTP 长轮询或 WebSocket 建立),默认值 false
multiplex: 是否重用现有的 Manager 实例(与 forceNew 相反)
auth: 用于身份验证和授权,以确保客户端有权访问服务器的资源或执行操作
- Engine.io
- transports: 用于指定客户端与服务器之间使用的传输协议,默认值
["polling", "websocket"]
- upgrade: 客户端是否应尝试将传输从 HTTP 长轮询升级到更好的传输,默认值 true
- rememberUpgrade: 用于控制在初始 HTTP 长轮询(HTTP Long Polling)传输失败后,是否记住传输升级的尝试,默认值 false
- path: 用于指定客户端连接到服务器端 WebSocket 的路径(注意:除非在两者之间使用路径重写代理,否则服务器和客户端值必须匹配),默认值
/socket.io/
- query: 用于指定连接查询参数(query parameters),这些参数会被发送到服务器端以在建立连接时携带额外的数据
- extraHeaders: 自定义标头(在服务器端的 socket.handshake.headers 对象中找到。注意:浏览器中的 WebSocket API 不允许提供自定义标头)
- withCredentials: 是否应使用 Cookie、Authorization 标头或 TLS 客户端证书等凭据发出跨站点请求,默认值 false (设置为 true 时不能使用
origin: *
) - forceBase64: 是否对通过 WebSocket 发送的二进制内容强制使用 base64 编码(始终启用 HTTP 长轮询),默认值 false
- timestampRequests: 是否将时间戳查询参数添加到每个请求(用于缓存无效化),默认值 true
- timestampParam: 用于指定在握手时发送到服务器的时间戳参数的名称,默认值 “t”
- closeOnBeforeunload: 用于控制在页面即将卸载(beforeunload)时是否关闭 Socket 连接,默认值 true
- protocols: 用于指定客户端与服务器之间通信时使用的子协议(protocols)
- autoUnref: 是否自动将连接定时器设置为 "unref" 模式,默认值 false
- Manager 实例
- reconnection: 用于控制是否允许客户端在连接断开后自动尝试重新连接到服务器,默认值 true
- reconnectionAttempts: 用于指定在连接断开后尝试重新连接的最大次数
- reconnectionDelay: 用于指定在连接断开后的第一次重新连接尝试之前的延迟时间(以毫秒为单位),默认值 1,000 毫秒(1 秒)
- reconnectionDelayMax: 用于指定重新连接尝试之间的最大延迟时间(以毫秒为单位),默认值为 5,000 毫秒(5 秒)
- randomizationFactor: 用于指定重新连接延迟的随机因子,以防止多个客户端在相同时间重新连接,默认值为 0.5
- timeout: 用于指定连接超时时间(以毫秒为单位)。如果在指定的时间内无法建立连接,则连接会被视为失败,默认值为 20,000 毫秒(20 秒)
- autoConnect: 用于控制是否在创建 Socket 实例后自动连接到服务器,默认值为 true
- parser: 用于指定自定义消息解析器,以允许处理不同的消息格式,默认值
require("socket.io-parser")
Manager 实例
通过 socket.io-client
库中的 Manager 构造函数来手动创建客户端 Manager 实例,用于配置和管理客户端连接的行为。Manager 实例管理 Engine.IO 客户端,该实例是建立与服务器连接的底层引擎(通过使用 WebSocket 或 HTTP 长轮询等传输)。一个 Manager 可以被多个 Socket 使用。
请注意,在大多数情况下,不会直接使用 Manager 实例,而是使用 Socket 实例。
APIs
new Manager(url[, options])
: 用于手动创建一个 Manager 实例以控制客户端与服务器的连接行为manager.reconnection([value])
: 用于配置客户端 Manager 实例启用或禁用重新连接功能,并设置重新连接的选项manager.reconnectionAttempts([value])
: 用于配置客户端 Manager 实例重新连接的最大尝试次数manager.reconnectionDelay([value])
: 配置重新连接时的延迟时间manager.reconnectionDelayMax([value])
: 配置重新连接时的最大延迟时间manager.timeout([value])
: 配置连接超时时间manager.open([callback])
: 手动触发连接操作manager.connect([callback])
: 手动触发重新连接操作(如果重新连接已被禁用)manager.socket(nsp, options)
: 创建一个连接到指定命名空间 nsp 的客户端 Socket 实例,并可选地配置一些选项。
事件
error
: 在连接错误时触发reconnect
: 成功重新连接后触发reconnect_attempt
: 在尝试重新连接时被触发reconnect_error
: 在重新连接尝试错误时触发reconnect_failed
: 无法在 reconnectionAttempts 中重新连接时触发ping
: 从服务器接收 ping 数据包时触发
Socket 实例
通过调用 io()
函数来创建客户端 Socket 实例。用于表示客户端与服务器之间建立连接、发送和接收消息,以及执行与服务器通信相关的操作,使客户端能够与服务器进行双向实时通信。通常情况下,一个客户端应用程序只需要一个客户端 Socket 实例来与服务器通信,但你可以根据需要创建多个 Socket 实例,每个实例可以连接到不同的命名空间或服务器端端点。
APIs
- 实例化
io([url][, options])
: 用于创建 Socket.IO 客户端 Socket 实例(即:Manager 实例)
- 发送:
socket.emit(eventName[, ...args][, ack])
: 向由字符串名称标识的 Socket 发出事件socket.volatile.emit
: 用于标记消息为“不稳定的”或“临时的”,这意味着服务器应该尽快将消息发送给接收方,而不必等待将其放入消息队列中
- 监听:
socket.on(eventName, callback)
: 监听由字符串名称标识的 Socket 发出的事件socket.off([eventName][, listener])
:删除由字符串名称标识的 Socket 发出的事件socket.offAny([listener])
: 取消订阅所有事件的通用监听器(不限于特定事件名称)socket.offAnyOutgoing([listener])
: 取消订阅所有发出事件的通用监听器(即,发送事件前的监听器)socket.onAny(callback)
: 添加一个通用事件监听器,用于捕获和处理所有事件,不论事件名称是什么socket.prependAny(callback)
: 添加一个通用事件监听器,但将其插入到事件监听器列表的最前面,以便它首先处理事件通知
- 常用:
socket.id
: Socket 会话的唯一标识符。 connect 在触发事件后设置,并在 reconnect 事件后更新socket.connected
: Socket 是否连接到服务器socket.disconnected
: Socket 是否与服务器断开连接socket.io
: 对基础 Manager 实例的引用socket.connect()
: 手动连接 Socketsocket.compress(value)
:配置客户端是否启用消息压缩socket.timeout(value)
:配置客户端连接的超时时间socket.disconnect()
:断开客户端与服务器的连接socket.close()
:用于断开客户端与服务器的连接,与 socket.disconnect() 相同
事件
connect
: 在连接到命名空间时触发(包括成功的重新连接)disconnect
: 断开连接时触发connect_error
: 发生命名空间中间件错误时触发
TIP
在 Socket.IO 中,有两个关键的实体:服务器端服务器实例和 Socket 实例,以及客户端的 Socket 实例。
服务器端服务器实例
服务器端服务器实例是 Socket.IO 服务器的主要实体。它是整个 Socket.IO 服务器的控制中心,负责接收客户端连接、处理连接请求、管理房间、发送广播消息等核心功能。
作用:
- 接受和处理客户端的连接请求。
- 管理和维护所有连接的状态。
- 处理消息传递和广播,允许服务器向所有连接或特定房间的连接发送消息。
- 提供用于创建命名空间(Namespace)的功能,以便将不同部分的应用程序隔离开来。
- 允许配置服务器端选项,如传输协议、消息压缩等。
服务器端 Socket 实例
Socket 实例是 Socket.IO 中表示单个客户端连接的对象。每个客户端连接都对应一个 Socket 实例,它代表了客户端与服务器之间的实时通信通道。
作用:
- 用于在服务器和客户端之间双向传递消息。
- 允许连接加入和离开房间,以实现房间级别的消息分发。
- 提供连接的相关信息,如连接的 ID、握手数据等。
客户端 Socket 实例
客户端 Socket 实例是 Socket.IO 客户端库(通常是在浏览器中运行的 JavaScript 代码)中的对象,用于向服务器发送事件和从服务器接收事件的接口。。
作用:
- 用于在客户端与服务器之间双向传递消息。
- 允许客户端监听来自服务器的消息,并发送消息到服务器。
- 允许客户端加入和离开房间,以实现房间级别的消息分发。
- 提供连接的相关信息,如连接的 ID、握手数据等。
修饰符
volatile
: 为后续事件发出设置一个修饰符,如果客户端未准备好接收消息(由于网络速度缓慢或其他问题,或者因为它们通过长轮询连接并且处于请求-响应周期的中间),事件数据可能会丢失
适配器
Socket.IO 适配器(Adapter)是一个服务器端组件,用于将事件广播到所有或部分 Socket.IO 客户端实现多个 Socket.IO 服务器之间的通信和共享数据。适配器允许不同的 Socket.IO 服务器实例之间共享状态信息,以便它们能够协同工作并传递实时数据。
Socket.IO 提供了多种适配器实现,以满足不同的需求,包括以下一些常见的适配器:
Redis 适配器:使用 Redis 数据库来共享状态和传递消息。Redis 是一个高性能的键值存储数据库,常用于实时应用的数据同步和消息传递。
MongoDB 适配器:使用 MongoDB 数据库来共享状态和传递消息。MongoDB 是一种流行的 NoSQL 数据库,可以用于存储 Socket.IO 的房间和消息数据。
WARNING
通常情况下,为了保持简单性和性能,人们更倾向于使用 MongoDB 原生驱动程序来实现 MongoDB 适配器。
如果你想使用 Mongoose 作为适配器的一部分,你需要编写自定义的适配器代码来实现 Mongoose 与 Socket.IO 的集成。这可能涉及到将适配器的事件模型与 Mongoose 模型进行映射,以便在不同的 Socket.IO 服务器之间传递事件和消息。
- 内存适配器:将状态存储在内存中,适用于单服务器部署或测试环境,但不适合分布式部署。
发射器
Socket.IO 适配器中的 Emitter 对象充当了事件的发射器,它继承了 Node.js 的 EventEmitter 类,因此可以使用 Node.js 的事件 API,如 on、emit、removeListener 等。这使得在 Socket.IO 的适配器中能够轻松地处理事件和消息传递,同时与 Node.js 中的其他事件驱动模块和库保持一致性。
发射器还支持在 socket.io@4.0.0 中添加的实用程序方法:
emitter.socketsJoin(room[, callback])
: 用于将指定的 Socket.IO 客户端加入到一个指定的房间(room)中emitter.socketsLeave(room[, callback])
: 用于将指定的 Socket.IO 客户端从一个指定的房间中移除emitter.disconnectSockets(close[, callback])
: 是否断开与适配器关联的所有 Socket.IO 客户端的连接,默认值 false (只会断开连接但不关闭)emitter.serverSideEmit(eventName, data[, rooms, options])
: 用于从服务器端向指定的房间或客户端发送自定义事件和数据
API
server.adapter([value])
:用于获取或设置 Socket.IO 适配器(Adapter)namespace.adapter
: 用于命名空间的“适配器”,可以使用io.of("/").adapter
访问主命名空间的适配器实例(每个 Adapter 实例都会发出“房间”事件)
管理界面
Socket.IO 管理 UI 可用于概述 Socket.IO 部署的状态。
服务器端
- 安装
npm i @socket.io/admin-ui
- 使用
const { createServer } = require('http')
const { Server } = require('socket.io')
const { instrument } = require('@socket.io/admin-ui')
const httpServer = createServer()
const io = new Server(httpServer, {
cors: {
origin: ['https://admin.socket.io'],
credentials: true
}
})
instrument(io, {
auth: false
})
httpServer.listen(3000)
APIs
auth
(必需): 是否启用身份验证;若启用,则需要设置type
username
password
字段且password
仅支持 bcrypt 加密namespaceName
: 为处理管理任务而创建的命名空间的名称,默认值/admin
readonly
: 是否将管理 UI 置于只读模式(不允许加入、离开或断开连接),默认值 falseserverId
:指定 Socket 服务器的 ID (在同一台机器上有多个 Socket.IO 服务器),默认值require("os").hostname()
store
: 用于存储会话 ID (用户不必在重新连接时重新键入凭据),默认值new InMemoryStore()
mode
: 在生产模式下,服务器不会发送有关套接字实例和房间的所有详细信息,从而减少检测的内存占用,默认值 development
客户端
前往 https://admin.socket.io ,输入服务器的 URL(例如:https://example.com/admin)和凭据(如果有的话)。
PM2 启动
安装
npm install -g @socket.io/pm2
注意
如果 pm2 已安装,则必须先将其删除。@socket.io/pm2
可以用作 pm2 的替代品,并支持 pm2 的所有命令。唯一的区别来自这个 commit。
使用
// app.js
const { createServer } = require('http')
const { Server } = require('socket.io')
const { createAdapter } = require('@socket.io/cluster-adapter')
const { setupWorker } = require('@socket.io/sticky')
const httpServer = createServer()
const io = new Server(httpServer)
io.adapter(createAdapter())
setupWorker(io)
io.on('connection', (socket) => {
console.log(`connect ${socket.id}`)
})
// ecosystem.config.js
module.exports = {
apps: [
{
script: 'worker.js',
instances: 'max',
exec_mode: 'cluster'
}
]
}
TypeScript
服务器
// 类型声明
interface ServerToClientEvents {
noArg: () => void
basicEmit: (a: number, b: string, c: Buffer) => void
withAck: (d: string, callback: (e: number) => void) => void
}
interface ClientToServerEvents {
hello: () => void
}
interface InterServerEvents {
ping: () => void
}
interface SocketData {
name: string
age: number
}
// 服务器初始化
const io = new Server<
ClientToServerEvents,
ServerToClientEvents,
InterServerEvents,
SocketData
>()
// 发送和广播事件
io.on('connection', (socket) => {
socket.emit('noArg')
socket.emit('basicEmit', 1, '2', Buffer.from([3]))
socket.emit('withAck', '4', (e) => {
// e is inferred as number
})
// works when broadcast to all
io.emit('noArg')
// works when broadcasting to a room
io.to('room1').emit('basicEmit', 1, '2', Buffer.from([3]))
// 接收事件
socket.on('hello', () => {
// ...
})
})
// 服务器间通信
io.serverSideEmit('ping')
io.on('ping', () => {
// ...
})
// 键入 socket.data 属性
io.on('connection', (socket) => {
socket.data.name = 'john'
socket.data.age = 42
})
客户端
const { io, Socket } = require('socket.io-client')
// 类型声明
const socket: Socket<ServerToClientEvents, ClientToServerEvents> = io()
// 发送事件
socket.emit('hello')
// 接收事件
socket.on('noArg', () => {
// ...
})
socket.on('basicEmit', (a, b, c) => {
// a is inferred as number, b as string and c as buffer
})
socket.on('withAck', (d, callback) => {
// d is inferred as string and callback as a function that takes a number as argument
})