ByteArray(难点)
ByteArray
ByteArray封装了和一个客户端进行通信时的byte数据处理操作集合。
ByteArray循环利用初始的byte数组,初始值为1024byte。
当写入的数据大于1024byte,自动扩容。
ByteArray属于常用的工具类,在实际使用中根据情况可以使用ObjectPoolManager进行优化。这里先略
在Net文件夹中添加ByteArray类
1 | namespace SimpleServer.Net |
MoveBytes是我们循环利用byte数组的核心方法。其被CheckAndMoveBytes方法调用。在
CheckAndMoveBytes方法中,先判断if (Length < 8),其中Length表示目前读取的数据大小,这个判断有两个意义,一个是目前需要读取的数据非常小,那么我们将数组内的数据重置到开头,另一个是目前byte数组前面的已经没有用的缓存满了,我们将数据重置到开头。为什么是8个字节呢,因为每次发送来的报文头部一般都有一个int32类型的数据,这个数据就占了4个字节,一般包含了数据协议以及分包粘包的信息,报文头部后面根据的就是具体的数据信息,我们按照最小的计算,也是一个int32,所以一共占用8个字节。所以我们必须至少要留出8个字节的空间。一旦不满就要重置数据。
在
MoveBytes方法中使用Array.Copy(Bytes, ReadIdx, Bytes, 0, Length);来实现重置byte数组内的数据 重置到开头。第一个参数是需要复制的数组,第二个参数是需要复制的数组开始的索引,第三个参数是目标数组,第四个参数是目标数组开始的索引,第五个参数是需要复制的长度。这里Byte数组自己复制给自己的开头,实现重置的效果。在
ReSize(int size)方法中使用while(n < size) { n *= 2; }来实现扩容,byte数组按照1024的倍数进行扩容,生成一个newBytes数组,然后我们将Bytes老数组中的数据拷贝到newBytes前面。用newBytes替换老的Bytes数组。注意这个方法仅仅完成了扩容操作而已,newBytes面还是老的数据。
只看ByteArray类理解还是不够全面的,我们需要在它被调用的地方进行分析理解。
ByteArray的调用
首先修改ClientSocket,添加ReadBuff属性
1 | public ByteArray ReadBuff = new ByteArray(); |
也就是说,每一个连接到服务端的客户端,都维护一个读取用的ByteArray。
然后回到ServerSocket,我们完善一下ReadClient函数并添加OnReceiveData函数
1 | void ReadClient(Socket client) |
在
ReadClient函数中,我们先获取到对应SocketClient的ByteArray,声明为readBuff;首先要经过一个判断
if (readBuff.Remain <= 0),我们先看ReadClient函数最后,我们调用了readBuff.CheckAndMoveBytes();在前面说过,如果当前byte数组缓存的数据非常大(通常是个分包),就不会触发CheckAndMoveBytes()操作,所以我们先判断if (readBuff.Remain <= 0),因为剩余空间没了意味着我们需要扩容了。需要注意到的是,第一次接收客户端信息是不需要扩容的,只有发现客户端消息太大缓存不够用了才会进行扩容操作。
在进行扩容之前,会先进行一次byte数组解析和重置操作,这是为了readBuff.Remain正巧等于0时,将数据移动到index=0的位置(存疑)
这个扩容的while函数的意思是如果我们是第一次扩容,就按照byte数组默认大小的2倍进行扩容,如果第一次扩容还不够,就按照扩容后的byte数组大小的2倍
我们使用
client.Receive(readBuff.Bytes, readBuff.WriteIdx, readBuff.Remain, SocketFlags.None);来获取客户端发来的信息。它返回的count表示接受的byte数。注意第三个参数表示向readBuff写入的长度,我们把Remain全写进去。我们可以看到当客户端发来的消息很大,会把readBuff写满,第一次写入时,count会变成1024。当count = 0时,表示客户端已经断开。执行断开的操作。
将
readBuff.WriteIdx += count;这意味着我们空出来了需要读取的内容,此时readBuff.Length就有了意义。可以调用OnReceiveData(clientSocket);了。
OnReceiveData是另一个重点难点函数,它涉及到了分包粘包的处理,以及**readBuff.ReadIdx的移动**,对于我们进一步理解ByteArray有更大的意义,这里先略过。具体见后面“服务器收发数据”一节


