簡易RPC框架-私有協議棧

来源:http://www.cnblogs.com/ASPNET2008/archive/2017/09/24/7588822.html
-Advertisement-
Play Games

HTTP協議 客戶機與服務端之間的數據交互需要遵守一定的約定,比如協議版本,數據類型,是否有緩存,是否有壓縮等,只有在這些約定的基礎上才能相互之間愉快的工作。 Netty通信過程中的編解碼 這時說的是基於TCP/IP的Netty之間的通信。TCP/IP協議下客戶端與服務端之間要進行數據交互,一般需要 ...


HTTP協議

客戶機與服務端之間的數據交互需要遵守一定的約定,比如協議版本,數據類型,是否有緩存,是否有壓縮等,只有在這些約定的基礎上才能相互之間愉快的工作。

Netty通信過程中的編解碼

這時說的是基於TCP/IP的Netty之間的通信。TCP/IP協議下客戶端與服務端之間要進行數據交互,一般需要將數據轉換成二進位格式,直接傳java bean是不能支持的。在RPC模式下客戶端在向服務端發起請求前需要將數據做編碼,服務端在接收客戶端發的數據後需要做解碼之後才能正常工作。

  • 解碼流程

  • 編碼流程

Netty 私有協議棧

為了更好的控制RPC客戶端與服務端之間的通信,也可以編寫私有的協議棧來支撐。

定義消息體

類似HTTP協議,包含頭信息以及內容信息。


public class RpcMessage implements Serializable {

    private RpcMessageHeader messageHeader;

    private Object messageBody;

}

頭信息,包含內容體長度,消息類型等信息。可以根據消息類型來做不同的業務,比如區分是心跳信息還是業務或者是監控之類的信息。


public class RpcMessageHeader implements Serializable {
    private int length;

    private int type;
   
}

定義解碼器

因為TCP/IP協議容易出現粘包拆包現象,這裡為了簡單直接選擇繼承組件提供的LengthFieldBasedFrameDecoder,只需要重寫下麵的方法即可:


 public Object decode(ChannelHandlerContext ctx, ByteBuf in) throws Exception {
        ByteBuf frame=(ByteBuf)super.decode(ctx,in);
        if(null==frame){
            return null;
        }

        RpcMessage message=new RpcMessage();
        RpcMessageHeader messageHeader=new RpcMessageHeader();
        messageHeader.setLength(frame.readInt());
        message.setMessageHeader(messageHeader);

        byte[] data = new byte[message.getMessageHeader().getLength()];
        frame.readBytes(data);

        Object obj = ProtoStuffSerializeUtil.deserialize(data, genericClass);
        message.setMessageBody(obj);
        return message;
    }

定義編碼器

編碼器繼承MessageToByteEncoder,將對象轉換成位元組的編碼器


public class RpcEncoder extends MessageToByteEncoder<RpcMessage>

重點是下麵的編碼函數,在ByteBuf中輸出數據長度以及數據體,如有其它需要可以補充其它的欄位,比如消息類型。


 public void encode(ChannelHandlerContext ctx, RpcMessage in, ByteBuf out) throws Exception {
        if(null==in){
            throw new RpcException("RpcMessage is null");
        }
        if (genericClass.isInstance(in.getMessageBody())) {
            byte[] data = ProtoStuffSerializeUtil.serialize(in.getMessageBody());
            out.writeInt(data.length);
            out.writeBytes(data);
        }
    }

ServerHandle

  • 修改服務端執行器消息實體類型為新定義的RpcMessage

public class RpcServerInvoker extends AbstractInvoker<RpcMessage> 
  • 修改服務端回調

從服務端方法獲取到返回的結果後,重新封裝成消息對象(RpcMessage)發送給客戶端。


protected void channelRead0(ChannelHandlerContext channelHandlerContext, RpcMessage message) {

        this.executor.execute(new Runnable() {
            @Override
            public void run() {
                RpcInvoker rpcInvoker=RpcServerInvoker.this.buildInvokerChain(RpcServerInvoker.this);
                RpcResponse response=(RpcResponse) rpcInvoker.invoke(RpcServerInvoker.this.buildRpcInvocation((RpcRequest) message.getMessageBody()));
                RpcMessage responseMessage=new RpcMessage();
                byte[] data = ProtoStuffSerializeUtil.serialize(response);
                RpcMessageHeader messageHeader=new RpcMessageHeader();
                messageHeader.setLength(data.length);
                responseMessage.setMessageHeader(messageHeader);
                responseMessage.setMessageBody(response);
                channelHandlerContext.writeAndFlush(responseMessage);
            }
        });

    }

ClientHandle

  • 修改客戶端執行器消息實體類型為新定義的RpcMessage

