1. 程式人生 > >C#輕量級通通訊元件StriveEngine —— C/S通訊開源demo(2) —— 使用二進位制協議 (附原始碼)

C#輕量級通通訊元件StriveEngine —— C/S通訊開源demo(2) —— 使用二進位制協議 (附原始碼)

前段時間,有幾個研究的朋友對我說,ESFramework有點龐大,對於他們目前的專案來說有點“殺雞用牛刀”的意思,因為他們的專案不需要檔案傳送、不需要P2P、不存在好友關係、也不存在組廣播、不需要伺服器均衡、不需要跨伺服器通訊、甚至都不需要使用UserID,只要客戶端能與服務端進行簡單的穩定高效的通訊就可以了。於是,他們建議我,整一個輕量級的C#通訊元件來滿足類似他們這種專案的需求。我覺得這個建議是有道理的,於是,花了幾天時間,我將ESFramework的核心抽離出來,經過修改封裝後,形成了,其最大的特點就是穩定高效、易於使用。

在網路上,互動的雙方基於TCP或UDP進行通訊,通訊協議的格式通常分為兩類:文字訊息、二進位制訊息。

文字協議相對簡單,通常使用一個特殊的標記符作為一個訊息的結束。

二進位制協議,通常是由訊息頭(Header)和訊息體(Body)構成的,訊息頭的長度固定,而且,通過解析訊息頭,可以知道訊息體的長度。如此,我們便可以從網路流中解析出一個個完整的二進位制訊息。

兩種型別的協議格式各有優劣:文字協議直觀、容易理解,但是在文字訊息中很難嵌入二進位制資料,比如嵌入一張圖片;而二進位制協議的優缺點剛剛相反。

在 一文中,我們演示瞭如何使用了相對簡單的文字協議,這篇文章我們將構建一個使用二進位制訊息進行通訊的Demo。本Demo所做的事情是:客戶端提交運算請求給服務端,服務端處理後,將結果返回給客戶端。demo中定義訊息頭固定為8個位元組:前四個位元組為一個int,其值表示訊息體的長度;後四個位元組也是一個int,其值表示訊息的型別。

1.StriveEngine通訊元件Demo簡介

該Demo總共包括三個專案:

(1)StriveEngine.BinaryDemoServer:基於開發的二進位制通訊服務端,處理來自客戶端的請求並返回結果。

(2)StriveEngine.BinaryDemo:基於StriveEngine開發的二進位制通訊客戶端,提交使用者請求,並顯示處理結果。

(3)StriveEngine.BinaryDemoCore:用於定義客戶端和服務端都要用到的公共的訊息型別和訊息協議的基礎程式集。

Demo執行起來後的截圖如下所示:

    

2.訊息頭

首先,我們按照前面的約定,定義訊息頭MessageHead。

    public class MessageHead
    {
        public const int HeadLength = 8;

        public MessageHead() { }
        public MessageHead(int bodyLen, int msgType)
        {
            this.bodyLength = bodyLen;
            this.messageType = msgType;
        }

        private int bodyLength;
        /// <summary>
              /// 訊息體長度
        /// </summary>
              public int BodyLength
        {
            get { return bodyLength; }
            set { bodyLength = value; }
        }

        private int messageType;
        /// <summary>
              /// 訊息型別
        /// </summary>
              public int MessageType
        {
            get { return messageType; }
            set { messageType = value; }
        }

        public byte[] ToStream()
        {
            byte[] buff = new byte[MessageHead.HeadLength];
            byte[] bodyLenBuff = BitConverter.GetBytes(this.bodyLength) ;
            byte[] msgTypeBuff = BitConverter.GetBytes(this.messageType) ;
            Buffer.BlockCopy(bodyLenBuff,0,buff,0,bodyLenBuff.Length) ;
            Buffer.BlockCopy(msgTypeBuff,0,buff,4,msgTypeBuff.Length) ;
            return buff;
        }
    }

訊息頭由兩個int構成,正好是8個位元組。而且在訊息頭的定義中增加了ToStream方法,用於將訊息頭序列化為位元組陣列。

通過ToStream方法,我們已經可以對訊息轉化為流(即所謂的序列化)的過程窺見一斑了,基本就是操作分配空間、設定偏移、拷貝位元組等。

3.訊息型別

根據業務需求,需要定義客戶端與伺服器之間通訊訊息的型別MessageType。

    public static class MessageType
    {
        /// <summary>
        /// 加法請求
        /// </summary>
        public const int Add = 0;

        /// <summary>
        /// 乘法請求
        /// </summary
        public const int Multiple = 1;

        /// <summary>
        /// 運算結果回覆
        /// </summary
        public const int Result = 2;        
    }

