Smack網(wǎng)絡層分析

概述

Smack是一個開源的實現(xiàn)了XMPP協(xié)議的庫苟翻,特別是4.1.0版本以后,直接支持Android系統(tǒng)骗污,無需再使用以前那個專門針對Android系統(tǒng)的aSmack移植庫了.雖然在移動端上崇猫,用XMPP協(xié)議來做IM并不是一個最優(yōu)選擇,市面上這些大公司基本都是用自己定制的私有協(xié)議需忿,沒有采用XMPP協(xié)議的诅炉,不過我們可以拋開協(xié)議層面,只分析一下Smack庫在網(wǎng)絡層的實現(xiàn)屋厘,也是有借鑒意義的涕烧。

總體結構

network.png

Smack抽象出一個XMPPConnection的概念,要想收發(fā)消息汗洒,首先得建立這個connection议纯,而且這種connection是可以由多個實例的。XMPPConnection只是一個接口溢谤,AbstractXMPPConnection實現(xiàn)了這個接口并加入了login瞻凤,connect,processStanza等方法溯香。AbstractXMPPConnection有兩個實現(xiàn)類鲫构,XMPPBOSHConnection和XMPPTCPConnection。其中XMPPBOSHConnection是基于Http協(xié)議來實現(xiàn)的玫坛,而XMPPTCPConnection是直接用Socket來實現(xiàn)的長連接通信,本文分析的也就是XMPPTCPConnection包晰。一個簡單的使用實例如下:

XMPPTCPConnection con = new XMPPTCPConnection("igniterealtime.org");
  // Connect to the server
  con.connect();
  // Most servers require you to login before performing other tasks.
  con.login("jsmith", "mypass");
  // Start a new conversation with John Doe and send him a message.
  Chat chat = ChatManager.getInstanceFor(con).createChat("jdoe@igniterealtime.org", new MessageListener() {
      public void processMessage(Chat chat, Message message) {
          // Print out any messages we get back to standard out.
          System.out.println("Received message: " + message);
      }
  });
  chat.sendMessage("Howdy!");
  // Disconnect from the server
  con.disconnect();

接口介紹

XMPPConnection這個接口里有幾個主要的方法 :

public void sendStanza(Stanza stanza) throws NotConnectedException, InterruptedException;
public void addConnectionListener(ConnectionListener connectionListener);
public void addPacketInterceptor(StanzaListener packetInterceptor, StanzaFilter packetFilter);
public void addPacketSendingListener(StanzaListener packetListener, StanzaFilter packetFilter);
public PacketCollector createPacketCollector(StanzaFilter packetFilter);
public void addAsyncStanzaListener(StanzaListener packetListener, StanzaFilter packetFilter);
public void addSyncStanzaListener(StanzaListener packetListener, StanzaFilter packetFilter);
  • sendStanza 發(fā)送包到服務器湿镀。在最新版的Smack中炕吸,Stanza就是以前版本中的Packet

  • addConnectionListener 添加ConnectionListener到XMPPConnection中。在該Listener中勉痴,監(jiān)聽者可以得到連接是否成功建立赫模,連接關閉,連接異常關閉蒸矛,重連是否成功等事件

  • addPacketInterceptor 向Connection中注冊攔截器StanzaListener瀑罗,所有發(fā)往服務器的包都會先過一遍攔截器,你可以在攔截器中對這些包進行處理雏掠;StanzaFilter過濾器可以允許你定制哪些包才需要攔截; StanzaListener和StanzaFilter常常配對使用斩祭,代碼中有各種wrapper類(如ListenerWrapper、InterceptorWrapper等)乡话,就是把這兩個接口組合在一個類中摧玫,一個負責過濾包,一個負責實際處理包

  • addPacketSendingListener 注冊一個Listener绑青,當把包通過Socket寫出去后诬像,會回調(diào)這個Listener告知正在發(fā)送狀態(tài)

  • createPacketCollector 當你想接收某種類型的包時,可以新建一個包收集器闸婴。和StanzaListener不同坏挠,包收集器是阻塞式的,直到指定的包收到或者出現(xiàn)超時(我們可以設置等待一個包的最大時間)等異常

