分享套接字資料包序列化與反序列化方法
簡單說一下,本文不涉及Socket的連接、資料接收,只是對資料包(byte[])的序列化和反序列化方法的封裝介紹,
本文目錄
- 本文背景
- 一般操作
- 本文操作
- 總結
1.本文背景
經常做C/S,客戶端與服務端通信基本是TCP/UDP通信,套接字用得飛起,
比如我們有一個系統,這個系統又分幾個系統子模塊行程:
- C++服務端
- Android 客戶端
- iOS 客戶端
- WPF桌面管理端
......
幾個模塊之間通過TCP或者UDP通信,資料包決議與組裝是常規操作,我們定義資料包格式如下:
一個資料包包含包頭和包體,定義如下:
包頭
| 序號 | 欄位名 | 資料型別 | 備注 |
|---|---|---|---|
| 1 | 訊息標識 | int | 用于標識資料包是否合法 |
| 2 | 名稱 | string | 當前訊息名稱,用于標識資料包型別 |
| 3 | 版本號 | int | 當前訊息版本號,允許程式中訊息存在多個版本,用于版本迭代 |
包含這三個欄位:訊息標識、名稱、版本號,唯一確定訊息物件,
包體
| 序號 | 欄位名 | 資料型別 | 備注 |
|---|---|---|---|
| 1 | 欄位1 | 資料型別 | 欄位1 |
| 2 | 欄位2 | 資料型別 | 欄位2 |
包體直接定義欄位資訊,就像定義類屬性一樣,
另包頭與包體中資料型別定義如下:
資料包欄位型別定義
| 序號 | 資料型別 | 備注 |
|---|---|---|
| 1 | int | 4個位元組的整型值 |
| 2 | string | 組成格式:字串實際值位元組長度(2個位元組)+字串實際值byte |
| 3 | char | 單位元組值 |
| 4 | 串列 | 組成格式:4個位元組串列長度+串列實際資料值byte |
| 5 | 字典 | 同上,具體看原始碼 |
其他資料型別類似,復雜資料型別使用4個位元組的值位元組長度+實際值byte,
給一個測驗資料包
| 序號 | 欄位名 | 資料型別 | 備注 |
|---|---|---|---|
| 1 | 訊息標識 | int | 取值:0x4A534604 |
| 2 | 訊息名稱 | string | 三國資訊,取值:"ThreeCountries" |
| 3 | 版本號 | int | 取值:1 |
| 4 | 編號 | int | 給三國一個編號吧,取值:1 |
| 5 | 國名 | string | 取值:"蜀國" |
| 6 | 皇帝 | string | 取值:"劉備" |
| 7 | 大將個數 | int | 5 |
| 8 | 大將1編號 | int | 取值:1 |
| 9 | 大將1名字 | string | 取值:"張飛" |
| 10 | 大將1備注 | string | 取值:"三板斧" |
| 11 | 大將2編號 | int | 取值:2 |
| 12 | 大將2名字 | string | 取值:"關羽" |
| 13 | 大將2備注 | string | 取值:"青龍偃月刀" |
| 14 | 大將3編號 | int | 取值:3 |
| 15 | 大將3名字 | string | 取值:"趙云" |
| 16 | 大將3備注 | string | 取值:"很猛的" |
| 17 | 大將4編號 | int | 取值:4 |
| 18 | 大將4名字 | string | 取值:"馬超" |
| 19 | 大將4備注 | string | 取值:"強" |
| 20 | 大將5編號 | int | 取值:5 |
| 21 | 大將5名字 | string | 取值:"黃忠" |
| 22 | 大將5備注 | string | 取值:"老當益壯" |
大致理解下:
- 前三個欄位是包體:用于標識整個資料包,便于包體決議;
- 后面的包體,簡單說就是三國中的國家資訊簡介,前三個欄位為三國中的一個國家基本資訊:編號、國名、皇帝,后面是該國家大將資訊串列,每個大將有編號、名稱、備注等,
定義資料物件
根據資料包定義,我們可以很快定義類進行使用,不管你是C++還是Java,下面是我用C#寫的對應類,用于序列化與反序列化使用:
/// <summary>
/// 三國
/// </summary>
public class ThreeCountries
{
/// <summary>
/// 獲取或者設定 ID
/// </summary>
public int ID { get; set; }
/// <summary>
/// 獲取或者設定 國名
/// </summary>
public string Name { get; set; }
/// <summary>
/// 獲取或者設定 皇帝
/// </summary>
public string Emperor { get; set; }
/// <summary>
/// 獲取或者設定 所選課程串列
/// </summary>
public List<FamousGeneral> Courses { get; set; }
public override string ToString()
{
return $"三國之一{ID}:{Name}皇帝{Emperor},有 {Courses.Count}名大將";
}
}
/// <summary>
/// 三國名將
/// </summary>
public class FamousGeneral
{
/// <summary>
/// 獲取或者設定 編號
/// </summary>
public int ID { get; set; }
/// <summary>
/// 獲取或者設定 名字
/// </summary>
public string Name { get; set; }
/// <summary>
/// 獲取或者設定 描述
/// </summary>
public string Memo { get; set; }
public override string ToString()
{
return $"{ID}:{Name}=>{Memo}";
}
}
對于上面給的資料包你怎么序列化及反序列化?轉換成資料如下,下節接著討論
ThreeCountries shuKingdom = new ThreeCountries
{
ID = 1,
Name = "蜀國",
Emperor = "劉備",
Courses = new System.Collections.Generic.List<FamousGeneral>
{
new FamousGeneral{ ID=1,Name="張飛",Memo="三板斧"},
new FamousGeneral{ ID=2,Name="關羽",Memo="青龍偃月刀"},
new FamousGeneral{ ID=3,Name="趙云",Memo="很猛的"},
new FamousGeneral{ ID=3,Name="馬超",Memo="強"},
new FamousGeneral{ ID=3,Name="黃忠",Memo="老當益壯"},
}
};
2. 常規操作
序列化
代碼太繁瑣,我就寫個不正規的偽代碼吧
定義一個byte陣列;
一、寫包頭
1、寫入4位元組的訊息標識:0x4A534604
計算訊息物件名稱字串“ThreeCountries”長度,及轉換字串為byte陣列
2、寫入2位元組的bytes陣列長度,寫入實際的byte陣列值
3、寫入4位元組的訊息版本號
二、寫包體
4、寫入4位元組的大將個數
回圈每個大將資訊,依次寫入
5、寫入大將1編號
6、寫入大將1名稱
7、寫入大獎1備注
8、寫入大將2編號
9、寫入大將3名稱
10、寫入大獎4備注
...寫吐了,省略號
反序列化
不想寫了,累
常規操作
定義一個序列化介面,每個網路物件實作其中的序列化與反序列化介面
public interface ISerializeInterface
{
byte[] Serialize<T>(T t);
T Deserialize<T>(byte[] arr);
}
public class ThreeCountries : ISerializeInterface
{
public byte[] Serialize<T>(T t)
{
// 將上面的序列化代碼寫在這
}
T Deserialize<T>(byte[] arr)
{
// 將上面的反序列化代碼寫在這,不好意思我沒寫
}
}
3. 本文操作
寫了半天的Demo,文章可能就寫的有點水了,我估計讀者也不會仔細看代碼,直接去Github check專案去了,哈哈,
我還是簡單說說吧,實作很簡單,定義一些特性,下面紅框里的代碼檔案:

