stream-deploy/ZLM/3rdpart/ZLToolKit/src/Network/Kcp.h

386 lines
13 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* Copyright (c) 2021 The ZLToolKit project authors. All Rights Reserved.
*
* This file is part of ZLToolKit(https://github.com/ZLMediaKit/ZLToolKit).
*
* Use of this source code is governed by MIT license that can be found in the
* LICENSE file in the root of the source tree. All contributing project authors
* may be found in the AUTHORS file in the root of the source tree.
*
* code reference github.com/skywind3000/kcp/releases/tag/1.7.
*/
#ifndef TOOLKIT_NETWORK_KCP_H
#define TOOLKIT_NETWORK_KCP_H
#include "Network/Buffer.h"
#include "Network/sockutil.h"
#include "Poller/EventPoller.h"
#include "Poller/Timer.h"
#include "Util/TimeTicker.h"
#include "Socket.h"
namespace toolkit {
class KcpHeader {
public:
static const size_t HEADER_SIZE = 24;
enum class Cmd : uint8_t {
CMD_PUSH = 81, // cmd: push data
CMD_ACK = 82, // cmd: ack
CMD_WASK = 83, // cmd: window probe (ask)
CMD_WINS = 84, // cmd: window size (tell)
};
uint32_t _conv; // 会话ID,用于标识一个会话
Cmd _cmd; // 命令字段,用于标识数据包类型
uint8_t _frg = 0; // 分片序号,用于消息分片,0表示最后一片
uint16_t _wnd; // 接受窗口大小
uint32_t _ts; // 时间戳,2^32ms,约49.7天会溢出一次
uint32_t _sn; // 序列号
uint32_t _una; // 待接收的第一个未确认包序号
uint32_t _len = 0; // payload部分数据长度(不包含头长度)
public:
// Getters for KcpHeader members
uint32_t getConv() const { return _conv; }
Cmd getCmd() const { return _cmd; }
uint8_t getFrg() const { return _frg; }
uint16_t getWnd() const { return _wnd; }
uint32_t getTs() const { return _ts; }
uint32_t getSn() const { return _sn; }
uint32_t getUna() const { return _una; }
uint32_t getLen() const { return _len; }
// Setters for KcpHeader members
void setConv(uint32_t conv) { _conv = conv; }
void setCmd(Cmd cmd) { _cmd = cmd; }
void setFrg(uint8_t frg) { _frg = frg; }
void setWnd(uint16_t wnd) { _wnd = wnd; }
void setTs(uint32_t ts) { _ts = ts; }
void setSn(uint32_t sn) { _sn = sn; }
void setUna(uint32_t una) { _una = una; }
void setLen(uint32_t len) { _len = len; }
uint32_t getPacketSize() const { return _len + HEADER_SIZE; }
bool loadHeaderFromData(const char *data, size_t len);
bool storeHeaderToData(char *buf, size_t size);
};
class KcpPacket : public KcpHeader, public toolkit::BufferRaw {
public:
using Ptr = std::shared_ptr<KcpPacket>;
static KcpPacket::Ptr parse(const char* data, size_t len);
KcpPacket() {};
KcpPacket(uint32_t conv, Cmd cmd, size_t payloadSize) {
setConv(conv);
setCmd(cmd);
setPayLoadSize(payloadSize);
};
KcpPacket(size_t payloadSize) {
setPayLoadSize(payloadSize);
}
virtual ~KcpPacket();
bool storeToData();
char *getPayloadData() {
return data() + HEADER_SIZE;
};
uint32_t getResendts() const { return _resendts; }
uint32_t getRto() const { return _rto; }
uint32_t getFastack() const { return _fastack; }
uint32_t getXmit() const { return _xmit; }
void setResendts(uint32_t resendts) { _resendts = resendts; }
void setRto(uint32_t rto) {_rto = rto; }
void setFastack(uint32_t fastack) { _fastack = fastack; }
void setXmit(uint32_t xmit) { _xmit = xmit; }
void setPayLoadSize(size_t len) {
setCapacity(len + HEADER_SIZE + 1);
setSize(len + HEADER_SIZE);
setLen(len);
}
protected:
bool loadFromData(const char *data, size_t len);
private:
uint32_t _resendts; // 重传超时时间戳,表示该数据包下次重传的时间戳
uint32_t _rto; // 超时重传时间表示数据包在多长时间没收到ACK就重传,会基于rtt动态调整
uint32_t _fastack; // 快速确认计数器
uint32_t _xmit; // 传输次数,用于统计重传次数
};
//数据包
class KcpDataPacket : public KcpPacket {
public:
KcpDataPacket(uint32_t conv, size_t payloadSize)
: KcpPacket(conv, KcpHeader::Cmd::CMD_WASK, payloadSize) {
}
};
//ACK包
class KcpAckPacket : public KcpPacket {
public:
KcpAckPacket(uint32_t conv)
: KcpPacket(conv, KcpHeader::Cmd::CMD_ACK, 0) {
}
};
//探测窗口大小包
class KcpProbePacket : public KcpPacket {
public:
KcpProbePacket(uint32_t conv)
: KcpPacket(conv, KcpHeader::Cmd::CMD_WASK, 0) {
}
};
//告知窗口大小包
class KcpTellPacket : public KcpPacket {
public:
KcpTellPacket(uint32_t conv)
: KcpPacket(conv, KcpHeader::Cmd::CMD_WINS, 0) {
}
};
//可以根据实际需要调整参数
//参考kcp V.1.7实现由以下推荐模式和参数
//默认,开启流控: setDelayMode(DELAY_MODE_NORMAL); setInterval(10); setFastResend(0); setNoCwnd(false)
//普通,关闭流控: setDelayMode(DELAY_MODE_NORMAL); setInterval(10); setFastResend(0); setNoCwnd(true)
//快速,关闭流控: setDelayMode(DELAY_MODE_NO_DELAY); setInterval(10); setFastResend(1); setNoCwnd(true); setRxMinrto(10)
class KcpTransport : public std::enable_shared_from_this<KcpTransport> {
public:
using Ptr = std::shared_ptr<KcpTransport>;
enum DelayMode {
DELAY_MODE_NORMAL = 0, // 正常模式, 每次重发rto翻倍,往外增加12.5%的最小rto
DELAY_MODE_FAST = 1, // 快速模式, 每次重发rto增加当前包rto的一半,不额外增加延时
DELAY_MODE_NO_DELAY = 2, // 极速模式, 每次重发rto增加基础rto的一半,不额外增加延时
};
static const uint32_t IKCP_ASK_SEND = 1; // need to send IKCP_CMD_WASK
static const uint32_t IKCP_ASK_TELL = 2; // need to send IKCP_CMD_WINS
static const uint32_t IKCP_RTO_NDL = 30; // no delay min rto
static const uint32_t IKCP_RTO_MIN = 100; // normal min rto
static const uint32_t IKCP_RTO_DEF = 200;
static const uint32_t IKCP_RTO_MAX = 60000;
static const uint32_t IKCP_WND_SND = 32;
static const uint32_t IKCP_WND_RCV = 128; // must >= max fragment size
static const uint32_t IKCP_MTU_DEF = 1400;
static const uint32_t IKCP_ACK_FAST = 3;
static const uint32_t IKCP_INTERVAL = 100;
static const uint32_t IKCP_THRESH_INIT = 2;
static const uint32_t IKCP_THRESH_MIN = 2;
static const uint32_t IKCP_PROBE_INIT = 7000; // 7 secs to probe window size
static const uint32_t IKCP_PROBE_LIMIT = 120000; // up to 120 secs to probe window
using onReadCB = std::function<void(const Buffer::Ptr &buf)>;
using onWriteCB = std::function<void(const Buffer::Ptr &buf)>;
using OnErr = std::function<void(const SockException &)>;
KcpTransport(bool serverMode);
KcpTransport(bool serverMode, const EventPoller::Ptr &poller);
virtual ~KcpTransport();
void setOnRead(onReadCB cb) { _on_read = std::move(cb); }
void setOnWrite(onWriteCB cb) { _on_write = std::move(cb); }
void setOnErr(OnErr cb) { _on_err = std::move(cb); }
void setPoller(const EventPoller::Ptr &poller) {
_poller = poller ? poller : EventPollerPool::Instance().getPoller();
}
// 应用层将数据放到发送队列中
ssize_t send(const Buffer::Ptr &buf, bool flush = false);
// 应用层将socket层接收到的数据输入
void input(const Buffer::Ptr &buf);
// change MTU size, default is 1400
void setMtu(int mtu);
void setInterval(int intervoal);
void setRxMinrto(int rx_minrto);
// set maximum window size: sndwnd=32, rcvwnd=32 by default
void setWndSize(int sndwnd, int rcvwnd);
//设置低延时模式
//默认DELAY_MODE_NORMAL
void setDelayMode(DelayMode delay_mode);
//设置快速重传的阈值
//默认0,即不会快速重传
void setFastResend(int resend);
//设置快速重传保守模式
//默认保守模式
void setFastackConserve(bool flag);
//设置是否关闭拥塞控制
//默认开启
void setNoCwnd(bool flag);
//设置是否开启流传输模式
//默认不开启
void setStreamMode(bool flag);
protected:
void onWrite(const Buffer::Ptr &buf) {
if (_on_write) {
_on_write(buf);
}
}
void onRead(const Buffer::Ptr &buf) {
if (_on_read) {
_on_read(buf);
}
}
void onErr(const SockException &err) {
DebugL;
if (_on_err) {
_on_err(err);
}
}
void startTimer();
//处理收到的数据,rcv_buf中有新数据时调用
void onData();
//测量rcv_queue 下一个可以提取的包的长度
int peeksize();
void handleAnyPacket(KcpPacket::Ptr packet);
void handleCmdAck(KcpPacket::Ptr packet, uint32_t current);
void handleCmdPush(KcpPacket::Ptr packet);
// move available data from rcv_buf -> rcv_queue
void sortRecvBuf();
void sortSendQueue();
//流模式,合并发送包
size_t mergeSendQueue(const char *buffer, size_t len);
// 将发送队列的数据真正发送出去
void update();
void sendSendQueue();
void sendAckList();
void sendProbePacket();
void sendPacket(KcpPacket::Ptr pkt, bool flush = false);
void flushPool();
//将发送缓存中对端已经确认的数据包丢弃
//UNA模式,指定序列之前的包都已经确认,可以Drop
void dropCacheByUna(uint32_t una);
//将发送缓存中对端已经确认的数据包丢弃
//ACK模式,仅指定序列的包被确认
void dropCacheByAck(uint32_t sn);
//更新rtt
void updateRtt(int32_t rtt);
//更新发送cache中packet的Faskack计数
void updateFastAck(uint32_t sn, uint32_t ts);
//扩大拥塞窗口
void increaseCwnd();
//缩小拥塞窗口
void decreaseCwnd(bool change, bool lost);
// get how many packet is waiting to be sent
int getWaitSnd();
int getRcvWndUnused();
private:
onReadCB _on_read = nullptr;
onWriteCB _on_write = nullptr;
OnErr _on_err = nullptr;
bool _server_mode;
bool _conv_init = false;
EventPoller::Ptr _poller = nullptr;
Timer::Ptr _timer;
//刷新计时器
Ticker _alive_ticker;
bool _fastack_conserve = false; //快速重传保守模式
uint32_t _conv; // 会话ID,用于标识一个会话
uint32_t _mtu = IKCP_MTU_DEF; // 最大传输单元,默认1400
uint32_t _mss = IKCP_MTU_DEF - KcpPacket::HEADER_SIZE; // 最大分片大小,由MTU计算得到
uint32_t _interval = IKCP_INTERVAL; //内部flush的率先哪个间隔
uint32_t _fastresend = 0; //快速重传触发阈值,当packet的_fastack超过该值时,触发快速重传
int _fastlimit = 5; //快速重传限制,限制触发快速重传的最大次数,防止过度重传
uint32_t _xmit = 0; //重传次数计数器
uint32_t _dead_link = 20; //最大重传次数,当某个包的重传次数超过该值时,认为链路断开
uint32_t _snd_una = 0; //发送缓冲区中第一个未确认的包序号
uint32_t _snd_nxt = 0; //下一个待分配的序号
uint32_t _rcv_nxt = 0; //接收队列中待接收的下一个包序号
uint32_t _ts_recent = 0; //最近一次收到数据包的时间戳
uint32_t _ts_lastack = 0;//最近一次发送ACK的时间戳
//rtt
int32_t _rx_rttval = 0; //RTT方差
int32_t _rx_srtt = 0; //RTT(平滑后)
int32_t _rx_rto = IKCP_RTO_DEF; //重传超时时间(会基于rtt和rtt方差动态调整)
int32_t _rx_minrto = IKCP_RTO_MIN; //最小重传超时时间,防止RTO过小
//for 拥塞窗口控制
uint32_t _snd_wnd = IKCP_WND_SND; //发送队列窗口,用于限制发送速率,用户配置(单位分片数量)
uint32_t _rcv_wnd = IKCP_WND_RCV; //接收队列窗口,用于限制接收速率,用户配置(单位分片数量)
uint32_t _rmt_wnd = IKCP_WND_RCV; //对端接收缓存拥塞窗口,对端通告(单位分片数量)
uint32_t _cwnd = 1; //发送缓存拥塞窗口大小,算法动态调整(单位分片数量)
uint32_t _incr = 0; //拥塞窗口增量,用于拥塞控制算法中动态窗口大小(单位字节)
uint32_t _ssthresh = IKCP_THRESH_INIT; //慢启动阈值
uint32_t _probe = 0; //探测标志,用于探测对端窗口大小
uint32_t _ts_probe = 0; //探测时间戳,记录发送窗口探测包的时间戳
uint32_t _probe_wait = 0;//探测等待时间, 控制探测包发送的时间间隔
DelayMode _delay_mode = DELAY_MODE_NORMAL;
int _nocwnd = false; //是否禁用拥塞控制
bool _stream = false; //是否开启流传输模式
//传输链路: userdata->_snd_queue->_snd_buf->网络发送
//_snd_queue:无限制
//_snd_buf: min(_snd_wnd, _rmt_wnd, _cwnd)
//传输链路: 网络接收->_rcv_buf->_snd_queue->userdata
//_rcv_buf:无限制,乱序数据暂存
//_snd_queue: _rcv_wnd
std::list<KcpDataPacket::Ptr> _snd_queue; //发送队列,还未进入发送窗口
std::list<KcpDataPacket::Ptr> _rcv_queue; //接收队列,已经接收完全的包等待交给应用层
std::list<KcpDataPacket::Ptr> _snd_buf; //发送缓存,已经进入发送窗口,用于重传
std::list<KcpDataPacket::Ptr> _rcv_buf; //接收缓存,已经接受,但是因为乱序丢包等还不能交给应用层
//待发送的ACK列表
std::deque<std::pair<uint32_t /*sn*/, uint32_t /*ts*/>>_acklist;
BufferRaw::Ptr _buffer_pool; //用于合并多个kcp包到一个udp包中
};
} // namespace toolkit
#endif // TOOLKIT_NETWORK_KCP_H