拆包组包提炼于一位大拿的文章,这里提供链接:
c#中关于udp实现可靠地传输(数据包的分组发送)
仅仅使用了大拿所设计的包的拆分、组装与序列化部分。
在此工程中,将拆包组包模块单独作为了一个library工程,包含了PacketHelper文件。
其中包序列化和反序列化类如下:
public class SerializationUnit
{
public static byte[] SerializeObject(object obj)
{
if (obj == null)
return null;
MemoryStream ms = new MemoryStream();
BinaryFormatter formatter = new BinaryFormatter();
formatter.Serialize(ms, obj);
ms.Position = 0;
byte[] bytes = ms.GetBuffer();
ms.Read(bytes, 0, bytes.Length);
ms.Close();
return bytes;
}
public static object DeserializeObject(byte[] bytes)
{
object obj = null;
if (bytes == null)
return obj;
MemoryStream ms = new MemoryStream(bytes);
ms.Position = 0;
BinaryFormatter formatter = new BinaryFormatter();
obj = formatter.Deserialize(ms);
ms.Close();
return obj;
}
}
屏幕数据包设计如下,在项目中屏幕广播的接收端并不是C#所写,在此进行了提炼,省去了其他的包体信息等:
[Serializable]
public class CastPacket
{
public double TimeStamp { get; set; }
public Int32 Index { get; set; }
public Int32 Total { get; set; }
public Int32 TotalLength { get; set; }
public Int32 DataOffset { get; set; }
public Int32 DataLength { get; set; }
public byte[] Data { get; set; }
public CastPacket(double timeStamp, Int32 total, Int32 index, byte[] data, Int32 dataOffset, Int32 totalLength)
{
this.TimeStamp = timeStamp;
this.Total = total;
this.Index = index;
this.Data = data;
this.DataOffset = dataOffset;
this.DataLength = data.Length;
this.TotalLength = totalLength;
}
public byte[] ToArray()
{
return SerializationUnit.SerializeObject(this);
}
}
public class PacketSplitter
{
public static ICollection Split(double TimeStamp, byte[] data, int chunkLength = 8174)
{
List packets = new List();
int chunks = data.Length / chunkLength;
int remainder = data.Length % chunkLength;
int total = chunks;
if (remainder > 0) total++;
for (int i = 1; i <= chunks; i++)
{
byte[] chunk = new byte[chunkLength];
Buffer.BlockCopy(data, (i - 1) * chunkLength, chunk, 0, chunkLength);
packets.Add(new CastPacket(TimeStamp, total, i, chunk, chunkLength * (i - 1), data.Length));
}
if (remainder > 0)
{
int length = data.Length - (chunkLength * chunks);
byte[] chunk = new byte[length];
Buffer.BlockCopy(data, chunkLength * chunks, chunk, 0, length);
packets.Add(new CastPacket(TimeStamp, total, total, chunk, chunkLength * chunks, data.Length));
}
return packets;
}
}
组包事件和组包类为:
public class PacketCollectorEventArgs : EventArgs
{
public byte[] data;
}
public class PacketCollector
{
private Object ObjLock = new Object();
public EventHandler OnCollectorEventHandler;
private Dictionary> DicCollectedPacket = new Dictionary>();
private double CurrentTimeStamp = -1;
private Thread CollectThreadHandler;
private Boolean bRunning;
public PacketCollector()
{
bRunning = true;
CollectThreadHandler = new Thread(new ThreadStart(CollectThread)) { IsBackground = true };
CollectThreadHandler.Start();
}
public void Dispose()
{
bRunning = false;
//WinHelper.WaitForThreadExit(CollectThreadHandler);
DicCollectedPacket = null;
}
private void CollectThread()
{
while (bRunning == true)
{
lock (ObjLock)
{
if (DicCollectedPacket.Count == 0) continue;
var SortedDic = DicCollectedPacket.OrderBy(p => p.Key).ToDictionary(p => p.Key, p => p.Value);
foreach (var ListPack in SortedDic)
{
if (CurrentTimeStamp - ListPack.Key >= 2000)
{
//Console.WriteLine("Abbanded package.");
DicCollectedPacket.Remove(ListPack.Key);
continue;
}
if (ListPack.Value.Count == ListPack.Value[0].Total)
{
RePackMsg(ListPack.Value);
DicCollectedPacket.Remove(ListPack.Key);
}
}
}
Thread.Sleep(5);
}
}
public void Collect(Byte[] Msg)
{
CastPacket Pac = (CastPacket)SerializationUnit.DeserializeObject(Msg);
if (Pac == null)
return;
if (Pac.Data == null)
return;
lock (ObjLock)
{
List ListPacket = null;
Boolean bContain = DicCollectedPacket.TryGetValue(Pac.TimeStamp, out ListPacket);
if (bContain == false)
{
ListPacket = new List();
ListPacket.Add(Pac);
DicCollectedPacket.Add(Pac.TimeStamp, ListPacket);
}
else
{
ListPacket.Add(Pac);
}
CurrentTimeStamp = Math.Max(Pac.TimeStamp, CurrentTimeStamp);
}
}
private void RePackMsg(List listPack)
{
Byte[] Data = new Byte[listPack[0].TotalLength];
foreach (var Pac in listPack)
{
try
{
Buffer.BlockCopy(Pac.Data, 0, Data, Pac.DataOffset, Pac.Data.Length);
}
catch
{
Console.WriteLine("Wrong package.");
return;
}
}
if (this.OnCollectorEventHandler != null)
this.OnCollectorEventHandler(this, new PacketCollectorEventArgs() { data = Data });
}
}
以上即为拆包组包所用到的共用模块,很奇怪的一点是,我把这个类分别放在客户端和服务端中会报出转换异常,还没找到原因。
源码传送门:
C#屏幕广播源码V1.0