联系方式
Java服务器开发群:66728073
游戏开发者高级群:398808948
Unity3d游戏开发:286114103

游戏长连接网络通信之消息编码(JSON篇)

 二维码 39
发表时间:2019-11-24 23:20


在程序开发的时候,我们使用的数据都是明文的,比如一个对象中有int,string等类型的字段数据,通过断点,我们可以看到当前这个字段的值。但是在网络传输过程中,传输的都是0,1编码的二进制数据,在程序中是以byte字节的形式存在的,所以在客户端使用Socket向服务器发送数据时,需要把对象中的数据转化为byte数组。这个过程就叫做消息编码,也叫消息序列化。


在游戏开发中,最常用的网络通信是基于TCP的协议实现的。它全称是传输控制协议(TCP,Transmission Control Protocol)是一种面向连接的、可靠的、基于字节流的传输层通信协议。什么叫基于字节流呢?这就像一个水管,在水管中流动的水就是数据流,一进数据进入水管之中,它就是有序的,进来是什么样,出来还是什么样。

这里面还有一个重要的概念,叫大小端:

  • 大端模式,是指数据的高字节保存在内存的低地址中,而数据的低字节保存在内存的高地址中,这样的存储模式有点儿类似于把数据当作字符串顺序处理:地址由小向大增加,而数据从高位往低位放;

  • 小端模式,是指数据的高字节保存在内存的高地址中,而数据的低字节保存在内存的低地址中,这种存储模式将地址的高低和数据位权有效地结合起来,高地址部分权值高,低地址部分权值低,和我们的逻辑方法一致。

比如整形十进制数字:305419896 ,转化为十六进制表示 : 0x12345678 。其中按着十六进制的话,每两位占8个字节。如图

在操作系统中,x86和一般的OS(如windows,FreeBSD,Linux)使用的是小端模式。但比如Mac OS是大端模式。但是在JVM中,默认是使用的大端模式。在网络上传输数据时,由于数据传输的两端对应不同的硬件平台,采用的存储字节顺序可能不一致。所以在TCP/IP协议规定了在网络上必须采用网络字节顺序,也就是大端模式。对于char型数据只占一个字节,无所谓大端和小端。而对于非char类型数据,必须在数据发送到网络上之前将其转换成大端模式。接收网络数据时按符合接受主机的环境接收。


基于以上的规则,客户端与服务器通信中,需要制定一个双方都理解明白的协议,只有这样,才能从TCP流中获取想要的数据。常见的协议如下所示:

根据协议的数据性质,可以把协议分成包头,和包体两部分,包头是固定的字段值,包体可以是任何数据,这些数据是游戏中的业务数据,所以说包本是变化的,它的类型不可确定,但是我们可以将程序中包体的数据转化为json格式的字符串,然后将字段串转化为byte[]。在Socket发送数据的时候,按照这个协议,把包体,包头都封装到一个固定长度的byte[]中。服务器再根据协议解析客户端发送的数据。


本示例的客户端是使用的Unity3d,使用的开发语言是C#,为了方便操作数据和字节之间的转换,参考Netty的ByteBuf类,实现了一个C#版本的ByteBuf类,代码如下所示:


using System;

using System.Collections.Generic;

using System.Linq;

using System.Net;

