在c#中關於udp實現可靠地傳輸(數據包的分組發送) 中我們討論了,UDP包的發送,但是上一個程序有一個問題,就是數據比較大,一個Message類序列化後都有2048B,而實際的數據量也就不過 50B罷了,這就說明其中數據有效的很少,這樣當傳送的數據包過多後,效率會極大的降低。因此我們只有想辦法減少冗余數據。
此項目中借用了飛鴿傳書中的一個《FSLib.IPMessager》項目中的思想,並加以改善,感謝此項目作者,讓我對此有了深刻的理解
我們需要自己定義數據的傳輸結構 我們可以定義一個數據頭 其中包含一些基本的必要信息,然後再把要傳送的數據寫入尾部。我把這部分代碼貼出來,要具體完整的項目請到我的資源區下載,由於LZ原創希望給點分哈!
項目下載地址
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Net; namespace Netframe.Model { ////// 消息封包類 /// public class MessagePacker { /* * 消息包注意: * 1.第一位始終是2(ASCII碼50) * 2.第二位到第九位是一個long類型的整數,代表消息編號 * 3.第十位到第十三位是一個int類型的整數,代表消息內容總長度 * 4.第十四位到第十七位是一個int類型的整數,代表分包的總數 * 5.第十八位到第二十一位是一個int類型的整數,代表當前的分包編號 * 6.第二十二位表示是否需要返回一個確認標識(1/0) * 7.第二十三到第三十一位是保留的(Reserved) * 8.第三十二字節以後是數據包 * */ ////// 消息版本號 /// public static byte VersionHeader { get { return 50; } } ////// 返回當前消息封包的頭字節數 /// public static int PackageHeaderLength { get { return 32; } } ////// 獲得消息包的字節流 /// /// 要打包的消息對象 ///public static UdpPacketMsg[] BuildNetworkMessage(Msg message) { if (message.ExtendMessageBytes != null) { return BuildNetworkMessage( message.RemoteAddr, message.PackageNo, message.Command, message.UserName, message.HostName, message.Type, message.NormalMsgBytes, message.ExtendMessageBytes, message.IsRequireReceive ); } else { return BuildNetworkMessage( message.RemoteAddr, message.PackageNo, message.Command, message.UserName, message.HostName, message.Type, System.Text.Encoding.Unicode.GetBytes(message.NormalMsg), System.Text.Encoding.Unicode.GetBytes(message.ExtendMessage), message.IsRequireReceive ); } } /// /// 獲得消息包的字節流 /// /// 遠程主機地址 /// 包編號 /// 命令 /// 參數 /// 用戶名 /// 主機名 /// 正文消息 /// 擴展消息 ///public static UdpPacketMsg[] BuildNetworkMessage(IPEndPoint remoteIp, long packageNo, Commands command, string userName, string hostName,Consts type ,byte[] content, byte[] extendContents, bool RequireReceiveCheck) { //每次發送所能容下的數據量 int maxBytesPerPackage = (int)Consts.MAX_UDP_PACKAGE_LENGTH - PackageHeaderLength; //壓縮數據流 System.IO.MemoryStream ms = new System.IO.MemoryStream(); System.IO.Compression.GZipStream zip = new System.IO.Compression.GZipStream(ms, System.IO.Compression.CompressionMode.Compress); System.IO.BinaryWriter bw = new System.IO.BinaryWriter(zip, System.Text.Encoding.Unicode); //寫入頭部數據 bw.Write(packageNo); //包編號 bw.Write(userName); //用戶名 bw.Write(hostName); //主機名 bw.Write((long)command); //命令 bw.Write((long)type); //數據類型 bw.Write(content == null ? 0 : content.Length);//數據長度 //寫入消息數據 if (content != null) bw.Write(content); bw.Write(extendContents == null ? 0 : extendContents.Length);//補充數據長度 if (extendContents != null) bw.Write(extendContents); //bw.Close(); //zip.Close(); ms.Flush(); ms.Seek(0, System.IO.SeekOrigin.Begin); //打包數據總量 int dataLength = (int)ms.Length; int packageCount = (int)Math.Ceiling(dataLength * 1.0 / maxBytesPerPackage); UdpPacketMsg[] pnma = new UdpPacketMsg[packageCount]; for (int i = 0; i < packageCount; i++) { int count = i == packageCount - 1 ? dataLength - maxBytesPerPackage * (packageCount - 1) : maxBytesPerPackage; byte[] buf = new byte[count + PackageHeaderLength]; buf[0] = VersionHeader;//版本號 第1位 BitConverter.GetBytes(packageNo).CopyTo(buf, 1);//消息編號 第2到9位 long類型的整數 BitConverter.GetBytes(dataLength).CopyTo(buf, 9);//消息內容長度 第10到13位 int類型的整數 BitConverter.GetBytes(packageCount).CopyTo(buf, 13);//分包總數 第14位到第17位 int類型的整數 BitConverter.GetBytes(i).CopyTo(buf, 17);//分包編號 第18位到第21位 int類型的整數 buf[21] = RequireReceiveCheck ? (byte)1 : (byte)0;//是否回確認包 第22位 //第23到第31位是保留的(Reserved) ms.Read(buf, 32, buf.Length - 32);//第32字節以後是,具體的數據包 pnma[i] = new UdpPacketMsg() { Data = buf, PackageCount = packageCount, PackageIndex = i, PackageNo = packageNo, RemoteIP = remoteIp, SendTimes = 0, Version = 2, IsRequireReceiveCheck = buf[21] == 1 }; } bw.Close(); zip.Close(); ms.Close(); return pnma; } /// /// 檢測確認是否是這個類型的消息包 /// /// ///public static bool Test(byte[] buffer) { return buffer != null && buffer.Length > PackageHeaderLength && buffer[0] == VersionHeader; } /// /// 緩存接收到的片段 /// static DictionarypackageCache = new Dictionary (); /// /// 分析網絡數據包並進行轉換為信息對象 /// /// 接收到的封包對象 ////// /// 對於分包消息,如果收到的只是片段並且尚未接收完全,則不會進行解析 /// public static Msg ParseToMessage(params UdpPacketMsg[] packs) { if (packs.Length == 0 || (packs[0].PackageCount > 1 && packs.Length != packs[0].PackageCount)) return null; //嘗試解壓縮,先排序 Array.Sort(packs); //嘗試解壓縮 System.IO.MemoryStream ms = new System.IO.MemoryStream(); System.IO.Compression.GZipStream zip = new System.IO.Compression.GZipStream(ms, System.IO.Compression.CompressionMode.Decompress); //System.IO.BinaryWriter bw = new System.IO.BinaryWriter(zip, System.Text.Encoding.Unicode); try { foreach (var s in packs) { if (zip.CanWrite) { zip.Write(s.Data, 0, s.Data.Length); } } //Array.ForEach(packs, s => zip.Write(s.Data, 0, s.Data.Length)); } catch (Exception e) { //觸發事件 return null; } zip.Close(); ms.Flush(); ms.Seek(0, System.IO.SeekOrigin.Begin); //構造讀取流 System.IO.BinaryReader br = new System.IO.BinaryReader(ms, System.Text.Encoding.Unicode); //開始讀出數據 Msg m = new Msg(packs[0].RemoteIP); m.PackageNo = br.ReadInt64();//包編號 m.UserName = br.ReadString();//用戶名 m.HostName = br.ReadString();//主機名 m.Command = (Commands)br.ReadInt64(); //命令 m.Type = (Consts)br.ReadInt64();//數據類型 int length = br.ReadInt32(); //數據長度 m.NormalMsgBytes = new byte[length]; br.Read(m.NormalMsgBytes, 0, length);//讀取內容 length = br.ReadInt32(); //附加數據長度 m.ExtendMessageBytes = new byte[length]; br.Read(m.ExtendMessageBytes, 0, length);//讀取附加數據 if (m.Type == Consts.MESSAGE_TEXT) { m.NormalMsg = System.Text.Encoding.Unicode.GetString(m.NormalMsgBytes, 0, length); //正文 m.ExtendMessage = System.Text.Encoding.Unicode.GetString(m.ExtendMessageBytes, 0, length); //擴展消息 m.ExtendMessageBytes = null; m.NormalMsgBytes = null; } return m; } ////// 嘗試將收到的網絡包解析為實體 /// /// 收到的網絡包 ////// 如果收到的包是分片包,且其所有子包尚未接受完全,則會返回空值 public static Msg TryToTranslateMessage(UdpPacketMsg pack) { if (pack == null || pack.PackageIndex >= pack.PackageCount - 1) return null; else if (pack.PackageCount == 1) return ParseToMessage(pack); else { if (packageCache.ContainsKey(pack.PackageNo)) { UdpPacketMsg[] array = packageCache[pack.PackageNo]; array[pack.PackageIndex] = pack; //檢測是否完整 if (Array.FindIndex(array, s => s == null) == -1) { packageCache.Remove(pack.PackageNo); return ParseToMessage(array); } else { return null; } } else { UdpPacketMsg[] array = new UdpPacketMsg[pack.PackageCount]; array[pack.PackageIndex] = pack; packageCache.Add(pack.PackageNo, array); return null; } } } ////// 將網絡信息解析為封包 /// /// ///public static UdpPacketMsg Parse(byte[] buffer, IPEndPoint clientAddress) { if (!Test(buffer)) return null; UdpPacketMsg p = new UdpPacketMsg() { RemoteIP = clientAddress, SendTimes = 0 }; p.PackageNo = BitConverter.ToInt64(buffer, 1);//包編號 p.DataLength = (int)BitConverter.ToInt64(buffer, 9); //內容長度 p.PackageCount = BitConverter.ToInt32(buffer, 17);//分包總數 p.PackageIndex = BitConverter.ToInt32(buffer, 21);//索引 p.IsRequireReceiveCheck = buffer[21] == 1;//是否需要回包 p.Data = new byte[buffer.Length - PackageHeaderLength]; Array.Copy(buffer, PackageHeaderLength, p.Data, 0, p.Data.Length); return p; } } }