Socket(三)製作基本包結構
數據都是以流的形式傳遞的,物理上,信號的傳遞是一種脈衝波的形式。
從開發層面,我們假定最小單位為1byte。
傳遞數據就跟寄送包裹一樣。要路過很多關卡。
比如你要郵寄10噸的貨物,路過有些路口,只限載重5噸的車過,那你的10噸貨物就要分成2個5噸的運過去。(分包)
如果你要郵寄1噸的貨物,為了快速傳遞,那麼可能把1噸和別的4噸合到一輛車上運過去。(粘包)
這種情況,不論任何都會存在也不可避免。
比如你發了一條1024的數據,對方收到的可能是2個512。或者一個2048。那麼如何解決呢。
快遞運送中都有包裹來區分彼此。發送數據也需要一個包來區分彼此。一個標準的包結構如下:
包頭|類型|內容……
包頭通常為uint(4byte)或者ushort(2byte)根據需求來指定。
類型是為了我們對數據進行區分。通常為int類型的枚舉。包含一些錯誤提示等信息。
後面接著我們需要傳遞的具體內容。
以下是我個人的數據封包類:
/// <summary> /// 網路消息類型 /// </summary> public enum NetMessageType : int { /// <summary> /// 警告消息 /// </summary> Warning, /// <summary> /// 錯誤消息 /// </summary> Error, /// <summary> /// 數據 /// </summary> Data, /// <summary> /// 心跳包 /// </summary> Heartbeat, } /// <summary> /// 數據包 /// </summary> public struct NetData { public IPEndPoint RemoteIP { get; set; } public NetMessageType MessageType { get; set; } private byte[] data; public byte[] Data { get { return data; } } public int Index { get; private set; } public NetData(NetMessageType t) { RemoteIP = null; MessageType = t; data = new byte[0]; Index = 0; } public void Reset() { Index = 0; } //是否有可讀數據 public bool IsRead() { return Index < Data.Length; } //轉換成byte[],增加包頭和數據類型 internal byte[] ToNetData() { byte[] d = new byte[data.Length + 8]; Array.Copy(BitConverter.GetBytes((uint)d.Length), 0, d, 0, 4); Array.Copy(BitConverter.GetBytes((int)MessageType), 0, d, 4, 4); Array.Copy(data, 0, d, 8, data.Length); return d; } //讀取數據,並返回數據包類型 public static NetData ReadBuffer(byte[] d) { int t = BitConverter.ToInt32(d, 0); NetData n = new NetData((NetMessageType)t); n.data = new byte[d.Length - 4]; Array.Copy(d, 4, n.data, 0, n.data.Length); return n; } private void WriteBuffer(params byte[] b) { int l = data.Length; Array.Resize(ref data, data.Length + b.Length); Array.Copy(b, 0, data, l, b.Length); } private byte[] ReadBuffer(uint len) { byte[] _b = PeekBuffer(len); Index += _b.Length; return _b; } private byte[] PeekBuffer(uint len) { byte[] _b = new byte[len]; Array.Copy(data, Index, _b, 0, len); return _b; } public void Write(byte[] val) { WriteBuffer(BitConverter.GetBytes((uint)val.Length)); WriteBuffer(val); } public byte[] PeekBytes() { uint l = BitConverter.ToUInt32(PeekBuffer(4), 0); byte[] a = PeekBuffer(l + 4); byte[] b = new byte[l]; Array.Copy(a, 4, b, 0, l); return b; } public byte[] ReadBytes() { uint l = BitConverter.ToUInt32(ReadBuffer(4), 0); return ReadBuffer(l); } public void Write(int val) { WriteBuffer(BitConverter.GetBytes(val)); } public int PeekInt() { return BitConverter.ToInt32(PeekBuffer(4), 0); } public int ReadInt() { return BitConverter.ToInt32(ReadBuffer(4), 0); } public void Write(bool val) { WriteBuffer(BitConverter.GetBytes(val)); } public bool PeekBool() { return BitConverter.ToBoolean(PeekBuffer(1), 0); } public bool ReadBool() { return BitConverter.ToBoolean(ReadBuffer(1), 0); } public void Write(float val) { WriteBuffer(BitConverter.GetBytes(val)); } public float PeekFloat() { return BitConverter.ToSingle(PeekBuffer(4), 0); } public float ReadFloat() { return BitConverter.ToSingle(ReadBuffer(4), 0); } public void Write(float[] val) { byte[] d = new byte[val.Length * 4]; for (int i = 0; i < val.Length; i++) { byte[] _d = BitConverter.GetBytes(val[i]); int _i = i * 4; d[_i] = _d[0]; d[_i + 1] = _d[1]; d[_i + 2] = _d[2]; d[_i + 3] = _d[3]; } Write(d); } public float[] PeekFloats() { byte[] d = PeekBytes(); float[] f = new float[d.Length / 4]; for (int i = 0; i < f.Length; i++) { f[i] = BitConverter.ToSingle(d, i * 4); } return f; } public float[] ReadFloats() { byte[] d = ReadBytes(); float[] f = new float[d.Length / 4]; for (int i = 0; i < f.Length; i++) { f[i] = BitConverter.ToSingle(d, i * 4); } return f; } public void Write(Vector3 val) { WriteBuffer(BitConverter.GetBytes(val.x)); WriteBuffer(BitConverter.GetBytes(val.y)); WriteBuffer(BitConverter.GetBytes(val.z)); } public Vector3 PeekVector3() { byte[] b = PeekBuffer(12); return new Vector3(BitConverter.ToSingle(b, 0), BitConverter.ToSingle(b, 4), BitConverter.ToSingle(b, 8)); } public Vector3 ReadVector3() { return new Vector3(ReadFloat(), ReadFloat(), ReadFloat()); } public void Write(Quaternion val) { WriteBuffer(BitConverter.GetBytes(val.x)); WriteBuffer(BitConverter.GetBytes(val.y)); WriteBuffer(BitConverter.GetBytes(val.z)); WriteBuffer(BitConverter.GetBytes(val.w)); } public Quaternion PeekQuaternion() { byte[] b = PeekBuffer(12); return new Quaternion(BitConverter.ToSingle(b, 0), BitConverter.ToSingle(b, 4), BitConverter.ToSingle(b, 8), BitConverter.ToSingle(b, 12)); } public Quaternion ReadQuaternion() { return new Quaternion(ReadFloat(), ReadFloat(), ReadFloat(), ReadFloat()); } public void Write(Color val) { byte[] col = new byte[] { (byte)(val.r * 255), (byte)(val.g * 255), (byte)(val.b * 255), (byte)(val.a * 255) }; WriteBuffer(col); } public Color PeekColor() { byte[] b = PeekBuffer(4); return new Color(b[0] / 255f, b[1] / 255f, b[2] / 255f, b[3] / 255f); } public Color ReadColor() { byte[] b = ReadBuffer(4); return new Color(b[0] / 255f, b[1] / 255f, b[2] / 255f, b[3] / 255f); } public void Write(byte b) { WriteBuffer(b); } public byte PeekByte() { return PeekBuffer(1)[0]; } public byte ReadByte() { return ReadBuffer(1)[0]; } public void Write(string val) { byte[] b = Encoding.UTF8.GetBytes(val); WriteBuffer(BitConverter.GetBytes((ushort)b.Length)); WriteBuffer(b); } public string PeekString() { ushort l = BitConverter.ToUInt16(PeekBuffer(2), 0); return Encoding.UTF8.GetString(PeekBuffer((uint)l + 2), 2, l); } public string ReadString() { ushort l = BitConverter.ToUInt16(ReadBuffer(2), 0); return Encoding.UTF8.GetString(ReadBuffer(l), 0, l); } }
上面的代碼,包括了一些我常傳遞的數據類型,需要的人可以根據自己的需要增刪。
這樣當接收端收到數據後,就可以先取固定包頭來計算整個包的長度。如果接收的數據大於包長度,則取對應長度的信息出來。如果接收的數據小於包長度,則存起來等待長度滿足再取出。
一下我個人封裝的數據緩存類
/// <summary> /// 數據包緩存 /// </summary> protected class DataBuffer { //總緩存 private byte[] data = new byte[0]; //總長度 public uint DataLength { get { return (uint)data.Length; } } //當前數據包長度 private uint readLength = 0; public uint Readlength { get { if (readLength == 0) readLength = BitConverter.ToUInt32(data, 0); return readLength; } } /// <summary> /// 是否能讀 /// </summary> /// <returns></returns> public bool IsRead() { return DataLength > Readlength; } /// <summary> /// 寫入數據 /// </summary> /// <param name="d">數據</param> public void Write(byte[] d) { int l = data.Length; Array.Resize(ref data, data.Length + d.Length); Array.Copy(d, 0, data, l, d.Length); } /// <summary> /// 讀取數據 /// </summary> /// <returns>數據,如果長度不足返回空</returns> public byte[] Read() { if (IsRead()) { byte[] b = new byte[Readlength - 4]; Array.Copy(data, 4, b, 0, Readlength - 4); uint l = DataLength - Readlength; if (l > 0) { byte[] a = new byte[l]; Array.Copy(data, Readlength, a, 0, a.Length); data = a; } else { data = new byte[0]; } readLength = 0; return b; } return null; } } }
該類會將接收到的數據保存起來,按照需求取出。
這樣我們就能完整的交換網路數據了。所有的網路數據都是在這樣條件下交換的,只不過不同的平台有不同的協議來約束數據。
如果數據包限制了大小,可以自己進行分包傳遞。
後面我們來講如何tcp/udp按照不同的需求、混搭網路交互。
推薦閱讀: