码迷,mamicode.com
首页 > 其他好文 > 详细

c#中关于udp实现可靠地传输(数据包的分组发送) 升级版

时间:2014-05-21 17:06:30      阅读:352      评论:0      收藏:0      [点我收藏+]

标签:blog   class   c   code   tar   ext   

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
{
    /// <summary>
    /// 消息封包类
    /// </summary>
    public class MessagePacker
    {
        /*
		 * 消息包注意:
		 * 1.第一位始终是2(ASCII码50)
		 * 2.第二位到第九位是一个long类型的整数,代表消息编号
		 * 3.第十位到第十三位是一个int类型的整数,代表消息内容总长度
		 * 4.第十四位到第十七位是一个int类型的整数,代表分包的总数
		 * 5.第十八位到第二十一位是一个int类型的整数,代表当前的分包编号
		 * 6.第二十二位表示是否需要返回一个确认标识(1/0)
		 * 7.第二十三到第三十一位是保留的(Reserved)
		 * 8.第三十二字节以后是数据包
		 * */

        /// <summary>
        /// 消息版本号
        /// </summary>
        public static byte VersionHeader { get { return 50; } }
        /// <summary>
        /// 返回当前消息封包的头字节数
        /// </summary>
        public static int PackageHeaderLength { get { return 32; } }

        /// <summary>
        /// 获得消息包的字节流
        /// </summary>
        /// <param name="message">要打包的消息对象</param>
        /// <returns></returns>
        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
                );
            }
        }

        /// <summary>
        /// 获得消息包的字节流
        /// </summary>
        /// <param name="remoteIp">远程主机地址</param>
        /// <param name="packageNo">包编号</param>
        /// <param name="command">命令</param>
        /// <param name="options">参数</param>
        /// <param name="userName">用户名</param>
        /// <param name="hostName">主机名</param>
        /// <param name="content">正文消息</param>
        /// <param name="extendContents">扩展消息</param>
        /// <returns></returns>
        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;
        }


        /// <summary>
        /// 检测确认是否是这个类型的消息包
        /// </summary>
        /// <param name="buffer"></param>
        /// <returns></returns>
        public static bool Test(byte[] buffer)
        {
            return buffer != null && buffer.Length > PackageHeaderLength && buffer[0] == VersionHeader;
        }

        /// <summary>
        /// 缓存接收到的片段
        /// </summary>
        static Dictionary<long, UdpPacketMsg[]> packageCache = new Dictionary<long, UdpPacketMsg[]>();

        /// <summary>
        /// 分析网络数据包并进行转换为信息对象
        /// </summary>
        /// <param name="packs">接收到的封包对象</param>
        /// <returns></returns>
        /// <remarks>
        /// 对于分包消息,如果收到的只是片段并且尚未接收完全,则不会进行解析
        /// </remarks>
        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;
        }

        /// <summary>
        /// 尝试将收到的网络包解析为实体
        /// </summary>
        /// <param name="pack">收到的网络包</param>
        /// <returns></returns>
        /// <remarks>如果收到的包是分片包,且其所有子包尚未接受完全,则会返回空值</remarks>
        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;
                }
            }

        }

        /// <summary>
        /// 将网络信息解析为封包
        /// </summary>
        /// <param name="buffer"></param>
        /// <returns></returns>
        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;
        }

    }
}


c#中关于udp实现可靠地传输(数据包的分组发送) 升级版,布布扣,bubuko.com

c#中关于udp实现可靠地传输(数据包的分组发送) 升级版

标签:blog   class   c   code   tar   ext   

原文地址:http://blog.csdn.net/zhujunxxxxx/article/details/26414649

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!