ET---KChannel学习笔记

本文介绍了KChannel的工作原理,包括如何处理UDP数据包的乱序和丢失问题,以及发送和接收数据的具体流程。KChannel利用KCP协议来确保数据传输的安全性和完整性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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;

这里,KChannelTChannel又有不一样了,这里的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林

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值