当前位置: 首页 > news >正文

python做软件的网站/百度客服中心人工在线咨询

python做软件的网站,百度客服中心人工在线咨询,黄页推广网站下载,多少钱可以注册一个公司1、kcp 的协议特点 1.1、RTO 不翻倍 RTO(Retransmission TimeOut),重传超时时间。tcp x 2,kcp x 1.5,提高传输速度 1.2、选择重传 TCP丢包时会全部重传从该包开始以后的数据,而KCP选择性重传,只重传真正丢失的数据包…

1、kcp 的协议特点

1.1、RTO 不翻倍

RTO(Retransmission TimeOut),重传超时时间。tcp x 2,kcp x 1.5,提高传输速度

1.2、选择重传
TCP丢包时会全部重传从该包开始以后的数据,而KCP选择性重传,只重传真正丢失的数据包。

1.3、快速重传
tcp 重传模式

   超时重传:超过规定的时间 RTO 则重传
   快速重传:收到三个冗余ACK,不去等待RTO ,直接重传
   这里指的是收到fastresend个失序报文后,不等待超时,直接重传,减少丢包等待时间。

1.4、非延迟 ACK
tcp 为充分利用带宽,延迟发送 ACK,RTT 时间较大,延长了丢包时的判断过程。而 kcp 的 ACK 是否延迟发送可以调节。

1.5、ACK + UNA
ARQ (自动重传请求,Automatic Repeat-reQuest)模型响应有两种方式

UNA:此编号前所有包已收到,tcp
ACK:该编号包已收到
只用 UNA 将导致全部重传,只用 ACK 则丢失成本太高,以往协议都是二选其一。而 kcp 协议中,除去单独的 ACK 包(精确)外,所有包都有 UNA 信息。

1.6、非退让流控
KCP正常模式同TCP一样使用公平退让法则,即发送窗口大小由:发送缓存大小、接收端剩余接收缓存大小、丢包退让、慢启动这四要素决定。但传送及时性要求很高的小数据时,可选择仅用前两项来控制发送频率。以牺牲部分公平性及带宽利用率之代价,换取了流畅传输的效果。

KCP 实时性好,但带宽利用率较低,因为

非退让流控,不断尝试发送数据,有效包不多
每个包应答,占用一定的带宽

2.kcp 实现

UDP收到的报文通过kcp_input传递给KCP,KCP会对数据进行解包,重新封装成应用层用户数据,应用层通过kcp_recv获取。应用层通过kcp_send发送数据,KCP会把用户数据拆分kcp报文,通过kcp_output,以UDP(send)的方式发送。

 

2.1、kcp 数据结构

kcp 报文结构:

conv会话编号,通信双方必须一致。
cmd:报文类型
IKCP_CMD_ACK 确认命令
IKCP_CMD_PUSH 数据推送命令
IKCP_CMD_WASK 接收窗口询问大小命令
IKCP_CMD_WINS 接收窗口大小告知命令
wnd: 己方可用接收窗口大小,接收窗口大小 - 接收队列大小
frg:segmen t分片。0,最后一个分片。3 2 1 0
sn:segment 报文的序列号。
ts:发送时间戳,用于计算RTO和RTT
una:待接收的序列号,其实确认号,表示该序列号之前的所有报文都收到了,可以删除
len:数据长度,DATA的长度
DATA: 用户数据


kcp 使用的 Segment 定义如下

struct IKCPSEG
{struct IQUEUEHEAD node;	// 用来串接多个 KCP segment,即前向后向指针IUINT32 conv;   // 会话编号IUINT32 cmd;    // 报文类型IUINT32 frg;    // 分片   IUINT32 wnd;    // 可用接收窗口大小(接收窗口大小-接收队列大小)IUINT32 ts;     // 发送时刻的时间戳IUINT32 sn;     // 分片 segment 的序号IUINT32 una;    // 待接收消息序号IUINT32 len;    // 数据长度IUINT32 resendts;	// 下次超时重传该报文的时间戳IUINT32 rto;	    // 重传超时时间//发送端在发送过程中携带着RTO,该发送端会启动一个定时器,进行定时,如果超过RTO就会重传IUINT32 fastack;    // 收到ack时该分片被跳过的次数,用于快速重传IUINT32 xmit;       // 记录了该报文被传输了几次char data[1];		// 实际传输的数据 payload
};

每一个 KCP 用户都需要调用 ikcp_create 创建一个 kcp 控制块 ikcpcbikcpcb 结构用来实现整个 KCP 协议。

