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

前段時間,有幾個研究ESFramework通訊框架的朋友對我說,ESFramework有點龐大,對於他們目前的項目來講有點「殺雞用牛刀」的意思,由於他們的項目不須要文件傳送、不須要P2P、不存在好友關係、也不存在組廣播、不須要服務器均衡、不須要跨服務器通訊、甚至都不須要使用UserID,只要客戶端能與服務端進行簡單的穩定高效的通訊就能夠了。因而,他們建議我,整一個輕量級的C#通信組件來知足相似他們這種項目的需求。我以爲這個建議是有道理的,因而,花了幾天時間,我將ESFramework的內核抽離出來,通過修改封裝後,造成了StriveEngine通信組件,其最大的特色就是穩定高效、易於使用。html

在網絡上,交互的雙方基於TCP或UDP進行通訊,通訊協議的格式一般分爲兩類:文本消息、二進制消息。數組

文本協議相對簡單,一般使用一個特殊的標記符做爲一個消息的結束。服務器

二進制協議,一般是由消息頭(Header)和消息體(Body)構成的,消息頭的長度固定,並且,經過解析消息頭,能夠知道消息體的長度。如此,咱們即可以從網絡流中解析出一個個完整的二進制消息。網絡

兩種類型的協議格式各有優劣:文本協議直觀、容易理解,可是在文本消息中很難嵌入二進制數據,好比嵌入一張圖片;而二進制協議的優缺點剛剛相反。多線程

在 輕量級通訊引擎StriveEngine —— C/S通訊demo(附源碼)一文中,咱們演示瞭如何使用了相對簡單的文本協議,這篇文章咱們將構建一個使用二進制消息進行通訊的Demo。本Demo所作的事情是:客戶端提交運算請求給服務端,服務端處理後,將結果返回給客戶端。demo中定義消息頭固定爲8個字節:前四個字節爲一個int,其值表示消息體的長度;後四個字節也是一個int,其值表示消息的類型。框架

1.StriveEngine通信組件Demo簡介

該Demo總共包括三個項目:tcp

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

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

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

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服務端

關於StriveEngine使用的部分,在 輕量級通訊引擎StriveEngine —— C/S通訊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源碼下載

二進制通訊demo源碼

 

 附相關係列:文本協議通訊demo源碼 說明文檔

              打通B/S與C/S通訊demo源碼與說明文檔

  另附:簡單即時通信Demo源碼及說明

相關文章
相關標籤/搜索