使用很簡單,在上面的資料類上加上特性,改動不多,看下面代碼:
/// <summary>
/// 三國
/// </summary>
[NetObject(Name = "ThreeCountries", Version = 1)]
public class ThreeCountries
{
/// <summary>
/// 獲取或者設定 ID
/// </summary>
[NetObjectProperty(ID = 1)]
public int ID { get; set; }
/// <summary>
/// 獲取或者設定 國名
/// </summary>
[NetObjectProperty(ID = 2)]
public string Name { get; set; }
/// <summary>
/// 獲取或者設定 皇帝
/// </summary>
[NetObjectProperty(ID = 3)]
public string Emperor { get; set; }
/// <summary>
/// 獲取或者設定 所選課程串列
/// </summary>
[NetObjectProperty(ID = 4)]
public List<FamousGeneral> Courses { get; set; }
public static NetObjectAttribute CurrentObject = null;
static ThreeCountries()
{
CurrentObject = NetObjectSerializeHelper.GetAttribute<ThreeCountries, NetObjectAttribute>(default(ThreeCountries));
}
public override string ToString()
{
return $"三國之一{ID}:{Name}皇帝{Emperor},有 {Courses.Count}名大將";
}
}
/// <summary>
/// 三國名將
/// </summary>
public class FamousGeneral
{
/// <summary>
/// 獲取或者設定 編號
/// </summary>
[NetObjectProperty(ID = 1)]
public int ID { get; set; }
/// <summary>
/// 獲取或者設定 名字
/// </summary>
[NetObjectProperty(ID = 2)]
public string Name { get; set; }
/// <summary>
/// 獲取或者設定 描述
/// </summary>
[NetObjectProperty(ID = 3)]
public string Memo { get; set; }
public override string ToString()
{
return $"{ID}:{Name}=>{Memo}";
}
}
仔細看的話,只在外層類(ThreeCountries)上加了NetObject特性,和屬性上加了NetObjectProperty特性,分別標識訊息名稱、版本號及每個屬性的序列化與反序列化順序即可,類中使用的子物件Courses屬性,也只需要加屬性特性即可,如上,
下面添加單元測驗,并且測驗通過:

4. 總結
用這套代碼(demo,有所改變,但也差不多),完成了幾個類似的專案,每次資料通信聯調、測驗問題,C++和java的同事找我時,我就說:
"你先看你自己資料包的序列化和反序列化代碼有沒有問題,我這不會出問題的,完全按資料包格式轉的,"
剛開始還在那鬧,后面定位幾次問題后,類似的問題他們就沒再找我了,偷笑中,
原始碼:見開源專案TerminalMACS,
原文鏈接:https://dotnet9.com/16583.html
歡迎關注我的微信公眾號:Dotnet9

轉載請註明出處,本文鏈接:https://www.uj5u.com/net/184085.html
標籤:C#