訊息型別有兩個請求型別,一個回覆型別。請注意訊息的方向,Add和Multiple型別的訊息是由客戶端發給伺服器的,而Result型別的訊息則是伺服器發給客戶端的。

4.訊息體

一般的訊息都由訊息體(MessageBody),用於封裝具體的業務資料。當然,也有些訊息只有訊息頭,沒有訊息體的。比如,心跳訊息,設計時,我們只需要使用一個訊息型別來表示它是一個心跳就可以了,不需要使用訊息體。

本demo中,三種類型的訊息都需要訊息體來封裝業務資料,所以,demo中本應該定義了3個訊息體,但demo中實際上只定義了兩個:RequestContract、ResponseContract。這是因為Add和Multiple型別的訊息公用的是同一個訊息體RequestContract。 

    [Serializable]
    public class RequestContract
    {
        public RequestContract() { }
        public RequestContract(int num1, int num2)
        {
            this.number1 = num1;
            this.number2 = num2;
        }

        private int number1;
        /// <summary>
        /// 運算的第一個數。
        /// </summary>
        public int Number1
        {
            get { return number1; }
            set { number1 = value; }
        }

        private int number2;
        /// <summary>
        /// 運算的第二個數。
        /// </summary>
        public int Number2
        {
            get { return number2; }
            set { number2 = value; }
        }
    }

    [Serializable]
    public class ResponseContract
    {
        public ResponseContract() { }
        public ResponseContract(int num1, int num2 ,string opType,int res)
        {
            this.number1 = num1;
            this.number2 = num2;
            this.operationType = opType;
            this.result = res;
        }

        private int number1;
        /// <summary>
        /// 運算的第一個數。
        /// </summary>
        public int Number1
        {
            get { return number1; }
            set { number1 = value; }
        }

        private int number2;
        /// <summary>
        /// 運算的第二個數。
        /// </summary>
        public int Number2
        {
            get { return number2; }
            set { number2 = value; }
        }

        private string operationType;
        /// <summary>
        /// 運算型別。
        /// </summary>
        public string OperationType
        {
            get { return operationType; }
            set { operationType = value; }
        }

        private int result;
        /// <summary>
        /// 運算結果。
        /// </summary>
        public int Result
        {
            get { return result; }
            set { result = value; }
        }
    }

關於訊息體的序列化,demo採用了.NET自帶的序列化器的簡單封裝(即SerializeHelper類)。當然,如果客戶端不是.NET平臺,序列化器不一樣,那就必須像訊息頭那樣一個欄位一個欄位就構造訊息體了

5.StriveEngine通訊元件Demo服務端

void tcpServerEngine_MessageReceived(IPEndPoint client, byte[] bMsg)
{
    //獲取訊息型別
    int msgType = BitConverter.ToInt32(bMsg, 4);//訊息型別是 從offset=4處開始 的一個整數
    //解析訊息體
    RequestContract request = (RequestContract)SerializeHelper.DeserializeBytes(bMsg, MessageHead.HeadLength, bMsg.Length - MessageHead.HeadLength); 
    int result = 0;
    string operationType = "";
    if (msgType == MessageType.Add)
    {
        result = request.Number1 + request.Number2;
        operationType = "加法";
    }
    else if (msgType == MessageType.Multiple)
    {
        result = request.Number1 * request.Number2;
        operationType = "乘法";
    }
    else
    {
        operationType = "錯誤的操作型別";
    }

    //顯示請求
    string record = string.Format("請求型別:{0},運算元1:{1},運算元2:{2}", operationType, request.Number1 , request.Number2);
    this.ShowClientMsg(client, record);

    //回覆訊息體
    ResponseContract response = new ResponseContract(request.Number1, request.Number2, operationType, result);
    byte[] bReponse = SerializeHelper.SerializeObject(response);      
    //回覆訊息頭
    MessageHead head = new MessageHead(bReponse.Length, MessageType.Result);
    byte[] bHead = head.ToStream();

    //構建回覆訊息
    byte[] resMessage = new byte[bHead.Length + bReponse.Length];
    Buffer.BlockCopy(bHead, 0, resMessage, 0, bHead.Length);
    Buffer.BlockCopy(bReponse, 0, resMessage, bHead.Length, bReponse.Length);

    //傳送回覆訊息
    this.tcpServerEngine.PostMessageToClient(client, resMessage);
}

其主要流程為:

(1)解析訊息頭,獲取訊息型別和訊息體的長度。

(2)根據訊息型別,解析訊息體,並構造協議物件。

(3)業務處理運算。(如 加法或乘法)

(4)根據業務處理結果,構造回覆訊息。

(5)傳送回覆訊息給客戶端。

6.StriveEngine通訊元件Demo客戶端

