Android XMPP 避免资源冲突
Posted
技术标签:
【中文标题】Android XMPP 避免资源冲突【英文标题】:Android XMPP avoid resource conflict 【发布时间】:2014-05-12 09:34:44 【问题描述】:我正在为我的 XMPP 连接/服务器使用 Smack 和 Openfire。但是我遇到了资源冲突的非常常见问题(显然)。谁能告诉我处理冲突的正确方法?
Openfire 设置为始终踢出原始资源(这是一个定制平台,不向公众开放)。但我仍然得到错误并且没有得到新的连接。我的 XMPP 课程如下。
package com.goosesys.gaggle.services;
import java.util.Collection;
import org.jivesoftware.smack.Chat;
import org.jivesoftware.smack.ChatManagerListener;
import org.jivesoftware.smack.Connection;
import org.jivesoftware.smack.ConnectionConfiguration;
import org.jivesoftware.smack.ConnectionListener;
import org.jivesoftware.smack.Roster;
import org.jivesoftware.smack.Roster.SubscriptionMode;
import org.jivesoftware.smack.RosterListener;
import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smack.ConnectionConfiguration.SecurityMode;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.packet.Message;
import org.jivesoftware.smack.packet.Presence;
import org.jivesoftware.smack.packet.Presence.Type;
import com.google.gson.Gson;
import com.goosesys.gaggle.Globals;
import com.goosesys.gaggle.application.AppSettings;
import com.goosesys.gaggle.application.Utility;
import android.app.Service;
import android.content.Intent;
import android.os.AsyncTask;
import android.os.Binder;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.util.Log;
import android.widget.Toast;
public class BackgroundXmppConnector extends Service
private ConnectionConfiguration acc;
private XMPPConnection xConnection;
private final IBinder mBinder = new XmppBinder();
private final Handler mHandler = new Handler();
private final int manualReconnectionTimer = (5 * 60 * 1000);
private static int mInterval1m = (2 * 60 * 1000);
private static int mInterval5m = (5 * 60 * 1000);
private static boolean bConnecting = false;
private static final Object connectLock = new Object();
private static final Object checkLock = new Object();
private final Runnable checkConnection = new Runnable()
@Override
public void run()
synchronized(checkLock)
Log.d("BXC", "Handler running - Checking connection");
checkConnectionStatus();
;
private final Runnable killConnection = new Runnable()
@Override
public void run()
synchronized(checkLock)
Log.d("BXC", "Killing connection and restarting");
// Manually disconnect and restart the connection every 5 minutes
if(xConnection != null)
xConnection.disconnect();
destroyConnectionAndRestart();
new LoginTask().execute();
mHandler.postDelayed(this, mInterval5m);
;
@Override
public void onCreate()
Log.i("BXC", "BackgroundXmppConnector Service has been created");
// Checks the connection state every 1 minute //
mHandler.postDelayed(checkConnection, mInterval1m);
mHandler.postDelayed(killConnection, mInterval5m);
@Override
public int onStartCommand(Intent intent, int flags, int startId)
Log.d("BXC", "Xmpp Connector Started");
new LoginTask().execute();
return Service.START_STICKY;
private void destroyConnectionAndRestart()
xConnection.disconnect();
xConnection = null;
Globals.backgroundXmppConnectorRunning = false;
bConnecting = false;
private void setupConnection()
Log.d("BXC", "Settting up XMPP connection");
try
if(!bConnecting && !Globals.backgroundXmppConnectorRunning)
acc = new ConnectionConfiguration(AppSettings.XMPP_SERVER_HOST,
AppSettings.XMPP_SERVER_PORT);
acc.setSecurityMode(SecurityMode.disabled);
acc.setSASLAuthenticationEnabled(false);
acc.setReconnectionAllowed(false);
acc.setSendPresence(true);
xConnection = new XMPPConnection(acc);
xConnection.addConnectionListener(new ConnectionListener()
@Override
public void connectionClosed()
Log.e("BXC", "Xmpp connection closed");
Globals.backgroundXmppConnectorRunning = false;
Globals.numberOfDisconnects += 1;
//destroyConnectionAndRestart();
Utility.writeToLog(getApplicationContext(), "Xmpp Connection closed - disconnected# (" + Globals.numberOfDisconnects + ")");
@Override
public void connectionClosedOnError(Exception e)
Log.e("BXC", "Xmpp connection closed with error: " + e);
Globals.backgroundXmppConnectorRunning = false;
Globals.numberOfDisconnectsOnError += 1;
// This is more than likely due to a conflict loop - it's best to disconnect and nullify
// our connection and let the software restart when it checks every 5 minutes
if(e.toString().toUpperCase().contains("CONFLICT"))
Log.e("BXC", "Conflict connection loop detected - Waiting");
Utility.writeToLog(getApplicationContext(), "Xmpp Connection closed with error [" + e + "] - disconnected# (" + Globals.numberOfDisconnectsOnError + ")");
@Override
public void reconnectingIn(int seconds)
Log.i("BXC", "Xmpp connection, reconnecting in " + seconds + " seconds");
Globals.backgroundXmppConnectorRunning = false;
bConnecting = true;
@Override
public void reconnectionFailed(Exception e)
Log.e("BXC", "Xmpp reconnection failed: " + e);
Globals.backgroundXmppConnectorRunning = false;
//destroyConnectionAndRestart();
Utility.writeToLog(getApplicationContext(), "Xmpp reConnection failed with error [" + e + "] - disconnected# (" + Globals.numberOfDisconnects + ")");
@Override
public void reconnectionSuccessful()
Log.i("BXC", "Xmpp reconnected successfully");
Globals.backgroundXmppConnectorRunning = true;
bConnecting = false;
);
else
Log.i("BXC", "Already in connecting state");
catch (Exception e)
Log.e("BXC", e.getMessage());
public boolean sendMessage(Intent intent)
if(xConnection != null && xConnection.isConnected())
String jsonObject;
Bundle extras = intent.getExtras();
if(extras != null)
jsonObject = extras.getString("MESSAGEDATA");
Message m = new Gson().fromJson(jsonObject, Message.class);
if(m != null)
sendMessage(m);
else
Log.e("BXC", "Message to send was/is null. Can't send.");
m = null;
jsonObject = null;
extras = null;
Log.i("BXC", "Sending Xmpp Packet");
return true;
return false;
/*
* Sends message to xmpp server - message packet in form of
*
* --------------------MESSAGE PACKET-------------------------
* TO
* -----------------------
* FROM
* -----------------------
* BODY
* TRANSACTION-------------------------------------------
* MessageType
* --------------------------------------------------
* TransactionObject
*/
private void sendMessage(Message m)
try
Log.d("BXC", "Sending transaction message to Xmpp Server");
xConnection.sendPacket(m);
//Toast.makeText(getApplicationContext(), "Packet sent to XMPP", Toast.LENGTH_LONG).show();
catch(Exception ex)
ex.printStackTrace();
private void checkConnectionStatus()
Log.d("BXC", "Checking Xmpp connection status");
if(xConnection == null || xConnection.isAuthenticated() == false ||
xConnection.isConnected() == false || xConnection.isSocketClosed() ||
Globals.backgroundXmppConnectorRunning == false)
Log.e("BXC", "Connection to server is dead. Retrying");
Toast.makeText(getApplicationContext(), "Connection dead - retrying", Toast.LENGTH_SHORT).show();
destroyConnectionAndRestart();
new LoginTask().execute();
else
Log.i("BXC", "Connection appears to be valid");
Toast.makeText(getApplicationContext(), "Connection valid", Toast.LENGTH_SHORT).show();
// BINDER ////////////////////////////////////////////////////////////////////////////////
@Override
public IBinder onBind(Intent intent)
return mBinder;
// INTERNAL CLASSES //////////////////////////////////////////////////////////////////////
public class XmppBinder extends Binder
public BackgroundXmppConnector getService()
return BackgroundXmppConnector.this;
private class LoginTask extends AsyncTask<Void, Void, Void>
@Override
protected Void doInBackground(Void... params)
// First ensure we've got a connection to work with first
if(Utility.hasActiveInternetConnection(getApplicationContext()) &&
((!bConnecting) || (!Globals.backgroundXmppConnectorRunning)))
try
//bConnecting = true;
Log.d("BXC", "Beginning connection");
synchronized(connectLock)
setupConnection();
xConnection.connect();
Log.i("BXC", "Login credentials: " + Utility.getAndroidID(getApplicationContext()) + " " + AppSettings.XMPP_KEYSTORE_PASSWORD);
xConnection.login(Utility.getAndroidID(getApplicationContext()), AppSettings.XMPP_KEYSTORE_PASSWORD);
xConnection.getChatManager().addChatListener(new ChatManagerListener()
@Override
public void chatCreated(final Chat chat, boolean createdLocally)
if(!createdLocally)
// add chat listener //
chat.addMessageListener(new BackgroundMessageListener(getApplicationContext()));
);
Presence p = new Presence(Presence.Type.subscribe);
p.setStatus("Out and About");
xConnection.sendPacket(p);
Roster r = xConnection.getRoster();
r.setSubscriptionMode(SubscriptionMode.accept_all);
r.createEntry(AppSettings.BOT_NAME, "AbleBot", null);
r.addRosterListener(new RosterListener()
@Override
public void entriesAdded(Collection<String> addresses)
for(String s : addresses)
Log.d("BXC", "Entries Added: " + s);
@Override
public void entriesDeleted(Collection<String> addresses)
for(String s : addresses)
Log.d("BXC", "Entries Deleted: " + s);
@Override
public void entriesUpdated(Collection<String> addresses)
for(String s : addresses)
Log.d("BXC", "Entries updated: " + s);
@Override
public void presenceChanged(Presence presence)
Log.d("BXC", "PresenceChanged: " + presence.getFrom());
);
catch(IllegalStateException ex)
Log.e("BXC", "IllegalStateException -->");
if(ex.getMessage().contains("Already logged in to server"))
Globals.backgroundXmppConnectorRunning = true;
else
Globals.backgroundXmppConnectorRunning = false;
Utility.writeExceptionToLog(getApplicationContext(), ex);
ex.printStackTrace();
catch(XMPPException ex)
Log.e("BXC", "XMPPException -->");
Globals.backgroundXmppConnectorRunning = false;
Utility.writeExceptionToLog(getApplicationContext(), ex);
ex.printStackTrace();
catch(NullPointerException ex)
Log.e("BXC", "NullPointerException -->");
Globals.backgroundXmppConnectorRunning = false;
Utility.writeExceptionToLog(getApplicationContext(), ex);
ex.printStackTrace();
catch(Exception ex)
Log.e("BXC", "Exception -->");
Globals.backgroundXmppConnectorRunning = false;
Utility.writeToLog(getApplicationContext(), ex.toString());
ex.printStackTrace();
return null;
else
Log.i("BXC", "No active internet data connection - will retry");
return null;
@Override
protected void onPostExecute(Void ignored)
if(xConnection != null)
if(xConnection.isConnected() && (!xConnection.isSocketClosed()))
Log.i("BXC", "Logged in to XMPP Server");
Globals.backgroundXmppConnectorRunning = true;
mHandler.postDelayed(checkConnection, mInterval1m);
else
Log.e("BXC", "Unable to log into XMPP Server.");
Globals.backgroundXmppConnectorRunning = false;
destroyConnectionAndRestart();
else
Log.e("BXC", "Xmpp Connection object is null");
Globals.backgroundXmppConnectorRunning = false;
根据我的阅读,openfire(当设置为始终启动时)将始终启动原始资源,然后允许新登录名连接,但我根本没有看到这一点。任何帮助是极大的赞赏。谢谢。
【问题讨论】:
【参考方案1】:处理资源冲突的正确方法是不使用固定资源,除非绝对必要。如果您的客户端在登录时未指定资源,则服务器应为其分配一个随机生成的永远不会冲突的资源。
您需要指定固定资源的原因很少,因为大多数客户端无论如何都会隐藏您联系人的资源,还有其他原因可以说明在每个连接上都有一个新资源是有利的(例如避免群聊不同步的常见错误,因为群聊的服务器没有意识到正在连接的用户实际上是一个新会话。
固定资源的一个大问题是重新连接循环,如果服务器配置为踢掉旧的冲突资源,两个客户端会反复踢对方。您应该确保在收到资源冲突错误时不会自动重新连接。
【讨论】:
感谢您的回答,但我从未见过任何随机资源标识符的证据。它总是类似于:“Smack”或其他 api 库名称。直到今天我还没有指定资源名称,但是现在,在登录函数中附加一个随机字符串是一种享受! 在 XMPP 核心 RFC 中必须提供支持:tools.ietf.org/html/rfc6120#section-7.6。我没有要测试的 OF 服务器,但来源表明它支持它:github.com/igniterealtime/Openfire/blob/…。【参考方案2】:根据我的阅读,openfire(当设置为始终启动时)将始终启动原始资源,然后允许新登录名连接,...
终止与同一资源的另一个连接,即。前一个,是处理它的一种选择。大多数(如果不是全部)XMPP 服务器都提供有关处理资源冲突的策略选项。
我无法评论为什么将 Openfire 设置为“始终启动”对您不起作用,但它对我来说确实有效。并且 IIRC 目前在 Openfire 中没有错误报告可以说明其他情况。
还有另一种简单的方法:如果您在 login()
上遇到资源冲突,只需指定不同的资源:@987654323@。
或者更简单:如果您不关心资源字符串是什么,您可以让服务器自动为您分配一个。只需使用“null”作为资源参数:login(user, password, null)
。
【讨论】:
公平地说,这可能是我编码连接的方式。虽然我不会自动重新连接。也就是说,指定资源字符串已经解决了它。所以,谢谢你:) xnyhps 记得我还可以让服务器自动为您分配资源。这是避免资源冲突的最简单方便的方法。 让服务器分配资源是正确的。请注意,您可能会遇到“资源决斗”错误,在重新连接之前您没有查看断开连接的错误代码。因此,可能是您的新客户端连接正确,然后立即被第一个客户端重新连接启动。几乎每个 XMPP 客户端和库都曾有过这个错误。 您好@LokiSinclair,我面临同样的错误“连接因错误而关闭,冲突...”您如何解决?并且我的连接代码在单独的线程中处于活动状态,您如何处理上述问题中的服务?消息到达和发送时如何调用服务?请大家帮帮我,因为我从过去 15 天找到了“冲突”的解决方案,但仍然没有解决方案......我的聊天工作了一段时间,但一段时间后连接冲突异常到来......所以请帮我解决这个问题..谢谢... 您应该在这里遵循@Flow 的建议。我自己不再从事该项目。但是,我通过处理各种连接异常来解决它。冲突循环是最难解决的部分。在这种情况下,当我知道 Open-fire 会删除连接引用时,我设置了超时以在 X 分钟后重新连接。以上是关于Android XMPP 避免资源冲突的主要内容,如果未能解决你的问题,请参考以下文章