struct IKCPCB
{IUINT32 conv;   // 标识会话IUINT32 mtu;    // 最大传输单元,默认数据为1400,最小为50IUINT32 mss;    // 最大分片大小,不大于mtuIUINT32 state;  // 连接状态(0xffffffff表示断开连接)IUINT32 snd_una;    // 第一个未确认的包IUINT32 snd_nxt;    // 下一个待分配包的序号IUINT32 rcv_nxt;    // 待接收消息序号.为了保证包的顺序,接收方会维护一个接收窗口,接收窗口有一个起始序号rcv_nxt 以及尾序号rcv_nxt + rcv_wnd(接收窗口大小)IUINT32 ts_recent;  IUINT32 ts_lastack;IUINT32 ssthresh;       // 拥塞窗口的阈值IINT32  rx_rttval;      // RTT的变化量,代表连接的抖动情况IINT32  rx_srtt;        // smoothed round trip time,平滑后的RTT;IINT32  rx_rto;         // 收ACK接收延迟计算出来的重传超时时间IINT32  rx_minrto;      // 最小重传超时时间IUINT32 snd_wnd;        // 发送窗口大小IUINT32 rcv_wnd;        // 接收窗口大小,本质上而言如果接收端一直不去读取数据则rcv_queue就会满(达到rcv_wnd)IUINT32 rmt_wnd;        // 远端接收窗口大小IUINT32 cwnd;           // 拥塞窗口大小, 动态变化IUINT32 probe;          // 探查变量, IKCP_ASK_TELL表示告知远端窗口大小。IKCP_ASK_SEND表示请求远端告知窗口大小;IUINT32 current;IUINT32 interval;       // 内部flush刷新间隔,对系统循环效率有非常重要影响, 间隔小了cpu占用率高, 间隔大了响应慢IUINT32 ts_flush;       // 下次flush刷新的时间戳IUINT32 xmit;           // 发送segment的次数, 当segment的xmit增加时,xmit增加(重传除外)IUINT32 nrcv_buf;       // 接收缓存中的消息数量IUINT32 nsnd_buf;       // 发送缓存中的消息数量IUINT32 nrcv_que;       // 接收队列中消息数量IUINT32 nsnd_que;       // 发送队列中消息数量IUINT32 nodelay;        // 是否启动无延迟模式。无延迟模式rtomin将设置为0,拥塞控制不启动;IUINT32 updated;         //是否调用过update函数的标识;IUINT32 ts_probe;       // 下次探查窗口的时间戳;IUINT32 probe_wait;     // 探查窗口需要等待的时间;IUINT32 dead_link;      // 最大重传次数,被认为连接中断;IUINT32 incr;           // 可发送的最大数据量;struct IQUEUEHEAD snd_queue;    //发送消息的队列 struct IQUEUEHEAD rcv_queue;    //接收消息的队列, 确认过用户可读取struct IQUEUEHEAD snd_buf;      //发送消息的缓存struct IQUEUEHEAD rcv_buf;      //接收消息的缓存IUINT32 *acklist;   //待发送的ack的列表 当收到一个数据报文时,将其对应的 ACK 报文的 sn 号以及时间戳 ts //同时加入到acklist 中,即形成如 [sn1, ts1, sn2, ts2 …] 的列表IUINT32 ackcount;   // 记录 acklist 中存放的 ACK 报文的数量 IUINT32 ackblock;   // acklist 数组的可用长度,当 acklist 的容量不足时,需要进行扩容void *user;     // 指针,可以任意放置代表用户的数据,也可以设置程序中需要传递的变量;char *buffer;   // 存储字节流信息 int fastresend; // 触发快速重传的重复ACK个数;int fastlimit;int nocwnd;     // 取消拥塞控制int stream;     // 是否采用流传输模式int logmask;    // 日志的类型,如IKCP_LOG_IN_DATA,方便调试int (*output)(const char *buf, int len, struct IKCPCB *kcp, void *user);//发送消息的回调函数void (*writelog)(const char *log, struct IKCPCB *kcp, void *user);  // 写日志的回调函数
};
2.2、kcp 报文发送

KCP 中,数据发送流程分为:

  • 上层应用调用 ikcp_send 将数据写入 snd_queue
  • 下层函数 ikcp_flush 决定将多少数据从 snd_queue 移动到 snd_buf,进行发送

 

ikcp_send 

 ikcp_send

ikcp_send 的功能:把用户发送的数据根据MSS分片成KCP的数据包格式,插入待发送队列

分片方式

流模式:检测每个发送队列⾥的分片是否达到 MSS,没有达到则用新的数据填充分片。
消息模式:将用户数据的每个分片设置 sn 和 frag,将分片后的数据存入发送队列,接收方通过 sn 和 frag 解包。即使⼀个分片的数据量可能不能达到MSS,也会作为⼀个包发送出去。
 

int ikcp_send(ikcpcb *kcp, const char *buffer, int len) 
{// 1、如果KCP开启流模式if (kcp->stream != 0) {		if (!iqueue_is_empty(&kcp->snd_queue)) {// 取出 snd_queue 中的最后一个报文,将其填充到 mss 的长度,设置frg为0IKCPSEG *old = iqueue_entry(kcp->snd_queue.prev, IKCPSEG, node);// 旧分片内数据长度小于mssif (old->len < kcp->mss) {int capacity = kcp->mss - old->len; // 还能容纳的数据长度int extend = (len < capacity)? len : capacity; // 需要填充的长度seg = ikcp_segment_new(kcp, old->len + extend); // 新建segmentassert(seg);if (seg == NULL) {return -2;}// 新分片添加到发送队列尾部iqueue_add_tail(&seg->node, &kcp->snd_queue);   // 拷贝旧分片的数据到新分片memcpy(seg->data, old->data, old->len); // 将buffer中的数据也拷贝到新分片if (buffer) {memcpy(seg->data + old->len, buffer, extend);buffer += extend; // buffer指向剩余数据的开头}seg->len = old->len + extend;seg->frg = 0;len -= extend; // 更新len为剩余数据长度iqueue_del_init(&old->node); // 删除oldikcp_segment_delete(kcp, old);}}if (len <= 0) {return 0;}}// 2、计算数据需要分成多少段报文if (len <= (int)kcp->mss) count = 1; // mss 1376 + head 24 = mtu 1400else count = (len + kcp->mss - 1) / kcp->mss;if (count >= (int)IKCP_WND_RCV) return -2;  // 超过对方的初始接收窗口if (count == 0) count = 1;  // fragment// 3、将数据全部新建 segment 插入发送队列尾部,队列计数递增, frag 递减for (i = 0; i < count; i++) {int size = len > (int)kcp->mss ? (int)kcp->mss : len;seg = ikcp_segment_new(kcp, size);assert(seg);if (seg == NULL) {return -2;}if (buffer && len > 0) { // 仍有待发送的数据memcpy(seg->data, buffer, size);}seg->len = size;// 分片编号,逆序。流模式情况下分片编号不用填写seg->frg = (kcp->stream == 0)? (count - i - 1) : 0;iqueue_init(&seg->node);iqueue_add_tail(&seg->node, &kcp->snd_queue); // 加入到 snd_queue 中kcp->nsnd_que++;if (buffer) {buffer += size;}len -= size;}
}

 应用层调用 ikcp_send 之后将用户数据置入 snd_queue 中,当 KCP 调用 ikcp_flush 时才将数据从 snd_queue 中 移入到 snd_buf 中,然后调用 kcp->output() 发送。