(1)提交請求 

    private void button1_Click(object sender, EventArgs e)
    {
        this.label_result.Text = "-";
        int msgType = this.comboBox1.SelectedIndex == 0 ? MessageType.Add : MessageType.Multiple;

        //請求訊息體
        RequestContract contract = new RequestContract(int.Parse(this.textBox1.Text), int.Parse(this.textBox2.Text));            
        byte[] bBody = SerializeHelper.SerializeObject(contract);
            
        //訊息頭
        MessageHead head = new MessageHead(bBody.Length,msgType) ;
        byte[] bHead = head.ToStream();

            //構建請求訊息
        byte[] reqMessage = new byte[bHead.Length + bBody.Length];
        Buffer.BlockCopy(bHead, 0, reqMessage, 0, bHead.Length);
        Buffer.BlockCopy(bBody, 0, reqMessage, bHead.Length, bBody.Length);

        //傳送請求訊息
        this.tcpPassiveEngine.PostMessageToServer(reqMessage);
    }

其流程為:構造訊息體、構造訊息頭、拼接為一個完整的訊息、傳送訊息給伺服器。

注意:必須將訊息頭和訊息體拼接為一個完整的byte[],然後通過一次PostMessageToServer呼叫傳送出去,而不能連續兩次呼叫PostMessageToServer來分別傳送訊息頭、再發送訊息體,這在多執行緒的情況下,是非常有可能在訊息頭和訊息體之間插入其它的訊息的,如果這樣的情況發生,那麼,接收方就無法正確地解析訊息了。

(2)顯示處理結果

    void tcpPassiveEngine_MessageReceived(System.Net.IPEndPoint serverIPE, byte[] bMsg)
    {
        //獲取訊息型別
        int msgType = BitConverter.ToInt32(bMsg, 4);//訊息型別是 從offset=4處開始 的一個整數
        if (msgType != MessageType.Result)
        {
            return;
        }

        //解析訊息體
        ResponseContract response = (ResponseContract)SerializeHelper.DeserializeBytes(bMsg, MessageHead.HeadLength, bMsg.Length - MessageHead.HeadLength);
        string result = string.Format("{0}與{1}{2}的答案是 {3}" ,response.Number1,response.Number2,response.OperationType,response.Result);
        this.ShowResult(result);
    }

過程與服務端處理接收到的訊息是類似的:從接收到的訊息中解析出訊息頭、再根據訊息型別解析出訊息體,然後,將運算結果從訊息體中取出並顯示在UI上。 

7.StriveEngine通訊元件Demo原始碼下載

相關推薦

C#輕量級通通元件StriveEngine —— C/S通訊開源demo(2) —— 使用二進位制協議 原始碼

前段時間,有幾個研究的朋友對我說,ESFramework有點龐大,對於他們目前的專案來說有點“殺雞用牛刀”的意思,因為他們的專案不需要檔案傳送、不需要P2P、不存在好友關係、也不存在組廣播、不需要伺服器均衡、不需要跨伺服器通訊、甚至都不需要使用UserID,只要客戶端能與服務端進行簡單的穩定高效的通訊就可以了

輕量級C#網路通訊元件StriveEngine —— C/S通訊開源demo原始碼

