Socket開發框架之資料傳輸協議
我在前面一篇隨筆《Socket開發框架之框架設計及分析》中,介紹了整個Socket開發框架的總體思路,對各個層次的基類進行了一些總結和抽象,已達到重用、簡化程式碼的目的。本篇繼續分析其中重要的協議設計部分,對其中訊息協議的設計,以及資料的拆包和封包進行了相關的介紹,使得我們在更高階別上更好利用Socket的特性。
1、協議設計思路
對Socket傳輸訊息的封裝和拆包,一般的Socket應用,多數採用基於順序位置和位元組長度的方式來確定相關的內容,這樣的處理方式可以很好減少資料大小,但是這些處理對我們分析複雜的協議內容,簡直是一場災難。對跟蹤解決過這樣協議的開發人員來說會很好理解其中的難處,協議位置一旦變化或者需要特殊的處理,就是很容易出錯的,而且大多數程式碼充斥著很多位置的數值變數,分析和理解都是非常不便的。隨著網路技術的發展,有時候傳輸的資料稍大一點,損失一些頻寬來傳輸資料,但是能成倍提高開發程式的效率,是我們值得追求的目標。例如,目前Web API在各種裝置大行其道,相對Socket訊息來說,它本身在資料大小上不佔優勢,但是開發的便利性和高效性,是眾所周知的。
借鑑了Web API的特點來考慮Socket訊息的傳輸,如果對於整體的內容,Socket應用也使用一種比較靈活的訊息格式,如JSON格式來傳輸資料,那麼我們可以很好的把訊息封裝和訊息拆包解析兩個部分,交給第三方的JSON解析器來進行,我們只需要關注具體的訊息處理邏輯就可以了,而且對於協議的擴充套件,就如JSON一樣,可以自由靈活,這樣瞬間,整個世界都會很清靜了。
對於Socket訊息的安全性和完整性,加密處理方面我們可以採用 RSA公鑰密碼系統。平臺通過傳送平臺RSA公鑰訊息向終端告知自己的RSA公鑰,終端回覆終端RSA公鑰訊息,這樣平臺和終端的訊息,就可以通過自身的私鑰加密,讓對方根據接收到的公鑰解密就可以了,雖然加密的資料長度會增加不少,但是對於安全性要求高的,採用這種方式也是很有必要的。
對於資料的完整性,傳統意義的CRC校驗碼其實沒有太多的用處了,因為我們的資料不會發生部分的丟失,而我們更應該關注的是資料是否被篡改過,這點我想到了微信公眾號API介面的設計,它們帶有一個安全簽名的加密字串,也就是對其中內容進行同樣規則的加密處理,然後對比兩個簽名內容是否一致即可。不過對於非對稱的加密傳輸,這種資料完整性的校驗也可以不必要。
前面介紹了,我們可以參照Web API的方式,以JSON格式作為我們傳輸的內容,方便序列號和反序列化,這樣我們可以大大降低Socket協議的分析難度和出錯機率,降低Socket開發難度並提高開發應用的速度。那麼我們應該如何設計這個格式呢?
首先我們需要為Socket訊息,定義好開始標識和結束標識,中間部分就是整個通用訊息的JSON內容。這樣,一條完整的Socket訊息內容,除了開始和結束標識位外,剩餘部分是一個JSON格式的字串資料。
我們準備根據需要,設計好整個JSON字串的內容,而且最好設計的較為通用一些,這樣便於我們承載更多的資料資訊。
2、協議設計分析和演化
參考微信的API傳遞訊息的定義,我設計了下面的訊息格式,包括了送達使用者ID,傳送使用者ID、訊息型別、建立時間,以及一個通用的內容欄位,這個通用的欄位應該是另外一個訊息實體的JSON字串,這樣我們整個訊息格式不用變化,但是具體的內容不同,我們把這個物件類稱之BaseMessage,常用欄位如下所示。
上面的Content欄位就是用來承載具體的訊息資料的,它會根據不同的訊息型別,傳送不同的內容的,而這些內容也是具體的實體類序列化為JSON字串的,我們為了方便,也設計了這些類的基類,也就是Socket傳遞資料的實體類基類BaseEntity。
我們在不同的請求和應答訊息,都繼承於它即可。我們為了方便讓它轉換為我們所需要的BaseMessage訊息,為它增加一個MsgType協議型別的標識,同時增加PackData的方法,讓它把實體類轉換為JSON字串。
例如我們一般情況下的請求Request和應答Response的訊息物件,都是繼承自BaseEntity的,我們可以把這兩類訊息物件放在不同的目錄下方便管理。
繼承關係示例如下所示。
其中子類都可以使用基類的PackData方法,直接序列號為JSON字串即可,那個PacketData的函式主要就是用來組裝好待傳送的物件BaseMessage的,函式程式碼如下所示:
/// <summary> /// 封裝資料進行傳送 /// </summary> /// <returns></returns> public BaseMessage PackData() { BaseMessage info = new BaseMessage() { MsgType = this.MsgType, Content = this.SerializeObject() }; return info; }
有時候我們需要根據請求的資訊,用來構造返回的應答訊息,因為需要把傳送者ID和送達者ID逆反過來。
/// <summary> /// 封裝資料進行傳送(複製請求部分資料) /// </summary> /// <returns></returns> public BaseMessage PackData(BaseMessage request) { BaseMessage info = new BaseMessage() { MsgType = this.MsgType, Content = this.SerializeObject(), CallbackID = request.CallbackID }; if(!string.IsNullOrEmpty(request.ToUserId)) { info.ToUserId = request.FromUserId; info.FromUserId = request.ToUserId; } return info; }
以登陸請求的資料實體物件介紹,它繼承自BaseEntity,同時指定好對應的訊息型別即可。
/// <summary> /// 登陸請求訊息實體 /// </summary> public class AuthRequest : BaseEntity { #region 欄位資訊 /// <summary> /// 使用者帳號 /// </summary> public string UserId { get; set; } /// <summary> /// 使用者密碼 /// </summary> public string Password { get; set; } #endregion /// <summary> /// 預設建構函式 /// </summary> public AuthRequest() { this.MsgType = DataTypeKey.AuthRequest; } /// <summary> /// 引數化建構函式 /// </summary> /// <param name="userid">使用者帳號</param> /// <param name="password">使用者密碼</param> public AuthRequest(string userid, string password) : this() { this.UserId = userid; this.Password = password; } }
這樣我們的訊息內容就很簡單,方便我們傳遞及處理了。
3、訊息的接收和傳送
前面我們介紹過了一些基類,包括Socket客戶端基類,和資料接收的基類設計,這些封裝能夠給我提供很好的便利性。
在上面的BaseSocketClient裡面,我們為了能夠解析不同協議的Socket訊息,把它轉換為我們所需要的基類物件,那麼我們這裡引入一個解析器MessageSplitter,這個類主要的職責就是用來分析位元組資料,並進行整條訊息的提取的。
因此我們把BaseSocketClient的類定義的程式碼設計如下所示。
/// <summary> /// 基礎的Socket操作類,提供連線、斷開、接收和傳送等相關操作。 /// </summary> /// <typeparam name="TSplitter">對應的訊息解析類,繼承自MessageSplitter</typeparam> public class BaseSocketClient<TSplitter> where TSplitter : MessageSplitter, new()
MessageSplitter物件,給我們處理低層次的協議解析,前面介紹了我們除了協議頭和協議尾標識外,其餘部分就是一個JSON的,那麼它就需要根據這個規則來實現位元組資料到物件級別的轉換。
首先需要把位元組資料進行拆分,把它完整的一條資料加到列表裡面後續進行處理。
其中結尾部分,我們就是需要提取快取的直接資料到一個具體的物件上了。
RawMessage msg = this.ConvertMessage(MsgBufferCache, from);
這個轉換的大概規則如下所示。
這樣我們在收到訊息後,利用TSplitter物件來進行解析就可以了,如下所示就是對Socket訊息的處理。
TSplitter splitter = new TSplitter(); splitter.InitParam(this.Socket, this.StartByte, this.EndByte);//指定分隔符,用來拆包 splitter.DataReceived += splitter_DataReceived;//如果有完整的包處理,那麼通過事件通知
資料接收並獲取一條訊息的直接資料物件後,我們就進一步把直接物件轉換為具體的訊息物件了
/// <summary> /// 訊息分拆類收到訊息事件 /// </summary> /// <param name="data">原始訊息物件</param> void splitter_DataReceived(RawMessage data) { ReceivePackCount += 1;//增加收到的包數量 OnReadRaw(data); } /// <summary> /// 接收資料後的處理,可供子類過載 /// </summary> /// <param name="data">原始訊息物件(包含原始的位元組資料)</param> protected virtual void OnReadRaw(RawMessage data) { //提供預設的包體處理:假設整個內容為Json的方式; //如果需要處理自定義的訊息體,那麼需要在子類重寫OnReadMessage方法。 if (data != null && data.Buffer != null) { var json = EncodingGB2312.GetString(data.Buffer); var msg = JsonTools.DeserializeObject<BaseMessage>(json); OnReadMessage(msg);//給子類過載 } }
在更高一層的資料解析上面,我們就可以對物件級別的訊息進行處理了
例如我們收到訊息後,它本身解析為一個實體類BaseMessage的,那麼我們就可以利用BaseMessage的訊息內容,也可以把它的Content內容轉換為對應的實體類進行處理,如下程式碼所示是接收物件後的處理。
void TextMsgAnswer(BaseMessage message) { var msg = string.Format("來自【{0}】的訊息:", message.FromUserId); var request = JsonTools.DeserializeObject<TextMsgRequest>(message.Content); if (request != null) { msg += string.Format("{0} {1}", request.Message, message.CreateTime.IntToDateTime()); } //MessageUtil.ShowTips(msg); Portal.gc.MainDialog.AppendMessage(msg); }
對於訊息的傳送處理,我們可以舉一個例子,如果客戶端登陸後,需要獲取線上使用者列表,那麼可以傳送一個請求命令,那麼伺服器需要根據這個命令返回列表資訊給終端,如下程式碼所示。
/// <summary> /// 處理客戶端請求使用者列表的應答 /// </summary> /// <param name="data">具體的訊息物件</param> private void UserListProcess(BaseMessage data) { CommonRequest request = JsonTools.DeserializeObject<CommonRequest>(data.Content); if (request != null) { Log.WriteInfo(string.Format("############\r\n{0}", data.SerializeObject())); List<CListItem> list = new List<CListItem>(); foreach(ClientOfShop client in Singleton<ShopClientManager>.Instance.LoginClientList.Values) { list.Add(new CListItem(client.Id, client.Id)); } UserListResponse response = new UserListResponse(list); Singleton<ShopClientManager>.Instance.AddSend(data.FromUserId, response.PackData(data), true); } }
相關文章
- Android使用Socket(Tcp/Udp)協議進行資料傳輸(傳輸大檔案)AndroidTCPUDP協議
- 網路協議之:基於UDP的高速資料傳輸協議UDT協議UDP
- 網路協議之:socket協議詳解之Socket和Stream Socket協議
- 網路協議之:socket協議詳解之Datagram Socket協議
- 網路協議之:socket協議詳解之Unix domain Socket協議AI
- WebRTC:資料傳輸相關協議簡介Web協議
- DDTP 分散式資料傳輸協議白皮書分散式協議
- 傳輸層協議協議
- 串列埠通訊上位機資料傳輸協議串列埠協議
- 流媒體傳輸協議之 RTP (上篇)協議
- 流媒體傳輸協議之 RTP(下篇)協議
- 流媒體技術之傳輸協議協議
- 網路傳輸協議協議
- 超文字傳輸協議協議
- 傳輸層協議、應用層、socket套接字、半連結池協議
- 4種傳輸協議設定,檔案傳輸協議如何選擇?協議
- TCP協議如何保證資料的順序傳輸TCP協議
- netty系列之:kequeue傳輸協議詳解Netty協議
- Socket開發框架之框架設計及分析框架
- 【傳輸協議】http協議GET與POST傳遞資料的最大長度能夠達到多少協議HTTP
- TCP傳輸協議詳解TCP協議
- HTTP超文字傳輸協議HTTP協議
- 【傳輸協議】HttpClient基本使用協議HTTPclient
- [TCPIP] 傳輸控制協議 NoteTCP協議
- Python 網路資料傳輸協議 TCP 程式設計Python協議TCP程式設計
- 傳輸層協議 TCP 和 UDP協議TCPUDP
- Raysync檔案傳輸協議(FTP)協議FTP
- 檔案傳輸協議介紹協議
- 如何看待鐳速傳輸的Raysync高速傳輸協議?協議
- 網路協議之:加密傳輸中的NPN和ALPN協議加密
- Socket 開發框架 SuperSocket框架
- Socket:UDP協議小白UDP協議
- iOS BLE藍芽開發資料傳輸協議詳解 常用演算法(AES加密,HMAC_hash,PRF)iOS藍芽協議演算法加密Mac
- 《Apache資料傳輸加密、證書的製作》——涉及HTTPS協議Apache加密HTTP協議
- 伺服器傳輸協議介紹伺服器協議
- Git傳輸協議的對比分析Git協議
- Android Wifi熱點 資料傳輸Socket 通訊AndroidWiFi
- 「完整案例」基於Socket開發TCP傳輸客戶端TCP客戶端