C# 手动打包字节以在网络上发送

C# 手动打包字节以在网络上发送,c#,serialization,bit-manipulation,C#,Serialization,Bit Manipulation,我有一个具有以下变量的对象: bool firstBool; float firstFloat; (0.0 to 1.0) float secondFloat (0.0 to 1.0) int firstInt; (0 to 10,000) 我使用ToString方法获取可以通过网络发送的字符串。放大我遇到了这样占用数据量的问题。 此时字符串看起来如下所示: “false:1.0:1.0:10000”这是19个字符,每个so 38字节2个字节 我知道我可以通过手动将数据存储在4个字

我有一个具有以下变量的对象:

bool firstBool;  
float firstFloat; (0.0 to 1.0)  
float secondFloat (0.0 to 1.0)  
int firstInt; (0 to 10,000)
我使用ToString方法获取可以通过网络发送的字符串。放大我遇到了这样占用数据量的问题。 此时字符串看起来如下所示:
“false:1.0:1.0:10000”
这是19个字符,每个so 38字节2个字节

我知道我可以通过手动将数据存储在4个字节中来节省这个大小,如下所示:

A|B|B|B|B|B|B|B  
C|C|C|C|C|C|C|D  
D|D|D|D|D|D|D|D  
D|D|D|D|D|X|X|X  

A = bool(0 or 1), B = int(0 to 128), C = int(0 to 128), D = int(0 to 16384), X = Leftover bits  
  • 我将
    float(0.0到1.0)
    转换为
    int(0到128)
    ,因为我可以在另一端重建它们,并且精度不是非常重要
我一直在尝试使用BitArray和byte[]将数据转换为二进制结构,并将其转换为二进制结构

经过一些实验,我最终完成了这个序列化过程(我知道它需要清理和优化)

public byte[]Serialize(){
byte[]firstFloatBytes=BitConverter.GetBytes(Mathf.FloorToInt(firstFloat*128));//将浮点从(0到128)转换为int
byte[]secondFloatBytes=BitConverter.GetBytes(Mathf.FloorToInt(secondFloat*128));//将浮点从(0到128)转换为int
byte[]firstIntData=BitConverter.GetBytes(Mathf.FloorToInt(firstInt));//获取整数的字节
BitArray data=new BitArray(32);//创建大小为32的BitArray以保存所有数据
int i=0;//创建索引值
data[i]=firstBool;//设置0位
BitArray ffBits=新的位数组(firstFloatBytes);
对于(i=1;i<8;i++){
数据[i]=ffBits[i-1];//设置位1到7
}
BitArray sfBits=新的位数组(字节);
对于(i=8;i<15;i++){
数据[i]=sfBits[i-8];//设置位8到14
}
BitArray fiBits=新的BitArray(firstIntData);
对于(i=15;i<29;i++){
数据[i]=fibit[i-15];//设置位15到28
}
字节[]输出=新字节[4];//创建一个字节[]来保存输出
data.CopyTo(输出,0);//将位复制到字节[]
返回输出;
}
从这个结构中获取信息要比将信息转换成这个表单复杂得多。我想我可以使用位运算符和位掩码来训练一些东西


事实证明,这比我预想的要复杂得多。我认为访问byte[]的位可以很容易地直接操作数据,提取位的范围,然后转换回重建对象所需的值。这种类型的数据序列化是否有最佳实践?有人知道我可以阅读的教程或示例参考吗?

