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 避免资源冲突的主要内容,如果未能解决你的问题,请参考以下文章

如何在 iOS 的 XMPP 框架中设置资源

处理Gradle依赖冲突

ruby 保留用户名列表,以避免与资源路径发生虚假URL冲突

Jabber - xmpp 冲突

是否有一种有效的算法以既避免冲突又允许偏见的方式分配资源?

Android - XMPP:“已经登录到服务器”异常