代码之家  ›  专栏  ›  技术社区  ›  StewVanB

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

  •  2
  • StewVanB  · 技术社区  · 7 年前

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

    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 to 1.0) int(0 to 128) 因为我可以在另一端重建它们,而且精确度不是特别重要。

    我一直在尝试使用BitArray和byte[]将数据转换为二进制结构,并将其转换为二进制结构。

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

    public byte[] Serialize() {
        byte[] firstFloatBytes = BitConverter.GetBytes(Mathf.FloorToInt(firstFloat * 128)); //Convert the float to int from (0 to 128)
    
        byte[] secondFloatBytes = BitConverter.GetBytes(Mathf.FloorToInt(secondFloat * 128)); //Convert the float to int from (0 to 128)
    
        byte[] firstIntData = BitConverter.GetBytes(Mathf.FloorToInt(firstInt)); // Get the bytes for the int
    
        BitArray data = new BitArray(32); // create the size 32 bitarray to hold all the data
    
        int i = 0; // create the index value
    
        data[i] = firstBool; // set the 0 bit
    
        BitArray ffBits = new BitArray(firstFloatBytes);
        for(i = 1; i < 8; i++) {
            data[i] = ffBits[i-1]; // Set bits 1 to 7
        }
    
        BitArray sfBits = new BitArray(secondFloatBytes);
        for(i = 8; i < 15; i++) {
            data[i] = sfBits[i-8]; // Set bits 8 to 14
        }
    
        BitArray fiBits = new BitArray(firstIntData);
        for(i = 15; i < 29; i++) {
            data[i] = fiBits[i-15]; // Set bits 15 to 28
        }
        byte[] output = new byte[4]; // create a byte[] to hold the output
        data.CopyTo(output,0); // Copy the bits to the byte[]
        return output;
    }
    

    从这个结构中获取信息要比将信息获取到这个表单中复杂得多。我想我可能可以使用位运算符和位掩码来训练一些东西。

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

    2 回复  |  直到 7 年前
        1
  •  2
  •   Benoit Sanchez    7 年前

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

    1. 使用 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();
            ...
         }
      }
      
    2. 使用 protobuf.net

    二进制编写器 / 二进制读取器 速度快得多(大约7倍)。Protobuf更加灵活、易于使用、非常流行,并且可以将字节序列化为大约少33%。(当然,这些数字是数量级,取决于序列化的内容和方式)。

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

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

        2
  •  1
  •   jdweng    7 年前

    这里有一个简单的打包/解包方法。但将浮点值转换为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
                    );
    
    
            }
        }
    }