TBuffer
请大家关注我的微博:@NormanLin_BadPixel坏像素
3.0已经把TBuffer整合到CircularBuffer。不过建议大家继续看下去吧,毕竟作者的思路大致是没变化的,这篇我可能讲的比较详细。(毕竟我花心思写了T_T)
大致浏览了一下,难道这里用到了类似这篇文章提到的技术?(需要安全上网工具)我只是随便一猜,大家还是继续看代码吧。
public const int ChunkSize = 8192;
这里是定义了一个常量,决定了每个缓存区块的大小为8KB。为什么是8KB呢?可能是8KB的数据传输比较快吧。
private readonly LinkedList<byte[]> bufferList = new LinkedList<byte[]>();
哇,看到这里我才发现自己的知识量是多么的浅薄。我竟然连LinkedList都不知道。羞于见人。赶紧去学习一下。
这真的是一个很方便的类型。这里我忍不住想要为大家介绍一下。我们平常都在使用List<>。当我们在遍历List的时候,我们遍历的是里面的每一个值。而当我们在遍历LinkedList<>的时候,我们遍历的是它的节点LinkedListNode<>。这个节点可就神奇了,它就像树里面的节点,储存了与它相连的数据,不过它储存的不是左子树和右子树,而是它的前节点和后节点。储存了这些信息有什么用呢?这样方便我们插入啊!普通的链表List,当我们插入一个新数据的时候,我们需要把插入的地方后面的数据全部都往后移,而这个LinkedList只要修改插入节点的前节点的后节点,插入节点的前节点就好了。(有点绕哈哈,不过很好理解,无非是一个顺序问题。)而且,这个可以很方便的删除第一个节点或者最后一个节点,用来做有限撤销操作的储存非常合适。
看到这,我发现我其实对这个类型并不是很陌生,在大学里数据结构课程上老师就有对这种结构进行过描述。只不过我不知道C#里面就是LinkedList。(其实就是没记起来:_))
复习了LinkedList<>(厚颜无耻),我们继续往下看。
public int LastIndex { get; set; }
public int FirstIndex { get; set; }
这俩个变量我们现在还不知道他们是做啥的,大概是对这个缓冲区有效信息的范围下标的储存吧。因为我们的数据不一定刚好就填充满8KB的区块,可能只填充了一半,这个时候,就需要标识有效数据的下标了。
public TBuffer()
{
this.bufferList.AddLast(new byte[ChunkSize]);
}
OK,我们在构造函数里就先给了双向链表一个值,虽然是空的数组,但保证bufferList不为空嘛。
public int Count{get;}
这个Count属性的获取器代码很容易看懂。
后面的AddLast、RemoveFirst、byte[] First、byte[] Last也一样,不需要我讲的吧。我们来看看重要的收发数据。我们先来看看发送数据,这里的发送数据不是说向远端发送数据,而是往这个缓冲区发送数据。
public void SendTo(byte[] buffer)
{
int alreadyCopyCount = 0;
while (alreadyCopyCount < buffer.Length)
{
if (this.LastIndex == ChunkSize)
{
this.bufferList.AddLast(new byte[ChunkSize]);
this.LastIndex = 0;
}
int n = buffer.Length - alreadyCopyCount;
if (ChunkSize - this.LastIndex > n)
{
Array.Copy(buffer, alreadyCopyCount, this.bufferList.Last.Value, this.LastIndex, n);
this.LastIndex += buffer.Length - alreadyCopyCount;
alreadyCopyCount += n;
}
else
{
Array.Copy(buffer, alreadyCopyCount, this.bufferList.Last.Value, this.LastIndex, ChunkSize - this.LastIndex);
alreadyCopyCount += ChunkSize - this.LastIndex;
this.LastIndex = ChunkSize;
}
}
}
首先,alreadyCopyCount这个变量记录的是已经往缓冲区里面存入的数据量。while循环我就不多说了。
if (this.LastIndex == ChunkSize),这种情况是到了缓冲区里面的小区块满了的时候,需要对缓冲区进行扩增,并把LastIndex归0,因为是开始往新的区块里存了。
n代表剩余的数据量,如果剩余的数据量小于区块剩余的量,则把buffer的[alreadyCopyCount,alreadyCopyCount + n)区间内的数据拷贝到最新的区块也就是bufferList的最后一个节点的值LastIndex下标之后。这是Array.Copy的方法,大家了解一下就理解了。此后,对LastIndex和alreadyCopyCount的值进行更新。
如果剩余的数据量还是很大,超过了每个区块的最大8KB,那就要对数据进行分区块处理,也就是else里面的代码做的处理。
好的,我们继续来看接收数据,当然,这里的接收数据是从缓冲区中接收也就是读取数据。
public void RecvFrom(byte[] buffer)
{
if (this.Count < buffer.Length || buffer.Length == 0)
{
throw new Exception($"bufferList size < n, bufferList: {this.Count} buffer length: {buffer.Length}");
}
int alreadyCopyCount = 0;
while (alreadyCopyCount < buffer.Length)
{
int n = buffer.Length - alreadyCopyCount;
if (ChunkSize - this.FirstIndex > n)
{
Array.Copy(this.bufferList.First.Value, this.FirstIndex, buffer, alreadyCopyCount, n);
this.FirstIndex += n;
alreadyCopyCount += n;
}
else
{
Array.Copy(this.bufferList.First.Value, this.FirstIndex, buffer, alreadyCopyCount, ChunkSize - this.FirstIndex);
alreadyCopyCount += ChunkSize - this.FirstIndex;
this.FirstIndex = 0;
this.bufferList.RemoveFirst();
}
}
}
经过了前面的解释,这里无非就是对上面存的操作进行一次逆操作。不过需要注意的是,用来存读取得到的数据的byte[] 长度不能为0也不能超过缓冲区中原有的数据长度。这样可能是为了避免出现空的字节导致后续反序列化出现问题吧。