KChannel学习笔记
请大家关注我的微博:@NormanLin_BadPixel坏像素
有我们之前学习TChannel的经验,这篇的学习应该不会很困难。(先立个Flag)
public struct WaitSendBuffer
{
public byte[] Bytes;
public int Index;
public int Length;
public WaitSendBuffer(byte[] bytes, int index, int length)
{
this.Bytes = bytes;
this.Index = index;
this.Length = length;
}
}
嗯?“等待发送的缓冲区”。我们记住这个数据结构了。我猜有点东西应该是Index,你呢?
private Kcp kcp;
我好奇的进入了KCP这个类想一探究竟,结果我秒退了。咯咯,我还是不要了解它是怎么实现的了,还是看怎么用吧。T_T。有兴趣的可以去了解一下:skywind3000/kcp
private readonly CircularBuffer recvBuffer = new CircularBuffer(8192);
好的,我们分P啦。 ET—Circularbuffer学习笔记
private readonly Queue<WaitSendBuffer> sendBuffer = new Queue<WaitSendBuffer>();
private readonly PacketParser parser;
这里,KChannel跟TChannel又有不一样了,这里的sendBuffer是WaitSendBuffer的队列。具体为什么要这么设计,可能是KCP跟TCP不一样,这些我们后面会具体讲。
PacketParser就是包解析工具。
TChannel的构造方法大家自己看就好。
public void HandleRecv(byte[] date, uint timeNow)
{
this.kcp.Input(date);
// 加入update队列
this.GetService().AddToUpdate(this.Id);
while (true)
{
int n = kcp.PeekSize();
if (n == 0)
{
this.OnError(this, SocketError.NetworkReset);
return;
}
int count = this.kcp.Recv(this.cacheBytes);
if (count <= 0)
{
return;
}
lastRecvTime = timeNow;
// 收到的数据放入缓冲区
byte[] sizeBuffer = BitConverter.GetBytes((ushort)count);
this.recvBuffer.Write(sizeBuffer, 0, sizeBuffer.Length);
this.recvBuffer.Write(cacheBytes, 0, count);
if (this.recvTcs != null)
{
bool isOK = this.parser.Parse();
if (isOK)
{
Packet pkt = this.parser.GetPacket();
var tcs = this.recvTcs;
this.recvTcs = null;
tcs.SetResult(pkt);
}
}
}
}
这段是大家应该了解的。当我们收到UDP传来的消息后,是怎么经过KCP处理的。data就是我们从UDP获得的数据,我们把它传入这个Channel的kcp中,让kcp接管data,kcp具体会对data进行怎样的处理,有兴趣的同学可以去看kcp的源码。我们现在来看看这个脚本是怎么使用kcp的。
首先,它会在父层级的KService处注册该Channel的Id,告诉KService,我开始工作了,之后有数据更新的时候也告诉我。具体的内容我们在KService里面会讲。
之后,我们便可以从kcp当中获取到安全的数据,通过kcp.Recv(Byte[]);。等我们取到这个数据后,我们需要把这段数据按照我们定好的结构存入缓冲区,也就是2个字节的长度+具体数据。
当然,如果有注册接收到消息的回调,我们也在此时进行回调。这里需要注意的是,因为接收到的消息不一定是一个完整的数据包,所以每次在进行回调前,会先判断是否已经得到一个完整的数据包。
*这里我们不妨大胆猜测。我们从UDP获取到的数据是不安全的,我们获取到后,把它传入kcp,kcp内部对其进行了处理,我们再从kcp中取出的时候,就是安全的数据了。举个例子。服务器传过来一堆消息。*
**P1-D1,P1-D2,P1-D3,P2-D1,P2-D2,P3-D1,P4-D1,P4-D2.**
*这里的P表示包,D代表数据块。P1-D1 = 数据包1的区块1。但是因为我们是用UDP协议,很可能我们获取到数据的时候会出现包乱序或者丢包的情况。比如我们获取到下面这样的:*
**P1-D1,P1-D3,P1-D2,P2-D1,P2-D2,P3-D1,P4-D2.**
*我们看到, P1-D3跟P1-D2顺序出错了。P4-D1丢包了。然后我们把它丢到kcp当中,当然这些数据都应该是符合kcp的包体结构。kcp内部会帮我们把乱序的包重新排序,丢失的包尝试重新拉取。等我们从kcp当中获取数据的时候,就又是我们安全完整的数据了。*
以上内容纯属猜测。
private void KcpSend(byte[] buffers, int index, int length)
{
this.kcp.Send(buffers, index, length);
this.GetService().AddToUpdate(this.Id);
}
public override void Send(byte[] buffer, int index, int length)
{
if (isConnected)
{
this.KcpSend(buffer, index, length);
return;
}
this.sendBuffer.Enqueue(new WaitSendBuffer(buffer, index, length));
}
看完接收的,我们来看看发送的方法,很好理解,唯一需要知道的是,当这个通道还没有连接的时候,如果要发送消息,因为不知道给谁发送,所以要先把消息存放在发送缓冲区中,待连接成功,则先把发送缓冲区内的消息发送过去。这个在HandleConnnect方法里面调用。如果已经连接了,则直接通过kcp发送。最后,真正发送的方法是:
public void Output(byte[] bytes, int count)
{
this.socket.Send(bytes, count, this.remoteEndPoint);
}
这个方法在创建kcp实例的时候就传入了kcp,kcp会在发送的时候自己调用的。
跟TChannel一样,提供了从这个消息通道接收消息的方法
public override Task<Packet> Recv()
实现的跟TChannel差不多,就不讲了。
讲道理,有点学累了。我现在需要去写点项目了。等转换一波心情后再开始学习吧。
劳逸结合—学习新东西是“劳”,做项目是“逸”——————Norman林