Android XMPP避免资源冲突

时间:2014-05-12 09:34:45

标签: android xmpp smack

我在我的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(当设置为始终踢时)将始终启动原始资源,然后允许新登录连接,但我根本没有看到这一点。任何帮助是极大的赞赏。感谢。

2 个答案:

答案 0 :(得分:3)

处理资源冲突的正确方法是不使用固定资源,除非绝对必须。如果您的客户端在登录时未指定资源,则服务器应为其分配一个永远不会发生冲突的随机生成的资源。

您需要指定固定资源的原因很少,因为大多数客户端无论如何都会隐藏您的联系人资源,还有其他原因可以解释为什么在每个连接上都有新资源是有利的(比如避免使用由于群聊的服务器没有意识到连接用户实际上是新会话,因此群聊的常见错误会导致无法同步。

固定资源的一个大问题是重新连接循环,其中,如果服务器配置为踢出旧的冲突资源,则两个客户端反复互相攻击。您应该确保在收到资源冲突错误时不会自动重新连接。

答案 1 :(得分:2)

  

根据我的阅读,openfire(当设置为始终踢时)将始终踢原始资源,然后允许新登录连接,...

使用相同的资源终止其他连接,即。前一个,是一个处理它的选项。大多数(如果不是全部)XMPP服务器提供有关处理资源冲突的此策略选项。

我无法评论为什么将Openfire设置为“Always kick”对你不起作用,但它肯定对我有用。而且IIRC在Openfire中没有当前的错误报告,否则就是这样。

还有另一种简单的方法:如果您在login()上遇到资源冲突,只需指定其他资源:login(String username, String password, String resource)

或者更简单:如果您不关心资源字符串是什么,您可以让服务器自动为您分配一个。只需使用'null'作为资源参数:login(user, password, null)