private ITcpServerEngine tcpServerEngine; private void button1_Click(object sender, EventArgs e) { try { //初

一個簡單的用ASP.NET/C#開發的元件化Web應用程式原始碼

==============================================================================1)建立一個類來處理使用者登入,將該類編譯成一個裝配件(assembly),併發布到站點的bin目錄下。========

C#設計模式02-抽象工廠模式原始碼

        抽象工廠模式是所有工廠模式中最為抽象的模式,是抽象程度最高的模式,也是最難理解的一種工廠模式。         現在舉一個生活中的案例來

自然語言處理之:c++中文分詞原始碼

githup地址:https://github.com/jbymy 一、簡介 中文分詞是地然語言處理中的最基礎的環節,到目前為止已經有不少優秀的分詞工具的出現,如“中科院分詞”,“結

C#實現視訊會議系統 GGMeeting原始碼

      前段時間做了個線上教育培訓的專案,與視訊會議比較類似,所以了,我打算像 廣域網即時通訊系統GG(QQ高仿版)一樣,寫一個視訊會議系統並把實現的原理和原始碼都分享出來,讓有興趣的朋友可以參考下。繼承GG的名稱,我把這個視訊會議系統命名為GGMeeting,目前版本為

原理和C++實現的演示程式原始碼

http://blog.csdn.net/mahabharata_/article/details/71856907    大二的時候,曾受老師所託,用C++而不是OpenGL去寫B樣條曲線的教學程式。時隔一年,發現原始碼找不見了,所以重新寫了一遍,也完善了部分功能,順

基於C#的內網穿透學習筆記原始碼

如何讓兩臺處在不同內網的主機直接互連?你需要內網穿透!          上圖是一個非完整版內外網通訊圖由內網端先發起,內網裝置192.168.1.2:6677傳送資料到外網時候必須經過nat會轉換成對應的外網ip+埠,然後在傳送給外網裝置,

C#推流RTMP,攝像頭、麥克風、桌面、音效卡原始碼

  這段時間一直都在研究推流的技術,經過斷斷續續將近兩個月的摸索實踐,終於能穩定地推流了。        這個demo的主要功能就是將採集到的攝像頭或桌面的視訊、以及麥克風或音效卡的音訊資料推到Nginx-RTMP伺服器上,再由Web瀏覽器去拉流並播放。   接下來介紹

半透明視窗中顯示標準控制元件控制元件與文字不透明的實現方案原始碼

原文 http://blog.csdn.net/harbinzju/article/details/7907127 和大家分享一下在半透明視窗中顯示標準控制元件的實現方案。通過層疊視窗可以簡單實現半透明與不規則形狀視窗的效果,但在其上顯示標準控制元件(控制元件與文字不

B/S架構實現員工工資管理系統原始碼

好久都沒有這麼累過了持續了大半個月的學期末階段就這樣結束了,期間經歷了一個禮拜左右的時間去完成資料庫課設,說是說資料庫課設,但給人的感覺其實就是一個完整的網站開發專案,開始理一理 題目:員工工資管理系統 軟體架構:B/S 開發環境:Win10+Ecli

C#輕量級高效能日誌元件EasyLogger(六)

一、課程介紹 本次分享課程屬於《C#高階程式設計實戰技能開發寶典課程系列》中的第六部分,阿笨後續會計劃將實際專案中的一些比較實用的關於C#高階程式設計的技巧分享出來給大家進行學習,不斷的收集、整理和完善此係列課程! 一、本高階系列課程適合人群如下 1、有一定的NET開發基礎。 2、喜歡阿笨的乾貨分

C# 控制元件 [2] : ProgressBar 顯示百分比

1 繼承關係 Object→MarshalByRefObject→Component→Control→ProgressBar ProgressBar表示Windows進度欄控制元件。 2 重要屬性 序號 屬性 型別 用法

C#進階系列——一步一步封裝自己的HtmlHelper元件:BootstrapHelper三:原始碼

前言:之前的兩篇封裝了一些基礎的表單元件,這篇繼續來封裝幾個基於bootstrap的其他元件。和上篇不同的是,這篇的有幾個元件需要某些js檔案的支援。 BootstrapHelper系列文章目錄 一、NumberBoxExtensions NumberBoxExtensions是一個基於boot

【閱讀筆記】《C程序員 從校園到職場》第三章 程序的樣式大括號

突出 char s 結構體 需要 初始化 detail 處理 思維 https 參考: https://blog.csdn.net/zhouzhaoxiong1227/article/details/22820533 一、.初始化數組變量 在實際的軟件開

C語言花式玩法之把函數拷貝到數組執行 需要mprotect

return 內容 報錯 rwx UNC error errno.h 價值 沒有 在閱讀內核代碼的時候,明白了內核是通過頁表項中的標誌位_PAGE_READ,_PAGE_WRITE,_PAGE_EXECUTE來區分頁的權限的。 進程在內核中的地址空間代碼段,數據段,堆,棧之

混合使用Delphi和C ++下載

您想將C ++新增到Delphi應用程式中嗎?或者將Delphi程式碼新增到C ++應用程式中?這是如何做。 您可能不知道的一件事是如何在RAD Studio中整合C ++和Delphi語言。您可以將單個專案中的單個應用程式編譯為單個EXE,混合使用兩種語言。(當然,你也可以使用DLL或包來實現。)如果使用

(轉)C# 獲取漢字的拼音首字母和全拼原始碼[A]

  https://blog.csdn.net/younghaiqing/article/details/62417269   C# 獲取漢字的拼音首字母 一種是把所有中文字元集合起來組成一個對照表;另一種是依照漢字在Unicode編碼表中的排序來確定拼音的首字母

高併發、低延遲之C#玩轉CPU快取記憶體示例

寫在前面 好久沒有寫部落格了,一直在不斷地探索響應式DDD,又get到了很多新知識,解惑了很多老問題,最近讀了Martin Fowler大師一篇非常精彩的部落格The LMAX Architecture,裡面有一個術語Mechanical Sympathy,姑且翻譯成軟硬體協同程式設計(Hardware an

C#LeetCode刷題之#67-二進位制求和Add Binary

問題 給定兩個二進位制字串,返回他們的和(用二進位制表示)。 輸入為非空字串且只包含數字 1 和 0。 輸入: a = "11", b = "1" 輸出: "100" 輸入: a = "