当客户端突然失去连接时,XMPP消息会丢失。

10

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

  1. 当A和B在线时,A可以成功地向B发送消息。
  2. 如果B离线,B可以在再次上线时接收到消息。
  3. 但是,当B突然/意外地失去连接,例如手动关闭Wi-Fi时,A发送的消息会丢失。B将永远不会收到此消息。

我猜测原因是B突然失去连接,而服务器仍然认为B在线。因此,在这种情况下,离线消息无法正常工作。

如何确保A发送的消息将被B接收,即没有消息丢失?

4个回答

12

我过去的一周一直在尝试追踪我的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上断开连接。但当客户端失去连接或流出现未知原因断开时,存在一段时间窗口,客户端处于离线状态,但是服务器尚不知道,因为预计的 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。在构建OutboundMessage时添加带有唯一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中,在显示之前检查是否有重复。


9
我猜原因是B突然失去了连接,但服务器仍认为B在线。因此,在这种情况下,离线消息确实有效。
是的,你完全正确,这是TCP连接的众所周知的限制。
对于你的问题,有两种方法。

1 服务器端

我看到您正在使用ejabbed作为XMPP服务器,您可以实现mod_ping,启用此模块将启用服务器端心跳[ping],在与服务器[ejabbed]的连接中断的情况下,服务器将尝试向连接发送心跳,并检测服务器和客户端之间的连接是否丢失。使用这种方法有一个缺点,即mod_ping模块具有称为ping_interval的属性,该属性说明要向已连接的客户端发送心跳的频率,在此处,较低的限制为32秒,任何低于32的值都会被ejabbed忽略,这意味着如果用户在线,则您有32秒的黑屏窗口,消息可能会丢失。

2 客户端

从客户端方面,您可以实现消息传递回执机制。每次聊天消息发送收据给接收者用户,一旦接收者用户收到消息,立即发送回执ID。这样,您可以检测到您的消息实际上已传递给接收者。如果在一定时间间隔内未收到此类确认,则可以在本地(在移动电话上)将用户显示为离线状态,将任何进一步的消息存储为该用户的离线消息(在SQLLight数据库中),并等待该用户的脱机存在标准,一旦您收到脱机存在标准,就意味着服务器最终检测到与该用户的连接已丢失,并将使用户状态为离线,现在您可以向该用户发送所有消息,这些消息将再次存储为服务器上的离线消息。这是避免黑屏窗口的最佳方法。

结论 你可以使用方法2设计你的客户端,也可以将方法1与方法2结合起来,以最小化服务器断开连接的时间。


3
如果B突然离线,用户A发送消息给用户B时需要检查B的在线/离线状态。如果用户B离线,则用户A必须使用Web服务将该消息上传到服务器上。并且用户B必须调用下面的函数来调用Web服务。
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions

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

这是一个解决方案。我也使用XEP-198和XEP-199解决了这个问题。 - xhsoldier

0

1
请你能详细解释一下吗? - Femina Brahmbhatt

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接