检查 kcp->update 是否更新,未更新直接返回。kcp->update 由 ikcp_update 更新,上层应用需要每隔一段时间(10-100ms)调用 ikcp_update 来驱动 KCP 发送数据;

// 'ikcp_update' haven't been called. 
if (kcp->updated == 0) return;

准备将 acklist 中记录的 ACK 报文发送出去,即从 acklist 中填充 ACK 报文的 sn 和 ts 字段;

// flush acknowledges
// 逐一获取 acklist 中的 sn 和 ts,编码成 segment,以流的方式凑够 MTU 发送
count = kcp->ackcount;		// 需要应答的分片数量
for (i = 0; i < count; i++) {size = (int)(ptr - buffer);// 超过 MTU 大小直接发送if (size + (int)IKCP_OVERHEAD > (int)kcp->mtu) {ikcp_output(kcp, buffer, size);ptr = buffer; // 新建分片}ikcp_ack_get(kcp, i, &seg.sn, &seg.ts); // 应答包ptr = ikcp_encode_seg(ptr, &seg);       // 编码segment协议头
}kcp->ackcount = 0; 

检查当前是否需要对远端窗口进行探测。由于 KCP 流量控制依赖于远端通知其可接受窗口的大小,一旦远端接受窗口 kcp->rmt_wnd 为0,那么本地将不会再向远端发送数据,因此就没有机会从远端接受 ACK 报文,从而没有机会更新远端窗口大小。在这种情况下,KCP 需要发送窗口探测报文到远端,待远端回复窗口大小后,后续传输可以继续:
 

// probe window size (if remote window size equals zero)
// 1、远端窗口大小为0,需要发送窗口探测报文
if (kcp->rmt_wnd == 0) {// 初始化探测间隔和下一次探测时间if (kcp->probe_wait == 0) { kcp->probe_wait = IKCP_PROBE_INIT;  // 默认7秒探测kcp->ts_probe = kcp->current + kcp->probe_wait; // 下一次探测时间}	else {//远端窗口为0,发送过探测请求,但是已经超过下次探测的时间  // 检测是否到了探测时间if (_itimediff(kcp->current, kcp->ts_probe) >= 0) { // 更新探测间隔probe_waitif (kcp->probe_wait < IKCP_PROBE_INIT) kcp->probe_wait = IKCP_PROBE_INIT;kcp->probe_wait += kcp->probe_wait / 2;if (kcp->probe_wait > IKCP_PROBE_LIMIT)kcp->probe_wait = IKCP_PROBE_LIMIT;// 更新下次探测时间ts_probekcp->ts_probe = kcp->current + kcp->probe_wait;// 更新探测变量probe为IKCP_ASK_SEND,发送探测消息 kcp->probe |= IKCP_ASK_SEND;}}
}	
// 2、远端窗口正常,则不需要发送窗口探测  
else {kcp->ts_probe = 0;	// 更新下次探测时间为0kcp->probe_wait = 0; // 更新探测窗口等待时间为0
}

 将窗口探测报文和窗口回复报文发送出去

// flush window probing commands
if (kcp->probe & IKCP_ASK_SEND) {seg.cmd = IKCP_CMD_WASK;	// 窗口探测[询问对方窗口size]size = (int)(ptr - buffer);if (size + (int)IKCP_OVERHEAD > (int)kcp->mtu) {ikcp_output(kcp, buffer, size);ptr = buffer;}ptr = ikcp_encode_seg(ptr, &seg);
}// flush window probing commands
if (kcp->probe & IKCP_ASK_TELL) {seg.cmd = IKCP_CMD_WINS;	// 窗口告知[告诉对方我方窗口size]size = (int)(ptr - buffer);if (size + (int)IKCP_OVERHEAD > (int)kcp->mtu) {ikcp_output(kcp, buffer, size);ptr = buffer;}ptr = ikcp_encode_seg(ptr, &seg);
}kcp->probe = 0;	//清空标识

 计算本次发送可用的窗口大小,这里 KCP 采用了可以配置的策略,正常情况下,KCP 的窗口大小由发送窗口 snd_wnd,远端接收窗口 rmt_wnd 以及根据流控计算得到的 kcp->cwnd 共同决定;但是当开启了 nocwnd 模式时,窗口大小仅由前两者决定;

