送达保证
消息顺序
¥Message ordering
Socket.IO 确实保证消息排序,无论使用哪种底层传输(即使在从 HTTP 长轮询升级到 WebSocket 期间)。
¥Socket.IO does guarantee message ordering, no matter which low-level transport is used (even during an upgrade from HTTP long-polling to WebSocket).
这是由于:
¥This is achieved thanks to:
底层 TCP 连接提供的保证
¥the guarantees provided by the underlying TCP connection
升级机制 的精心设计
¥the careful design of the upgrade mechanism
示例:
¥Example:
socket.emit("event1");
socket.emit("event2");
socket.emit("event3");
在上面的示例中,事件将始终以相同的顺序被另一方接收(前提是它们实际到达,请参见 如下)。
¥In the example above, the events will always be received in the same order by the other side (provided that they actually arrive, see below).
消息到达
¥Message arrival
最多一次
¥At most once
默认情况下,Socket.IO 提供最多一次的交付保证:
¥By default, Socket.IO provides an at most once guarantee of delivery:
如果在发送事件时连接中断,则不能保证另一方已收到该事件,并且重新连接时不会重试
¥if the connection is broken while an event is being sent, then there is no guarantee that the other side has received it and there will be no retry upon reconnection
断开连接的客户端将 缓冲事件直到重新连接(尽管上一点仍然适用)
¥a disconnected client will buffer events until reconnection (though the previous point still applies)
服务器上没有这样的缓冲区,这意味着断开连接的客户端遗漏的任何事件在重新连接时都不会传输到该客户端
¥there is no such buffer on the server, which means that any event that was missed by a disconnected client will not be transmitted to that client upon reconnection
截至目前,你的应用中必须实现额外的交付保证。
¥As of now, additional delivery guarantees must be implemented in your application.
至少一次
¥At least once
从客户端到服务器
¥From client to server
从客户端来看,你可以通过 回执和超时 实现至少一次保证:
¥From the client side, you can achieve an at least once guarantee with acknowledgements and timeouts:
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");
在上面的示例中,客户端将在给定的延迟后重试发送事件,因此服务器可能会多次收到相同的事件。
¥In the example above, the client will retry to send the event after a given delay, so the server might receive the same event several times.
即使在这种情况下,如果用户刷新其选项卡,任何待处理事件都将丢失。
¥Even in that case, any pending event will be lost if the user refreshes its tab.
从服务器到客户端
¥From server to client
对于服务器发送的事件,可以通过以下方式实现额外的传递保证:
¥For events sent by the server, additional delivery guarantees can be implemented by:
为每个事件分配唯一的 ID
¥assigning a unique ID to each event
将事件保存在数据库中
¥persisting the events in a database
在客户端存储最后接收到的事件的偏移量,并在重新连接时发送它
¥storing the offset of the last received event on the client side, and send it upon reconnection
示例:
¥Example:
客户端
¥Client
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;
});
服务器
¥Server
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()
)是特定于数据库的,留给读者作为练习。
¥Implementing the missing methods (fetchMissedEventsFromDatabase()
, generateUniqueId()
and persistEventToDatabase()
) is database-specific and is left as an exercise for the reader.
参考:
¥References:
socket.auth
(客户端)¥
socket.auth
(client)socket.handshake
(服务器)¥
socket.handshake
(server)