消息传递保证
消息排序
Socket.IO 保证消息排序,无论使用哪种底层传输(即使在从 HTTP 长轮询升级到 WebSocket 期间)。
这是通过以下方式实现的
- 底层 TCP 连接提供的保证
- 仔细设计的 升级机制
示例
socket.emit("event1");
socket.emit("event2");
socket.emit("event3");
在上面的示例中,事件将始终以相同的顺序被另一方接收(前提是它们确实到达,请参阅 下面)。
消息到达
最多一次
默认情况下,Socket.IO 提供了最多一次的传递保证
- 如果在发送事件时连接断开,则无法保证另一方已收到该事件,并且在重新连接时不会重试
- 断开的客户端将 缓冲事件直到重新连接(尽管前一点仍然适用)
- 服务器上没有这样的缓冲区,这意味着断开的客户端错过的任何事件都不会在重新连接时传输到该客户端
信息
截至目前,必须在您的应用程序中实现额外的传递保证。
至少一次
从客户端到服务器
从客户端,您可以使用 确认和超时 实现至少一次的保证
function emit(socket, event, arg) {
socket.timeout(2000).emit(event, arg, (err) => {
if (err) {
// no ack from the server, let's retry
emit(socket, event, arg);
}
});
}
emit(socket, "foo", "bar");
在上面的示例中,客户端将在给定延迟后重试发送事件,因此服务器可能会收到同一个事件多次。
注意
即使在这种情况下,如果用户刷新其选项卡,任何挂起的事件都将丢失。
从服务器到客户端
对于服务器发送的事件,可以通过以下方式实现额外的传递保证
- 为每个事件分配一个唯一的 ID
- 将事件持久化到数据库中
- 存储客户端上最后一个接收事件的偏移量,并在重新连接时发送
示例
客户端
const socket = io({
auth: {
offset: undefined
}
});
socket.on("my-event", ({ id, data }) => {
// do something with the data, and then update the offset
socket.auth.offset = id;
});
服务器
io.on("connection", async (socket) => {
const offset = socket.handshake.auth.offset;
if (offset) {
// this is a reconnection
for (const event of await fetchMissedEventsFromDatabase(offset)) {
socket.emit("my-event", event);
}
} else {
// this is a first connection
}
});
setInterval(async () => {
const event = {
id: generateUniqueId(),
data: new Date().toISOString()
}
await persistEventToDatabase(event);
io.emit("my-event", event);
}, 1000);
实现缺失的方法(fetchMissedEventsFromDatabase()
、generateUniqueId()
和 persistEventToDatabase()
)是特定于数据库的,留给读者练习。
参考
socket.auth
(客户端)socket.handshake
(服务器)