// calculate window size 
// 若没有流控,取发送窗口和远端接收窗口最小值
cwnd = _imin_(kcp->snd_wnd, kcp->rmt_wnd);      
// 若存在流控,则取当前拥塞窗口、发送窗口和远端接收窗口三者最小值
if (kcp->nocwnd == 0) cwnd = _imin_(kcp->cwnd, cwnd);  

 将缓存在 snd_queue 中的数据移到 snd_buf 中等待发送

// move data from snd_queue to snd_buf
// 从snd_queue移动到snd_buf的数量不能超出对方的接收能力,发送符合拥塞范围的分片
while (_itimediff(kcp->snd_nxt, kcp->snd_una + cwnd) < 0) {IKCPSEG *newseg;if (iqueue_is_empty(&kcp->snd_queue)) break;newseg = iqueue_entry(kcp->snd_queue.next, IKCPSEG, node);iqueue_del(&newseg->node);iqueue_add_tail(&newseg->node, &kcp->snd_buf); // 添加到发送缓存kcp->nsnd_que--;kcp->nsnd_buf++;//设置数据分片的属性newseg->conv = kcp->conv;newseg->cmd = IKCP_CMD_PUSH;newseg->wnd = seg.wnd;	// 告知对方当前的接收窗口newseg->ts = current;	// 当前时间newseg->sn = kcp->snd_nxt++;	// 序号newseg->una = kcp->rcv_nxt;		// 告诉对方可以发送的下一个包序号newseg->resendts = current;		// 当前发送的时间newseg->rto = kcp->rx_rto;		// 超时重传的时间newseg->fastack = 0;			// 是否快速重传newseg->xmit = 0;				// 重传次数
}

 在发送数据之前,先设置快重传的次数和重传间隔;KCP 允许设置快重传的次数,即 fastresend 参数。例如设置 fastresend 为2,并且发送端发送了1,2,3,4,5几个包,收到远端的ACK: 1, 3, 4, 5,当收到ACK3时,KCP知道2被跳过1次,收到ACK4时,知道2被**“跳过”**了2次,此时可以认为2号丢失,不用等超时,直接重传2号包;每个报文的 fastack 记录了该报文被跳过了几次,由函数 ikcp_parse_fastack 更新。于此同时,KCP 也允许设置 nodelay 参数,当激活该参数时,每个报文的超时重传时间将由 x2 变为 x1.5,即加快报文重传:

// calculate resent 
// 是否设置快重传次数
resent = (kcp->fastresend > 0)? (IUINT32)kcp->fastresend : 0xffffffff; 
// 是否开启nodelay
rtomin = (kcp->nodelay == 0)? (kcp->rx_rto >> 3) : 0;

将 snd_buf 中的数据发送出去

// flush data segments
// 发送snd buf的分片,只要数据还在snd_buf 说明对方还没有应答
// 1、新的报文,正常发送
// 2、超时重传
// 3、快速重传(如果有)
for (p = kcp->snd_buf.next; p != &kcp->snd_buf; p = p->next) {IKCPSEG *segment = iqueue_entry(p, IKCPSEG, node);int needsend = 0;// 1、如果该报文是第一次传输,那么直接发送if (segment->xmit == 0) {   needsend = 1;segment->xmit++; // 分片发送次数 + 1segment->rto = kcp->rx_rto; // 超时时间间隔segment->resendts = current + segment->rto + rtomin; // 下一次要发送的时间}// 2、当前时间达到了该报文的重传时间,但并没有新的ack到达,出现丢包, 重传  else if (_itimediff(current, segment->resendts) >= 0) { needsend = 1;segment->xmit++;kcp->xmit++;// 根据 nodelay 参数更新重传时间if (kcp->nodelay == 0) {segment->rto += _imax_(segment->rto, (IUINT32)kcp->rx_rto);} else {IINT32 step = (kcp->nodelay < 2)? ((IINT32)(segment->rto)) : kcp->rx_rto;segment->rto += step / 2; //报文超时等待时间更新,控制RTO=1.5 }segment->resendts = current + segment->rto;	//下一次发送的时间lost = 1;	// 丢包,反应到拥塞控制策略去了}// 3、该报文的的被跳过次数超过设置的快速重传次数,需要重传  else if (segment->fastack >= resent) {  if ((int)segment->xmit <= kcp->fastlimit || kcp->fastlimit <= 0) {needsend = 1;segment->xmit++;segment->fastack = 0;  // 重置该分片被跳过的次数segment->resendts = current + segment->rto;change++;	// 标识快速重传的发生}}// 需要发送数据if (needsend) {int need;segment->ts = current;segment->wnd = seg.wnd; // 己方可用接收窗口大小segment->una = kcp->rcv_nxt;   // 待接收的下一个包序号size = (int)(ptr - buffer);need = IKCP_OVERHEAD + segment->len;// 小包封装成大包发送if (size + need > (int)kcp->mtu) {		ikcp_output(kcp, buffer, size);ptr = buffer;}// 把segment封装成线性buffer发送 头部+数据ptr = ikcp_encode_seg(ptr, segment);    if (segment->len > 0) {memcpy(ptr, segment->data, segment->len);ptr += segment->len;}if (segment->xmit >= kcp->dead_link) {kcp->state = (IUINT32)-1;}}
}// flash remain segments
size = (int)(ptr - buffer);	// 剩余的数据
// 最终只要有数据要发送,一定发出去
if (size > 0) {ikcp_output(kcp, buffer, size);	
}

 根据设置的 lost 和 change 更新窗口大小;注意 快重传和丢包时的窗口更新算法不一致,这一点类似于 TCP 协议的拥塞控制和快恢复算法

