1. 程式人生 > >Android IM之基於Openfire+Smack的聊天伺服器的搭建與測試

Android IM之基於Openfire+Smack的聊天伺服器的搭建與測試

XMPP協議(Extensible Messaging and PresenceProtocol,可擴充套件訊息處理現場協議)是一種基於XML的協議,目的是為了解決及時通訊標準而提出來的,最早是在Jabber上實現的。它繼承了在XML環境中靈活的發展性。因此,基於XMPP的應用具有超強的可擴充套件性。並且XML很易穿過防火牆,所以用XMPP構建的應用不易受到防火牆的阻礙。利用XMPP作為通用的傳輸機制,不同組織內的不同應用都可以進行有效的通訊。

先來了解幾個概念

  • Openfire主要是作為伺服器,負責管理客戶端的通訊連線,以及提供客戶端一些通訊資訊和連線資訊。

  • Smack主要是xmpp協議的實現,提供了一套很好的api,所以下面操作xmpp都是通過使用Smack的api來實現,從4.1.0開始,它就支援Android了,所以我們直接使用Smack即可,當然在這不支援之前是使用Asmack這個包的,裡面方法跟smack包差不多。

  • Spark 是IM客戶端的實現,其實就是使用了Smack 的api實現的。

這裡寫圖片描述

第二個是一套基於XMPP實現的API,我們直接引用其即可,在Android Studio中,我們直接在gradle中新增依賴即可。

    compile 'org.igniterealtime.smack:smack-android-extensions:4.1.4'
    compile 'org.igniterealtime.smack:smack-tcp:4.1.4'

然後我們需要新增網路許可權

    <uses-permission android:name="android.permission.INTERNET"
/>

接下來我們先不管android端,我們先進行兩個軟體的安裝。首先安裝openfire.

點選安裝包開啟進行初始化

這裡寫圖片描述

選擇語言,這裡選擇中文

這裡寫圖片描述

確定後再點下一步

這裡寫圖片描述

同意許可點選下一步

這裡寫圖片描述

選擇安裝目錄點選下一步,這裡是預設目錄

這裡寫圖片描述

繼續下一步

這裡寫圖片描述

耐心等待檔案解壓完成

這裡寫圖片描述

點選完成後執行Openfire

這裡寫圖片描述

執行成功後點擊Launch Admin進入後臺完成剩下的安裝工作

這裡寫圖片描述

選擇語言

這裡寫圖片描述
伺服器配置,我們將域修改為本機的區域網IP地址

我們需要獲得我們電腦的IP地址。

這裡寫圖片描述

獲得的IP地址為10.0.0.24,將域修改為這個值

這裡寫圖片描述

資料庫我們選擇使用外部資料庫,所以勾選第一個

這裡寫圖片描述

接下來就是一些值,第一項的下拉選擇mysql,之後值會被填充。接下來我們就需要在mysql中新增一個數據庫。

這裡寫圖片描述

這裡假設你的本地有mysql伺服器,開啟後臺,新增一個使用者,勾選建立與使用者名稱同名的資料庫並授予所有許可權

這裡寫圖片描述

把database name和hostname修改成對應的值,使用者名稱和密碼為你剛才mysql中建立的使用者和密碼

這裡寫圖片描述

選擇初始設定

這裡寫圖片描述

設定openfire管理員賬號密碼,這裡賬號設定為admin,密碼自己設定

這裡寫圖片描述

點選登入到管理控制檯

這裡寫圖片描述

進入到後臺,輸入賬號密碼進行登陸

這裡寫圖片描述

登陸成功後就是後臺了

這裡寫圖片描述

然後安裝Spark,點選下載的安裝包

這裡寫圖片描述

選擇安裝目錄

這裡寫圖片描述

點選下一步

這裡寫圖片描述

繼續點選下一步

這裡寫圖片描述

等待安裝完成

這裡寫圖片描述

點選finish執行spark

這裡寫圖片描述

使用我們的管理員賬號admin進行登陸,伺服器為本地,127.0.0.1

這裡寫圖片描述

如果登陸成功了就會出現下面的介面

這裡寫圖片描述

然後我們新增兩個測試賬號,在openfire後臺,輸入這些資訊進行新增使用者

這裡寫圖片描述

添加了兩個測試賬號,分別為test和test1

這裡寫圖片描述

接下來最重要的事就是Android端了,在這之前,我們需要讓我們的手機和電腦出於同一個區域網內,如果你使用的是模擬器,那麼,不存在這個問題。

獲得的IP地址為10.0.0.24,接下來就是編寫程式碼進行登陸了。

