在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 Dictionary packageCache = 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;
}
}
}