// update ssthresh
//如果发生了快速重传,拥塞窗口阈值降低为当前未确认包数量的一半或最小值 
if (change) {   IUINT32 inflight = kcp->snd_nxt - kcp->snd_una;kcp->ssthresh = inflight / 2;if (kcp->ssthresh < IKCP_THRESH_MIN)kcp->ssthresh = IKCP_THRESH_MIN;kcp->cwnd = kcp->ssthresh + resent;	// 动态调整拥塞控制窗口kcp->incr = kcp->cwnd * kcp->mss;
}
// 如果发生了丢包,阈值减半, cwd 窗口保留为 1
if (lost) {     kcp->ssthresh = cwnd / 2;if (kcp->ssthresh < IKCP_THRESH_MIN)kcp->ssthresh = IKCP_THRESH_MIN;kcp->cwnd = 1;  // 动态调整拥塞控制窗口 kcp->incr = kcp->mss;
}if (kcp->cwnd < 1) {kcp->cwnd = 1;  kcp->incr = kcp->mss;
}
2.3、kcp 报文接收

ikcp_recv
应用层接收函数为 ikcp_recv,主要做三件事

读取组好包的数据 rcv_queue -> 用户 buffer
将接收缓存 rcv_buf 的分片转移到接收队列 rcv_queue
如果有接收空间则将 kcp->probe |= IKCP_ASK_TELL ; 以在update的时候告知对方可以发送数据了。
首先检测一下本次接收数据之后,是否需要进行窗口恢复。在前面的内容中解释过,KCP 协议在远端窗口为0的时候将会停止发送数据,此时如果远端调用 ikcp_recv 将数据从 rcv_queue 中移动到应用层 buffer 中之后,表明其可以再次接受数据,为了能够恢复数据的发送,远端可以主动发送 IKCP_ASK_TELL 来告知窗口大小;
 

if (kcp->nrcv_que >= kcp->rcv_wnd)recover = 1;  // 标记可以开始窗口恢复

开始将 rcv_queue 中的数据根据分片编号 frg merge 起来,然后拷贝到用户的 buffer 中。