PacketCollector messageCollector = connection.createPacketCollector(messageFilter);
        try {
            connection.createPacketCollectorAndSend(request).nextResultOrThrow();
            // Collect the received offline messages
            Message message = messageCollector.nextResult();
            while (message != null) {
                messages.add(message);
                message = messageCollector.nextResult();
            }
        }
        finally {
            // Stop queuing offline messages
            messageCollector.cancel();
        }
        return messages;
  • addAsyncStanzaListener和addSyncStanzaListener 添加處理收到的包的回調(diào)接口邪乍;其中一個叫同步一個叫異步區(qū)別在于,執(zhí)行回調(diào)方法所用的線程池不一樣癞揉,其中異步用的是Executors.newCachedThreadPool,而同步用的是一個Executors.newSingleThreadExecutor溺欧,可以保證執(zhí)行順序
// First handle the async recv listeners. Note that this code is very similar to what follows a few lines below,
        // the only difference is that asyncRecvListeners is used here and that the packet listeners are started in
        // their own thread.
        final Collection<StanzaListener> listenersToNotify = new LinkedList<StanzaListener>();
        synchronized (asyncRecvListeners) {
            for (ListenerWrapper listenerWrapper : asyncRecvListeners.values()) {
                if (listenerWrapper.filterMatches(packet)) {
                    listenersToNotify.add(listenerWrapper.getListener());
                }
            }
        }

        for (final StanzaListener listener : listenersToNotify) {
            asyncGo(new Runnable() {
                @Override
                public void run() {
                    try {
                        listener.processPacket(packet);
                    } catch (Exception e) {
                        LOGGER.log(Level.SEVERE, "Exception in async packet listener", e);
                    }
                }
            });
        }

        // Loop through all collectors and notify the appropriate ones.
        for (PacketCollector collector: collectors) {
            collector.processPacket(packet);
        }

        // Notify the receive listeners interested in the packet
        listenersToNotify.clear();
        synchronized (syncRecvListeners) {
            for (ListenerWrapper listenerWrapper : syncRecvListeners.values()) {
                if (listenerWrapper.filterMatches(packet)) {
                    listenersToNotify.add(listenerWrapper.getListener());
                }
            }
        }

        // Decouple incoming stanza processing from listener invocation. Unlike async listeners, this uses a single
        // threaded executor service and therefore keeps the order.
        singleThreadedExecutorService.execute(new Runnable() {
            @Override
            public void run() {
                for (StanzaListener listener : listenersToNotify) {
                    try {
                        listener.processPacket(packet);
                    } catch(NotConnectedException e) {
                        LOGGER.log(Level.WARNING, "Got not connected exception, aborting", e);
                        break;
                    } catch (Exception e) {
                        LOGGER.log(Level.SEVERE, "Exception in packet listener", e);
                    }
                }
            }
        });

AbstractXMPPConnection實現(xiàn)了XMPPConnection接口喊熟,各種Listener的注冊和回調(diào)就是在這個類里完成的,但如login姐刁,connect芥牌,shutdown等方法的具體實現(xiàn)是位于其子類中的。

連接過程

真正執(zhí)行連接動作的是XMPPTCPConnection中connectInternal的方法

protected void connectInternal() throws SmackException, IOException, XMPPException, InterruptedException {
        closingStreamReceived.init();
        // Establishes the TCP connection to the server and does setup the reader and writer. Throws an exception if
        // there is an error establishing the connection
        connectUsingConfiguration();

        // We connected successfully to the servers TCP port
        initConnection();

        // Wait with SASL auth until the SASL mechanisms have been received
        saslFeatureReceived.checkIfSuccessOrWaitOrThrow();

        // Make note of the fact that we're now connected.
        connected = true;
        callConnectionConnectedListener();
    }

connectUsingConfiguration方法中聂使,用配置類XMPPTCPConnectionConfiguration提供的hostAddress壁拉,timeout等數(shù)據(jù)創(chuàng)建一個Socket連接出來。隨后進行了一些初始化柏靶,例如初始化reader弃理,writer變量:

private void initReaderAndWriter() throws IOException {
        InputStream is = socket.getInputStream();
        OutputStream os = socket.getOutputStream();
        if (compressionHandler != null) {
            is = compressionHandler.getInputStream(is);
            os = compressionHandler.getOutputStream(os);
        }
        // OutputStreamWriter is already buffered, no need to wrap it into a BufferedWriter
        writer = new OutputStreamWriter(os, "UTF-8");
        reader = new BufferedReader(new InputStreamReader(is, "UTF-8"));

        // If debugging is enabled, we open a window and write out all network traffic.
        initDebugger();
    }

