<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    Jack Jiang

    我的最新工程MobileIMSDK:http://git.oschina.net/jackjiang/MobileIMSDK
    posts - 494, comments - 13, trackbacks - 0, articles - 1

    本文作者小傅哥,原題“使用DDD+Netty,開發一個分布式IM(即時通信)系統”。為了提升閱讀體驗,有大量修訂和改動,感謝原作者。

    0、系列文章

    跟著源碼學IM(一):手把手教你用Netty實現心跳機制、斷線重連機制

    跟著源碼學IM(二):自已開發IM很難?手把手教你擼一個Andriod版IM

    跟著源碼學IM(三):基于Netty,從零開發一個IM服務端

    跟著源碼學IM(四):拿起鍵盤就是干,教你徒手開發一套分布式IM系統

    跟著源碼學IM(五):正確理解IM長連接、心跳及重連機制,并動手實現

    跟著源碼學IM(六):手把手教你用Go快速搭建高性能、可擴展的IM系統

    跟著源碼學IM(七):手把手教你用WebSocket打造Web端IM聊天

    跟著源碼學IM(八):萬字長文,手把手教你用Netty打造IM聊天

    跟著源碼學IM(九):基于Netty實現一套分布式IM系統》(* 本文)

    1、本文引言

    計算機編程的學習,能不能把知識學到手,講究的是動手實踐。在我編寫的文章中,基本都是以實踐代碼驗證結果為核心來講述文章內容。

    從小我就喜歡動手,就以一個即時通信的項目為例,已經基于不同技術方案實現了5、6次,僅僅為了實踐技術,截圖如下。

    正如上圖這樣:

    • 1)有些是剛學完Socket和Swing的時候,想動手試試這些技術能不能寫個QQ出來;
    • 2)也有的是因為實習培訓需要完成的項目,不過在有了一些基礎后,一周時間就能寫完全部功能;
    • 3)雖然這些項目在現在看上去還是丑丑的界面,以及代碼邏輯可能也不是那么完善。但放在學習階段的每一次實現中,都能為自己帶來很多技術上的成長。

    那么,這次借本文的機會,將IM實踐的機會留給你,希望你能用的上。

    接下來的內容,我會為你介紹如何開發一個IM的方方面面,包括系統架構、通信協議、單聊群聊、表情發送、UI事件驅動等,以及全套的實踐源碼讓你可以上手學習。

    注:源碼在本文“4、本文源碼”一節的附件處可下載。

    學習交流:

    - 即時通訊/推送技術開發交流5群:215477170 [推薦]

    - 移動端IM開發入門文章:《新手入門一篇就夠:從零開發移動端IM

    - 開源IM框架源碼:https://github.com/JackJiang2011/MobileIMSDK 

    本文已同步發布于:http://www.52im.net/thread-3789-1-1.html

    2、知識準備

    * 重要提示:本文不是一篇即時通訊理論文章,文章內容全部由實戰代碼組織而成,如果你對即時通訊(IM)技術理論了解的太少,建議先詳細閱讀:《新手入門一篇就夠:從零開發移動端IM》。

    可能有人不知道 Netty 是什么,這里簡單介紹下:

    Netty 是一個 Java 開源框架。Netty 提供異步的、事件驅動的網絡應用程序框架和工具,用以快速開發高性能、高可靠性的網絡服務器和客戶端程序。

    也就是說,Netty 是一個基于 NIO 的客戶、服務器端編程框架,使用Netty 可以確保你快速和簡單的開發出一個網絡應用,例如實現了某種協議的客戶,服務端應用。

    Netty 相當簡化和流線化了網絡應用的編程開發過程,例如,TCP 和 UDP 的 Socket 服務開發。

    以下是幾篇有關Netty的入門文章,值得一讀:

    1. 新手入門:目前為止最透徹的的Netty高性能原理和框架架構解析
    2. 寫給初學者:Java高性能NIO框架Netty的學習方法和進階策略
    3. 史上最通俗Netty框架入門長文:基本介紹、環境搭建、動手實戰

    如果你連Java的NIO都不知道是什么,下面的文章建議優先讀一下:

    1. 少啰嗦!一分鐘帶你讀懂Java的NIO和經典IO的區別
    2. 史上最強Java NIO入門:擔心從入門到放棄的,請讀這篇!
    3. Java的BIO和NIO很難懂?用代碼實踐給你看,再不懂我轉行!

    Netty源碼和API的在線閱讀地址:

    3、運行效果

    在開始學習之前,先給大家演示下本文配套源碼的運行效果(源碼在本文“4、本文源碼”一節的附件處可下載)。

    聊天頁面:

    添加好友:

    消息提醒:

    4、本文源碼

    本文完整代碼附件下載:

    請從同步發布鏈接中下載:http://www.52im.net/thread-3789-1-1.html

    源碼的目錄結構,如下圖所示:

    這套 IM 代碼分為了三組模塊:UI、客戶端、服務端。

    之所以這樣拆分,是為了將UI展示與業務邏輯隔離,使用事件和接口進行驅動,讓代碼層次更加干凈整潔易于擴展和維護。

    各模塊的作用,具體解釋如下:

    5、系統設計

    在這套IM中,服務端采用DDD領域驅動設計模式進行搭建。將 Netty 的功能交給 SpringBoot 進行啟停控制,同時在服務端搭建控制臺可以非常方便的操作通信系統,進行用戶和通信管理。在客戶端的建設上采用UI分離的方式進行搭建,以保證業務代碼與UI展示分離,做到非常易于擴展的控制。

    另外,在功能實現上包括:完美仿照微信桌面版客戶端、登錄、搜索添加好友、用戶通信、群組通信、表情發送等核心功能。如果有對于實際需要使用的功能,可以按照這套系統框架進行擴展。

     

    解釋一下:

    • 1)UI開發:使用JavaFx與Maven搭建UI桌面工程,逐步講解登錄框體、聊天框體、對話框、好友欄等各項UI展示及操作事件;
    • 2)架構設計:使用DDD領域驅動設計的四層模型結構與Netty結合使用,架構出合理的分層框架(相應庫表功能的設計);
    • 3)功能實現:包括;登錄、添加好友、對話通知、消息發送、斷線重連等各項功能。

    6、UI開發

    6.1 功能劃分

    聊天窗體,相對于登陸窗體來說,聊天窗體的內容會比較多,同時也會相對復雜一些。

    下圖是聊天窗體的功能定義草圖:

     

    如上圖所示:

    • 1)首先是我們整個聊天主窗體的定義,是一塊空白面板,并去掉默認的邊框按鈕 (最小化、退出等);
    • 2)之后是我們左側邊欄,我們稱之為條形 Bar,功能區域的實現;
    • 3)最后添加窗體事件,當點擊按鈕時變換 內容面板 中的填充信息。

    6.2 聊天界面

    對話框選中后的內容區域展現,也就是用戶之間信息發送和展現。

    從整體上看這是一個聯動的過程,點擊左側的對話框用戶,右側就有相應內容的填充。那么右側被填充對話列表 ListView 需要與每一個對話用戶關聯,點擊聊天用戶的時候,是通過反復切換填充的過程。效果如下圖所示。

    參見上圖,我解釋一下:

    • 1)點擊左側的每一個對話框體,右側聊天框填充內容即隨之變化(同時還有相應的對話名稱也會也變化);
    • 2)對話框中左側展示好友發送的信息,右側展示個人發送的信息(同時消息內容會隨著內容的增多而增加高度和寬度);
    • 3)最下面是文本輸入框,在后面的實現里我們文本輸入框采用公用的方式進行設計,當然你也可以設計為單獨的個人使用。

    6.3 好友列表

    大家都經常使用 PC 端的微信,可以知道在好友欄里是分了幾段內容的,其中包含:新的朋友、公眾號、群組和最下面的好友(功能劃分如下圖)。

    參見上圖,我解釋一下:

    • 1)最上面的搜索框這部分內容不變,和前面的一樣。我們目前使用的方式是 fxml 設計,例如這部分是通用功能,可以抽取出來放到代碼中,設計成一個組件元素類;
    • 2)經過我們的分析,在使用 JavaFx 組件開發為基礎下,這部分是一種嵌套 ListView,也就是最底層的面板是一個 ListView,好友和群組有各是一個 ListView,這樣處理后我們會很方便的進行數據填充;
    • 3)另外這樣的結構主要有利于在我們程序運行過程中,如果你添加了好友,那么我們需要將好友信息刷新到好友欄中,而在數據填充的時候,為了更加便捷高效,所以我們設計了嵌套的 ListView(如果還不是特別理解,可以從后續的代碼中獲得答案)。

    6.4 事件定義

    在桌面版 UI 開發中,為了能使 UI 與業務邏輯隔離,需要在我們把 UI 打包后提供出操作界面的展示效果的接口以及界面操作事件抽象類。

    那么可以按照下圖理解:

    以上這些接口就是我們目前 UI 為外部提供的所有行為接口,這些接口的一個鏈路描述就是:打開窗口、搜索好友、添加好友、打開對話框、發送消息。

    7、通信設計

    7.1 系統架構

    在前面我說到更適合的架構,才是符合你當下需要最好的架構。

    那么怎么設計需要的架構呢?

    之所以這樣設計,在這個系統里有如下幾點前提:

    • 1)系統在服務端要有 web 頁面進行管理通信用戶以及服務端的控制和監控;
    • 2)數據庫的對象類,不要被外部污染,要有隔離性(比如:你的數據庫類暴漏給外部做展示類使用了,那么現在需要增加一個字段,而這個字段又不是你數據庫存在的屬性。那么這個時候就已經把數據庫類污染了)。
    • 3)因為目前都是在 Java 語言下實現 Netty 通信,那么服務端與客戶端都會需要使用到通信過程中的協議定義和解析。那么我們需要抽離這一層對外提供 Jar 包(利于重用,不然客戶端和服務端復制同樣的代碼維護,就太惡心了);
    • 4)接口、業務處理、底層服務、通信交互,要有明確的區分和實現,避免造成混亂難以維護。

    結合我們上面這四點的前提,你頭腦中有什么模型結構體現了?以及相應的技術棧選擇上是否有計劃了?

    接下來我會介紹兩種架構設計的模型,一種是你非常熟悉的 MVC,另外一種是你可能聽說過的 DDD 領域驅動設計。

    7.2 通信協議

    從圖稿上來看,我們在傳輸對象的時候需要在傳輸包中添加一個“幀標識”以此來判斷當前的業務對象是哪個對象,也就可以讓我們的業務更加清晰,避免使用大量的 if 語句判斷。

    協議框架:

    agreement

    └── src

        ├── main

        │   ├── java

        │   │   └── org.itstack.naive.chat

        │   │       ├── codec

        │   │       │    ├── ObjDecoder.java

        │   │       │    └── ObjEncoder.java

        │   │       ├── protocol

        │   │       │    ├── demo

        │   │       │    ├── Command.java

        │   │       │    └── Packet.java

        │   │       └── util

        │   │             └── SerializationUtil.java

        │   ├── resources   

        │   │   └── application.yml

        │   └── webapp

        │       └── chat

        │       └── res

        │       └── index.html

        └── test

             └── java

                 └── org.itstack.demo.test

                     └── ApiTest.java

    協議包:

    public abstract class Packet {

        private final static Map<Byte, Class<? extendsPacket>> packetType = new ConcurrentHashMap<>();

        static{

            packetType.put(Command.LoginRequest, LoginRequest.class);

            packetType.put(Command.LoginResponse, LoginResponse.class);

            packetType.put(Command.MsgRequest, MsgRequest.class);

            packetType.put(Command.MsgResponse, MsgResponse.class);

            packetType.put(Command.TalkNoticeRequest, TalkNoticeRequest.class);

            packetType.put(Command.TalkNoticeResponse, TalkNoticeResponse.class);

            packetType.put(Command.SearchFriendRequest, SearchFriendRequest.class);

            packetType.put(Command.SearchFriendResponse, SearchFriendResponse.class);

            packetType.put(Command.AddFriendRequest, AddFriendRequest.class);

            packetType.put(Command.AddFriendResponse, AddFriendResponse.class);

            packetType.put(Command.DelTalkRequest, DelTalkRequest.class);

            packetType.put(Command.MsgGroupRequest, MsgGroupRequest.class);

            packetType.put(Command.MsgGroupResponse, MsgGroupResponse.class);

            packetType.put(Command.ReconnectRequest, ReconnectRequest.class);

        }

        public static Class<? extends Packet> get(Byte command) {

            return packetType.get(command);

        }

     

        /**

         * 獲取協議指令

         *

         * @return 返回指令值

         */

        public abstract Byte getCommand();

    }

    7.3 添加好友

    從上面的流程圖中可以看到,這里包含了兩部分內容:搜索好友和添加好友。

    當添加完成好友后,好友會出現到我們的好友欄中。

    并且這里面我們采用的是單方面同意加好友,也就是你添加一個好友的時候,對方也同樣有你的好友信息。

    如果你的業務中是需要添加好友并同意的,那么可以在發起好友添加的時候,添加一條狀態信息,請求加好友。對方同意后,兩個用戶才能成為好友并進行通信。

    添加好友的樣例代碼:

    public class AddFriendHandler extends MyBizHandler<AddFriendRequest> {

        public AddFriendHandler(UserService userService) {

            super(userService);

        }

        @Override

        public void channelRead(Channel channel, AddFriendRequest msg) {

            // 1. 添加好友到數據庫中[A->B B->A]

            List<UserFriend> userFriendList = newArrayList<>();

            userFriendList.add(newUserFriend(msg.getUserId(), msg.getFriendId()));

            userFriendList.add(newUserFriend(msg.getFriendId(), msg.getUserId()));

            userService.addUserFriend(userFriendList);

            // 2. 推送好友添加完成 A

            UserInfo userInfo = userService.queryUserInfo(msg.getFriendId());

            channel.writeAndFlush(newAddFriendResponse(userInfo.getUserId(), userInfo.getUserNickName(), userInfo.getUserHead()));

            // 3. 推送好友添加完成 B

            Channel friendChannel = SocketChannelUtil.getChannel(msg.getFriendId());

            if(null== friendChannel) return;

            UserInfo friendInfo = userService.queryUserInfo(msg.getUserId());

            friendChannel.writeAndFlush(newAddFriendResponse(friendInfo.getUserId(), friendInfo.getUserNickName(), friendInfo.getUserHead()));

        }

    }

    7.4 消息應答

    從整體的流程可以看到:在用戶發起好友、群組通信的時候,會觸發一個事件行為,接下來客戶端向服務端發送與好友的對話請求。

    服務端收到對話請求后:如果是好友對話,那么需要保存與好友的通信信息到對話框中。同時通知好友,我與你要通信了。你在自己的對話框列表中,把我加進去。

    如果是群組通信:是可以不用這樣通知的,因為不可能把還沒有在線的所有群組用戶全部通知(人家還沒登錄呢),所以這部分只需要在用戶上線收到信息后,創建出對話框到列表中即可。可以仔細理解下,同時也可以想想其他實現的方式。

    消息應答樣例代碼:

    public class MsgHandler extends MyBizHandler<MsgRequest> {

        public MsgHandler(UserService userService) {

            super(userService);

        }

        @Override

        public void channelRead(Channel channel, MsgRequest msg) {

            logger.info("消息信息處理:{}", JSON.toJSONString(msg));

            // 異步寫庫

            userService.asyncAppendChatRecord(newChatRecordInfo(msg.getUserId(), msg.getFriendId(), msg.getMsgText(), msg.getMsgType(), msg.getMsgDate()));

            // 添加對話框[如果對方沒有你的對話框則添加]

            userService.addTalkBoxInfo(msg.getFriendId(), msg.getUserId(), Constants.TalkType.Friend.getCode());

            // 獲取好友通信管道

            Channel friendChannel = SocketChannelUtil.getChannel(msg.getFriendId());

            if(null== friendChannel) {

                logger.info("用戶id:{}未登錄!", msg.getFriendId());

                return;

            }

            // 發送消息

            friendChannel.writeAndFlush(newMsgResponse(msg.getUserId(), msg.getMsgText(), msg.getMsgType(), msg.getMsgDate()));

        }

    }

    7.5 斷線重連

    從上述流程中我們看到:當網絡連接斷開以后,會像服務端發送重新鏈接的請求。 那么在這個發起鏈接的過程,和系統的最開始鏈接有所區別。斷線重連是需要將用戶的 ID 信息一同發送給服務端,好讓服務端可以去更新用戶與通信管道 Channel 的綁定關系。

    同時還需要更新群組內的重連信息,把用戶的重連加入群組映射中。此時就可以恢復用戶與好友和群組的通信功能。

    消息應答樣例代碼:

    // Channel 狀態定時巡檢;3 秒后每 5 秒執行一次

    scheduledExecutorService.scheduleAtFixedRate(() -> {while(!nettyClient.isActive()) {System.out.println("通信管道巡檢:通信管道狀態"+ nettyClient.isActive());

            try{System.out.println("通信管道巡檢:斷線重連 [Begin]");

                Channel freshChannel = executorService.submit(nettyClient).get();

                if(null== CacheUtil.userId) continue;

                freshChannel.writeAndFlush(newReconnectRequest(CacheUtil.userId));

            } catch(InterruptedException | ExecutionException e) {System.out.println("通信管道巡檢:斷線重連 [Error]");}

        }

    }, 3, 5, TimeUnit.SECONDS);

    相關文章學習:

    1. 為何基于TCP協議的移動端IM仍然需要心跳保活機制?
    2. 一文讀懂即時通訊應用中的網絡心跳包機制:作用、原理、實現思路等
    3. 融云技術分享:融云安卓端IM產品的網絡鏈路保活技術實踐
    4. 正確理解IM長連接的心跳及重連機制,并動手實現(有完整IM源碼)
    5. 一種Android端IM智能心跳算法的設計與實現探討(含樣例代碼)
    6. 手把手教你用Netty實現網絡通信程序的心跳機制、斷線重連機制
    7. Web端即時通訊實踐干貨:如何讓你的WebSocket斷網重連更快速?

    7.6 集群通信

    如上圖所示,我是這樣實現IM集群通信的:

    • 1)跨服務之間案例采用redis的發布和訂閱進行傳遞消息,如果你是大型服務可以使用zookeeper;
    • 2)用戶A在發送消息給用戶B時候,需要傳遞B的channeId,以用于服務端進行查找channeId所屬是否自己的服務內;
    • 3)單臺機器也可以啟動多個Netty服務,程序內會自動尋找可用端口。

    8、本文小結

    此IM系統涉及到的技術棧內容較多:Netty4.x、SpringBoot、Mybatis、Mysql、JavaFx、layui等技術棧的使用,以及整個系統框架結構采用DDD四層架構+Socket模塊的方式進行搭建,所有的UI都以前后端分離事件驅動方式進行設計。在這個過程中只要你能堅持學習下來,那么一定會收獲非常多的內容。足夠吹牛啦!

    任何一個新技術棧的學習過程都會包括這樣一條路線:運行HelloWorld、熟練使用API、項目實踐以及最后的深度源碼挖掘。 那么在聽到這樣一個需求時候,Java程序員肯定會想到一些列的技術知識點來填充我們項目中的各個模塊(例如:界面用JavaFx、Swing等,通信用Socket或者知道Netty框架、服務端控制用MVC模型加上SpringBoot等)。但是怎么將這些各個技術棧合理的架設出我們的系統確是學習、實踐、成長過程中最重要的部分。

    好了,IM開發實際上涉及的知識維度非常多,限于篇幅就不在這里啰嗦更多,各位讀者務必對著源碼同步進行學習,這樣效果會更好(源碼在本文“4、本文源碼”一節的附件處可下載)。

    9、參考資料

    [1] 新手入門:目前為止最透徹的的Netty高性能原理和框架架構解析

    [2] 寫給初學者:Java高性能NIO框架Netty的學習方法和進階策略

    [3] 史上最強Java NIO入門:擔心從入門到放棄的,請讀這篇!

    [4] Java的BIO和NIO很難懂?用代碼實踐給你看,再不懂我轉行!

    [5] 史上最通俗Netty框架入門長文:基本介紹、環境搭建、動手實戰

    [6] 理論聯系實際:一套典型的IM通信協議設計詳解

    [7] 淺談IM系統的架構設計

    [8] 簡述移動端IM開發的那些坑:架構設計、通信協議和客戶端

    [9] 一套海量在線用戶的移動端IM架構設計實踐分享(含詳細圖文)

    [10] 一套原創分布式即時通訊(IM)系統理論架構方案

    [11]一套高可用、易伸縮、高并發的IM群聊、單聊架構方案設計實踐

    [12] 一套億級用戶的IM架構技術干貨(上篇):整體架構、服務拆分等

    [13] 一套億級用戶的IM架構技術干貨(下篇):可靠性、有序性、弱網優化等

    [14] 從新手到專家:如何設計一套億級消息量的分布式IM系統

    [15] 基于實踐:一套百萬消息量小規模IM系統技術要點總結

    本文已同步發布于“即時通訊技術圈”公眾號。

    同步發布鏈接是:http://www.52im.net/thread-3789-1-1.html



    作者:Jack Jiang (點擊作者姓名進入Github)
    出處:http://www.52im.net/space-uid-1.html
    交流:歡迎加入即時通訊開發交流群 215891622
    討論:http://www.52im.net/
    Jack Jiang同時是【原創Java Swing外觀工程BeautyEye】【輕量級移動端即時通訊框架MobileIMSDK】的作者,可前往下載交流。
    本博文 歡迎轉載,轉載請注明出處(也可前往 我的52im.net 找到我)。


    只有注冊用戶登錄后才能發表評論。


    網站導航:
     
    Jack Jiang的 Mail: jb2011@163.com, 聯系QQ: 413980957, 微信: hellojackjiang
    主站蜘蛛池模板: 亚洲精品宾馆在线精品酒店| 亚洲精品国产美女久久久| 亚洲精品人成电影网| 国产激情免费视频在线观看 | 欧洲精品码一区二区三区免费看| 蜜桃视频在线观看免费网址入口| 亚洲av成人综合网| 天天看片天天爽_免费播放| 亚洲av无码专区在线电影天堂| 国产精品视频免费一区二区三区| 国产精品亚洲色图| 国产亚洲?V无码?V男人的天堂| 国产免费内射又粗又爽密桃视频 | 亚洲av鲁丝一区二区三区| 99在线观看免费视频| 亚洲成av人片不卡无码| 成人毛片免费观看视频| 爱情岛论坛免费视频| 亚洲综合图色40p| 18禁无遮挡无码国产免费网站| 亚洲精品中文字幕无乱码麻豆| 日本特黄特色aa大片免费| 青青草国产免费国产是公开| 亚洲色精品88色婷婷七月丁香| 91热久久免费精品99| 亚洲熟妇无码AV不卡在线播放| 免费人成在线观看网站视频 | 色多多A级毛片免费看| 久久综合日韩亚洲精品色| 免费在线观看视频网站| 久久亚洲精品高潮综合色a片| 自拍偷自拍亚洲精品情侣| 84pao国产成视频免费播放| 亚洲国产精品嫩草影院| 中文亚洲AV片在线观看不卡| 成人在线免费看片| a毛片成人免费全部播放| 亚洲成AV人综合在线观看| 亚洲第一区精品日韩在线播放| 99久久99久久免费精品小说| 亚洲AV无码一区二区三区网址|