我正在使用ejabberd服务器和ios xmppframework。有两个客户端,A和B。
- 当A和B在线时,A可以成功地向B发送消息。
- 如果B离线,B可以在再次上线时接收到消息。
- 但是,当B突然/意外地失去连接,例如手动关闭Wi-Fi时,A发送的消息会丢失。B将永远不会收到此消息。
我猜测原因是B突然失去连接,而服务器仍然认为B在线。因此,在这种情况下,离线消息无法正常工作。
如何确保A发送的消息将被B接收,即没有消息丢失?
我正在使用ejabberd服务器和ios xmppframework。有两个客户端,A和B。
- 当A和B在线时,A可以成功地向B发送消息。
- 如果B离线,B可以在再次上线时接收到消息。
- 但是,当B突然/意外地失去连接,例如手动关闭Wi-Fi时,A发送的消息会丢失。B将永远不会收到此消息。
我猜测原因是B突然失去连接,而服务器仍然认为B在线。因此,在这种情况下,离线消息无法正常工作。
如何确保A发送的消息将被B接收,即没有消息丢失?
我过去的一周一直在尝试追踪我的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
中,在显示之前检查是否有重复。
1 服务器端
我看到您正在使用ejabbed作为XMPP服务器,您可以实现mod_ping,启用此模块将启用服务器端心跳[ping],在与服务器[ejabbed]的连接中断的情况下,服务器将尝试向连接发送心跳,并检测服务器和客户端之间的连接是否丢失。使用这种方法有一个缺点,即mod_ping模块具有称为ping_interval的属性,该属性说明要向已连接的客户端发送心跳的频率,在此处,较低的限制为32秒,任何低于32的值都会被ejabbed忽略,这意味着如果用户在线,则您有32秒的黑屏窗口,消息可能会丢失。
2 客户端
从客户端方面,您可以实现消息传递回执机制。每次聊天消息发送收据给接收者用户,一旦接收者用户收到消息,立即发送回执ID。这样,您可以检测到您的消息实际上已传递给接收者。如果在一定时间间隔内未收到此类确认,则可以在本地(在移动电话上)将用户显示为离线状态,将任何进一步的消息存储为该用户的离线消息(在SQLLight数据库中),并等待该用户的脱机存在标准,一旦您收到脱机存在标准,就意味着服务器最终检测到与该用户的连接已丢失,并将使用户状态为离线,现在您可以向该用户发送所有消息,这些消息将再次存储为服务器上的离线消息。这是避免黑屏窗口的最佳方法。
结论 你可以使用方法2设计你的客户端,也可以将方法1与方法2结合起来,以最小化服务器断开连接的时间。
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions