客户端连接突然断开时 xmpp 消息丢失

xmpp messages are lost when client connection lost suddently

我正在使用 ejabberd 服务器和 ios xmppframework。 有两个客户端,A 和 B。

  1. When A and B are online, A can send message to B successfully.
  2. If B is offline, B can receive the message when B is online again.
  3. But when B is suddenly/unexpectedly lost connection, such as manually close wi-fi, the message sent by A is lost. B will never receive this message.

我猜是B突然断线了,服务器还认为B在线。因此离线消息在这种情况下确实有效。

所以我的问题是如何保证A发送的消息能被B收到?确保没有消息丢失。

如果B突然掉线那么用户A必须检查B是否online/offline同时发送消息给用户B。如果用户B离线那么用户A必须使用Web服务上传该消息到服务器。用户 B 必须在以下功能上调用 Web 服务。

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions

因此用户 B 将收到所有因连接丢失而丢失的离线消息。

I guess the reason is that B lost connection suddenly and the server still think B is online. Thus the offline message does work under this condition

是的,你完全正确,这是众所周知的 TCP 连接限制。

有两种方法可以解决您的问题

1 Server side

As I can see you are using ejabbed as XMPP server you can implement mod_ping , Enabling this module will enables server side heartbeat[ping] ,in case of broken connection to server[ejabbed] will try to send heartbeat to connection and will detect connection is lost between server and client. Use of this approach has one drawback,module mod_ping has property called ping_interval which states how often to send heartbeat to connected clients, here lower limit is 32 seconds any value below 32 is ignored by ejabbed,means you have 32 seconds black window in which messages can be lost if user is sowing as online

2 Client side

From client side you can implement Message Delivery Receipts mechanism .With each Chat message send a receipt to receiver user of as soon as receiver user receives message send back this receipt id. This way you can detect that your message is actually delivered to receiver. If you don't receive such acknowledgement between certain time interval you can show user as offline locally(on mobile phone),store any further messages to this user as offline message locally[in SQLLight database ],and wait for offline presence stanza for that user ,as soon as you receive offline presence stanza it means that server has finally detected connection to that user is lost and makes user status as offline ,now you can send all messages to that user ,which will be again stored as offline messages on server.This is best approach to avoid black-window.

结论 您可以使用方法 2 并以这种方式设计您的客户端,您也可以使用方法 1 和方法 2 来最小化服务器断开连接的减少时间。

最后,我将 Ping 与 Stream Management 结合使用: http://xmpp.org/extensions/xep-0198.html 这个问题解决了。

上周我一直在尝试追踪我的 XMPPFramework 和 eJabberd 消息传递应用程序中丢失的消息。以下是我为保证消息传递而经历的完整步骤以及每个步骤的效果。

Mod_offline

在 ejabberd.yml 配置文件中,确保您在访问规则中具有以下内容:

max_user_offline_messages:
  admin: 5000
  all: 100

这在模块部分:

mod_offline:
  access_max_user_messages: max_user_offline_messages

当服务器知道消息的收件人离线时,他们会存储它并在他们重新连接时发送。

Ping (XEP-199)

xmppPing = XMPPPing()
xmppPing.respondsToQueries = true
xmppPing.activate(xmppStream)

xmppAutoPing = XMPPAutoPing()
xmppAutoPing.pingInterval = 2 * 60
xmppAutoPing.pingTimeout = 10.0
xmppAutoPing.activate(xmppStream)

Ping 就像心跳一样,因此服务器知道用户何时离线但没有正常断开连接。最好不要通过 applicationDidEnterBackground 断开连接来依赖它,但是当客户端断开连接或流由于未知原因断开连接时,会有 window 客户端离线但服务器不离线的时间还不知道,因为 ping 直到将来的某个时候才被预期。在这种情况下,邮件未送达且未存储以供脱机送达。

流管理 (XEP-198)

xmppStreamManagement = XMPPStreamManagement(storage: XMPPStreamManagementMemoryStorage(), dispatchQueue: dispatch_get_main_queue())
xmppStreamManagement.autoResume = true
xmppStreamManagement.addDelegate(self, delegateQueue: dispatch_get_main_queue())
xmppStreamManagement.activate(xmppStream)

然后在 xmppStreamDidAuthenticate

xmppStreamManagement.enableStreamManagementWithResumption(true, maxTimeout: 100)

快到了。最后一步是返回 ejabberd.yml 并将此行添加到 access: c2s:

下面的监听端口部分
resend_on_timeout: true

流管理在每次消息传递后添加 req/akn 握手。它本身不会对服务器端产生任何影响,除非设置了 resend_on_timeout(eJabberd 默认情况下它不是)。

当收到消息的确认未到达服务器并且它决定保留它以进行离线传递时,需要考虑最后一个边缘情况。下次客户端登录时,他们可能会收到重复的消息。为了处理这个问题,我们为 XMPPStreamManager 设置了委托。实施 xmppStreamManagement getIsHandled:,如果消息有聊天正文,则将 isHandledPtr 设置为 false。当您构建出站消息时,添加一个具有唯一 ID 的 xmppElement:

let xmppMessage = XMPPMessage(type: "chat", to: partnerJID)
let xmppElement = DDXMLElement(name: "message")
xmppElement.addAttributeWithName("id", stringValue: xmppStream.generateUUID())
xmppElement.addAttributeWithName("type", stringValue: "chat")
xmppElement.addAttributeWithName("to", stringValue: partnerJID.bare())
xmppMessage.addBody(message)
xmppMessage.addChild(xmppElement)
xmppMessage.addReceiptRequest()
xmppStream.sendElement(xmppMessage)

然后当你收到消息时,通知流管理器消息已处理 xmppStreamManager.markHandledStanzaId(message.from().resource)

这最后一步的目的是建立一个唯一标识符,您可以将其添加到 XMPPMessageArchivingCoreDataStorage 并在显示之前检查重复项。