1. 程式人生 > >通訊框架 T-io 學習——給初學者的Demo:ShowCase設計分析

通訊框架 T-io 學習——給初學者的Demo:ShowCase設計分析

char bst row 重復 框架 enc 簡單介紹 packet 處理器

  前言

  最近閑暇時間研究Springboot,正好需要用到即時通訊部分了,雖然springboot 有websocket,但是我還是看中了 t-io框架。看了部分源代碼和示例,先把helloworld敲了一遍,又把showcase代碼敲了一遍,決定做一個總結。本篇文章並不會解釋T-io是如何通訊的,而是從showcase這個給t-io初學者寫的demo分析showcase的設計思路,以及為什麽這麽設計等。不過都是我個人理解,疏漏之處在所難免。

T-io簡單介紹

  t-io 源代碼:https://gitee.com/tywo45/t-io/

  代碼結構很簡單,首先我們知道通訊有客戶端(client)和服務端(server).它們之間又會存在一些重復的業務處理邏輯,於是就有common的存在。那麽整體的showcase下包含三部分:client,server,common。在代碼分析之前呢,先簡單介紹一下關於使用tio實現通訊的基礎思路。我從tio官方截了兩個圖:

技術分享

  server端,我們只看紅色部分。沒錯,要實現AioHandler中的encode,decode,handler方法。然後創建ServerGroupContext,最後調用start方法開啟服務端。

技術分享

  client端,同樣也需要實現AioHandler中的encode,decode,handler方法。不過客戶端可以看到,多了一個心跳包(heartbeatPacket)。

通訊流程

  我們知道,最基本的通訊流程就是,客戶端發送消息到服務端,服務端處理之後,返回響應結果到客戶端。或者服務端主動推送消息到客戶端。因為客戶端發送的消息格式不固定,所以t-io把編解碼的權利交給開發者,這樣可以自定義消息結構。那麽Demo中由於采用的是同樣的編解碼方式。所以會有一個在common中的一個基礎實現類。當然,由於消息類型的不同,具體的handler方法實現還是得區分不同的處理。

結構分析

  以下的圖都是根據我自己的理解畫的,錯誤之處歡迎指正。  

  首先,我們看一下接口,類關系圖:

  技術分享

  首先,AioHandler,ClientAioHandler,ServerAioHandler 都是t-io中的Hander接口。我們從ShowcaseAbsAioHander開始看。上文中說道編解碼屬於通用部分,於是ShowcaseAbsAioHander 實現了AioHandler 接口中的 encode,decode方法。就是說不管客戶端,服務端編碼,解碼方式都是同樣的。其中有一個基礎包 ShowcasePacket 。 它是貫穿整個通訊流程的。我們看一下代碼:

public class ShowcasePacket extends Packet {
        
    private byte type;//消息類型(用於消息處理)