using System.Text;



   /// <summary>

   /// 这个类的实现,是参考netty的Bytebuf实现的。且使用的是大端通信

   /// </summary>

   public class ByteBuf

   {

       private const int CALCULATE_THRESHOLD = 1048576 * 4;

       private int readerIndex;

       private int writerIndex;

       private int markedReaderIndex;

       private int markedWriterIndex;

       private int capacity;

       private byte[] array;


       public ByteBuf(int capacity)

       {

           this.capacity = capacity;

           this.array = new byte[capacity];

       }

       public ByteBuf(byte[] array)

   {

       this.capacity = array.Length;

       this.array = array;

   }


       public int Capacity

       {

           get

           {

               return capacity;

           }

       }


       public ByteBuf Clear()

       {

           readerIndex = writerIndex = 0;

           return this;

       }


       public bool IsReadable()

       {

           return writerIndex > readerIndex;

       }


       public bool IsReadable(int numBytes)

       {

           return writerIndex - readerIndex >= numBytes;

       }


       public bool IsWriteable()

       {

           return this.capacity > writerIndex;

       }


       public bool IsWriteable(int numBytes)

       {

           return this.capacity - writerIndex >= numBytes;

       }


       public int ReadableBytes()

       {

           return writerIndex - readerIndex;

       }


       public int WriteableBytes()

       {

           return this.capacity - writerIndex;

       }


       public ByteBuf ResetReaderIndex()

       {

           this.readerIndex = markedReaderIndex;

           return this;

       }


       public ByteBuf ResetWriterIndex()

       {

           this.writerIndex = this.markedWriterIndex;

           return this;

       }


       public ByteBuf MarkReaderIndex()

       {

           this.markedReaderIndex = this.readerIndex;

           return this;

       }


       public ByteBuf MarkWriteIndex()

       {

           this.markedWriterIndex = this.writerIndex;

           return this;

       }


       public ByteBuf DiscardReadBytes()

       {

           if (readerIndex == 0)

           {

               return this;

           }

           if (readerIndex != writerIndex)

           {

               Array.Copy(this.array, this.readerIndex, this.array, 0, this.writerIndex - this.readerIndex);

               this.writerIndex = this.writerIndex - this.readerIndex;

               this.readerIndex = 0;


           }

           else

           {

               this.readerIndex = this.writerIndex = 0;

           }

           return this;

       }


       private void EnsureWriteable(int minWriteableBytes)

       {

           if (minWriteableBytes < 0)

           {

               throw new ArgumentException("minWriteableBytes 不能小于0");

           }


           if (minWriteableBytes <= WriteableBytes())

           {

               return;

           }

           int newCapacity = this.writerIndex + minWriteableBytes;

           //如果新的buf大小大于4M,则直接增加4M

           if (newCapacity > CALCULATE_THRESHOLD)

           {

               newCapacity = newCapacity / CALCULATE_THRESHOLD * CALCULATE_THRESHOLD;

           }

           else

           {

               //找到第一个大于等于newCapacity的2的n次方数

               //从64开始

               int temp = 64;

               while (temp < newCapacity)

               {

                   temp <<= 1;

               }

               newCapacity = temp;

           }

           this.NewCapacity(newCapacity);


       }


       public ByteBuf NewCapacity(int newCapacity)

       {

           int oldCapacity = this.capacity;

           byte[] oldArray = this.array;

           byte[] newArray = new byte[newCapacity];

           if (newCapacity > oldCapacity)

           {

               this.capacity = newCapacity;

               Array.Copy(oldArray, newArray, oldCapacity);

           }

           else if (newCapacity < oldCapacity)

           {

               if (readerIndex < newCapacity)

               {

                   //如果readerIndex小于newCapacity,说明还有字节未读取完,如果写的索引大于newCapacity,则最多只能读取到newCapacity了。

                   if (writerIndex > newCapacity)

                   {

                       this.writerIndex = newCapacity;

                   }

                   Array.Copy(oldArray, this.readerIndex, newArray, readerIndex, writerIndex - readerIndex);

               }

               else

               {

                   //如果读取索引大于等于新的buf容量,则说明无法读取,也无法写了,这样把读和写索引变成newCapacity即可。

                   this.readerIndex = newCapacity;

                   this.writerIndex = newCapacity;

               }


           }

           this.array = newArray;

           return this;

       }


       private void CheckReadableBytes0(int minimumReadableBytes)

       {

           if (readerIndex > writerIndex - minimumReadableBytes)

           {

               throw new IndexOutOfRangeException("读取越界,当前可读取字节不足" + minimumReadableBytes);

           }

       }

       private byte GetByte0(int index)

       {

           return array[index];

       }


       public byte ReadByte()

       {

           this.CheckReadableBytes0(1);

           int i = readerIndex;

           byte b = GetByte0(i);

           readerIndex = i + 1;

           return b;

       }


       public bool ReadBool()

       {

           return this.ReadByte() != 0;

       }


       public short ReadShort()

       {

           CheckReadableBytes0(2);

           short v = (short)(array[readerIndex] << 8 | array[readerIndex + 1] & 0xFF);

           readerIndex += 2;

           return v;

       }


       public int ReadInt()

       {

           CheckReadableBytes0(4);

           int v = (array[this.readerIndex] & 0xff) << 24 | (array[this.readerIndex + 1] & 0xff) << 16 |

               (array[this.readerIndex + 2] & 0xff) << 8 | array[this.readerIndex + 3] & 0xff;

           this.readerIndex += 4;

           return v;

       }

       public long ReadLong()

       {

           CheckReadableBytes0(8);

           long v = ((long)array[this.readerIndex] & 0xff) << 56 |

               ((long)array[this.readerIndex + 1] & 0xff) << 48 |

               ((long)array[this.readerIndex + 2] & 0xff) << 40 |

               ((long)array[this.readerIndex + 3] & 0xff) << 32 |

               ((long)array[this.readerIndex + 4] & 0xff) << 24 |

               ((long)array[this.readerIndex + 5] & 0xff) << 16 |

               ((long)array[this.readerIndex + 6] & 0xff) << 8 |

               (long)array[this.readerIndex + 7] & 0xff;

           this.readerIndex += 8;

           return v;

       }


       public ByteBuf ReadBytes(byte[] dst, int dstIndex, int length)

       {

           Array.Copy(array, readerIndex, dst, dstIndex, length);

           this.readerIndex += length;

           return this;

       }


       public ByteBuf ReadBytes(byte[] dst)

       {

           ReadBytes(dst, 0, dst.Length);

           return this;

       }


       public ByteBuf WriteBool(bool value)

       {

           this.WriteByte(value ? 1 : 0);

           return this;

       }

       public ByteBuf WriteByte(int value)

       {

           this.EnsureWriteable(1);

           this.array[this.writerIndex] = (byte)value;

           this.writerIndex++;

           return this;

       }


       public ByteBuf WriteShort(int value)

       {

           EnsureWriteable(2);

           this.array[this.writerIndex] = (byte)(value >> 8);

           this.array[this.writerIndex + 1] = (byte)value;

           this.writerIndex += 2;

           return this;

       }


       public ByteBuf WriteInt(int value)

       {

           EnsureWriteable(4);

           this.array[this.writerIndex] = (byte)(value >> 24);

           this.array[this.writerIndex + 1] = (byte)(value >> 16);

           this.array[this.writerIndex + 2] = (byte)(value >> 8);

           this.array[this.writerIndex + 3] = (byte)value;

           this.writerIndex += 4;

           return this;

       }


       public ByteBuf WriteLong(long value)

       {

           EnsureWriteable(8);

           this.array[this.writerIndex] = (byte)(value >> 56);

           this.array[this.writerIndex + 1] = (byte)(value >> 48);

           this.array[this.writerIndex + 2] = (byte)(value >> 40);

           this.array[this.writerIndex + 3] = (byte)(value >> 32);

           this.array[this.writerIndex + 4] = (byte)(value >> 24);

           this.array[this.writerIndex + 5] = (byte)(value >> 16);

           this.array[this.writerIndex + 6] = (byte)(value >> 8);

           this.array[this.writerIndex + 7] = (byte)value;

           this.writerIndex += 8;

           return this;


       }


       public ByteBuf WriteBytes(byte[] src, int srcIndex, int length)

       {

           EnsureWriteable(length);

           if (this.IsOutBounds(length))

           {

               throw new IndexOutOfRangeException("WriteBytes时,越界了");

           }

           Array.Copy(src, srcIndex, this.array, this.writerIndex, length);

           this.writerIndex += length;

           return this;

       }


       public ByteBuf WriteBytes(byte[] src)

       {

           this.WriteBytes(src, 0, src.Length);

           return this;

       }

       private bool IsOutBounds(int length)

       {

           return (this.writerIndex | length | (this.writerIndex + length) | (this.capacity - (this.writerIndex + length))) < 0;

       }


       public byte[] IntToBytes(int value)

       {

           //把本地字节值转换成网络字节值

           value = IPAddress.HostToNetworkOrder(value);

           byte[] bytes = BitConverter.GetBytes(value);

           return bytes;

       }


       public int BytesToInt(byte[] bytes)

       {


           int value = BitConverter.ToInt32(bytes, 0);

           //把网络字节值转化为本地字节值

           value = IPAddress.NetworkToHostOrder(value);


           return value;

       }


       public byte[] ToArray()

       {

           return array;

       }


   }



客户端的编码实现如下代码所示:

public static byte[] EncodeMessage(MessageRequest request)

{

int totalSize = MessageHeader.HeaderLength;

byte[] bodyBytes = null;

if(request.Body != null)

{

string body = JsonConvert.SerializeObject(request.Body);   //将消息体转化为json

bodyBytes = System.Text.Encoding.UTF8.GetBytes(body);

totalSize += bodyBytes.Length;

}

ByteBuf byteBuf = new ByteBuf(totalSize);

MessageHeader header = request.Header;

byteBuf.WriteInt(totalSize);

byteBuf.WriteInt(header.SeqId);

byteBuf.WriteInt(header.MessageId);

byteBuf.WriteByte(header.MessageType);

byteBuf.WriteLong(header.ClientSendTime);

if(bodyBytes != null)

{

byteBuf.WriteBytes(bodyBytes);

}

return byteBuf.ToArray();

}



MessageRequest类是在程序中使用到的类,它包括了协议中所有的字段信息,详细可以参考源码实现:http://www.xinyues.com/h-nd-195.html#_np=2_627





文章分类: Unity3d
分享到: