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

386 lines
13 KiB
C++
Raw Normal View History

/*
* 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