    private byte[] body;//消息體
}

  其中,type消息類型是對應在common中的Type接口,它定義了不同的消息類型。

 1 public interface Type {
 2 
 3     /**
 4      * 登錄消息請求
 5      */
 6     byte LOGIN_REQ = 1;
 7     /**
 8      * 登錄消息響應
 9      */
10     byte LOGIN_RESP = 2;
11 
12     /**
13      * 進入群組消息請求
14      */
15     byte JOIN_GROUP_REQ = 3;
16     /**
17      * 進入群組消息響應
18      */
19     byte JOIN_GROUP_RESP = 4;
20 
21     /**
22      * 點對點消息請求
23      */
24     byte P2P_REQ = 5;
25     /**
26      * 點對點消息響應
27      */
28     byte P2P_RESP = 6;
29 
30     /**
31      * 群聊消息請求
32      */
33     byte GROUP_MSG_REQ = 7;
34     /**
35      * 群聊消息響應
36      */
37     byte GROUP_MSG_RESP = 8;
38 
39     /**
40      * 心跳
41      */
42     byte HEART_BEAT_REQ = 99;
43 
44 }

  我們繼續看上圖,ShowcaseClientAioHandlerShowCaseServerAioHandler 這兩個類的實現差不多。都是做基礎消息處理。並且根據消息類型創建(獲取)不同的消息處理器(handler)。實現代碼如下:

    @Override
    public void handler(Packet packet, ChannelContext channelContext) throws Exception {
        //接收到的消息包
        ShowcasePacket showcasePacket = (ShowcasePacket) packet;
        //獲取消息類型
        Byte type = showcasePacket.getType();
        //從handleMap中獲取到具體的消息處理器
        AbsShowcaseBsHandler<?> showcaseBsHandler = handlerMap.get(type);
     //服務端的處理可能由於type類型不正確拿不到相應的消息處理器,直接return不給客戶端響應。(或者統一返回錯誤消息)
//處理消息 showcaseBsHandler.handler(showcasePacket, channelContext); return; }

  下面我們看一下,handler相關接口的設計。

技術分享

  可以看到,消息處理類使用了泛型。AbsShowcaseBsHandler<T> 實現了ShowcaseBsHandlerIntf 中的handler方法。並且定義了一個抽象方法 handler,其中多了 T bsBody 參數。可以知道,他對消息的實現,就是將消息字符轉換為具體的消息對象,然後在調用具體的消息處理器處理相應的消息邏輯。代碼如下:

  

public abstract class AbsShowcaseBsHandler<T extends BaseBody> implements ShowcaseBsHandlerIntf {
    private static Logger log = LoggerFactory.getLogger(AbsShowcaseBsHandler.class);

    /**
     *
     * @author tanyaowu
     */
    public AbsShowcaseBsHandler() {
    }
    //抽象方法,具體是什麽類型的由子類實現
    public abstract Class<T> bodyClass();

    @Override
    public Object handler(ShowcasePacket packet, ChannelContext channelContext) throws Exception {
        String jsonStr = null;
        T bsBody = null;
        if (packet.getBody() != null) {
            //將body轉化為string
            jsonStr = new String(packet.getBody(), Const.CHARSET);
            //根據類型反序列化消息,得到具體類型的消息對象
            bsBody = Json.toBean(jsonStr, bodyClass());
        }
        //調用具體的消息處理的實現
        return handler(packet, bsBody, channelContext);
    }

    //抽象方法,由每個消息處理類來實現具體的消息處理邏輯
    public abstract Object handler(ShowcasePacket packet, T bsBody, ChannelContext channelContext) throws Exception;

}

  我們以登錄消息為例,分析具體消息處理流程。

  首先客戶端發起登錄請求。(比如用戶名:panzi,密碼:123123)

        LoginReqBody loginReqBody = new LoginReqBody();
            loginReqBody.setLoginname(loginname);
            loginReqBody.setPassword(password);
       //具體的消息都會包裝在ShowcasePacket中(byte[] body)
            ShowcasePacket reqPacket = new ShowcasePacket();
       //這裏呢就是傳相應的消息類型
            reqPacket.setType(Type.LOGIN_REQ);
reqPacket.setBody(Json.toJson(loginReqBody).getBytes(ShowcasePacket.CHARSET));        
//調用 t-io 發送消息方法 Aio.send(clientChannelContext, reqPacket);

  服務端收到消息。這時候我們回過頭看 ShowcaseServerAioHandler 中的 handle方法。(上文中有介紹)此時消息類型為Type.LOGIN_REQ.可以很容易的想到,需要用 LoginReqHandler來處理這條消息。

  技術分享

  我們看一下LoginReqHandler的具體實現

  

    @Override
    public Object handler(ShowcasePacket packet, LoginReqBody bsBody, ChannelContext channelContext) throws Exception {
        log.info("收到登錄請求消息:{}", Json.toJson(bsBody));
        //定義響應對象
        LoginRespBody loginRespBody = new LoginRespBody();
        //模擬登錄,直接給Success
        loginRespBody.setCode(JoinGroupRespBody.Code.SUCCESS);
        //返回一個模擬的token
        loginRespBody.setToken(newToken());
        
        //登錄成功之後綁定用戶
        String userid = bsBody.getLoginname();
        Aio.bindUser(channelContext, userid);

        //給全局Context設置用戶ID
        ShowcaseSessionContext showcaseSessionContext = (ShowcaseSessionContext) channelContext.getAttribute();
        showcaseSessionContext.setUserid(userid);

        //構造響應消息包
        ShowcasePacket respPacket = new ShowcasePacket();
        //響應消息類型為 Type.LOGIN_RESP
        respPacket.setType(Type.LOGIN_RESP);
        //將loginRespBody轉化為byte[]
        respPacket.setBody(Json.toJson(loginRespBody).getBytes(ShowcasePacket.CHARSET));
        //發送響應到客戶端(告訴客戶端登錄結果)
        Aio.send(channelContext, respPacket);

        return null;
    }

  這個時候就要到客戶端處理了。同理,客戶端處理拿到具體的處理器(LoginRespHandler)

  技術分享

  看一下客戶端消息處理代碼

  @Override
    public Object handler(ShowcasePacket packet, LoginRespBody bsBody, ChannelContext channelContext) throws Exception {
        System.out.println("收到登錄響應消息:" + Json.toJson(bsBody));
        if (LoginRespBody.Code.SUCCESS.equals(bsBody.getCode())) {
            ShowcaseSessionContext showcaseSessionContext = (ShowcaseSessionContext) channelContext.getAttribute();
            showcaseSessionContext.setToken(bsBody.getToken());
            System.out.println("登錄成功,token是:" + bsBody.getToken());
        }
        return null;
    }

  這樣,整個消息流程就結束了。為了更清晰一點,我們將它以流程圖的形式展現。

  技術分享


總結

  雖然一個簡單的Showcase,但是作者也是用了心思。通過這個例子可以既讓我們學習到如何使用t-io,又能領略到程序設計的魅力,一個小小demo都這麽多東西,看來讀源代碼之路還是比較遙遠啊。以上是我對Showcase的代碼理解,多有不當之處敬請指正。

  showcase地址:https://gitee.com/tywo45/t-io/tree/master/src/example/showcase

通訊框架 T-io 學習——給初學者的Demo:ShowCase設計分析