node.js Websocket 传输可靠性(重新连接期间 Socket.io 数据丢失)
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/20685208/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Websocket transport reliability (Socket.io data loss during reconnection)
提问by igorpavlov
Used
用过的
NodeJS, Socket.io
NodeJS、Socket.io
Problem
问题
Imagine there are 2 users U1& U2, connected to an app via Socket.io. The algorithm is the following:
假设有 2 个用户U1和U2通过 Socket.io 连接到一个应用程序。算法如下:
- U1completely loses Internet connection (ex. switches Internet off)
- U2sends a message to U1.
- U1does not receive the message yet, because the Internet is down
- Serverdetects U1disconnection by heartbeat timeout
- U1reconnects to socket.io
- U1never receives the message from U2- it is lost on Step 4 I guess.
- U1完全失去互联网连接(例如关闭互联网)
- U2向U1发送消息。
- U1还没有收到消息,因为互联网已关闭
- 服务器通过心跳超时检测U1断开
- U1重新连接到 socket.io
- U1永远不会收到来自U2的消息- 我猜它在第 4 步中丢失了。
Possible explanation
可能的解释
I think I understand why it happens:
我想我明白为什么会这样:
- on Step 4 Serverkills socket instance and the queue of messages to U1as well
- Moreover on Step 5 U1and Servercreate new connection (it is not reused), so even if message is still queued, the previous connection is lost anyway.
- 第4步服务器杀死Socket实例和消息队列U1以及
- 此外,在第 5 步U1和服务器创建新连接(未重用),因此即使消息仍在排队,无论如何之前的连接都会丢失。
Need help
需要帮忙
How can I prevent this kind of data loss? I have to use hearbeats, because I do not people hang in app forever. Also I must still give a possibility to reconnect, because when I deploy a new version of app I want zero downtime.
如何防止此类数据丢失?我必须使用心跳,因为我不会让人们永远挂在应用程序中。此外,我仍然必须提供重新连接的可能性,因为当我部署新版本的应用程序时,我希望停机时间为零。
P.S. The thing I call "message" is not just a text message I can store in database, but valuable system message, which delivery must be guaranteed, or UI screws up.
PS 我所说的“消息”不仅仅是我可以存储在数据库中的文本消息,而是有价值的系统消息,必须保证交付,否则 UI 会出错。
Thanks!
谢谢!
Addition 1
加1
I do already have a user account system. Moreover, my application is already complex. Adding offline/online statuses won't help, because I already have this kind of stuff. The problem is different.
我已经有一个用户帐户系统。而且,我的申请已经很复杂了。添加离线/在线状态无济于事,因为我已经有了这种东西。问题是不同的。
Check out step 2. On this step we technically cannot say if U1 goes offline, he just loses connection lets say for 2 seconds, probably because of bad internet. So U2 sends him a message, but U1 doesn't receive it because internet is still down for him (step 3). Step 4 is needed to detect offline users, lets say, the timeout is 60 seconds. Eventually in another 10 seconds internet connection for U1 is up and he reconnects to socket.io. But the message from U2 is lost in space because on server U1 was disconnected by timeout.
查看第 2 步。在这一步我们技术上不能说 U1 是否脱机,他只是失去连接,可以说 2 秒钟,可能是因为互联网不好。所以 U2 向他发送了一条消息,但 U1 没有收到它,因为他的互联网仍然处于关闭状态(第 3 步)。第 4 步需要检测离线用户,假设超时为 60 秒。最终,在 10 秒后,U1 的互联网连接建立,他重新连接到 socket.io。但是来自 U2 的消息在空间中丢失了,因为服务器 U1 因超时而断开连接。
That is the problem, I wan't 100% delivery.
这就是问题所在,我不想 100% 交货。
Solution
解决方案
- Collect an emit (emit name and data) in {} user, identified by random emitID. Send emit
- Confirm the emit on client side (send emit back to server with emitID)
- If confirmed - delete object from {} identified by emitID
- If user reconnected - check {} for this user and loop through it executing Step 1 for each object in {}
- When disconnected or/and connected flush {} for user if necessary
- 在 {} 用户中收集一个发射(发射名称和数据),由随机发射 ID 标识。发送发射
- 在客户端确认发射(使用emitID将发射发送回服务器)
- 如果确认 - 从由emitID 标识的{} 中删除对象
- 如果用户重新连接 - 检查此用户的 {} 并循环遍历它,为 {} 中的每个对象执行步骤 1
- 如有必要,断开连接或/和连接时为用户刷新 {}
// Server
const pendingEmits = {};
socket.on('reconnection', () => resendAllPendingLimits);
socket.on('confirm', (emitID) => { delete(pendingEmits[emitID]); });
// Client
socket.on('something', () => {
socket.emit('confirm', emitID);
});
Solution 2 (kinda)
解决方案2(有点)
Added 1 Feb 2020.
2020 年 2 月 1 日添加。
While this is not really a solution for Websockets, someone may still find it handy. We migrated from Websockets to SSE + Ajax. SSE allows you to connect from a client to keep a persistent TCP connection and receive messages from a server in realtime. To send messages from a client to a server - simply use Ajax. There are disadvantages like latency and overhead, but SSE guarantees reliability because it is a TCP connection.
虽然这并不是 Websockets 的真正解决方案,但仍然有人会觉得它很方便。我们从 Websockets 迁移到 SSE + Ajax。SSE 允许您从客户端连接以保持持久的 TCP 连接并实时接收来自服务器的消息。要将消息从客户端发送到服务器 - 只需使用 Ajax。有延迟和开销等缺点,但 SSE 保证可靠性,因为它是 TCP 连接。
Since we use Express we use this library for SSE https://github.com/dpskvn/express-sse, but you can choose the one that fits you.
由于我们使用 Express,因此我们将这个库用于 SSE https://github.com/dpskvn/express-sse,但您可以选择适合您的那个。
SSE is not supported in IE and most Edge versions, so you would need a polyfill: https://github.com/Yaffle/EventSource.
IE 和大多数 Edge 版本不支持 SSE,因此您需要一个 polyfill:https: //github.com/Yaffle/EventSource。
回答by Michelle Tilley
Others have hinted at this in other answers and comments, but the root problem is that Socket.IO is just a delivery mechanism, and you cannotdepend on it alone for reliable delivery. The only person who knows for sure that a message has been successfully delivered to the client is the client itself. For this kind of system, I would recommend making the following assertions:
其他人已经在其他答案和评论中暗示了这一点,但根本问题是 Socket.IO 只是一种传递机制,您不能单独依赖它来进行可靠传递。唯一知道消息已成功传递给客户端的人是客户端本身。对于这种系统,我建议做出以下断言:
- Messages aren't sent directly to clients; instead, they get sent to the server and stored in some kind of data store.
- Clients are responsible for asking "what did I miss" when they reconnect, and will query the stored messages in the data store to update their state.
- If a message is sent to the server while the recipient client is connected,that message will be sent in real time to the client.
- 消息不会直接发送给客户端;相反,它们被发送到服务器并存储在某种数据存储中。
- 客户端负责在重新连接时询问“我错过了什么”,并将查询数据存储中存储的消息以更新其状态。
- 如果在接收客户端连接时将消息发送到服务器,则该消息将实时发送到客户端。
Of course, depending on your application's needs, you can tune pieces of this--for example, you can use, say, a Redis list or sorted set for the messages, and clear them out if you know for a fact a client is up to date.
当然,根据您的应用程序的需要,您可以调整其中的部分——例如,您可以使用 Redis 列表或消息的排序集,如果您知道客户端已启动,则将它们清除迄今为止。
Here are a couple of examples:
下面是几个例子:
Happy path:
快乐之路:
- U1 and U2 are both connected to the system.
- U2 sends a message to the server that U1 should receive.
- The server stores the message in some kind of persistent store, marking it for U1 with some kind of timestamp or sequential ID.
- The server sends the message to U1 via Socket.IO.
- U1's client confirms (perhaps via a Socket.IO callback) that it received the message.
- The server deletes the persisted message from the data store.
- U1 和 U2 都连接到系统。
- U2 向服务器发送 U1 应该接收的消息。
- 服务器将消息存储在某种持久存储中,并使用某种时间戳或顺序 ID 将其标记为 U1。
- 服务器通过 Socket.IO 将消息发送到 U1。
- U1 的客户端确认(可能通过 Socket.IO 回调)它收到了消息。
- 服务器从数据存储中删除持久化消息。
Offline path:
离线路径:
- U1 looses internet connectivity.
- U2 sends a message to the server that U1 should receive.
- The server stores the message in some kind of persistent store, marking it for U1 with some kind of timestamp or sequential ID.
- The server sends the message to U1 via Socket.IO.
- U1's client does notconfirm receipt, because they are offline.
- Perhaps U2 sends U1 a few more messages; they all get stored in the data store in the same fashion.
- When U1 reconnects, it asks the server "The last message I saw was X / I have state X, what did I miss."
- The server sends U1 all the messages it missed from the data store based on U1's request
- U1's client confirms receipt and the server removes those messages from the data store.
- U1 失去互联网连接。
- U2 向服务器发送 U1 应该接收的消息。
- 服务器将消息存储在某种持久存储中,并使用某种时间戳或顺序 ID 将其标记为 U1。
- 服务器通过 Socket.IO 将消息发送到 U1。
- U1的客户端不确认收货,因为他们离线。
- 也许 U2 再给 U1 发几条消息;它们都以相同的方式存储在数据存储中。
- 当 U1 重新连接时,它会询问服务器“我看到的最后一条消息是 X/我有状态 X,我错过了什么。”
- 服务器根据 U1 的请求向 U1 发送它从数据存储中错过的所有消息
- U1 的客户端确认收到,服务器从数据存储中删除这些消息。
If you absolutely want guaranteed delivery, then it's important to design your system in such a way that being connected doesn't actually matter, and that realtime delivery is simply a bonus; this almost always involves a data store of some kind. As user568109 mentioned in a comment, there are messaging systems that abstract away the storage and delivery of said messages, and it may be worth looking into such a prebuilt solution. (You will likely still have to write the Socket.IO integration yourself.)
如果您绝对想要有保证的交付,那么重要的是要设计您的系统,使连接实际上无关紧要,而实时交付只是一种奖励;这几乎总是涉及某种数据存储。正如 user568109 在评论中提到的那样,有一些消息传递系统可以抽象出所述消息的存储和传递,可能值得研究这样一个预构建的解决方案。(您可能仍然需要自己编写 Socket.IO 集成。)
If you're not interested in storing the messages in the database, you may be able to get away with storing them in a local array; the server tries to send U1 the message, and stores it in a list of "pending messages" until U1's client confirms that it received it. If the client is offline, then when it comes back it can tell the server "Hey I was disconnected, please send me anything I missed" and the server can iterate through those messages.
如果您对在数据库中存储消息不感兴趣,您可以将它们存储在本地数组中;服务器尝试向 U1 发送消息,并将其存储在“待处理消息”列表中,直到 U1 的客户端确认它收到了它。如果客户端离线,那么当它返回时,它可以告诉服务器“嘿,我已断开连接,请将我错过的任何内容发送给我”,服务器可以遍历这些消息。
Luckily, Socket.IO provides a mechanism that allows a client to "respond" to a message that looks like native JS callbacks. Here is some pseudocode:
幸运的是,Socket.IO 提供了一种机制,允许客户端“响应”看起来像原生 JS 回调的消息。这是一些伪代码:
// server
pendingMessagesForSocket = [];
function sendMessage(message) {
pendingMessagesForSocket.push(message);
socket.emit('message', message, function() {
pendingMessagesForSocket.remove(message);
}
};
socket.on('reconnection', function(lastKnownMessage) {
// you may want to make sure you resend them in order, or one at a time, etc.
for (message in pendingMessagesForSocket since lastKnownMessage) {
socket.emit('message', message, function() {
pendingMessagesForSocket.remove(message);
}
}
});
// client
socket.on('connection', function() {
if (previouslyConnected) {
socket.emit('reconnection', lastKnownMessage);
} else {
// first connection; any further connections means we disconnected
previouslyConnected = true;
}
});
socket.on('message', function(data, callback) {
// Do something with `data`
lastKnownMessage = data;
callback(); // confirm we received the message
});
This is quite similar to the last suggestion, simply without a persistent data store.
这与上一个建议非常相似,只是没有持久数据存储。
You may also be interested in the concept of event sourcing.
您可能还对事件溯源的概念感兴趣。
回答by Ryan
Michelle's answer is pretty much on point, but there are a few other important things to consider. The main question to ask yourself is: "Is there a difference between a user and a socket in my app?" Another way to ask that is "Can each logged in user have more than 1 socket connection at one time?"
米歇尔的回答非常中肯,但还有一些其他重要的事情需要考虑。要问自己的主要问题是:“我的应用程序中的用户和套接字之间有区别吗?” 另一种提问方式是“每个登录的用户可以同时拥有 1 个以上的套接字连接吗?”
In the web world it is probably always a possibility that a single user has multiple socket connections, unless you have specifically put something in place that prevents this. The simplest example of this is if a user has two tabs of the same page open. In these cases you don't care about sending a message/event to the human user just once... you need to send it to each socket instance for that user so that each tab can run it's callbacks to update the ui state. Maybe this isn't a concern for certain applications, but my gut says it would be for most. If this is a concern for you, read on....
在 Web 世界中,单个用户可能总是有多个套接字连接,除非您专门放置了一些东西来防止这种情况发生。最简单的例子是如果用户打开了同一页面的两个选项卡。在这些情况下,您不关心向人类用户发送一次消息/事件……您需要将其发送到该用户的每个套接字实例,以便每个选项卡都可以运行它的回调来更新 ui 状态。也许这对某些应用程序来说不是问题,但我的直觉说它对大多数应用程序都是如此。如果这是您的问题,请继续阅读......
To solve this (assuming you are using a database as your persistent storage) you would need 3 tables.
要解决这个问题(假设您使用数据库作为持久存储),您需要 3 个表。
- users - which is a 1 to 1 with real people
- clients - which represents a "tab" that couldhave a single connection to a socket server. (any 'user' may have multiple)
- messages - a message that needs sent to a client (not a message that needs sent to a user or to a socket)
- 用户 - 与真人一对一
- 客户端 - 代表一个“选项卡”,可以与套接字服务器有一个单一的连接。(任何“用户”可能有多个)
- 消息 - 需要发送给客户端的消息(不是需要发送给用户或套接字的消息)
The users table is optional if your app doesn't require it, but the OP said they have one.
如果您的应用不需要,则用户表是可选的,但 OP 表示他们有一个。
The other thing that needs properly defined is "what is a socket connection?", "When is a socket connection created?", "when is a socket connection reused?". Michelle's psudocode makes it seem like a socket connection can be reused. With Socket.IO, they CANNOT be reused. I've seen be the source of a lot of confusion. There are real life scenarios where Michelle's example does make sense. But I have to imagine those scenarios are rare. What really happens is when a socket connection is lost, that connection, ID, etc will never be reused. So any messages marked for that socket specifically will never be delivered to anyone because when the client who had originally connected, reconnects, they get a completely brand new connection and new ID. This means it's up to you to do something to track clients (rather than sockets or users) across multiple socket connections.
需要正确定义的另一件事是“什么是套接字连接?”、“何时创建套接字连接?”、“何时重用套接字连接?”。Michelle 的伪代码看起来像是可以重用套接字连接。使用 Socket.IO,它们不能被重用。我已经看到了很多混乱的根源。在现实生活中,米歇尔的例子确实有意义。但我不得不想象这些场景很少见。真正发生的情况是,当套接字连接丢失时,该连接、ID 等将永远不会被重用。因此,任何专门为该套接字标记的消息将永远不会传递给任何人,因为当最初连接的客户端重新连接时,他们会获得一个全新的连接和新 ID。这意味着它'
So for a web based example here would be the set of steps I'd recommend:
因此,对于基于 Web 的示例,这里将是我推荐的一组步骤:
- When a user loads a client (typically a single webpage) that has the potential for creating a socket connection, add a row to the clients database which is linked to their user ID.
- When the user actually does connect to the socket server, pass the client ID to the server with the connection request.
- The server should validate the user is allowed to connect and the client row in the clients table is available for connection and allow/deny accordingly.
- Update the client row with the socket ID generated by Socket.IO.
- Send any items in the messages table connected to the client ID. There wouldn't be any on initial connection, but if this was from the client trying to reconnect, there may be some.
- Any time a message needs to be sent to that socket, add a row in the messages table which is linked to the client ID you generated (not the socket ID).
- Attempt to emit the message and listen for the client with the acknowledgement.
- When you get the acknowledgement, delete that item from the messages table.
- You may wish to create some logic on the client side that discards duplicate messages sent from the server since this is technically a possibility as some have pointed out.
- Then when a client disconnects from the socket server (purposefully or via error), DO NOT delete the client row, just clear out the socket ID at most. This is because that same client could try to reconnect.
- When a client tries to reconnect, send the same client ID it sent with the original connection attempt. The server will view this just like an initial connection.
- When the client is destroyed (user closes the tab or navigates away), this is when you delete the client row and all messages for this client. This step may be a bit tricky.
- 当用户加载有可能创建套接字连接的客户端(通常是单个网页)时,向链接到其用户 ID 的客户端数据库添加一行。
- 当用户确实连接到套接字服务器时,将客户端 ID 与连接请求一起传递给服务器。
- 服务器应验证允许用户连接并且客户端表中的客户端行可用于连接并相应地允许/拒绝。
- 使用 Socket.IO 生成的套接字 ID 更新客户端行。
- 发送连接到客户端 ID 的消息表中的任何项目。初始连接上不会有任何内容,但如果这是来自尝试重新连接的客户端,则可能会有一些。
- 每当需要将消息发送到该套接字时,请在消息表中添加一行,该行链接到您生成的客户端 ID(而不是套接字 ID)。
- 尝试发出消息并通过确认收听客户端。
- 收到确认后,从消息表中删除该项目。
- 您可能希望在客户端创建一些逻辑来丢弃从服务器发送的重复消息,因为正如某些人指出的那样,这在技术上是可行的。
- 然后当客户端与套接字服务器断开连接时(有意或通过错误),不要删除客户端行,最多只清除套接字 ID。这是因为同一个客户端可能会尝试重新连接。
- 当客户端尝试重新连接时,发送与原始连接尝试一起发送的相同客户端 ID。服务器会像初始连接一样查看它。
- 当客户端被销毁(用户关闭选项卡或导航离开)时,这是当您删除客户端行和该客户端的所有消息时。这一步可能有点棘手。
Because the last step is tricky (at least it used to be, I haven't done anything like that in a long time), and because there are cases like power loss where the client will disconnect without cleaning up the client row and never tries to reconnect with that same client row - you probably want to have something that runs periodically to cleanup any stale client and message rows. Or, you can just permanently store all clients and messages forever and just mark their state appropriately.
因为最后一步很棘手(至少以前是这样,我已经很长时间没有做过类似的事情了),并且因为有断电之类的情况,客户端会在不清理客户端行的情况下断开连接并且从不尝试重新连接同一个客户端行 - 您可能希望定期运行一些东西来清理任何陈旧的客户端和消息行。或者,您可以永久存储所有客户端和消息,并适当标记它们的状态。
So just to be clear, in cases where one user has two tabs open, you will be adding two identical message to the messages table each marked for a different client because your server needs to know if each client received them, not just each user.
所以要明确一点,在一个用户打开两个选项卡的情况下,您将向消息表中添加两条相同的消息,每条消息都标记为不同的客户端,因为您的服务器需要知道每个客户端是否收到了它们,而不仅仅是每个用户。
回答by damphat
It is seem that you already have user account system. You know which account is online/offline, you you can handle connect/disconnect event:
您似乎已经拥有用户帐户系统。您知道哪个帐户在线/离线,您可以处理连接/断开事件:
So the solution is, add online/offline and offline messages on database for each user:
所以解决方案是,为每个用户在数据库上添加在线/离线和离线消息:
chatApp.onLogin(function (user) {
user.readOfflineMessage(function (msgs) {
user.sendOfflineMessage(msgs, function (err) {
if (!err) user.clearOfflineMessage();
});
})
});
chatApp.onMessage(function (fromUser, toUser, msg) {
if (user.isOnline()) {
toUser.sendMessage(msg, function (err) {
// alert CAN NOT SEND, RETRY?
});
} else {
toUser.addToOfflineQueue(msg);
}
})
回答by Are Wojciechowski
Look here: Handle browser reload socket.io.
看这里:处理浏览器重新加载 socket.io。
I think you could use solution which I came up with. If you modify it properly, it should work as you want.
我想你可以使用我想出的解决方案。如果您正确修改它,它应该可以如您所愿。
回答by Ari Porad
What I think you want is to have a reusable socket for each user, something like:
我认为您想要的是为每个用户提供一个可重用的套接字,例如:
Client:
客户:
socket.on("msg", function(){
socket.send("msg-conf");
});
Server:
服务器:
// Add this socket property to all users, with your existing user system
user.socket = {
messages:[],
io:null
}
user.send = function(msg){ // Call this method to send a message
if(this.socket.io){ // this.io will be set to null when dissconnected
// Wait For Confirmation that message was sent.
var hasconf = false;
this.socket.io.on("msg-conf", function(data){
// Expect the client to emit "msg-conf"
hasconf = true;
});
// send the message
this.socket.io.send("msg", msg); // if connected, call socket.io's send method
setTimeout(function(){
if(!hasconf){
this.socket = null; // If the client did not respond, mark them as offline.
this.socket.messages.push(msg); // Add it to the queue
}
}, 60 * 1000); // Make sure this is the same as your timeout.
} else {
this.socket.messages.push(msg); // Otherwise, it's offline. Add it to the message queue
}
}
user.flush = function(){ // Call this when user comes back online
for(var msg in this.socket.messages){ // For every message in the queue, send it.
this.send(msg);
}
}
// Make Sure this runs whenever the user gets logged in/comes online
user.onconnect = function(socket){
this.socket.io = socket; // Set the socket.io socket
this.flush(); // Send all messages that are waiting
}
// Make sure this is called when the user disconnects/logs out
user.disconnect = function(){
self.socket.io = null; // Set the socket to null, so any messages are queued not send.
}
Then the socket queue is preserved between disconnects.
然后在断开连接之间保留套接字队列。
Make sure it saves each users socketproperty to the database and make the methods part of your user prototype. The database does not matter, just save it however you have been saving your users.
确保它将每个用户socket属性保存到数据库中,并使方法成为用户原型的一部分。数据库无关紧要,只需保存它,但是您一直在保存您的用户。
This will avoid the problem mentioned in Additon 1 by requiring a confirmation from the client before marking the message as sent. If you really wanted to, you could give each message an id and have the client send the message id to msg-conf, then check it.
这将通过在将消息标记为已发送之前要求客户端进行确认来避免 Additon 1 中提到的问题。如果你真的想要,你可以给每条消息一个 id 并让客户端将消息 id 发送到msg-conf,然后检查它。
In this example, useris the template user that all users are copied from, or like the user prototype.
在这个例子中,user是所有用户都被复制的模板用户,或者像用户原型。
Note:This has not been tested.
注意:这还没有经过测试。
回答by Il Pisanello
As already written in another answer, I also believe you should look at the realtime as a bonus : the system should be able to work also with no realtime.
正如在另一个答案中已经写的那样,我也相信您应该将实时视为奖励:系统也应该能够在没有实时的情况下工作。
I'm developing an enterprise chat for a large company (ios, android, web frontend and .net core + postGres backend) and after having developed a way for the websocket to re-establish connection (through a socket uuid) and get undelivered messages (stored in a queue) I understood there was a better solution: resync via rest API.
我正在为一家大公司(ios、android、web 前端和 .net core + postGres 后端)开发企业聊天,并且在开发了一种让 websocket 重新建立连接(通过 socket uuid)并获取未传递消息的方法之后(存储在队列中)我知道有一个更好的解决方案:通过 rest API 重新同步。
Basically I ended up by using websocket just for realtime, with an integer tag on each realtime message (user online, typers, chat message and so on) for monitoring lost messages.
基本上,我最终只是实时使用 websocket,在每条实时消息(用户在线、打字机、聊天消息等)上都有一个整数标签,用于监控丢失的消息。
When the client gets an id which is not monolithic (+1) then it understands it is out of sync so it drops all the socket messages and asks a resync of all its observers through REST api.
当客户端获得一个非整体 (+1) 的 id 时,它会理解它不同步,因此它会删除所有套接字消息并通过 REST api 要求其所有观察者重新同步。
This way we can handle many variations in the state of the application during the offline period without having to parse tons of websocket messages in a row on reconnection and we are sure to be synced (because the last sync date is set just by the REST api, not from the socket).
通过这种方式,我们可以在离线期间处理应用程序状态的许多变化,而无需在重新连接时连续解析大量 websocket 消息,并且我们确保同步(因为上次同步日期仅由 REST api 设置) ,而不是来自插座)。
The only tricky part is monitoring for realtime messages from the moment you call REST api to the moment the server replies because what is read from the db takes time to get back to the client and in the meanwhile variations could happen so they need to be cached and took into account.
唯一棘手的部分是从您调用 REST api 到服务器回复的那一刻监视实时消息,因为从数据库读取的内容需要时间返回到客户端,同时可能发生变化,因此需要缓存它们并考虑在内。
We are going into production in a couple of months, I hope to get back sleeping by then :)
我们将在几个月后投入生产,我希望届时能恢复睡眠:)
回答by True Solutions
Been looking at this stuff latterly and think different path might be better.
最近一直在看这个东西,并认为不同的路径可能会更好。
Try looking at Azure Service bus, ques and topic take care of the off line states. The message wait for user to come back and then they get the message.
尝试查看 Azure 服务总线、问题和主题处理离线状态。消息等待用户回来,然后他们收到消息。
Is a cost to run a queue but its like $0.05 per million operations for a basic queue so cost of dev would be more from hours work need to write a queuing system. https://azure.microsoft.com/en-us/pricing/details/service-bus/
运行队列需要花费 0.05 美元,但对于基本队列,每百万次操作需要花费 0.05 美元,因此开发成本会更多,因为需要编写排队系统的工作时间。 https://azure.microsoft.com/en-us/pricing/details/service-bus/
And azure bus has libraries and examples for PHP, C#, Xarmin, Anjular, Java Script etc.
azure bus 有 PHP、C#、Xarmin、Anjular、Java Script 等的库和示例。
So server send message and does not need to worry about tracking them. Client can use message to send back also as means can handle load balancing if needed.
所以服务器发送消息,不需要担心跟踪它们。客户端也可以使用消息发回,如果需要,也可以处理负载平衡。
回答by Oladimeji Ajeniya
Try this emit chat list
试试这个发出聊天列表
io.on('connect', onConnect);
function onConnect(socket){
// sending to the client
socket.emit('hello', 'can you hear me?', 1, 2, 'abc');
// sending to all clients except sender
socket.broadcast.emit('broadcast', 'hello friends!');
// sending to all clients in 'game' room except sender
socket.to('game').emit('nice game', "let's play a game");
// sending to all clients in 'game1' and/or in 'game2' room, except sender
socket.to('game1').to('game2').emit('nice game', "let's play a game (too)");
// sending to all clients in 'game' room, including sender
io.in('game').emit('big-announcement', 'the game will start soon');
// sending to all clients in namespace 'myNamespace', including sender
io.of('myNamespace').emit('bigger-announcement', 'the tournament will start soon');
// sending to individual socketid (private message)
socket.to(<socketid>).emit('hey', 'I just met you');
// sending with acknowledgement
socket.emit('question', 'do you think so?', function (answer) {});
// sending without compression
socket.compress(false).emit('uncompressed', "that's rough");
// sending a message that might be dropped if the client is not ready to receive messages
socket.volatile.emit('maybe', 'do you really need it?');
// sending to all clients on this node (when using multiple nodes)
io.local.emit('hi', 'my lovely babies');
};

