سؤال

I'm using Smack and Openfire for my XMPP connection/server. But I've run into the very common problem (apparently) of resource conflicts. Can anyone tell me the proper way of handling a conflict?

Openfire is set to always kick the original resource (it's a bespoke platform, not open to the public). But I still get the error and don't get a new connection. My XMPP class is below.

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;
            }
        }
    }

}

From what I've read, openfire (when set to always kick) will always kick the original resource and then allow the new login to connect, but I'm not seeing this at all. Any help is greatly appreciated. Thanks.

هل كانت مفيدة؟

المحلول 2

From what I've read, openfire (when set to always kick) will always kick the original resource and then allow the new login to connect,…

Terminating the other connection with the same resource, ie. the previous one, is one option to deal with it. Most, if not all, XMPP servers provide this policy option regarding handling resource conflicts.

I can't comment on why setting Openfire to "Always kick" doesn't work for you, but it certainly does for me. And IIRC there are no current bug reports in Openfire that tell otherwise.

There is also another trivial approach: If you get a resource conflict on login(), simply specify a different resource: login(String username, String password, String resource).

Or even more trivial: If you don't care how what the resource String is, you can have the server auto assign one to you. Simply use 'null' as resource argument: login(user, password, null).

نصائح أخرى

The proper way to deal with resource conflicts is to not use a fixed resource unless you absolutely must. If your client specifies no resource when logging in, the server should assign it a randomly generated one which will never conflict.

There are very few reasons why you'd ever need to specify a fixed resource, as most clients hide the resource of your contacts anyway, and there are other reasons why having a new resource on every connection is advantageous (like avoiding a common bug with group chats getting out of sync because the group chat's server didn't realize the connecting user is actually a new session).

A big problem with fixed resources is reconnection loops, where, if the server is configured to kick the old conflicting resource, two clients kick each other repeatedly. You should make sure you don't automatically reconnect when receiving a resource conflict error.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top