// merge fragment   
// 将属于同一个消息的各分片重组完整数据,并删除rcv_queue中segment,nrcv_que减少 
// 经过 ikcp_send 发送的数据会进行分片,分片编号为倒序序号,因此frg为0的数据包标记着完整接收到了一次 send 发送过来的数据
for (len = 0, p = kcp->rcv_queue.next; p != &kcp->rcv_queue; ) {int fragment;seg = iqueue_entry(p, IKCPSEG, node);p = p->next;if (buffer) {memcpy(buffer, seg->data, seg->len); // 把queue的数据就放入用户bufferbuffer += seg->len;}len += seg->len;fragment = seg->frg;if (ikcp_canlog(kcp, IKCP_LOG_RECV)) {ikcp_log(kcp, IKCP_LOG_RECV, "recv sn=%lu", (unsigned long)seg->sn);}if (ispeek == 0) {iqueue_del(&seg->node);ikcp_segment_delete(kcp, seg);  // 删除节点kcp->nrcv_que--;    // nrcv_que接收队列-1}// frg = 0,完整的数据接收到, 本次数据接收结束if (fragment == 0) 	// break;
}

 下一步将 rcv_buf 中的数据转移到 rcv_queue 中,这个过程根据报文的 sn 编号来确保转移到 rcv_queue 中的数据一定是按序的:

// move available data from rcv_buf -> rcv_queue
// 将 rcv_buf 中的数据转移到 rev_queue 
// 根据报文的sn来确保转移到 rcv_queue 中的数据一定是按序的
while (! iqueue_is_empty(&kcp->rcv_buf)) {seg = iqueue_entry(kcp->rcv_buf.next, IKCPSEG, node);// 1、根据 sn 确保数据是按序转移到 rcv_queue 中// 2、接收队列nrcv_que < 接收窗口rcv_wnd; if (seg->sn == kcp->rcv_nxt && kcp->nrcv_que < kcp->rcv_wnd) {iqueue_del(&seg->node);kcp->nrcv_buf--;iqueue_add_tail(&seg->node, &kcp->rcv_queue);kcp->nrcv_que++;    // 接收队列 有多少个分片 + 1kcp->rcv_nxt++;     // 接收序号 + 1} else {break;}
}

 最后进行窗口恢复。此时如果 recover 标记为1,表明在此次接收之前,可用接收窗口为0,如果经过本次接收之后,可用窗口大于0,将主动发送 IKCP_ASK_TELL 数据包来通知对方已可以接收数据:

// fast recover 
// nrcv_que小于rcv_wnd, 说明接收端有空间继续接收数据了
if (kcp->nrcv_que < kcp->rcv_wnd && recover) {// ready to send back IKCP_CMD_WINS in ikcp_flush// tell remote my window sizekcp->probe |= IKCP_ASK_TELL;
}

 kcp_input

 

ikcp_recv 仅为上层调用的接口,KCP 协议需要从底层接受数据到 rcv_buf 中,这是通过函数 ikcp_input 实现。ikcp_input 中的所有功能都在一个外层的循环中实现:

首先将接收到的数据包进行解码,并进行基本的数据包长度和类型校验;KCP 协议只会接收到前文中所介绍的四种数据包;

调用 ikcp_parse_una 来确定已经发送的数据包有哪些被对方接收到。KCP 中所有的报文类型均带有 una 信息。发送端发送的数据都会缓存在 snd_buf 中,直到接收到对方确认信息之后才会删除。当接收到 una 信息后,表明 sn 小于 una 的数据包都已经被对方接收到,因此可以直接从 snd_buf 中删除。同时调用 ikcp_shrink_buf 来更新 KCP 控制块的 snd_una 数值。

// 删除小于snd_buf中小于una的segment
ikcp_parse_una(kcp, una);       
// 更新snd_una为snd_buf中seg->sn或kcp->snd_nxt ,更新下一个待应答的序号
ikcp_shrink_buf(kcp);     

处理 IKCP_CMD_ACK 报文

if (cmd == IKCP_CMD_ACK) {if (_itimediff(kcp->current, ts) >= 0) { // 根据应答判断rtt//更新rx_srtt,rx_rttval,计算kcp->rx_rtoikcp_update_ack(kcp, _itimediff(kcp->current, ts));}//遍历snd_buf中(snd_una, snd_nxt),将sn相等的删除,直到大于sn  ikcp_parse_ack(kcp, sn);    // 将已经ack的分片删除ikcp_shrink_buf(kcp);       // 更新控制块的 snd_unaif (flag == 0) {flag = 1;       //快速重传标记maxack = sn;    // 记录最大的 ACK 编号latest_ts = ts;}	else {if (_itimediff(sn, maxack) > 0) {maxack = sn;        // 记录最大的 ACK 编号 latest_ts = ts;}}

处理 IKCP_CMD_PUSH 报文

else if (cmd == IKCP_CMD_PUSH) {	//接收到具体的数据包if (_itimediff(sn, kcp->rcv_nxt + kcp->rcv_wnd) < 0) {// 对该报文的确认 ACK 报文放入 ack 列表中ikcp_ack_push(kcp, sn, ts);//  判断接收的数据分片编号是否符合要求,即:在接收窗口(滑动窗口)范围之内if (_itimediff(sn, kcp->rcv_nxt) >= 0) {    // 是要接受起始的序号seg = ikcp_segment_new(kcp, len);seg->conv = conv;seg->cmd = cmd;seg->frg = frg;seg->wnd = wnd;seg->ts = ts;seg->sn = sn;seg->una = una;seg->len = len;if (len > 0) {memcpy(seg->data, data, len);}// 将该报文插入到 rcv_buf 链表中ikcp_parse_data(kcp, seg);  }}
}

对于接收到的 IKCP_CMD_WASK 报文,直接标记下次将发送窗口通知报文;而对于报文 IKCP_CMD_WINS 无需做任何特殊操作;

else if (cmd == IKCP_CMD_WASK) {		// ready to send back IKCP_CMD_WINS in ikcp_flush // tell remote my window size // 如果是探测包,添加相应的标识位kcp->probe |= IKCP_ASK_TELL;        
}
else if (cmd == IKCP_CMD_WINS) {// do nothing,如果是 tell me 远端窗口大小,什么都不做
}

据记录的最大的 ACK 编号 maxack 来更新 snd_buf 中的报文的 fastack,这个过程在介绍 ikcp_flush 中提到过,对于 fastack 大于设置的 resend 参数时,将立马进行快重传;

最后,根据接收到报文的 una 和 KCP 控制块的 una 参数进行流控;
 

相关文章:

不一样的网络协议-------KCP协议

1、kcp 的协议特点 1.1、RTO 不翻倍 RTO(Retransmission TimeOut)&#xff0c;重传超时时间。tcp x 2&#xff0c;kcp x 1.5&#xff0c;提高传输速度 1.2、选择重传 TCP丢包时会全部重传从该包开始以后的数据&#xff0c;而KCP选择性重传&#xff0c;只重传真正丢失的数据包…...

前端-关于分辨率和屏幕大小关系的浅谈

最近在工作中&#xff0c;总有些非前端小伙伴在问分辨率和屏幕的大小关系问题&#xff0c;故在此记录一下&#xff0c;方便不清楚的小伙伴订阅观看。 一&#xff0c;分辨率跟屏幕大小关系 &#xff08;1&#xff09;分辨率跟屏幕大小有关吗&#xff1f; 前端中的分辨率与屏幕…...

where怎么等于多个值,sql where多个值

在SQL中&#xff0c;可以使用IN和OR操作符来匹配多个值&#xff0c;以在WHERE语句中执行过滤。以下是一些示例&#xff1a; 使用IN操作符匹配多个值 可以使用IN操作符来匹配多个可能的值&#xff0c;如下所示&#xff1a; SELECT * FROM 表名 WHERE 字段名 IN (值1, 值2, 值3…...

02.Oracle的启动过程

Oracle的启动过程 一、Oracle数据库的四种状态二、Oracle的启动过程 一、Oracle数据库的四种状态 Oracle数据库有四种状态&#xff1a;SHUTDOWN、NOMOUNT、MOUNT、OPEN. 1.SHUTDOWN状态 数据库没有启动 2.NOMOUNT状态 启动了instance&#xff08;数据库实例&#xff09;启动…...

git跳过用户名密码验证,以及配置credential-helper

平时我们在使用git命令时&#xff0c;如果使用http方式拉取代码每次都需要使用填写用户名和密码&#xff0c;非常的麻烦。 如何才能绕过每次繁琐的填充? 如果想要绕过git的交互方式&#xff0c;首先需要了解git的密码存储机制。 git使用的使用是一种名叫**[credential helpe…...

web前端常见开发工具汇总 你用过几个?

搬运旗下公众号的内容~ 目录 1.记事本 2.Visual studio code 3.Hbuilder 4.Eclipse 5.Webstorm 6.Notepad 随着信息时代的不断进步&#xff0c;互联网在人类社会中所占的地位愈发举足轻重。大大小小的网站&#xff0c;构成了如今光怪陆离的网络社会。我们知道&#xff0c…...

518抽奖软件,可从Excel~Word~Pdf~网页导入名单

518抽奖软件简介 518抽奖软件&#xff0c;518我要发&#xff0c;超好用的年会抽奖软件&#xff0c;简约设计风格。 包含文字号码抽奖、照片抽奖两种模式&#xff0c;支持姓名抽奖、号码抽奖、数字抽奖、照片抽奖。(www.518cj.net) 从Excel、WPS表格导入 整列&#xff1a; 用鼠…...

初学编程入门基础教学视频,中文编程开发语言工具箱之豪华编辑构件,免费版中文编程软件下载

初学编程入门基础教学视频&#xff0c;中文编程开发语言工具箱之豪华编辑构件&#xff0c;免费版中文编程软件下载 构件的其中一个属性、方法&#xff0c;查找内容&#xff0c;替换内容。 构件工具箱非常丰富&#xff0c;其中该构件在 文本件构件板菜单下。 编程系统化课程总目…...

Objective-C基本数据类型使用

// // main.m // OC_BASE_USEAGE // // Created by Hacker X on 2023/10/22. //#import <Foundation/Foundation.h>int main(int argc, const char * argv[]) {autoreleasepool {NSLog("Objective-C 数据类型基本使用");//Objective-C 数据类型对应的格式化…...

【前端早早聊直播回顾】Harmony Next 与 Flutter 的不解之缘

Hello 大家好&#xff0c;我是 Flutter GDE 郭树煜&#xff0c;本次要分享的话题是关于鸿蒙与 Flutter 的故事&#xff0c;可能没接触过的会感觉有点懵&#xff0c;Harmony 和 Flutter 有啥关系&#xff0c;它们怎么会被放到一起讲了呢&#xff1f;接下来就让我们来聊聊这个问题…...

Vue之CSS基础

CSS&#xff1a;层叠样式表 1、选择器 从模板template中选择某元素进行样式设置 需要注意的是作用域到底是当前模板还是整个html文档 1.1 基础(单一)选择器 标签、类、 id、通配符 标签、直接使用标签名&#xff0c;比如div,span… 优点&#xff1a;全选 模板中的名{。。。}…...

【c++|opencv】二、灰度变换和空间滤波---3.均值滤波

every blog every motto: You can do more than you think. https://blog.csdn.net/weixin_39190382?typeblog 0. 前言 均值滤波 1. 均值滤波 #include <iostream> #include <opencv2/opencv.hpp> #include"Salt.h"using namespace cv; using names…...

【Arduino环境下驱动合宙esp32c3单片机基本外设】

【esp32c3基本外设驱动】 1. GPIO调试1.1 源码分享2.2 实验效果 2. ADC调试2.1 源码分享2.2 实验效果 3. WS2812驱动3.1 源码分享3.2 实验效果 4. 旋转编码器4.1 源码分享4.2 测试效果 5. SSD1306屏幕驱动5.1 源码分享5.2 测试效果 6. 双cpu同时工作测试6.1 源码分享6.2 测试效…...

一站式迁移,人大金仓助力大地保险业务无感升级

2021年3月&#xff0c;国家发布《中华人民共和国国民经济和社会发展第十四个五年规划和2035年远景目标纲要》&#xff0c;纲要明确提出“稳妥发展金融科技&#xff0c;加快金融机构数字化转型”、“推进金融业信息化核心技术安全可控&#xff0c;维护金融基础设施安全”。 202…...

基于SSM的模具制造企业订单跟踪管理系统设计与实现

末尾获取源码 开发语言&#xff1a;Java Java开发工具&#xff1a;JDK1.8 后端框架&#xff1a;SSM 前端&#xff1a;采用JSP技术开发 数据库&#xff1a;MySQL5.7和Navicat管理工具结合 服务器&#xff1a;Tomcat8.5 开发软件&#xff1a;IDEA / Eclipse 是否Maven项目&#x…...

计算机基础知识41

前端 # 前端是所有跟用户直接打交道 比如&#xff1a;PC页面、手机页面、汽车显示屏&#xff0c;肉眼可以看见的 # 后端&#xff1a;一堆代码&#xff0c;用户不能够直接看到&#xff0c;不直接与用户打交道 常见的后端&#xff1a;Python、Java、Go等 # 学了前端就可以做全栈…...

requests之get请求实例-百度搜索

视频版教程&#xff1a;一天掌握python爬虫【基础篇】 涵盖 requests、beautifulsoup、selenium 百度搜索请求地址&#xff1a; https://www.baidu.com/s?wd宝马 如果我们直接用requests.get()进行访问&#xff0c;发现没有返回内容&#xff0c;因为百度服务器通过headers头…...

Linux进程程序替换

文章目录 进程程序替换程序替换函数execl()函数调用另外一个可执行程序 execlp()函数ecexv()函数execle()函数 替换函数总结 进程程序替换 什么是进程程序替换&#xff1f;为什么要有程序替换&#xff1f; 顾名思义&#xff0c;进程程序替换就是把该进程中的程序替换掉&#x…...

Istio实战(九)-Envoy 流量劫持

前言 Envoy 是一款面向 Service Mesh 的高性能网络代理服务。它与应用程序并行运行,通过以平台无关的方式提供通用功能来抽象网络。当基础架构中的所有服务流量都通过 Envoy 网格时,通过一致的可观测性,很容易地查看问题区域,调整整体性能。 Envoy也是istio的核心组件之一…...

Node.js 的pm2 库

Node.js 是一个非常流行的 JavaScript 运行时环境&#xff0c;用于编写服务器端代码。随着 Node.js 应用程序的增长&#xff0c; 我们需要一种可靠的方式来管理应用程序的生命周期。这就是 pm2 库的作用。它是一个流行的 Node.js 应用程序进程管理器&#xff0c;可以帮助我们管…...

AutoX.js - openCV多分辨率找图

AutoX.js - openCV多分辨率找图 一、起因 AutoXjs 中有两个找图相关的方法 findImage 和 matchTemplate&#xff0c;之前一直没发现什么问题&#xff0c;但最近在一次测试找图时&#xff0c;明明大图和模板图的轮廓都清晰&#xff0c;却怎么也找不到图&#xff0c;降低阈值参…...

Python爬虫实战案例——第七例

文章中所有内容仅供学习交流使用&#xff0c;不用于其他任何目的&#xff01;严禁将文中内容用于任何商业与非法用途&#xff0c;由此产生的一切后果与作者无关。若有侵权&#xff0c;请联系删除。 目标&#xff1a;LI视频采集 地址&#xff1a;aHR0cHM6Ly93d3cucGVhcnZpZGVv…...

C# 图解教程 第5版 —— 第13章 数组

文章目录 13.1 数组13.1.1 定义13.1.2 重要细节 13.2 数组的类型13.3 数组是对象13.4 一维数组和矩形数组13.5 实例化一维数组或矩形数组13.6 访问数组元素&#xff08;*&#xff09;13.7 初始化数组13.7.1 显示初始化一维数组13.7.2 显示初始化矩形数组13.7.3 初始化矩形数组的…...

android studio启动Task配置

Android studio 高版本默认不开启Task配置&#xff0c;需要自己手动开启 1.低版本配置路径&#xff1a;&#xff08;复制他人图片&#xff09; 2.高版本路径&#xff1a;添加下图勾选配置即可 3.gradle task 3.1 初识task gradle中所有的构建工作都是由task完成的,它帮我们处…...

Xcode运行程序提示 Executable Path is a Directory 问题解决

一、首先运行模拟器报错&#xff08;没有记录&#xff09;&#xff0c;解决办法&#xff1a; TARGET->Build Settings->Architectures -> Exclude Architectures里面填入arm64&#xff0c;后运行模拟器成功 二、其次模拟器开发完成后&#xff0c;xcode运行真机调试&…...

决策树的优缺点

决策树优点 1. 易于理解和解释&#xff0c;因为树木可以画出来被看见 2. 需要很少的数据准备。其他很多算法通常都需要数据规范化&#xff0c;需要创建虚拟变量并删除空值等。但请注意&#xff0c; sklearn中的决策树模块不支持对缺失值的处理。 3. 使用树的成本&#xff08;比…...

Flask后端开发(二) - 功能实现和项目总结

目录 1. 功能1:修改文件参数值1.1. 获取网页端传参1.2. 读取文件1.2.1. 一般文件读取方式1.2.2. 特殊文件 —— mlx文件1.2.3. 特殊文件 —— .xlx文件1.3. 查找数据修改位置,替换数据2. 功能2:读取结果数据2.1. 实时数据展示如何存储相关数据?2.2. 读取相关数据,整理、打…...

思维训练第五课插入语和主谓一致

系列文章目录 文章目录 系列文章目录前言一、插入语插入语的分类1、常用作插入语的副词 Indeed的确 certainly 当然 surely 无疑地 however 然而 等2、形容词及词组作插入语3、常作插入语的介词短语4、常作插入语的分词短语 Strictly speaking严格地说&#xff0c;generally sp…...

开源利器:it-tools 项目介绍

作为一名开发人员&#xff0c;我们在日常工作和学习中常常需要使用一系列小工具&#xff0c;如JSON格式化、JSON转表格、当前时间戳、XML格式化、SQL格式化、密码生成以及UUID生成等。通常情况下&#xff0c;我们会在网上搜索各种在线工具来满足这些需求。然而&#xff0c;这些…...

基于和声算法的无人机航迹规划-附代码

基于和声算法的无人机航迹规划 文章目录 基于和声算法的无人机航迹规划1.和声搜索算法2.无人机飞行环境建模3.无人机航迹规划建模4.实验结果4.1地图创建4.2 航迹规划 5.参考文献6.Matlab代码 摘要&#xff1a;本文主要介绍利用和声算法来优化无人机航迹规划。 1.和声搜索算法 …...