目标
soecet收发数据封包和解包
背景
一般都有固定长度、固定后缀等做法,但都花里胡哨,有各自的问题。
本文采用数据长度+ 数据buffer模式,其中最简单的是用4字节int来表示长度。但这还不够,很多情况下长度可能一个byte就够用了,浪费了3个字节,本文采用动态字节数来存储长度,至于数据buffer使用protobuf来编码和解码就完事了
详细方案
- 首先读取一个字节,也就8个bit,a b c d e f g h
- 判断a是否为0,如果是,则长度用1个字节0xxx xxxx表示,那么1个字节能表示的范围是[0, 128{0x80} ),
- a=1,继续判断b是否为0,如果是,长度用2个字节10xx xxxx xxxx xxxx表示,2个字节表示的范围是[128, 16k{0x4000} )
- a=1,b=1,继续判断c是否为0,如果是,长度用3个字节表示110x xxxx xxxx xxxx xxxx,3个字节表示的范围是[16k, 2M{0x200000} )
- a=1,b=1,c=1,此时长度用4个字节表示111x xxxx xxxx xxxx xxxx xxxx xxxx xxxx,4个字节表示的范围是[2M, 512M{0x20000000} )
首字节类型 | 需要字节数 | 范围 | 16进制最大值(不包含) |
---|---|---|---|
0xxx xxxx | 1 | [0, 128) | 0x80 |
10xx xxxx | 2 | [128, 16k) | 0x4000 |
110x xxxx | 3 | [16k, 2M) | 0x200000 |
111x xxxx | 4 | [2M, 512M) | 0x20000000 |
其实绝大部分包的大小是128字节以内,那么将会用1个字节来表示长度,相对原始方案,节省了3个字节
代码
- 长度编码
/// <summary>
/// 设置一个byte中各个bit的位值
/// </summary>
/// <param name="_word">目标,要设置的byte</param>
/// <param name="value">值,为目标位设置的值</param>
/// <returns></returns>
public static byte SetBits(byte _word, byte value)
{
return (byte)(_word & ~value | value);
}
/// <summary>
/// 0xxx xxxx [0-128{0x80}) 1个字节
/// 10xx xxxx [128, 16k{0x4000}) 2个字节
/// 110x xxxx [16k, 2M{0x200000}) 3个字节
/// 111x xxxx [2M, 512M{0x20000000}) 4个字节
/// </summary>
/// <param name="value"></param>
/// <returns></returns>
public static byte[] EncodeInt(int value)
{
if (value < 0)
{
throw new ArgumentException(string.Format("invalid data length < 0, cur_length={0}", value));
}
else if (value < 0x80)
{
return new byte[] { (byte)value };
}
else if (value < 0x4000)
{
return new byte[] { SetBits((byte)(value >> 8), 0x80), (byte)value };
}
else if (value < 0x200000)
{
return new byte[] { SetBits((byte)(value >> 16), 0xC0), (byte)(value >> 8), (byte)value };
}
else if (value < 0x20000000)
{
return new byte[] { SetBits((byte)(value >> 24), 0xE0), (byte)(value >> 16), (byte)(value >> 8), (byte)(value) };
}
else
{
throw new ArgumentException(string.Format("invalid data length, max_length({0}), cur_length({1})", 0x20000000, value));
}
}
- 长度解码
public static int DecodeInt(byte[] data, int offset, int count, out int lengthByteCount)
{
if ((data[offset] & 0x80) == 0)//0xxx xxxx
{
lengthByteCount = 1;
if (count < lengthByteCount) return -1;
return (int)data[offset];
}
else if ((data[offset] & 0x40) == 0)//10xx xxxx
{
lengthByteCount = 2;
if (count < lengthByteCount) return -1;
return (data[offset] & 0x3F) << 8 | data[offset + 1];
}
else if ((data[offset] & 0x20) == 0)//110xx xxxx
{
lengthByteCount = 3;
if (count < lengthByteCount) return -1;
return (data[offset] & 0x1F) << 16 | data[offset + 1] << 8 | data[offset + 2];
}
else//111x xxxx
{
lengthByteCount = 4;
if (count < lengthByteCount) return -1;
return (data[offset] & 0x1F) << 24 | data[offset + 1] << 16 | data[offset + 2] << 8 | data[offset + 3];
}
}
}