獲得一個連線

 private XMPPTCPConnection getConnection(){
        String server="10.0.0.24";
        int port=5222;
        XMPPTCPConnectionConfiguration.Builder builder = XMPPTCPConnectionConfiguration.builder();
        builder.setServiceName(server);
        builder.setHost(server);
        builder.setPort(port);
        builder.setCompressionEnabled(false);
        builder.setDebuggerEnabled(true);
        builder.setSendPresence(true);
        builder.setSecurityMode(ConnectionConfiguration.SecurityMode.disabled);
        XMPPTCPConnection connection = new XMPPTCPConnection(builder.build());
        return connection;
    }

初始化變數

private EditText account, password,to,content;
private Button login,logout,send;
private  XMPPTCPConnection connection;

connection=getConnection();

account = (EditText) findViewById(R.id.account);
password = (EditText) findViewById(R.id.password);
to = (EditText) findViewById(R.id.to);
content = (EditText) findViewById(R.id.content);
login = (Button) findViewById(R.id.login);
logout = (Button) findViewById(R.id.logout);
send = (Button) findViewById(R.id.send);
login.setOnClickListener(this);
logout.setOnClickListener(this);
send.setOnClickListener(this);

對應的點選事件的實現,也就是登陸,登出,傳送訊息的邏輯

@Override
    public void onClick(View view) {
        switch (view.getId()){
            case R.id.login:{
                final String a = account.getText().toString();
                final String p = password.getText().toString();
                if (TextUtils.isEmpty(a) || TextUtils.isEmpty(p)) {
                    Toast.makeText(getApplicationContext(), "賬號或密碼不能為空", Toast.LENGTH_LONG).show();
                    return;
                }
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        try {
                            connection.connect();
                            connection.login(a, p);
                            Presence presence = new Presence(Presence.Type.available);
                            presence.setStatus("我是線上狀態");
                            connection.sendStanza(presence);
                            ChatManager chatmanager = ChatManager.getInstanceFor(connection);
                            chatmanager.addChatListener(new ChatManagerListener() {
                                @Override
                                public void chatCreated(Chat chat, boolean createdLocally) {
                                    chat.addMessageListener(new ChatMessageListener() {
                                        @Override
                                        public void processMessage(Chat chat, Message message) {
                                            String content=message.getBody();
                                            if (content!=null){
                                                Log.e("TAG", "from:" + message.getFrom() + " to:" + message.getTo() + " message:" + message.getBody());
                                                android.os.Message message1= android.os.Message.obtain();
                                                message1.what=1;
                                                message1.obj="收到訊息:" + message.getBody()+" 來自:"+message.getFrom();
                                                mHandler.sendMessage(message1);
                                            }

                                        }
                                    });
                                }
                            });
                        } catch (SmackException e) {
                            e.printStackTrace();
                        } catch (IOException e) {
                            e.printStackTrace();
                        } catch (XMPPException e) {
                            e.printStackTrace();
                        }

                    }
                }).start();
                break;
            }
            case R.id.logout:
                connection.disconnect();
                break;
            case R.id.send:
                final String t = to.getText().toString();
                final String c = content.getText().toString();
                if (TextUtils.isEmpty(t)||TextUtils.isEmpty(c)) {
                    Toast.makeText(getApplicationContext(), "接收方或內容", Toast.LENGTH_LONG).show();
                    return;
                }

                try {
                    ChatManager chatmanager = ChatManager.getInstanceFor(connection);
                    Chat mChat = chatmanager.createChat(t+"@10.0.0.24");
                    mChat.sendMessage(c);
                }
                catch (SmackException.NotConnectedException e) {
                    e.printStackTrace();
                }
                break;
        }

    }

收到訊息後需要在主執行緒裡操作,簡單的Toast一下

private Handler mHandler=new Handler(){
    @Override
    public void handleMessage(android.os.Message msg) {
        switch (msg.what){
            case 1:
                Toast.makeText(getApplicationContext(),msg.obj+"",Toast.LENGTH_SHORT).show();
                break;
        }
        super.handleMessage(msg);
    }
};

這時候如果你使用測試賬號進行登陸,你會發現登陸不了,會報一個錯誤

這裡寫圖片描述

解決方法也比較簡單,到Openfire的安裝目錄中,尋找conf/openfire.xml檔案

這裡寫圖片描述

在最後一個節點閉合前加入程式碼

  <sasl>
    <mechs> PLAIN </mechs>
  </sasl>

這裡寫圖片描述

重啟OpenFire,這時候你會發現成功登陸了,並能正常的設定使用者的線上狀態了

這裡寫圖片描述

將我們的Spark使用測試賬號test1登陸,Android端使用test登陸,測試訊息是否能成功送到。

這裡寫圖片描述

很簡單的我們把整個流程走了一遍,後面如果有機會的話再繼續研究下這個東西,其實它的功能是很強大的。

最後貼一下Android的原始碼。