PacketWriter對包的發(fā)送進行了封裝,該類里維護一個BlockingQueue屎蜓,所有要發(fā)送的包都先插入到這個隊列中痘昌,同時起一個線程不停消費這個隊列,最終是通過writer把數(shù)據(jù)寫往服務器

                        while (!queue.isEmpty()) {
                            Element packet = queue.remove();
                            writer.write(packet.toXML().toString());
                        }
                        writer.flush();

而PacketReader則是對包的讀取和解析進行了封裝,類里面有個XmlPullParser辆苔,通過reader進行了初始化

 packetReader.parser = PacketParserUtils.newXmppParser(reader);

然后起了一個線程不停進行包的解析

            Async.go(new Runnable() {
                public void run() {
                    parsePackets();
                }
            }, "Smack Packet Reader (" + getConnectionCounter() + ")");
         }

解析出來的包回調(diào)到AbstractXMPPConnection類中的parseAndProcessStanza方法算灸,最終調(diào)用各種已注冊好的StanzaListener、PacketCollector來處理

XMPPConnectionRegistry

這個靜態(tài)類中有個ConnectionCreationListener的集合

private final static Set<ConnectionCreationListener> connectionEstablishedListeners =
            new CopyOnWriteArraySet<ConnectionCreationListener>();

當XMPPConnection初始化的時候驻啤,會通知給各個Listener

protected AbstractXMPPConnection(ConnectionConfiguration configuration) {
        saslAuthentication = new SASLAuthentication(this, configuration);
        config = configuration;
        // Notify listeners that a new connection has been established
        for (ConnectionCreationListener listener : XMPPConnectionRegistry.getConnectionCreationListeners()) {
            listener.connectionCreated(this);
        }
    }

像ReconnectionManager菲驴,PingManager等策略管理類,會在靜態(tài)代碼塊中直接注冊ConnectionCreationListener

static {
        XMPPConnectionRegistry.addConnectionCreationListener(new ConnectionCreationListener() {
            public void connectionCreated(XMPPConnection connection) {
                if (connection instanceof AbstractXMPPConnection) {
                    ReconnectionManager.getInstanceFor((AbstractXMPPConnection) connection);
                }
            }
        });
    }

ReconnectionManager

由于可以創(chuàng)建多個XMPPConnection的實例骑冗,ReconnectionManager的實例也有多個赊瞬,和XMPPConnection一一對應,實際上ReconnectionManager持有了XMPPConnection的弱引用贼涩,用于進行與Connection相關的操作巧涧。

類里面還定義了不同的重連策略ReconnectionPolicy,有按固定頻率重連的磁携,也有按隨機間隔重連的褒侧,

private int timeDelay() {
                attempts++;

                // Delay variable to be assigned
                int delay;
                switch (reconnectionPolicy) {
                case FIXED_DELAY:
                    delay = fixedDelay;
                    break;
                case RANDOM_INCREASING_DELAY:
                    if (attempts > 13) {
                        delay = randomBase * 6 * 5; // between 2.5 and 7.5 minutes (~5 minutes)
                    }
                    else if (attempts > 7) {
                        delay = randomBase * 6; // between 30 and 90 seconds (~1 minutes)
                    }
                    else {
                        delay = randomBase; // 10 seconds
                    }
                    break;
                default:
                    throw new AssertionError("Unknown reconnection policy " + reconnectionPolicy);
                }

                return delay;
            }

ReconnectionManager向XMPPConnection注冊了ConnectionListener,當XMPPConnection中發(fā)生連接異常時谊迄,如PacketWriter闷供、PacketReader讀寫包異常時,會通過ConnectionListener中的connectionClosedOnError方法统诺,通知ReconnectionManager進行重連重試歪脏。

PingManager、ServerPingWithAlarmManager

