實現過程全紀錄——自己寫一個“微信朋友圈”(包括移動端與PC端)


一.朋友圈的基本單元——動態

      首先定義一個自定義控件用來顯示每條動態。

二.運行效果

      

三.核心解讀

      PushedMessage 有個PushIndex屬性,表示發送消息的index,從0開始遞增。每個人Push的index都是從0開始,ResponseMoments 的 MessageList 是個字典,Key表示 index。

        //
        // 摘要:
        //     拉取我的朋友們發的消息。當收到服務器回復,將觸發ResponseMomentsReceived事件。
        //
        // 參數:
        //   startPullIndex:
        //     從哪個index的消息開始拉取(不包含該index對應的那條)。如果startPullIndex小於0,表示拉取最新的N條。
        //
        //   latest:
        //     latest為true,表示拉取更新的(大於startPullIndex);為false表示拉取之前的(小於startPullIndex)。
        void Pull(long startPullIndex, bool latest);
        //
        // 摘要:
        //     拉取目標用戶所發的消息。當收到服務器回復,將觸發ResponseMomentsReceived事件。
        //
        // 參數:
        //   ownerID:
        //     所要拉取的目標用戶的ID
        //
        //   startPushIndex:
        //     從哪個index的消息開始拉取(不包含該index對應的那條)。如果startPushIndex小於0,表示拉取最新的N條。
        //
        //   latest:
        //     latest為true,表示拉取更新的(大於startPullIndex);為false表示拉取之前的(小於startPullIndex)。
        void Pull(string ownerID, long startPushIndex, bool latest);

      當調用這個方法時,表示獲取我的朋友們的動態,我朋友們的動態會排序,index就是用這個key表示的。

      latest 參數 表示是下拉,還是上拉。 

四.DynamicsLoader解讀 

      DynamicsLoader中最主要的是Load方法。

  public void Load(Dictionary<long, PushedMessage> currentMessageList, FlowLayoutPanel flowLayoutPanel, IMomentsClient client)
        {
            this.preLastIndex = this.lastIndex;
            foreach (var item in currentMessageList)
            {
                if (this.firstIndex < 0)
                {
                    this.firstIndex = item.Key - currentMessageList.Count + 1;
                }
                if (item.Key < this.firstIndex)
                {
                    this.firstIndex = item.Key;
                }
                if (item.Key > this.lastIndex)
                {
                    this.lastIndex = item.Key;
                }
                if (!this.messageList.ContainsKey(item.Key))
                {
                    this.messageList.Add(item.Key, item.Value);
                }
            }
            this.freeIndex = this.lastIndex - 20;

            if ((this.lastIndex - this.preLastIndex) > 39)
            {
                this.firstIndex = this.lastIndex - currentMessageList.Count + 1;
                this.preLastIndex = this.firstIndex;
                this.freeIndex = this.preLastIndex;
                this.messageList.Clear();
                this.messageList = currentMessageList;
            }
            this.listIndex = new List<long>((IEnumerable<long>)this.messageList.Keys);
            this.listIndex.Sort();
            flowLayoutPanel.Controls.Clear();           
            foreach (var item in this.listIndex)
            {
                DynamicsControl crl = new DynamicsControl();
                crl.Initialize(this.messageList[item].OwnerID, Encoding.UTF8.GetString(this.messageList[item].Content), this.messageList[item].PushTime);
                flowLayoutPanel.Controls.Add(crl);
                flowLayoutPanel.Controls.SetChildIndex(crl, 0);            
            }
            this.listIndex.Clear();
            this.listIndex = null;
            if (this.isUp)
            {
                if (flowLayoutPanel.Controls.Count != 0)
                {
                    flowLayoutPanel.ScrollControlIntoView(flowLayoutPanel.Controls[0]);
                }              
            }
            else
            {
                if (flowLayoutPanel.Controls.Count != 0)
                {
                    flowLayoutPanel.ScrollControlIntoView(flowLayoutPanel.Controls[flowLayoutPanel.Controls.Count - 1]); 
                }       
            }          
            if (currentMessageList.Count == 0)
            {
                return;
            }
            if ((this.lastIndex - this.preLastIndex) < 20)
            {
                return;
            }
            do
            {
                client.Pull(this.freeIndex, false);
                this.freeIndex -= 20;
            }
            while (this.freeIndex > this.preLastIndex);
        }

      正如前面所言,每一條動態都有一個index,那么我每次獲取動態的時候,就需要保留一個最小的索引,用於下拉的時候使用;保留一個最大的索引,用於上拉的時候使用。當最新的動態比本地的動態多出20條以上時,則分為兩種情況:1.兩次可以加載完的就加載兩次 ,2兩次無法加載完的,就只加載最新的20條,並將本地舊動態全部刪除。 

五.移動端如何通信

一.消息編解碼總體策略  

NPush默認使用緊湊的序列化器,其遵循的策略如下:
字符串一律使用UTF-8編碼。如果是基礎數據類型,則直接記錄其字節。
如果是bool,則用一個字節表示,0表示false,1表示true。
如果是string,先記錄其長度(int,-1表示為null,0表示string.Empty),再記錄UTF8編碼的字節。
如果是byte[],先記錄其長度(int,-1表示為null),再記錄其內容。
如果是Image ,先記錄其長度(int,-1表示為null),再記錄是否為Gif(一個byte),再記錄序列化內容。
如果是Color ,長度固定為3個字節,一次記錄R/G/B的值。
如果是List<>,先記錄元素數(int,-1表示為null),再依次記錄每個元素的內容。
如果是Dictionary<,>,先記錄元素對的個數(int,-1表示為null),再依次記錄每個元素的Key,Value。
如果是自定義的class和struct,則先記錄其序列化的長度(int,-1表示為null),再記錄其序列化后的內容 

 二.消息頭

      消息頭固定長度為8。

     以登陸請求消息為例:LoginContract,C->S,MessageType為0 

三.消息

       DeviceType取值: 0 - DotNet ,1 - Android ,2 - iOS       

六.序列化原理

       在分布式通信系統中,網絡傳遞的是二進制流,而內存中是我們基於對象模型構建的各種各樣的對象,當我們需要將一個對象通過網絡傳遞給另一個節點時,首先需要將其序列化為字節流,然后通過網絡發送給目標節點,目標節點接收后,再反序列化為對象實例。

       為了將業務對象轉換為二進制流,大家通常有兩種方案可以選擇:使用.NET自帶的二進制序列化器,或,先將業務對象轉換為字符串(比如xml),再將結果用類似UTF8進行編碼得到字節流。 然而這兩種方案都有缺陷。

1..NET自帶的二進制序列化器

      缺點:強侵入性,序列化結果臃腫,其size巨大,效率低下,加密困難。

2.通過string進行中轉

      缺點:需要自己打造協議對象與字符串之間的相互轉換、序列化之后的結果的Size取決於我們協議對象與字符串之間相互轉換時所采用的規則,同樣也取決於我們的耐心程度 、同.NET自帶的二進制序列化器,對象屬性值的讀取/設置、對象的創建等通常也是基於反射的,所以,效率同樣存在問題。 

     基於上述,我選擇采用自己的序列化方式,性能較.NET自帶的二進制序列化器提高了7~8倍。  

七.源碼下載 

    (說明:壓縮包中有SQL腳本,我使用的是Mysql數據庫。客戶端登陸賬號為test01~test99,以及默認的testQQ。相應的好友關系可以查看服務端的TestFriendProvider類。 )

     安卓客戶端下載

——————————————————————————————————————

推薦閱讀 :那些年搞不懂的高深術語——依賴倒置•控制反轉•依賴注入•面向接口編程 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM