在做c#中面向无连接的传输时用到了UDP,虽然没有TCP稳定可靠。但是效率是要高些,优势也有,缺点也有
就是有的时候要丢包,有的时候不得不用UDP,但是如何才能比较稳定的实现可靠传输呢,这是一个问题。
TCP传输数据的时候没有大小限制,但是UDP传输的时候是有大小限制的,我们怎么才能够实现大数据的稳定传输呢。我们想到了,把数据包分包。
把一个大数据分割为一系列的小数据包然后分开发送,然后服务端收到了就拼凑起完整数据。
如果遇到中途丢包就重发。
UDP线程类,实现数据的分包发送和重发。具体的接收操作需要实现其中的事件
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net.Sockets;
using Model;
using System.Net;
using Tool;
using System.Threading;
namespace ZZUdp.Core
{
//udp的类
public class UDPThread
{
#region 私有变量
UdpClient client;//UDP客户端
List<UdpPacket> sendlist;// 用于轮询是否发送成功的记录
Dictionary<long, RecDataList> RecListDic = new Dictionary<long, RecDataList>();//数据接收列表,每一个sequence对应一个
IPEndPoint remotIpEnd = null;//用来在接收数据的时候对远程主机的信息存放
int port=6666;//定义服务器的端口号
#endregion
#region 属性
public int CheckQueueTimeInterval { get; set; }//检查发送队列间隔
public int MaxResendTimes { get; set; }//没有收到确认包时,最大重新发送的数目,超过此数目会丢弃并触发PackageSendFailture事件
#endregion
#region 事件
/// <summary>
/// 当数据包收到时触发
/// </summary>
public event EventHandler<PackageEventArgs> PackageReceived;
/// <summary>
/// 当数据包收到事件触发时,被调用
/// </summary>
/// <param name="e">包含事件的参数</param>
protected virtual void OnPackageReceived(PackageEventArgs e)
{
if (PackageReceived != null)
PackageReceived(this, e);
}
/// <summary>
/// 数据包发送失败
/// </summary>
public event EventHandler<PackageEventArgs> PackageSendFailure;
/// <summary>
/// 当数据发送失败时调用
/// </summary>
/// <param name="e">包含事件的参数</param>
protected virtual void OnPackageSendFailure(PackageEventArgs e)
{
if (PackageSendFailure != null)
PackageSendFailure(this, e);
}
/// <summary>
/// 数据包未接收到确认,重新发送
/// </summary>
public event EventHandler<PackageEventArgs> PackageResend;
/// <summary>
/// 触发重新发送事件
/// </summary>
/// <param name="e">包含事件的参数</param>
protected virtual void OnPackageResend(PackageEventArgs e)
{
if (PackageResend != null)
PackageResend(this, e);
}
#endregion
//无参构造函数
public UDPThread()
{
}
//构造函数
public UDPThread(string ipaddress, int port)
{
IPAddress ipA = IPAddress.Parse(ipaddress);//构造远程连接的参数
IPEndPoint ipEnd = new IPEndPoint(ipA, port);
client = new UdpClient();// client = new UdpClient(ipEnd)这样的话就没有创建远程连接
client.Connect(ipEnd);//使用指定的远程主机信息建立默认远程主机连接
sendlist = new List<UdpPacket>();
CheckQueueTimeInterval = 2000;//轮询间隔时间
MaxResendTimes = 5;//最大发送次数
new Thread(new ThreadStart(CheckUnConfirmedQueue)) { IsBackground = true }.Start();//启动轮询线程
//开始监听数据
AsyncReceiveData();
}
/// <summary>
/// 同步数据接收方法
/// </summary>
public void ReceiveData()
{
while (true)
{
IPEndPoint retip = null;
UdpPacket udpp = null;
try
{
byte[] data = client.Receive(ref retip);//接收数据,当Client端连接主机的时候,retip就变成Cilent端的IP了
udpp = (UdpPacket)SerializationUnit.DeserializeObject(data);
}
catch (Exception ex)
{
//异常处理操作
}
if (udpp != null)
{
PackageEventArgs arg = new PackageEventArgs(udpp, retip);
OnPackageReceived(arg);//数据包收到触发事件
}
}
}
//异步接受数据
public void AsyncReceiveData()
{
try
{
client.BeginReceive(new AsyncCallback(ReceiveCallback), null);
}
catch (SocketException ex)
{
throw ex;
}
}
//接收数据的回调函数
public void ReceiveCallback(IAsyncResult param)
{
if (param.IsCompleted)
{
UdpPacket udpp = null;
try
{
byte[] data = client.EndReceive(param, ref remotIpEnd);//接收数据,当Client端连接主机的时候,test就变成Cilent端的IP了
udpp = (UdpPacket)SerializationUnit.DeserializeObject(data);
}
catch (Exception ex)
{
//异常处理操作
}
finally
{
AsyncReceiveData();
}
if (udpp != null)//触发数据包收到事件
{
PackageEventArgs arg = new PackageEventArgs(udpp, null);
OnPackageReceived(arg);
}
}
}
/// <summary>
/// 同步发送分包数据
/// </summary>
/// <param name="message"></param>
public void SendData(Msg message)
{
ICollection<UdpPacket> udpPackets = UdpPacketSplitter.Split(message);
foreach (UdpPacket udpPacket in udpPackets)
{
byte[] udpPacketDatagram = SerializationUnit.SerializeObject(udpPacket);
//使用同步发送
client.Send(udpPacketDatagram, udpPacketDatagram.Length,udpPacket.remoteip);
if (udpPacket.IsRequireReceiveCheck)
PushSendItemToList(udpPacket);//将该消息压入列表
}
}
/// <summary>
/// 异步分包发送数组的方法
/// </summary>
/// <param name="message"></param>
public void AsyncSendData(Msg message)
{
ICollection<UdpPacket> udpPackets = UdpPacketSplitter.Split(message);
foreach (UdpPacket udpPacket in udpPackets)
{
byte[] udpPacketDatagram = SerializationUnit.SerializeObject(udpPacket);
//使用同步发送
//client.Send(udpPacketDatagram, udpPacketDatagram.Length);
//使用异步的方法发送数据
this.client.BeginSend(udpPacketDatagram, udpPacketDatagram.Length, new AsyncCallback(SendCallback), null);
}
}
//发送完成后的回调方法
public void SendCallback(IAsyncResult param)
{
if (param.IsCompleted)
{
try
{
client.EndSend(param);//这句话必须得写,BeginSend()和EndSend()是成对出现的
}
catch (Exception e)
{
//其他处理异常的操作
}
}
}
static object lockObj = new object();
/// <summary>
/// 自由线程,检测未发送的数据并发出,存在其中的就是没有收到确认包的数据包
/// </summary>
void CheckUnConfirmedQueue()
{
do
{
if (sendlist.Count > 0)
{
UdpPacket[] array = null;
lock (sendlist)
{
array = sendlist.ToArray();
}
//挨个重新发送并计数
Array.ForEach(array, s =>
{
s.sendtimes++;
if (s.sendtimes >= MaxResendTimes)
{
//sOnPackageSendFailure//出发发送失败事件
sendlist.Remove(s);//移除该包
}
else
{
//重新发送
byte[] udpPacketDatagram = SerializationUnit.SerializeObject(s);
client.Send(udpPacketDatagram, udpPacketDatagram.Length, s.remoteip);
}
});
}
Thread.Sleep(CheckQueueTimeInterval);//间隔一定时间重发数据
} while (true);
}
/// <summary>
/// 将数据信息压入列表
/// </summary>
/// <param name="item"></param>
void PushSendItemToList(UdpPacket item)
{
sendlist.Add(item);
}
/// <summary>
/// 将数据包从列表中移除
/// </summary>
/// <param name="packageNo">数据包编号</param>
/// <param name="packageIndex">数据包分包索引</param>
public void PopSendItemFromList(long packageNo, int packageIndex)
{
lock (lockObj)
{
Array.ForEach(sendlist.Where(s => s.sequence == packageNo && s.index == packageIndex).ToArray(), s => sendlist.Remove(s));
}
}
/// <summary>
/// 关闭客户端并释放资源
/// </summary>
public void Dispose()
{
if (client != null)
{
client.Close();
client = null;
}
}
}
}
首先是数据信息实体类
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
namespace Model
{
//封装消息类
[Serializable]
public class Msg
{
//所属用户的用户名
public string name { get; set; }
//所属用户的ip
public string host { get; set; }
//命令的名称
public string command { get; set; }
//收信人的姓名
public string desname { get; set; }
//你所发送的消息的目的地ip,应该是对应在服务器的列表里的主键值
public string destinationIP { get; set; }
//端口号
public int port { get; set; }
//文本消息
public string msg { get; set; }
//二进制消息
public byte[] byte_msg { get; set; }
//附加数据
public string extend_msg { get; set; }
//时间戳
public DateTime time { get; set; }
//构造函数
public Msg(string command,string desip,string msg,string host)
{
this.command = command;
this.destinationIP = desip;
this.msg = msg;
this.time = DateTime.Now;
this.host = host;
}
override
public string ToString()
{
return name + "说:" + msg;
}
}
}
MSG数据分割后生成分包数据
分包实体类
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Tool;
using System.Net;
namespace Model
{
[Serializable]
public class UdpPacket
{
public long sequence{get;set;}//所属组的唯一序列号 包编号
public int total { get; set; }//分包总数
public int index { get; set; }//消息包的索引
public byte[] data { get; set; }//包的内容数组
public int dataLength { get; set; }//分割的数组包大小
public int remainder { get; set; }//最后剩余的数组的数据长度
public int sendtimes { get; set; }//发送次数
public IPEndPoint remoteip { get; set; }//接受该包的远程地址
public bool IsRequireReceiveCheck { get; set; }//获得或设置包收到时是否需要返回确认包
public static int HeaderSize = 30000;
public UdpPacket(long sequence, int total, int index, byte[] data, int dataLength, int remainder,string desip,int port)
{
this.sequence = sequence;
this.total = total;
this.index = index;
this.data = data;
this.dataLength = dataLength;
this.remainder = remainder;
this.IsRequireReceiveCheck = true;//默认都需要确认包
//构造远程地址
IPAddress ipA = IPAddress.Parse(desip);
this.remoteip = new IPEndPoint(ipA, port);
}
//把这个对象生成byte[]
public byte[] ToArray()
{
return SerializationUnit.SerializeObject(this);
}
}
}
数据包分割工具类
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Tool;
namespace Model
{
/// <summary>
/// UDP数据包分割器
/// </summary>
public static class UdpPacketSplitter
{
public static ICollection<UdpPacket> Split(Msg message)
{
byte[] datagram = null;
try
{
datagram = SerializationUnit.SerializeObject(message);
}
catch (Exception e)
{
//AddTalkMessage("数据转型异常");
}
//产生一个序列号,用来标识包数据属于哪一组
Random Rd = new Random();
long SequenceNumber = Rd.Next(88888, 999999);
ICollection<UdpPacket> udpPackets = UdpPacketSplitter.Split(SequenceNumber, datagram, 10240, message.destinationIP, message.port);
return udpPackets;
}
/// <summary>
/// 分割UDP数据包
/// </summary>
/// <param name="sequence">UDP数据包所持有的序号</param>
/// <param name="datagram">被分割的UDP数据包</param>
/// <param name="chunkLength">分割块的长度</param>
/// <returns>
/// 分割后的UDP数据包列表
/// </returns>
public static ICollection<UdpPacket> Split(long sequence, byte[] datagram, int chunkLength,string desip,int port)
{
if (datagram == null)
throw new ArgumentNullException("datagram");
List<UdpPacket> packets = new List<UdpPacket>();
int chunks = datagram.Length / chunkLength;
int remainder = datagram.Length % chunkLength;
int total = chunks;
if (remainder > 0) total++;
for (int i = 1; i <= chunks; i++)
{
byte[] chunk = new byte[chunkLength];
Buffer.BlockCopy(datagram, (i - 1) * chunkLength, chunk, 0, chunkLength);
packets.Add(new UdpPacket(sequence, total, i, chunk, chunkLength, remainder, desip, port));
}
if (remainder > 0)
{
int length = datagram.Length - (chunkLength * chunks);
byte[] chunk = new byte[length];
Buffer.BlockCopy(datagram, chunkLength * chunks, chunk, 0, length);
packets.Add(new UdpPacket(sequence, total, total, chunk, chunkLength, remainder, desip, port));
}
return packets;
}
}
}
服务端存储数据的数据结构
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Tool;
using Model;
namespace Model
{
//一个sequence对应一组的数据包的数据结构
public class RecDataList
{
public long sequence { get; set; }//序列号
//对应的存储包的List
List<UdpPacket> RecudpPackets = new List<UdpPacket>();
public int total { get; set; }
public int dataLength { get; set; }
public int remainder { get; set; }
public byte[] DataBuffer = null;
public RecDataList(UdpPacket udp)
{
this.sequence = udp.sequence;
this.total = udp.total;
this.dataLength = udp.dataLength;
this.remainder = udp.remainder;
if (DataBuffer == null)
{
DataBuffer = new byte[dataLength * (total - 1) + remainder];
}
}
public RecDataList(long sequence, int total, int chunkLength, int remainder)
{
this.sequence = sequence;
this.total = total;
this.dataLength = chunkLength;
this.remainder = remainder;
if (DataBuffer == null)
{
DataBuffer = new byte[this.dataLength * (this.total - 1) + this.remainder];
}
}
public void addPacket(UdpPacket p)
{
RecudpPackets.Add(p);
}
public Msg show()
{
if (RecudpPackets.Count == total)//表示已经收集满了
{
//重组数据
foreach (UdpPacket udpPacket in RecudpPackets)
{
//偏移量
int offset = (udpPacket.index - 1) * udpPacket.dataLength;
Buffer.BlockCopy(udpPacket.data, 0, DataBuffer, offset, udpPacket.data.Length);
}
Msg rmsg = (Msg)SerializationUnit.DeserializeObject(DataBuffer);
DataBuffer = null;
RecudpPackets.Clear();
return rmsg;
}
else
{
return null;
}
}
public bool containskey(UdpPacket udp)
{
foreach (UdpPacket udpPacket in RecudpPackets)
{
if (udpPacket.index == udp.index)
return true;
}
return false;
}
}
}
编码工具类
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.Serialization.Formatters.Binary;
using System.IO;
namespace Tool
{
public class EncodingTool
{
//编码
public static byte[] EncodingASCII(string buf)
{
byte[] data = Encoding.Unicode.GetBytes(buf);
return data;
}
//解码
public static string DecodingASCII(byte[] bt)
{
string st = Encoding.Unicode.GetString(bt);
return st;
}
//编码
public static byte[] EncodingUTF_8(string buf)
{
byte[] data = Encoding.UTF8.GetBytes(buf);
return data;
}
//编码
public static string DecodingUTF_8(byte[] bt)
{
string st = Encoding.UTF8.GetString(bt);
return st;
}
}
}
序列化和反序列化的工具类
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.Serialization.Formatters.Binary;
using System.IO;
namespace Tool
{
public class SerializationUnit
{
/// <summary>
/// 把对象序列化为字节数组
/// </summary>
public static byte[] SerializeObject(object obj)
{
if (obj == null)
return null;
//内存实例
MemoryStream ms = new MemoryStream();
//创建序列化的实例
BinaryFormatter formatter = new BinaryFormatter();
formatter.Serialize(ms, obj);//序列化对象,写入ms流中
ms.Position = 0;
//byte[] bytes = new byte[ms.Length];//这个有错误
byte[] bytes = ms.GetBuffer();
ms.Read(bytes, 0, bytes.Length);
ms.Close();
return bytes;
}
/// <summary>
/// 把字节数组反序列化成对象
/// </summary>
public static object DeserializeObject(byte[] bytes)
{
object obj = null;
if (bytes == null)
return obj;
//利用传来的byte[]创建一个内存流
MemoryStream ms = new MemoryStream(bytes);
ms.Position = 0;
BinaryFormatter formatter = new BinaryFormatter();
obj = formatter.Deserialize(ms);//把内存流反序列成对象
ms.Close();
return obj;
}
/// <summary>
/// 把字典序列化
/// </summary>
/// <param name="dic"></param>
/// <returns></returns>
public static byte[] SerializeDic(Dictionary<string, object> dic)
{
if (dic.Count == 0)
return null;
MemoryStream ms = new MemoryStream();
BinaryFormatter formatter = new BinaryFormatter();
formatter.Serialize(ms, dic);//把字典序列化成流
byte[] bytes = new byte[ms.Length];//从流中读出byte[]
ms.Read(bytes, 0, bytes.Length);
return bytes;
}
/// <summary>
/// 反序列化返回字典
/// </summary>
/// <param name="bytes"></param>
/// <returns></returns>
public static Dictionary<string, object> DeserializeDic(byte[] bytes)
{
Dictionary<string, object> dic = null;
if (bytes == null)
return dic;
//利用传来的byte[]创建一个内存流
MemoryStream ms = new MemoryStream(bytes);
ms.Position = 0;
BinaryFormatter formatter = new BinaryFormatter();
//把流中转换为Dictionary
dic = (Dictionary<string, object>)formatter.Deserialize(ms);
return dic;
}
}
}
通用的数据包事件类
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading;
using Model;
namespace ZZUdp.Core
{
/// <summary>
/// 数据包事件数据
/// </summary>
public class PackageEventArgs : EventArgs
{
/// <summary>
/// 网络消息包
/// </summary>
public UdpPacket udpPackage { get; set; }
/// <summary>
/// 网络消息包组
/// </summary>
public UdpPacket[] udpPackages { get; set; }
/// <summary>
/// 远程IP
/// </summary>
public IPEndPoint RemoteIP { get; set; }
/// <summary>
/// 是否已经处理
/// </summary>
public bool IsHandled { get; set; }
/// <summary>
/// 创建一个新的 PackageEventArgs 对象.
/// </summary>
public PackageEventArgs(UdpPacket package, IPEndPoint RemoteIP)
{
this.udpPackage = package;
this.RemoteIP = RemoteIP;
this.IsHandled = false;
}
}
}