You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1329 lines
38 KiB
1329 lines
38 KiB
using System;
|
|
|
|
namespace SogClient
|
|
{
|
|
/*
|
|
0 4 5 6 8 (BYTE)
|
|
+---------------+---+---+-------+
|
|
| conv |cmd|frg| wnd |
|
|
+---------------+---+---+-------+ 8
|
|
| ts | sn |
|
|
+---------------+---------------+ 16
|
|
| una | len |
|
|
+---------------+---------------+ 24
|
|
| |
|
|
| DATA (optional) |
|
|
| |
|
|
+-------------------------------+
|
|
*/
|
|
public enum KcpProtocalType
|
|
{
|
|
SYN = 1,
|
|
ACK = 2,
|
|
FIN = 3,
|
|
}
|
|
|
|
public class Kcp
|
|
{
|
|
public const int IKCP_RTO_NDL = 30; // no delay min rto
|
|
public const int IKCP_RTO_MIN = 100; // normal min rto
|
|
public const int IKCP_RTO_DEF = 200;
|
|
public const int IKCP_RTO_MAX = 60000;
|
|
public const int IKCP_CMD_PUSH = 81; // cmd: push data
|
|
public const int IKCP_CMD_ACK = 82; // cmd: ack
|
|
public const int IKCP_CMD_WASK = 83; // cmd: window probe (ask)
|
|
public const int IKCP_CMD_WINS = 84; // cmd: window size (tell)
|
|
public const int IKCP_ASK_SEND = 1; // need to send IKCP_CMD_WASK
|
|
public const int IKCP_ASK_TELL = 2; // need to send IKCP_CMD_WINS
|
|
public const int IKCP_WND_SND = 32;
|
|
public const int IKCP_WND_RCV = 128;
|
|
public const int IKCP_MTU_DEF = 1400;
|
|
public const int IKCP_ACK_FAST = 3;
|
|
public const int IKCP_INTERVAL = 10; // tick间隔
|
|
public const int IKCP_OVERHEAD = 24;
|
|
public const int IKCP_DEADLINK = 20;
|
|
public const int IKCP_THRESH_INIT = 2;
|
|
public const int IKCP_THRESH_MIN = 2;
|
|
public const int IKCP_PROBE_INIT = 7000; // 7 secs to probe window size
|
|
public const int IKCP_PROBE_LIMIT = 120000; // up to 120 secs to probe window
|
|
|
|
public uint Conv
|
|
{
|
|
get { return conv; }
|
|
set { conv = value; }
|
|
}
|
|
|
|
public int State => state; // kcp state, -1 = dead link
|
|
|
|
public string Name;
|
|
|
|
// encode 8 bits unsigned int
|
|
public static int ikcp_encode8u(byte[] p, int offset, byte c)
|
|
{
|
|
p[0 + offset] = c;
|
|
return 1;
|
|
}
|
|
|
|
// decode 8 bits unsigned int
|
|
public static int ikcp_decode8u(byte[] p, int offset, ref byte c)
|
|
{
|
|
c = p[0 + offset];
|
|
return 1;
|
|
}
|
|
|
|
/* encode 16 bits unsigned int (lsb) */
|
|
public static int ikcp_encode16u(byte[] p, int offset, UInt16 w)
|
|
{
|
|
p[0 + offset] = (byte) (w >> 0);
|
|
p[1 + offset] = (byte) (w >> 8);
|
|
return 2;
|
|
}
|
|
|
|
/* decode 16 bits unsigned int (lsb) */
|
|
public static int ikcp_decode16u(byte[] p, int offset, ref UInt16 w)
|
|
{
|
|
UInt16 result = 0;
|
|
result |= p[0 + offset];
|
|
result |= (UInt16) (p[1 + offset] << 8);
|
|
w = result;
|
|
return 2;
|
|
}
|
|
|
|
/* encode 32 bits unsigned int (lsb) */
|
|
public static int ikcp_encode32u(byte[] p, int offset, UInt32 dw)
|
|
{
|
|
p[0 + offset] = (byte) (dw >> 0);
|
|
p[1 + offset] = (byte) (dw >> 8);
|
|
p[2 + offset] = (byte) (dw >> 16);
|
|
p[3 + offset] = (byte) (dw >> 24);
|
|
return 4;
|
|
}
|
|
|
|
/* decode 32 bits unsigned int (lsb) */
|
|
public static int ikcp_decode32u(byte[] p, int offset, ref UInt32 dw)
|
|
{
|
|
UInt32 result = 0;
|
|
result |= p[0 + offset];
|
|
result |= (UInt32) (p[1 + offset] << 8);
|
|
result |= (UInt32) (p[2 + offset] << 16);
|
|
result |= (UInt32) (p[3 + offset] << 24);
|
|
dw = result;
|
|
return 4;
|
|
}
|
|
|
|
// encode 64 bits unsigned int (lsb)
|
|
public static int ikcp_encode64u(byte[] p, int offset, UInt64 l)
|
|
{
|
|
p[0 + offset] = (byte) (l >> 0);
|
|
p[1 + offset] = (byte) (l >> 8);
|
|
p[2 + offset] = (byte) (l >> 16);
|
|
p[3 + offset] = (byte) (l >> 24);
|
|
p[4 + offset] = (byte) (l >> 32);
|
|
p[5 + offset] = (byte) (l >> 40);
|
|
p[6 + offset] = (byte) (l >> 48);
|
|
p[7 + offset] = (byte) (l >> 56);
|
|
return 8;
|
|
}
|
|
|
|
// decode 64 bits unsigned int (lsb)
|
|
public static int ikcp_decode64u(byte[] p, int offset, ref UInt64 l)
|
|
{
|
|
UInt64 result = 0;
|
|
result |= p[0 + offset];
|
|
result |= (UInt32) (p[1 + offset] << 8);
|
|
result |= (UInt32) (p[2 + offset] << 16);
|
|
result |= (UInt32) (p[3 + offset] << 24);
|
|
result |= (UInt32) (p[4 + offset] << 32);
|
|
result |= (UInt32) (p[5 + offset] << 40);
|
|
result |= (UInt32) (p[6 + offset] << 48);
|
|
result |= (UInt32) (p[7 + offset] << 56);
|
|
l = result;
|
|
return 8;
|
|
}
|
|
|
|
|
|
//todo slice太低效了
|
|
public static byte[] slice(byte[] p, int start, int stop)
|
|
{
|
|
var bytes = new byte[stop - start];
|
|
Array.Copy(p, start, bytes, 0, bytes.Length);
|
|
return bytes;
|
|
}
|
|
|
|
public static T[] slice<T>(T[] p, int start, int stop)
|
|
{
|
|
var arr = new T[stop - start];
|
|
int index = 0;
|
|
for (int i = start; i < stop; i++)
|
|
{
|
|
arr[index] = p[i];
|
|
index++;
|
|
}
|
|
|
|
return arr;
|
|
}
|
|
|
|
public static byte[] append(byte[] p, byte c)
|
|
{
|
|
var bytes = new byte[p.Length + 1];
|
|
Array.Copy(p, bytes, p.Length);
|
|
bytes[p.Length] = c;
|
|
return bytes;
|
|
}
|
|
|
|
public static T[] append<T>(T[] p, T c)
|
|
{
|
|
var arr = new T[p.Length + 1];
|
|
for (int i = 0; i < p.Length; i++)
|
|
{
|
|
arr[i] = p[i];
|
|
}
|
|
|
|
arr[p.Length] = c;
|
|
return arr;
|
|
}
|
|
|
|
public static T[] append<T>(T[] p, T[] cs)
|
|
{
|
|
var arr = new T[p.Length + cs.Length];
|
|
for (int i = 0; i < p.Length; i++)
|
|
{
|
|
arr[i] = p[i];
|
|
}
|
|
|
|
for (int i = 0; i < cs.Length; i++)
|
|
{
|
|
arr[p.Length + i] = cs[i];
|
|
}
|
|
|
|
return arr;
|
|
}
|
|
|
|
private static UInt32 _imin_(UInt32 a, UInt32 b)
|
|
{
|
|
return a <= b ? a : b;
|
|
}
|
|
|
|
private static UInt32 _imax_(UInt32 a, UInt32 b)
|
|
{
|
|
return a >= b ? a : b;
|
|
}
|
|
|
|
private static UInt32 _ibound_(UInt32 lower, UInt32 middle, UInt32 upper)
|
|
{
|
|
return _imin_(_imax_(lower, middle), upper);
|
|
}
|
|
|
|
private static Int32 _itimediff(UInt32 later, UInt32 earlier)
|
|
{
|
|
return (Int32) (later - earlier);
|
|
}
|
|
|
|
// KCP Segment Definition
|
|
internal class Segment
|
|
{
|
|
internal UInt32 conv;
|
|
internal UInt32 cmd;
|
|
internal UInt32 frg;
|
|
internal UInt32 wnd;
|
|
internal UInt32 ts;
|
|
internal UInt32 sn;
|
|
internal UInt32 una;
|
|
internal UInt32 resendts;
|
|
internal UInt32 rto;
|
|
internal UInt32 fastack;
|
|
internal UInt32 xmit;
|
|
internal byte[] data;
|
|
|
|
internal Segment(int size)
|
|
{
|
|
data = new byte[size];
|
|
}
|
|
|
|
// encode a segment into buffer
|
|
internal int encode(byte[] ptr, int offset)
|
|
{
|
|
int offset_ = offset;
|
|
|
|
offset += ikcp_encode32u(ptr, offset, conv);
|
|
offset += ikcp_encode8u(ptr, offset, (byte) cmd);
|
|
offset += ikcp_encode8u(ptr, offset, (byte) frg);
|
|
offset += ikcp_encode16u(ptr, offset, (UInt16) wnd);
|
|
offset += ikcp_encode32u(ptr, offset, ts);
|
|
offset += ikcp_encode32u(ptr, offset, sn);
|
|
offset += ikcp_encode32u(ptr, offset, una);
|
|
offset += ikcp_encode32u(ptr, offset, (UInt32) data.Length);
|
|
|
|
return offset - offset_;
|
|
}
|
|
}
|
|
|
|
// kcp members.
|
|
private /*readonly*/ UInt32 conv;
|
|
|
|
private UInt32 mtu;
|
|
private UInt32 mss;
|
|
private Int32 state;
|
|
private UInt32 snd_una;
|
|
private UInt32 snd_nxt;
|
|
|
|
private UInt32 rcv_nxt;
|
|
|
|
// private UInt32 ts_recent;
|
|
// private UInt32 ts_lastack;
|
|
private UInt32 ssthresh;
|
|
|
|
private UInt32 rx_rttval;
|
|
private UInt32 rx_srtt;
|
|
private UInt32 rx_rto;
|
|
private UInt32 rx_minrto;
|
|
private UInt32 snd_wnd;
|
|
private UInt32 rcv_wnd;
|
|
private UInt32 rmt_wnd;
|
|
private UInt32 cwnd;
|
|
private UInt32 probe;
|
|
private UInt32 current;
|
|
private UInt32 interval;
|
|
private UInt32 ts_flush;
|
|
private UInt32 xmit;
|
|
private UInt32 nodelay;
|
|
private UInt32 updated;
|
|
private UInt32 ts_probe;
|
|
private UInt32 probe_wait;
|
|
private readonly UInt32 dead_link;
|
|
private UInt32 incr;
|
|
|
|
private Segment[] snd_queue = new Segment[0];
|
|
private Segment[] rcv_queue = new Segment[0];
|
|
private Segment[] snd_buf = new Segment[0];
|
|
private Segment[] rcv_buf = new Segment[0];
|
|
|
|
private UInt32[] acklist = new UInt32[0];
|
|
|
|
private byte[] buffer;
|
|
private Int32 fastresend;
|
|
private Int32 nocwnd;
|
|
|
|
// private Int32 logmask;
|
|
|
|
// buffer, size
|
|
private readonly Action<byte[], int> output;
|
|
|
|
// create a new kcp control object, 'conv' must equal in two endpoint
|
|
// from the same connection.
|
|
public Kcp(UInt32 conv_, Action<byte[], int> output_)
|
|
{
|
|
conv = conv_;
|
|
snd_wnd = IKCP_WND_SND;
|
|
rcv_wnd = IKCP_WND_RCV;
|
|
rmt_wnd = IKCP_WND_RCV;
|
|
mtu = IKCP_MTU_DEF;
|
|
mss = mtu - IKCP_OVERHEAD;
|
|
|
|
rx_rto = IKCP_RTO_DEF;
|
|
rx_minrto = IKCP_RTO_MIN;
|
|
interval = IKCP_INTERVAL;
|
|
ts_flush = IKCP_INTERVAL;
|
|
ssthresh = IKCP_THRESH_INIT;
|
|
dead_link = IKCP_DEADLINK;
|
|
buffer = new byte[(mtu + IKCP_OVERHEAD) * 3];
|
|
output = output_;
|
|
|
|
Name = "";
|
|
}
|
|
|
|
// check the size of next message in the recv queue
|
|
public int PeekSize()
|
|
{
|
|
if (0 == rcv_queue.Length)
|
|
{
|
|
return -1;
|
|
}
|
|
|
|
Segment seq = rcv_queue[0];
|
|
|
|
if (0 == seq.frg)
|
|
{
|
|
return seq.data.Length;
|
|
}
|
|
|
|
if (rcv_queue.Length < seq.frg + 1)
|
|
{
|
|
return -1;
|
|
}
|
|
|
|
int length = 0;
|
|
|
|
foreach (Segment item in rcv_queue)
|
|
{
|
|
length += item.data.Length;
|
|
if (0 == item.frg)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
return length;
|
|
}
|
|
|
|
// user/upper level recv: returns size, returns below zero for EAGAIN
|
|
public int Recv(byte[] recvBuffer, int offSet, int len)
|
|
{
|
|
if (0 == rcv_queue.Length)
|
|
{
|
|
return -1;
|
|
}
|
|
|
|
int peekSize = PeekSize();
|
|
if (peekSize < 0)
|
|
{
|
|
return -2;
|
|
}
|
|
|
|
// 剩余空间一定要够一个包吗?如果不影响rcvQue\rcvBuf的话,我觉得这个判断没有必要
|
|
// 尽快把rcv_que消耗完,避免窗口变窄影响速率
|
|
// 判断对消息处理没影响,如果空间不够,还是必须下一次tick才能处理,拷出去也没啥用
|
|
|
|
if (peekSize > len)
|
|
{
|
|
return -3;
|
|
}
|
|
|
|
bool fast_recover = rcv_queue.Length >= rcv_wnd;
|
|
|
|
// merge fragment.
|
|
int count = 0;
|
|
int n = 0;
|
|
foreach (Segment seg in rcv_queue)
|
|
{
|
|
Array.Copy(seg.data, 0, recvBuffer, n, seg.data.Length);
|
|
n += seg.data.Length;
|
|
count++;
|
|
if (0 == seg.frg)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (0 < count)
|
|
{
|
|
//效率太低,考虑用linkedlist
|
|
rcv_queue = slice(rcv_queue, count, rcv_queue.Length);
|
|
}
|
|
|
|
// move available data from rcv_buf -> rcv_queue
|
|
count = 0;
|
|
foreach (Segment seg in rcv_buf)
|
|
{
|
|
if (seg.sn == rcv_nxt && rcv_queue.Length < rcv_wnd)
|
|
{
|
|
rcv_queue = append(rcv_queue, seg);
|
|
rcv_nxt++;
|
|
count++;
|
|
|
|
TraceLog.TraceDetail("Kcp.Recv conv {0} sn {1} move to rcv_queue num {2}"
|
|
, Conv, seg.sn, rcv_queue.Length);
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (0 < count)
|
|
{
|
|
rcv_buf = slice(rcv_buf, count, rcv_buf.Length);
|
|
|
|
TraceLog.TraceDetail("Kcp.Recv conv {0} rcv_buf new num {1}"
|
|
, Conv, rcv_buf.Length);
|
|
}
|
|
|
|
// fast recover
|
|
if (rcv_queue.Length < rcv_wnd && fast_recover)
|
|
{
|
|
probe |= IKCP_ASK_TELL;
|
|
}
|
|
|
|
return n;
|
|
}
|
|
|
|
// todo kcp需要一个控制sndQueue大小的机制,超过上限时告警
|
|
// user/upper level send, returns below zero for error
|
|
public int Send(byte[] bytes, int start, int length)
|
|
{
|
|
if (0 == bytes.Length)
|
|
{
|
|
return -1;
|
|
}
|
|
|
|
if (length == 0)
|
|
{
|
|
return -1;
|
|
}
|
|
|
|
int count = 0;
|
|
|
|
if (length < mss)
|
|
{
|
|
count = 1;
|
|
}
|
|
else
|
|
{
|
|
count = (int) (length + mss - 1) / (int) mss;
|
|
}
|
|
|
|
if (count > 255)
|
|
{
|
|
return -2;
|
|
}
|
|
|
|
if (count == 0)
|
|
{
|
|
count = 1;
|
|
}
|
|
|
|
int offset = 0;
|
|
|
|
for (int i = 0; i < count; i++)
|
|
{
|
|
int size = 0;
|
|
if (length - offset > mss)
|
|
{
|
|
size = (int) mss;
|
|
}
|
|
else
|
|
{
|
|
size = length - offset;
|
|
}
|
|
|
|
Segment seg = new Segment(size);
|
|
Array.Copy(bytes, start + offset, seg.data, 0, size);
|
|
offset += size;
|
|
seg.frg = (UInt32) (count - i - 1);
|
|
snd_queue = append(snd_queue, seg);
|
|
|
|
TraceLog.TraceDetail("Kcp.Send {3} conv {0} seg size {1} snd_queue num {2}"
|
|
, Conv, size, snd_queue.Length, Name);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
// 根据ack的时间动态修改rto
|
|
private void update_ack(Int32 rtt)
|
|
{
|
|
if (0 == rx_srtt)
|
|
{
|
|
rx_srtt = (UInt32) rtt;
|
|
rx_rttval = (UInt32) rtt / 2;
|
|
}
|
|
else
|
|
{
|
|
Int32 delta = (Int32) ((UInt32) rtt - rx_srtt);
|
|
if (delta < 0)
|
|
{
|
|
delta = -delta;
|
|
}
|
|
|
|
rx_rttval = (3 * rx_rttval + (uint) delta) / 4;
|
|
rx_srtt = (UInt32) ((7 * rx_srtt + rtt) / 8);
|
|
if (rx_srtt < 1)
|
|
{
|
|
rx_srtt = 1;
|
|
}
|
|
}
|
|
|
|
int rto = (int) (rx_srtt + _imax_(1, 4 * rx_rttval));
|
|
rx_rto = _ibound_(rx_minrto, (UInt32) rto, IKCP_RTO_MAX);
|
|
|
|
TraceLog.TraceDetail("Kcp.update_ack {7} conv {0} rx_rto {1} rx_minrto {2} rto {3} IKCP_RTO_MAX {4} rx_srtt {5} rx_rttval {6}"
|
|
, Conv, rx_rto, rx_minrto, rto, IKCP_RTO_MAX, rx_srtt, rx_rttval, Name);
|
|
}
|
|
|
|
private void shrink_buf()
|
|
{
|
|
if (snd_buf.Length > 0)
|
|
{
|
|
snd_una = snd_buf[0].sn;
|
|
}
|
|
else
|
|
{
|
|
snd_una = snd_nxt;
|
|
}
|
|
}
|
|
|
|
private void parse_ack(UInt32 sn)
|
|
{
|
|
if (_itimediff(sn, snd_una) < 0 || _itimediff(sn, snd_nxt) >= 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
int index = 0;
|
|
foreach (Segment seg in snd_buf)
|
|
{
|
|
if (sn == seg.sn)
|
|
{
|
|
snd_buf = append(slice(snd_buf, 0, index), slice(snd_buf, index + 1, snd_buf.Length));
|
|
|
|
TraceLog.TraceDetail("Kcp.parse_ack {3} conv {0} ack sn {1} snd_buf num {2}"
|
|
, Conv, sn, snd_buf.Length, Name);
|
|
break;
|
|
}
|
|
|
|
if (_itimediff(sn, seg.sn) < 0)
|
|
{
|
|
break;
|
|
}
|
|
|
|
index++;
|
|
}
|
|
}
|
|
|
|
private void parse_fastack(UInt32 sn)
|
|
{
|
|
if (_itimediff(sn, snd_una) < 0 || _itimediff(sn, snd_nxt) >= 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
//snd_buf收到后面sn的ack包时,会不会提前删掉该包
|
|
foreach (Segment seg in snd_buf)
|
|
{
|
|
if (_itimediff(seg.sn, sn) < 0)
|
|
{
|
|
seg.fastack++;
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
private void parse_una(UInt32 una)
|
|
{
|
|
int count = 0;
|
|
foreach (Segment seg in snd_buf)
|
|
{
|
|
if (_itimediff(una, seg.sn) > 0)
|
|
{
|
|
count++;
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (0 < count)
|
|
{
|
|
snd_buf = slice(snd_buf, count, snd_buf.Length);
|
|
}
|
|
}
|
|
|
|
private void ack_push(UInt32 sn, UInt32 ts)
|
|
{
|
|
acklist = append(acklist, new UInt32[2] {sn, ts});
|
|
}
|
|
|
|
private void ack_get(int p, ref UInt32 sn, ref UInt32 ts)
|
|
{
|
|
sn = acklist[p * 2 + 0];
|
|
ts = acklist[p * 2 + 1];
|
|
}
|
|
|
|
private void parse_data(Segment newseg)
|
|
{
|
|
uint sn = newseg.sn;
|
|
if (_itimediff(sn, rcv_nxt + rcv_wnd) >= 0 || _itimediff(sn, rcv_nxt) < 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
int n = rcv_buf.Length - 1;
|
|
int after_idx = -1;
|
|
bool repeat = false;
|
|
for (int i = n; i >= 0; i--)
|
|
{
|
|
Segment seg = rcv_buf[i];
|
|
if (seg.sn == sn)
|
|
{
|
|
repeat = true;
|
|
break;
|
|
}
|
|
|
|
if (_itimediff(sn, seg.sn) > 0)
|
|
{
|
|
after_idx = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (! repeat)
|
|
{
|
|
if (after_idx == -1)
|
|
{
|
|
rcv_buf = append(new Segment[1] {newseg}, rcv_buf);
|
|
}
|
|
else
|
|
{
|
|
rcv_buf = append(slice(rcv_buf, 0, after_idx + 1),
|
|
append(new Segment[1] {newseg}, slice(rcv_buf, after_idx + 1, rcv_buf.Length)));
|
|
}
|
|
|
|
TraceLog.TraceDetail("Kcp.parse_data {3} conv {0} recv new sn {1} rcv_buf num {2}"
|
|
, Conv, sn, rcv_buf.Length, Name);
|
|
}
|
|
|
|
// move available data from rcv_buf -> rcv_queue
|
|
int count = 0;
|
|
foreach (Segment seg in rcv_buf)
|
|
{
|
|
if (seg.sn == rcv_nxt && rcv_queue.Length < rcv_wnd)
|
|
{
|
|
rcv_queue = append(rcv_queue, seg);
|
|
rcv_nxt++;
|
|
count++;
|
|
|
|
TraceLog.TraceDetail("Kcp.parse_data {3} conv {0} sn {1} move to rcv_queue num {2}"
|
|
, Conv, sn, rcv_queue.Length, Name);
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (0 < count)
|
|
{
|
|
rcv_buf = slice(rcv_buf, count, rcv_buf.Length);
|
|
|
|
TraceLog.TraceDetail("Kcp.parse_data {3} conv {0} rcv_queue num {1} rcv_buf num {2}"
|
|
, Conv, rcv_queue.Length, rcv_buf.Length, Name);
|
|
}
|
|
}
|
|
|
|
// when you received a low level packet (eg. UDP packet), call it
|
|
public int Input(byte[] data, int dataLen)
|
|
{
|
|
uint s_una = snd_una;
|
|
|
|
// if (data.Length < IKCP_OVERHEAD)
|
|
if (dataLen < IKCP_OVERHEAD)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
int offset = 0;
|
|
uint maxack = 0;
|
|
|
|
while (true)
|
|
{
|
|
UInt32 ts = 0;
|
|
UInt32 sn = 0;
|
|
UInt32 length = 0;
|
|
UInt32 una = 0;
|
|
UInt32 conv_ = 0;
|
|
|
|
UInt16 wnd = 0;
|
|
|
|
byte cmd = 0;
|
|
byte frg = 0;
|
|
|
|
|
|
// if (data.Length - offset < IKCP_OVERHEAD)
|
|
if (dataLen - offset < IKCP_OVERHEAD)
|
|
{
|
|
break;
|
|
}
|
|
|
|
offset += ikcp_decode32u(data, offset, ref conv_);
|
|
|
|
// 这里我做了修改,不判断两端kcp conv相等,因为客户端也需要一个socket支持多个client连接
|
|
//if (conv != conv_)
|
|
// return -1;
|
|
|
|
offset += ikcp_decode8u(data, offset, ref cmd);
|
|
offset += ikcp_decode8u(data, offset, ref frg);
|
|
offset += ikcp_decode16u(data, offset, ref wnd);
|
|
offset += ikcp_decode32u(data, offset, ref ts);
|
|
offset += ikcp_decode32u(data, offset, ref sn);
|
|
offset += ikcp_decode32u(data, offset, ref una);
|
|
offset += ikcp_decode32u(data, offset, ref length);
|
|
|
|
// if (data.Length - offset < length)
|
|
if (dataLen - offset < length)
|
|
{
|
|
return -2;
|
|
}
|
|
|
|
switch (cmd)
|
|
{
|
|
case IKCP_CMD_PUSH:
|
|
case IKCP_CMD_ACK:
|
|
case IKCP_CMD_WASK:
|
|
case IKCP_CMD_WINS:
|
|
break;
|
|
default:
|
|
return -3;
|
|
}
|
|
|
|
rmt_wnd = wnd; //remote wnd
|
|
TraceLog.TraceDetail("Kcp.Input {4} conv {0} sn {1} remote wnd {2} una {3}"
|
|
, Conv, sn, rmt_wnd, una, Name);
|
|
|
|
parse_una(una);
|
|
shrink_buf();
|
|
|
|
if (IKCP_CMD_ACK == cmd)
|
|
{
|
|
if (_itimediff(current, ts) >= 0)
|
|
{
|
|
update_ack(_itimediff(current, ts));
|
|
}
|
|
|
|
parse_ack(sn);
|
|
shrink_buf();
|
|
|
|
if (_itimediff(sn, maxack) > 0)
|
|
{
|
|
maxack = sn;
|
|
}
|
|
}
|
|
else if (IKCP_CMD_PUSH == cmd)
|
|
{
|
|
if (_itimediff(sn, rcv_nxt + rcv_wnd) < 0)
|
|
{
|
|
ack_push(sn, ts);
|
|
if (_itimediff(sn, rcv_nxt) >= 0)
|
|
{
|
|
Segment seg = new Segment((int) length);
|
|
seg.conv = conv_;
|
|
seg.cmd = cmd;
|
|
seg.frg = frg;
|
|
seg.wnd = wnd; //对方的wnd
|
|
seg.ts = ts;
|
|
seg.sn = sn;
|
|
seg.una = una;
|
|
|
|
if (length > 0)
|
|
{
|
|
Array.Copy(data, offset, seg.data, 0, length);
|
|
}
|
|
|
|
parse_data(seg);
|
|
}
|
|
}
|
|
}
|
|
else if (IKCP_CMD_WASK == cmd)
|
|
{
|
|
// ready to send back IKCP_CMD_WINS in Ikcp_flush
|
|
// tell remote my window size
|
|
probe |= IKCP_ASK_TELL;
|
|
}
|
|
else if (IKCP_CMD_WINS == cmd)
|
|
{
|
|
// do nothing
|
|
}
|
|
else
|
|
{
|
|
return -3;
|
|
}
|
|
|
|
offset += (int) length;
|
|
}
|
|
|
|
if (maxack != 0)
|
|
{
|
|
parse_fastack(maxack);
|
|
}
|
|
|
|
if (_itimediff(snd_una, s_una) > 0)
|
|
{
|
|
if (cwnd < rmt_wnd)
|
|
{
|
|
uint mss_ = mss;
|
|
if (cwnd < ssthresh)
|
|
{
|
|
cwnd++;
|
|
incr += mss_;
|
|
}
|
|
else
|
|
{
|
|
if (incr < mss_)
|
|
{
|
|
incr = mss_;
|
|
}
|
|
|
|
incr += mss_ * mss_ / incr + mss_ / 16;
|
|
if ((cwnd + 1) * mss_ <= incr)
|
|
{
|
|
cwnd++;
|
|
}
|
|
}
|
|
if (cwnd > rmt_wnd)
|
|
{
|
|
cwnd = rmt_wnd;
|
|
incr = rmt_wnd * mss_;
|
|
}
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
private Int32 wnd_unused()
|
|
{
|
|
if (rcv_queue.Length < rcv_wnd)
|
|
{
|
|
return (int) rcv_wnd - rcv_queue.Length;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
// flush pending data
|
|
private void flush()
|
|
{
|
|
uint current_ = current;
|
|
var buffer_ = buffer;
|
|
int change = 0;
|
|
int lost = 0;
|
|
|
|
if (0 == updated)
|
|
{
|
|
return;
|
|
}
|
|
|
|
Segment seg = new Segment(0);
|
|
seg.conv = conv;
|
|
seg.cmd = IKCP_CMD_ACK;
|
|
seg.wnd = (UInt32) wnd_unused();
|
|
seg.una = rcv_nxt;
|
|
// TraceLog.TraceDetail("Kcp.flush {3} conv {0} local wnd {1} una {2}"
|
|
// , Conv, seg.wnd, seg.una, Name);
|
|
|
|
// flush acknowledges
|
|
int count = acklist.Length / 2;
|
|
int offset = 0;
|
|
for (int i = 0; i < count; i++)
|
|
{
|
|
if (offset + IKCP_OVERHEAD > mtu)
|
|
{
|
|
output(buffer, offset);
|
|
//Array.Clear(buffer, 0, offset);
|
|
offset = 0;
|
|
}
|
|
ack_get(i, ref seg.sn, ref seg.ts);
|
|
offset += seg.encode(buffer, offset);
|
|
}
|
|
acklist = new UInt32[0];
|
|
|
|
// probe window size (if remote window size equals zero)
|
|
if (0 == rmt_wnd)
|
|
{
|
|
if (0 == probe_wait)
|
|
{
|
|
probe_wait = IKCP_PROBE_INIT;
|
|
ts_probe = current + probe_wait;
|
|
}
|
|
else
|
|
{
|
|
if (_itimediff(current, ts_probe) >= 0)
|
|
{
|
|
if (probe_wait < IKCP_PROBE_INIT)
|
|
{
|
|
probe_wait = IKCP_PROBE_INIT;
|
|
}
|
|
|
|
probe_wait += probe_wait / 2;
|
|
if (probe_wait > IKCP_PROBE_LIMIT)
|
|
{
|
|
probe_wait = IKCP_PROBE_LIMIT;
|
|
}
|
|
|
|
ts_probe = current + probe_wait;
|
|
probe |= IKCP_ASK_SEND;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ts_probe = 0;
|
|
probe_wait = 0;
|
|
}
|
|
|
|
// flush window probing commands
|
|
if ((probe & IKCP_ASK_SEND) != 0)
|
|
{
|
|
seg.cmd = IKCP_CMD_WASK;
|
|
if (offset + IKCP_OVERHEAD > (int) mtu)
|
|
{
|
|
output(buffer, offset);
|
|
//Array.Clear(buffer, 0, offset);
|
|
offset = 0;
|
|
}
|
|
offset += seg.encode(buffer, offset);
|
|
}
|
|
|
|
probe = 0;
|
|
|
|
// calculate window size
|
|
uint cwnd_ = _imin_(snd_wnd, rmt_wnd);
|
|
if (0 == nocwnd)
|
|
{
|
|
cwnd_ = _imin_(cwnd, cwnd_);
|
|
}
|
|
|
|
count = 0;
|
|
for (int k = 0; k < snd_queue.Length; k++)
|
|
{
|
|
if (_itimediff(snd_nxt, snd_una + cwnd_) >= 0)
|
|
{
|
|
break;
|
|
}
|
|
|
|
Segment newseg = snd_queue[k];
|
|
newseg.conv = conv;
|
|
newseg.cmd = IKCP_CMD_PUSH;
|
|
newseg.wnd = seg.wnd;
|
|
newseg.ts = current_;
|
|
newseg.sn = snd_nxt;
|
|
newseg.una = rcv_nxt;
|
|
newseg.resendts = current_;
|
|
newseg.rto = rx_rto;
|
|
newseg.fastack = 0;
|
|
newseg.xmit = 0;
|
|
snd_buf = append(snd_buf, newseg);
|
|
snd_nxt++;
|
|
count++;
|
|
|
|
// TraceLog.TraceDetail("Kcp.flush {3} conv {0} sn {1} move to snd_buf num {2}"
|
|
// , Conv, seg.sn, snd_buf.Length, Name);
|
|
}
|
|
|
|
if (0 < count)
|
|
{
|
|
snd_queue = slice(snd_queue, count, snd_queue.Length);
|
|
// TraceLog.TraceDetail("Kcp.flush {2} conv {0} snd_queue new num {1}"
|
|
// , Conv, snd_queue.Length, Name);
|
|
}
|
|
|
|
// calculate resent
|
|
uint resent = (UInt32) fastresend;
|
|
if (fastresend <= 0)
|
|
{
|
|
resent = 0xffffffff;
|
|
}
|
|
|
|
uint rtomin = rx_rto >> 3;
|
|
if (nodelay != 0)
|
|
{
|
|
rtomin = 0;
|
|
}
|
|
|
|
// flush data segments
|
|
foreach (Segment segment in snd_buf)
|
|
{
|
|
bool needsend = false;
|
|
int debug = _itimediff(current_, segment.resendts);
|
|
if (0 == segment.xmit)
|
|
{
|
|
needsend = true;
|
|
segment.xmit++;
|
|
segment.rto = rx_rto;
|
|
segment.resendts = current_ + segment.rto + rtomin;
|
|
}
|
|
else if (_itimediff(current_, segment.resendts) >= 0)
|
|
{
|
|
needsend = true;
|
|
segment.xmit++;
|
|
xmit++;
|
|
if (0 == nodelay)
|
|
{
|
|
segment.rto += rx_rto;
|
|
}
|
|
else
|
|
{
|
|
segment.rto += rx_rto / 2;
|
|
}
|
|
|
|
segment.resendts = current_ + segment.rto;
|
|
lost = 1;
|
|
}
|
|
else if (segment.fastack >= resent)
|
|
{
|
|
needsend = true;
|
|
segment.xmit++;
|
|
segment.fastack = 0;
|
|
segment.resendts = current_ + segment.rto;
|
|
change++;
|
|
}
|
|
|
|
if (needsend)
|
|
{
|
|
segment.ts = current_;
|
|
segment.wnd = seg.wnd;
|
|
segment.una = rcv_nxt;
|
|
|
|
int need = IKCP_OVERHEAD + segment.data.Length;
|
|
if (offset + need > mtu)
|
|
{
|
|
output(buffer, offset);
|
|
//Array.Clear(buffer, 0, offset);
|
|
offset = 0;
|
|
}
|
|
|
|
offset += segment.encode(buffer, offset);
|
|
if (segment.data.Length > 0)
|
|
{
|
|
Array.Copy(segment.data, 0, buffer, offset, segment.data.Length);
|
|
offset += segment.data.Length;
|
|
}
|
|
|
|
if (segment.xmit >= dead_link)
|
|
{
|
|
state = -1; //重传超过上限
|
|
}
|
|
}
|
|
}
|
|
|
|
// flash remain segments
|
|
if (offset > 0)
|
|
{
|
|
output(buffer, offset);
|
|
//Array.Clear(buffer, 0, offset);
|
|
offset = 0;
|
|
}
|
|
|
|
// update ssthresh
|
|
if (change != 0)
|
|
{
|
|
uint inflight = snd_nxt - snd_una;
|
|
ssthresh = inflight / 2;
|
|
if (ssthresh < IKCP_THRESH_MIN)
|
|
{
|
|
ssthresh = IKCP_THRESH_MIN;
|
|
}
|
|
|
|
cwnd = ssthresh + resent;
|
|
incr = cwnd * mss;
|
|
}
|
|
|
|
if (lost != 0)
|
|
{
|
|
ssthresh = cwnd / 2;
|
|
if (ssthresh < IKCP_THRESH_MIN)
|
|
{
|
|
ssthresh = IKCP_THRESH_MIN;
|
|
}
|
|
|
|
cwnd = 1;
|
|
incr = mss;
|
|
}
|
|
|
|
if (cwnd < 1)
|
|
{
|
|
cwnd = 1;
|
|
incr = mss;
|
|
}
|
|
}
|
|
|
|
// update state (call it repeatedly, every 10ms-100ms), or you can ask
|
|
// ikcp_check when to call it again (without ikcp_input/_send calling).
|
|
// 'current' - current timestamp in millisec.
|
|
public void Update(UInt32 current_)
|
|
{
|
|
current = current_;
|
|
|
|
if (0 == updated)
|
|
{
|
|
updated = 1;
|
|
ts_flush = current;
|
|
}
|
|
|
|
int slap = _itimediff(current, ts_flush);
|
|
|
|
if (slap >= 10000 || slap < -10000)
|
|
{
|
|
ts_flush = current;
|
|
slap = 0;
|
|
}
|
|
|
|
if (slap >= 0)
|
|
{
|
|
ts_flush += interval;
|
|
if (_itimediff(current, ts_flush) >= 0)
|
|
{
|
|
ts_flush = current + interval;
|
|
}
|
|
|
|
flush();
|
|
}
|
|
}
|
|
|
|
// Determine when should you invoke ikcp_update:
|
|
// returns when you should invoke ikcp_update in millisec, if there
|
|
// is no ikcp_input/_send calling. you can call ikcp_update in that
|
|
// time, instead of call update repeatly.
|
|
// Important to reduce unnacessary ikcp_update invoking. use it to
|
|
// schedule ikcp_update (eg. implementing an epoll-like mechanism,
|
|
// or optimize ikcp_update when handling massive kcp connections)
|
|
public UInt32 Check(UInt32 current_)
|
|
{
|
|
if (0 == updated)
|
|
{
|
|
return current_;
|
|
}
|
|
|
|
uint ts_flush_ = ts_flush;
|
|
int tm_flush_ = 0x7fffffff;
|
|
int tm_packet = 0x7fffffff;
|
|
int minimal = 0;
|
|
|
|
if (_itimediff(current_, ts_flush_) >= 10000 || _itimediff(current_, ts_flush_) < -10000)
|
|
{
|
|
ts_flush_ = current_;
|
|
}
|
|
|
|
if (_itimediff(current_, ts_flush_) >= 0)
|
|
{
|
|
return current_;
|
|
}
|
|
|
|
tm_flush_ = _itimediff(ts_flush_, current_);
|
|
|
|
foreach (Segment seg in snd_buf)
|
|
{
|
|
int diff = _itimediff(seg.resendts, current_);
|
|
if (diff <= 0)
|
|
{
|
|
return current_;
|
|
}
|
|
|
|
if (diff < tm_packet)
|
|
{
|
|
tm_packet = diff;
|
|
}
|
|
}
|
|
|
|
minimal = tm_packet;
|
|
if (tm_packet >= tm_flush_)
|
|
{
|
|
minimal = tm_flush_;
|
|
}
|
|
|
|
if (minimal >= interval)
|
|
{
|
|
minimal = (int) interval;
|
|
}
|
|
|
|
return current_ + (UInt32) minimal;
|
|
}
|
|
|
|
// change MTU size, default is 1400
|
|
public int SetMtu(Int32 mtu_)
|
|
{
|
|
if (mtu_ < 50 || mtu_ < IKCP_OVERHEAD)
|
|
{
|
|
return -1;
|
|
}
|
|
|
|
var buffer_ = new byte[(mtu_ + IKCP_OVERHEAD) * 3];
|
|
if (null == buffer_)
|
|
{
|
|
return -2;
|
|
}
|
|
|
|
mtu = (UInt32) mtu_;
|
|
mss = mtu - IKCP_OVERHEAD;
|
|
buffer = buffer_;
|
|
return 0;
|
|
}
|
|
|
|
public int Interval(Int32 interval_)
|
|
{
|
|
if (interval_ > 5000)
|
|
{
|
|
interval_ = 5000;
|
|
}
|
|
else if (interval_ < 10)
|
|
{
|
|
interval_ = 10;
|
|
}
|
|
|
|
interval = (UInt32) interval_;
|
|
return 0;
|
|
}
|
|
|
|
// fastest: ikcp_nodelay(kcp, 1, 20, 2, 1)
|
|
// nodelay: 0:disable(default), 1:enable
|
|
// interval: internal update timer interval in millisec, default is 100ms
|
|
// resend: 0:disable fast resend(default), 1:enable fast resend
|
|
// nc: 0:normal congestion control(default), 1:disable congestion control
|
|
public int NoDelay(int nodelay_, int interval_, int resend_, int nc_)
|
|
{
|
|
if (nodelay_ > 0)
|
|
{
|
|
nodelay = (UInt32) nodelay_;
|
|
if (nodelay_ != 0)
|
|
{
|
|
rx_minrto = IKCP_RTO_NDL;
|
|
}
|
|
else
|
|
{
|
|
rx_minrto = IKCP_RTO_MIN;
|
|
}
|
|
}
|
|
|
|
if (interval_ >= 0)
|
|
{
|
|
if (interval_ > 5000)
|
|
{
|
|
interval_ = 5000;
|
|
}
|
|
else if (interval_ < 10)
|
|
{
|
|
interval_ = 10;
|
|
}
|
|
|
|
interval = (UInt32) interval_;
|
|
}
|
|
|
|
if (resend_ >= 0)
|
|
{
|
|
fastresend = resend_;
|
|
}
|
|
|
|
if (nc_ >= 0)
|
|
{
|
|
nocwnd = nc_;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
// set maximum window size: sndwnd=32, rcvwnd=32 by default
|
|
public int WndSize(int sndwnd, int rcvwnd)
|
|
{
|
|
if (sndwnd > 0)
|
|
{
|
|
snd_wnd = (UInt32) sndwnd;
|
|
}
|
|
|
|
if (rcvwnd > 0)
|
|
{
|
|
rcv_wnd = (UInt32) rcvwnd;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
// get how many packet is waiting to be sent
|
|
public int WaitSnd()
|
|
{
|
|
return snd_buf.Length + snd_queue.Length;
|
|
}
|
|
}
|
|
}
|