标准和高效的序列化方法有:

  • 使用
    BinaryWriter
    /
    BinaryReader

    public byte[] Serialize()
    {
       using(var s = new MemoryStream())
       using(var w = new BinaryWriter(s))
       {
          w.Write(firstBool);
          w.Write(firstFloat);
          ...
          return s.ToArray();
       }
    }
    
    public void Deserialize(byte[] bytes)
    {
       using(var s = new MemoryStream(bytes))
       using(var r = new BinaryReader(s))
       {
          firstBool = r.ReadBool();
          firstFload = r.ReadFloat();
          ...
       }
    }
    
  • 使用

  • BinaryWriter
    /
    BinaryReader
    的速度要快得多(大约7倍)。Protobuf更灵活,易于使用,非常流行,并且序列化为大约33%的字节。(当然,这些数字是数量级,取决于序列化的内容和方式)

    现在基本上,
    BinaryWriter
    将写入1+4+4+4=13字节。通过将值转换为bool、byte、byte、short,首先按您想要的方式舍入,将其压缩为5个字节。最后,如果您真的想要,很容易将bool与一个字节合并,得到4个字节


    我并不是真的不鼓励手动序列化。但就性能而言,它必须物有所值。代码很难读懂。直接对字节使用位掩码和二进制移位,但要尽可能简单。不要使用位数组。它速度慢,可读性差。

    这里有一个简单的打包/解包方法。但将浮点值转换为7/8位的精度不高

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    
    namespace ConsoleApplication1
    {
        class Program
        {
            static void Main(string[] args)
            {
                foreach (Data data in Data.input)
                {
                    Data.Print(data);
                    Data results = Data.Unpack(Data.Pack(data));
                    Data.Print(results);
                }
                Console.ReadLine();
            }
        }
        public class Data
        {
            public static List<Data> input = new List<Data>() {
                new Data() { firstBool = true, firstFloat = 0.2345F, secondFloat = 0.432F,   firstInt = 12},
                new Data() { firstBool = true, firstFloat = 0.3445F, secondFloat = 0.432F,   firstInt = 11},
                new Data() { firstBool = false, firstFloat = 0.2365F, secondFloat = 0.432F,   firstInt = 9},
                new Data() { firstBool = false, firstFloat = 0.545F, secondFloat = 0.432F,   firstInt = 8},
                new Data() { firstBool = true, firstFloat = 0.2367F, secondFloat = 0.432F,   firstInt = 7}
            };
    
    
            public bool firstBool { get; set; }
            public float firstFloat {get; set; } //(0.0 to 1.0)  
            public float secondFloat {get; set; } //(0.0 to 1.0)  
            public int firstInt { get; set; } //(0 to 10,000)
    
            public static byte[] Pack(Data data)
            {
                byte[] results = new byte[4];
    
                results[0] = (byte)((data.firstBool ? 0x80 : 0x00) | (byte)(data.firstFloat * 128));
                results[1] = (byte)(data.secondFloat * 256);
                results[2] = (byte)((data.firstInt >> 8) & 0xFF);
                results[3] = (byte)(data.firstInt & 0xFF);
    
                return results;
            }
            public static Data Unpack(byte[] data)
            {
                Data results = new Data();
    
                results.firstBool = ((data[0] & 0x80) == 0) ? false : true;
                results.firstFloat = ((float)(data[0] & 0x7F)) / 128.0F;
                results.secondFloat = (float)data[1] / 256.0F;
                results.firstInt = (data[2] << 8) | data[3];
    
                return results;
            }
            public static void Print(Data data)
            {
                Console.WriteLine("Bool : '{0}', 1st Float : '{1}', 2nd Float : '{2}', Int : '{3}'",
                    data.firstBool,
                    data.firstFloat,
                    data.secondFloat,
                    data.firstInt
                    );
    
    
            }
        }
    }
    
    使用系统;
    使用System.Collections.Generic;
    使用System.Linq;
    使用系统文本;
    命名空间控制台应用程序1
    {
    班级计划
    {
    静态void Main(字符串[]参数)
    {
    foreach(Data.input中的数据)
    {
    数据。打印(数据);
    数据结果=Data.Unpack(Data.Pack(Data));
    数据打印(结果);
    }
    Console.ReadLine();
    }
    }
    公共类数据
    {
    公共静态列表输入=新列表(){
    新数据(){firstBool=true,firstFloat=0.2345F,secondFloat=0.432F,firstInt=12},
    新数据(){firstBool=true,firstFloat=0.3445F,secondFloat=0.432F,firstInt=11},
    新数据(){firstBool=false,firstFloat=0.2365F,secondFloat=0.432F,firstInt=9},
    新数据(){firstBool=false,firstFloat=0.545F,secondFloat=0.432F,firstInt=8},
    新数据(){firstBool=true,firstFloat=0.2367F,secondFloat=0.432F,firstInt=7}
    };
    公共bool firstBool{get;set;}
    公共浮点firstFloat{get;set;}//(0.0到1.0)
    公共浮点secondFloat{get;set;}//(0.0到1.0)
    public int firstInt{get;set;}//(0到10000)
    公共静态字节[]包(数据)
    {
    字节[]结果=新字节[4];
    结果[0]=(字节)(data.firstBool?0x80:0x00)|(字节)(data.firstFloat*128));
    结果[1]=(字节)(data.secondFloat*256);
    结果[2]=(字节)((data.firstInt>>8)和0xFF);
    结果[3]=(字节)(data.firstInt和0xFF);
    返回结果;
    }
    公共静态数据解包(字节[]数据)
    {
    数据结果=新数据();
    results.firstBool=((数据[0]&0x80)==0)?false:true;
    results.firstFloat=((float)(数据[0]&0x7F))/128.0F;
    results.secondFloat=(float)数据[1]/256.0F;
    
    results.firstInt=(数据[2]您正在使它变得比您需要的困难得多。首先,使用BitConverter.Getbytes(D)将D放入int16,然后使用BitConverter.Getbytes(C)将C放入字节,这是有意义的,因为它将是9
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    
    namespace ConsoleApplication1
    {
        class Program
        {
            static void Main(string[] args)
            {
                foreach (Data data in Data.input)
                {
                    Data.Print(data);
                    Data results = Data.Unpack(Data.Pack(data));
                    Data.Print(results);
                }
                Console.ReadLine();
            }
        }
        public class Data
        {
            public static List<Data> input = new List<Data>() {
                new Data() { firstBool = true, firstFloat = 0.2345F, secondFloat = 0.432F,   firstInt = 12},
                new Data() { firstBool = true, firstFloat = 0.3445F, secondFloat = 0.432F,   firstInt = 11},
                new Data() { firstBool = false, firstFloat = 0.2365F, secondFloat = 0.432F,   firstInt = 9},
                new Data() { firstBool = false, firstFloat = 0.545F, secondFloat = 0.432F,   firstInt = 8},
                new Data() { firstBool = true, firstFloat = 0.2367F, secondFloat = 0.432F,   firstInt = 7}
            };
    
    
            public bool firstBool { get; set; }
            public float firstFloat {get; set; } //(0.0 to 1.0)  
            public float secondFloat {get; set; } //(0.0 to 1.0)  
            public int firstInt { get; set; } //(0 to 10,000)
    
            public static byte[] Pack(Data data)
            {
                byte[] results = new byte[4];
    
                results[0] = (byte)((data.firstBool ? 0x80 : 0x00) | (byte)(data.firstFloat * 128));
                results[1] = (byte)(data.secondFloat * 256);
                results[2] = (byte)((data.firstInt >> 8) & 0xFF);
                results[3] = (byte)(data.firstInt & 0xFF);
    
                return results;
            }
            public static Data Unpack(byte[] data)
            {
                Data results = new Data();
    
                results.firstBool = ((data[0] & 0x80) == 0) ? false : true;
                results.firstFloat = ((float)(data[0] & 0x7F)) / 128.0F;
                results.secondFloat = (float)data[1] / 256.0F;
                results.firstInt = (data[2] << 8) | data[3];
    
                return results;
            }
            public static void Print(Data data)
            {
                Console.WriteLine("Bool : '{0}', 1st Float : '{1}', 2nd Float : '{2}', Int : '{3}'",
                    data.firstBool,
                    data.firstFloat,
                    data.secondFloat,
                    data.firstInt
                    );
    
    
            }
        }
    }