public class RpcClientInvoker extends AbstractInvoker<RpcMessage>
  • 修改客戶端回調方法

接收的返回結果修改為RpcMessage,從body屬性中獲取原來的RpcResponse對象


public void channelRead0(ChannelHandlerContext ctx, RpcMessage message) {
        RpcResponse response=(RpcResponse) message.getMessageBody();
        String requestId = response.getRequestId();
        ResponseFuture responseFuture = pendingRPC.get(requestId);
        if (responseFuture != null) {
            pendingRPC.remove(requestId);
            responseFuture.done(response);
        }
    }
  • 修改發送請求的消息對象,組裝成RpcMessage發送

public ResponseFuture invoke(RpcInvocation invocation) {
        RpcRequest request=this.getRpcRequest();
        ResponseFuture responseFuture = new ResponseFuture(request);
        pendingRPC.put(request.getRequestId(), responseFuture);
        RpcMessage message=new RpcMessage();
        byte[] data = ProtoStuffSerializeUtil.serialize(request);
        RpcMessageHeader messageHeader=new RpcMessageHeader();
        messageHeader.setLength(data.length);
        message.setMessageHeader(messageHeader);
        message.setMessageBody(request);
        channel.writeAndFlush(message);
        return responseFuture;
    }

本文源碼

https://github.com/jiangmin168168/jim-framework

文中代碼是依賴上述項目的,如果有不明白的可下載源碼

引用

  • 文中插圖來自來網路
  • 文中的思路參考了Netty權威指南


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 初學者先廣在精,關註代碼背後的實現,關註內功修煉,瞭解實現原理和思想,形成自己完整的技術體系,知識成片之後就容易觸類旁通,進步的速度就會越來越快。最後以我在每一個項目組和開發人員聊天都會說的幾個例子結尾:“少林功夫裡面有功和拳之分,馬步功,石鎖功是功,蛇拳猴拳是拳,你不可能練會了蛇拳猴拳就能打人,你... ...
  • 簡單工廠模式 工廠模式 抽象工廠模式 簡單工廠模式 工廠模式 抽象工廠模式 簡單工廠模式 什麼是簡單工廠模式? 在現實生活中工廠是負責生產產品的,同樣在設計模式中,簡單工廠模式我們也可以理解為負責生產對象的一個類, 我們平常編程中,當使用"new"關鍵字創建一個對象時,此時該類就依賴與這個對象,也就 ...
  • 微服務里一個重要的概念就是服務註冊與發現技術,當你有一個新的服務運行後,我們的服務中心可以感知你,然後把加添加到服務列表裡,然後當你死掉後,會從服務中心把你移除,而你作為一個服務,對其它服務公開的只是服務名稱,而不是最終的服務地址URL,這對於雲平臺,容器化架構來說是非常重要的! 一 安裝單獨的Eu ...
  • 1.類圖 UML類圖是用來描述類、介面、協作及它們之間的關係的圖。用來顯示系統中各個類的靜態結構。 2.類圖的組成元素 類圖由以下六種元素組成:類,介面,泛化關係,關聯關係,依賴關係,實現關係。 3.類圖的繪製 3.1類圖的表示法 類的UML表示為一個長方形垂直分為三個部分:頂部為類的名稱部分,中間 ...
  • Redis是一個key value存儲系統,現在在各種系統中的使用越來越多,大部分情況下是因為其高性能的特性,被當做緩存使用,這裡介紹下Redis經常遇到的使用場景。 Redis特性 一個產品的使用場景肯定是需要根據產品的特性,先列舉一下Redis的特點: 讀寫性能優異 持久化 數據類型豐富 單線程 ...
  • GOF《設計模式》一書中提出了七條設計原則,七原則是一種理想狀態的表達,但實際項目開發中可能會不得不打破這些原則的限制。任何基類可以出現的地方,子類一定可以出現,且必須遵從基類所有規則定義,但反過來說,除了擴展基類,我們又為什麼要違背基類規則定義呢?這一條與開關原則結合起來理解就是,基類遵循關原則,... ...
  • 什麼是單例模式? 從“單例”字面意思上理解為——一個類只有一個實例,所以單例模式也就是保證一個類只有一個實例的一種實現方法罷了。其官方定義為:確保一個類只有一個實例,並提供一個全局訪問點。 為什麼會有單例模式? 從單例模式的定義中我們可以看出——單例模式的使用自然是當我們的系統中某個對象只需要一個實 ...
  • 工具: MyEclipse,MySQL 步驟: 1.打開MyEclipse,新建一個Java Project(取名:h1) 2.創建MySQL資料庫 3.找到MyEclipse下的MyEclipse Hibernate單擊 會出現以下界面,按照如下方式填寫鏈接資料庫 4.選中項目名,添加Hibern ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...