PingManager實現(xiàn)了協(xié)議規(guī)定的定時發(fā)送Ping消息到服務器的策略粮呢,默認是30分鐘的間隔婿失。ServerPingWithAlarmManager是針對Android平臺的實現(xiàn),用AlarmManager來實現(xiàn)的定時策略啄寡,在代碼里寫死是30分鐘的頻率豪硅,這在移動端肯定是不適用的,另外也沒看到針對各種網(wǎng)絡環(huán)境的處理挺物,看來為保證長連接的穩(wěn)定性懒浮,需要開發(fā)者自己再去實現(xiàn)一些心跳和重連策略

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市识藤,隨后出現(xiàn)的幾起案子砚著,更是在濱河造成了極大的恐慌,老刑警劉巖痴昧,帶你破解...
    沈念sama閱讀 211,348評論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件稽穆,死亡現(xiàn)場離奇詭異,居然都是意外死亡赶撰,警方通過查閱死者的電腦和手機舌镶,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,122評論 2 385
  • 文/潘曉璐 我一進店門柱彻,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人乎折,你說我怎么就攤上這事绒疗∏中” “怎么了骂澄?”我有些...
    開封第一講書人閱讀 156,936評論 0 347
  • 文/不壞的土叔 我叫張陵,是天一觀的道長惕虑。 經(jīng)常有香客問我坟冲,道長,這世上最難降的妖魔是什么溃蔫? 我笑而不...
    開封第一講書人閱讀 56,427評論 1 283
  • 正文 為了忘掉前任健提,我火速辦了婚禮,結果婚禮上伟叛,老公的妹妹穿的比我還像新娘私痹。我一直安慰自己,他們只是感情好统刮,可當我...
    茶點故事閱讀 65,467評論 6 385
  • 文/花漫 我一把揭開白布紊遵。 她就那樣靜靜地躺著,像睡著了一般侥蒙。 火紅的嫁衣襯著肌膚如雪暗膜。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,785評論 1 290
  • 那天鞭衩,我揣著相機與錄音学搜,去河邊找鬼。 笑死论衍,一個胖子當著我的面吹牛瑞佩,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播坯台,決...
    沈念sama閱讀 38,931評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼炬丸,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了捂人?” 一聲冷哼從身側響起御雕,我...
    開封第一講書人閱讀 37,696評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎滥搭,沒想到半個月后酸纲,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,141評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡瑟匆,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,483評論 2 327
  • 正文 我和宋清朗相戀三年闽坡,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,625評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡疾嗅,死狀恐怖外厂,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情代承,我是刑警寧澤汁蝶,帶...
    沈念sama閱讀 34,291評論 4 329
  • 正文 年R本政府宣布,位于F島的核電站论悴,受9級特大地震影響掖棉,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜膀估,卻給世界環(huán)境...
    茶點故事閱讀 39,892評論 3 312
  • 文/蒙蒙 一幔亥、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧察纯,春花似錦帕棉、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,741評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至握恳,卻和暖如春瞒窒,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背乡洼。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評論 1 265
  • 我被黑心中介騙來泰國打工崇裁, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人束昵。 一個月前我還...
    沈念sama閱讀 46,324評論 2 360
  • 正文 我出身青樓拔稳,卻偏偏與公主長得像,于是被迫代替她去往敵國和親锹雏。 傳聞我的和親對象是個殘疾皇子巴比,可洞房花燭夜當晚...
    茶點故事閱讀 43,492評論 2 348

推薦閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務發(fā)現(xiàn)礁遵,斷路器轻绞,智...
    卡卡羅2017閱讀 134,629評論 18 139
  • 一、Smack庫概述 ????Smack是一個開源佣耐、易用的XMPP/Jabber客戶端庫政勃,它使用Java語言開發(fā),...
    AndryYu閱讀 6,063評論 2 13
  • 從三月份找實習到現(xiàn)在兼砖,面了一些公司奸远,掛了不少既棺,但最終還是拿到小米、百度懒叛、阿里丸冕、京東、新浪薛窥、CVTE胖烛、樂視家的研發(fā)崗...
    時芥藍閱讀 42,211評論 11 349
  • 1. Java基礎部分 基礎部分的順序:基本語法,類相關的語法拆檬,內(nèi)部類的語法洪己,繼承相關的語法妥凳,異常的語法竟贯,線程的語...
    子非魚_t_閱讀 31,598評論 18 399
  • 換手機
    10e45c2d55b2閱讀 294評論 0 1