1841 lines
51 KiB
C#
1841 lines
51 KiB
C#
|
|
using System;
|
|||
|
|
using System.Collections;
|
|||
|
|
using System.Collections.Generic;
|
|||
|
|
using System.Linq;
|
|||
|
|
using DotNetty.Buffers;
|
|||
|
|
using fec;
|
|||
|
|
using Object = System.Object;
|
|||
|
|
|
|||
|
|
namespace base_kcp
|
|||
|
|
{
|
|||
|
|
public class Kcp
|
|||
|
|
{
|
|||
|
|
/**
|
|||
|
|
* no delay min rto
|
|||
|
|
*/
|
|||
|
|
public const int IKCP_RTO_NDL = 30;
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* normal min rto
|
|||
|
|
*/
|
|||
|
|
public const int IKCP_RTO_MIN = 100;
|
|||
|
|
|
|||
|
|
public const int IKCP_RTO_DEF = 200;
|
|||
|
|
|
|||
|
|
public const int IKCP_RTO_MAX = 60000;
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* cmd: push data
|
|||
|
|
*/
|
|||
|
|
public const byte IKCP_CMD_PUSH = 81;
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* cmd: ack
|
|||
|
|
*/
|
|||
|
|
public const byte IKCP_CMD_ACK = 82;
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* cmd: window probe (ask)
|
|||
|
|
* 询问对方当前剩余窗口大小 请求
|
|||
|
|
*/
|
|||
|
|
public const byte IKCP_CMD_WASK = 83;
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* cmd: window size (tell)
|
|||
|
|
* 返回本地当前剩余窗口大小
|
|||
|
|
*/
|
|||
|
|
public const byte IKCP_CMD_WINS = 84;
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* need to send IKCP_CMD_WASK
|
|||
|
|
*/
|
|||
|
|
public const int IKCP_ASK_SEND = 1;
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* need to send IKCP_CMD_WINS
|
|||
|
|
*/
|
|||
|
|
public const int IKCP_ASK_TELL = 2;
|
|||
|
|
|
|||
|
|
public const int IKCP_WND_SND = 32;
|
|||
|
|
|
|||
|
|
public const int IKCP_WND_RCV = 32;
|
|||
|
|
|
|||
|
|
public const int IKCP_MTU_DEF = 1400;
|
|||
|
|
|
|||
|
|
public const int IKCP_INTERVAL = 100;
|
|||
|
|
|
|||
|
|
public int IKCP_OVERHEAD = 24;
|
|||
|
|
|
|||
|
|
public const int IKCP_DEADLINK = 20;
|
|||
|
|
|
|||
|
|
public const int IKCP_THRESH_INIT = 2;
|
|||
|
|
|
|||
|
|
public const int IKCP_THRESH_MIN = 2;
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 7 secs to probe window size
|
|||
|
|
*/
|
|||
|
|
public const int IKCP_PROBE_INIT = 7000;
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* up to 120 secs to probe window
|
|||
|
|
*/
|
|||
|
|
public const int IKCP_PROBE_LIMIT = 120000;
|
|||
|
|
|
|||
|
|
public const int IKCP_SN_OFFSET = 12;
|
|||
|
|
|
|||
|
|
|
|||
|
|
private int ackMaskSize = 0;
|
|||
|
|
|
|||
|
|
/**会话id**/
|
|||
|
|
private int conv;
|
|||
|
|
|
|||
|
|
/**最大传输单元**/
|
|||
|
|
private int mtu = IKCP_MTU_DEF;
|
|||
|
|
|
|||
|
|
/**最大分节大小 mtu减去头等部分**/
|
|||
|
|
private int mss = 0;
|
|||
|
|
|
|||
|
|
/**状态**/
|
|||
|
|
private int state;
|
|||
|
|
|
|||
|
|
/**已发送但未确认**/
|
|||
|
|
private long sndUna;
|
|||
|
|
|
|||
|
|
/**下次发送下标**/
|
|||
|
|
private long sndNxt;
|
|||
|
|
|
|||
|
|
/**下次接收下标**/
|
|||
|
|
private long rcvNxt;
|
|||
|
|
|
|||
|
|
/**上次ack时间**/
|
|||
|
|
private long tsLastack;
|
|||
|
|
|
|||
|
|
/**慢启动门限**/
|
|||
|
|
private int ssthresh = IKCP_THRESH_INIT;
|
|||
|
|
|
|||
|
|
/**RTT(Round Trip Time)**/
|
|||
|
|
private int rxRttval;
|
|||
|
|
|
|||
|
|
/**SRTT平滑RTT*/
|
|||
|
|
private int rxSrtt;
|
|||
|
|
|
|||
|
|
/**RTO重传超时*/
|
|||
|
|
private int rxRto = IKCP_RTO_DEF;
|
|||
|
|
|
|||
|
|
/**MinRTO最小重传超时*/
|
|||
|
|
private int rxMinrto = IKCP_RTO_MIN;
|
|||
|
|
|
|||
|
|
/**发送窗口**/
|
|||
|
|
private int sndWnd = IKCP_WND_SND;
|
|||
|
|
|
|||
|
|
/**接收窗口**/
|
|||
|
|
private int rcvWnd = IKCP_WND_RCV;
|
|||
|
|
|
|||
|
|
/**当前对端可接收窗口**/
|
|||
|
|
private int rmtWnd = IKCP_WND_RCV;
|
|||
|
|
|
|||
|
|
/**拥塞控制窗口**/
|
|||
|
|
private int cwnd;
|
|||
|
|
|
|||
|
|
/**探测标志位**/
|
|||
|
|
private int probe;
|
|||
|
|
|
|||
|
|
///**当前时间**/
|
|||
|
|
//private long current;
|
|||
|
|
/**间隔**/
|
|||
|
|
private int interval = IKCP_INTERVAL;
|
|||
|
|
|
|||
|
|
/**发送**/
|
|||
|
|
private long tsFlush = IKCP_INTERVAL;
|
|||
|
|
|
|||
|
|
/**是否无延迟 0不启用;1启用**/
|
|||
|
|
private bool nodelay;
|
|||
|
|
|
|||
|
|
/**状态是否已更新**/
|
|||
|
|
private bool updated;
|
|||
|
|
|
|||
|
|
/**探测时间**/
|
|||
|
|
private long tsProbe;
|
|||
|
|
|
|||
|
|
/**探测等待**/
|
|||
|
|
private int probeWait;
|
|||
|
|
|
|||
|
|
/**死连接 重传达到该值时认为连接是断开的**/
|
|||
|
|
private int deadLink = IKCP_DEADLINK;
|
|||
|
|
|
|||
|
|
/**拥塞控制增量**/
|
|||
|
|
private int incr;
|
|||
|
|
|
|||
|
|
/**收到包立即回ack**/
|
|||
|
|
private bool ackNoDelay;
|
|||
|
|
|
|||
|
|
/**待发送窗口窗口**/
|
|||
|
|
private Queue<Segment> sndQueue = new Queue<Segment>();
|
|||
|
|
|
|||
|
|
/**收到后有序的队列**/
|
|||
|
|
private LinkedList<Segment> rcvQueue = new LinkedList<Segment>();
|
|||
|
|
|
|||
|
|
/**发送后待确认的队列**/
|
|||
|
|
public LinkedList<Segment> sndBuf = new LinkedList<Segment>();
|
|||
|
|
|
|||
|
|
/**收到的消息 无序的**/
|
|||
|
|
private LinkedList<Segment> rcvBuf = new LinkedList<Segment>();
|
|||
|
|
|
|||
|
|
private long[] acklist = new long[8];
|
|||
|
|
|
|||
|
|
private int ackcount;
|
|||
|
|
|
|||
|
|
private Object user;
|
|||
|
|
|
|||
|
|
/**是否快速重传 默认0关闭,可以设置2(2次ACK跨越将会直接重传)**/
|
|||
|
|
private int fastresend;
|
|||
|
|
|
|||
|
|
/**是否关闭拥塞控制窗口**/
|
|||
|
|
private bool nocwnd;
|
|||
|
|
|
|||
|
|
/**是否流传输**/
|
|||
|
|
private bool stream;
|
|||
|
|
|
|||
|
|
/**头部预留长度 为fec checksum准备**/
|
|||
|
|
private int reserved;
|
|||
|
|
|
|||
|
|
private KcpOutput output;
|
|||
|
|
|
|||
|
|
private IByteBufferAllocator byteBufAllocator = PooledByteBufferAllocator.Default;
|
|||
|
|
|
|||
|
|
/**ack二进制标识**/
|
|||
|
|
private long ackMask;
|
|||
|
|
private long lastRcvNxt;
|
|||
|
|
|
|||
|
|
|
|||
|
|
private static long long2Uint(long n)
|
|||
|
|
{
|
|||
|
|
return n & 0x00000000FFFFFFFFL;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private static int ibound(int lower, int middle, int upper)
|
|||
|
|
{
|
|||
|
|
return Math.Min(Math.Max(lower, middle), upper);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private static int itimediff(long later, long earlier)
|
|||
|
|
{
|
|||
|
|
return (int) (later - earlier);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private static void outPut(IByteBuffer data, Kcp kcp)
|
|||
|
|
{
|
|||
|
|
// if (log.isDebugEnabled()) {
|
|||
|
|
// log.debug("{} [RO] {} bytes", kcp, data.readableBytes());
|
|||
|
|
// }
|
|||
|
|
if (data.ReadableBytes == 0)
|
|||
|
|
{
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
kcp.output.outPut(data, kcp);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
private static void encodeSeg(IByteBuffer buf, Segment seg)
|
|||
|
|
{
|
|||
|
|
int offset = buf.WriterIndex;
|
|||
|
|
|
|||
|
|
buf.WriteIntLE(seg.Conv);
|
|||
|
|
buf.WriteByte(seg.Cmd);
|
|||
|
|
buf.WriteByte(seg.Frg);
|
|||
|
|
buf.WriteShortLE(seg.Wnd);
|
|||
|
|
buf.WriteIntLE((int) seg.Ts);
|
|||
|
|
buf.WriteIntLE((int) seg.Sn);
|
|||
|
|
buf.WriteIntLE((int) seg.Una);
|
|||
|
|
buf.WriteIntLE(seg.Data.ReadableBytes);
|
|||
|
|
switch (seg.AckMaskSize)
|
|||
|
|
{
|
|||
|
|
case 8:
|
|||
|
|
buf.WriteByte((int) seg.AckMask);
|
|||
|
|
break;
|
|||
|
|
case 16:
|
|||
|
|
buf.WriteShortLE((int) seg.AckMask);
|
|||
|
|
break;
|
|||
|
|
case 32:
|
|||
|
|
buf.WriteIntLE((int) seg.AckMask);
|
|||
|
|
break;
|
|||
|
|
case 64:
|
|||
|
|
buf.WriteLongLE(seg.AckMask);
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
Snmp.snmp.OutSegs++;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public Kcp(int conv, KcpOutput output)
|
|||
|
|
{
|
|||
|
|
this.conv = conv;
|
|||
|
|
this.output = output;
|
|||
|
|
this.mss = mtu - IKCP_OVERHEAD;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public void release()
|
|||
|
|
{
|
|||
|
|
release(sndBuf);
|
|||
|
|
release(rcvBuf);
|
|||
|
|
release(sndQueue);
|
|||
|
|
release(rcvQueue);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private void release(ICollection segQueue)
|
|||
|
|
{
|
|||
|
|
foreach (Segment seg in segQueue)
|
|||
|
|
{
|
|||
|
|
seg.recycle(true);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private IByteBuffer createFlushByteBuf()
|
|||
|
|
{
|
|||
|
|
return byteBufAllocator.DirectBuffer(this.mtu);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public IByteBuffer mergeRecv()
|
|||
|
|
{
|
|||
|
|
if (rcvQueue.Count == 0)
|
|||
|
|
{
|
|||
|
|
return null;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
int peek = peekSize();
|
|||
|
|
|
|||
|
|
if (peek < 0)
|
|||
|
|
{
|
|||
|
|
return null;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
bool recover = rcvQueue.Count >= rcvWnd;
|
|||
|
|
|
|||
|
|
IByteBuffer byteBuf = null;
|
|||
|
|
|
|||
|
|
// merge fragment
|
|||
|
|
int len = 0;
|
|||
|
|
|
|||
|
|
var itr = rcvQueue.First;
|
|||
|
|
while (itr != null)
|
|||
|
|
{
|
|||
|
|
Segment seg = itr.Value;
|
|||
|
|
var next = itr.Next;
|
|||
|
|
rcvQueue.Remove(itr);
|
|||
|
|
itr = next;
|
|||
|
|
|
|||
|
|
len += seg.Data.ReadableBytes;
|
|||
|
|
int fragment = seg.Frg;
|
|||
|
|
if (byteBuf == null)
|
|||
|
|
{
|
|||
|
|
if (fragment == 0)
|
|||
|
|
{
|
|||
|
|
byteBuf = seg.Data;
|
|||
|
|
seg.recycle(false);
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
byteBuf = byteBufAllocator.DirectBuffer(len);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
byteBuf.WriteBytes(seg.Data);
|
|||
|
|
seg.recycle(true);
|
|||
|
|
if (fragment == 0)
|
|||
|
|
{
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// move available data from rcv_buf -> rcv_queue
|
|||
|
|
moveRcvData();
|
|||
|
|
// fast recover
|
|||
|
|
if (rcvQueue.Count < rcvWnd && recover)
|
|||
|
|
{
|
|||
|
|
// ready to send back IKCP_CMD_WINS in ikcp_flush
|
|||
|
|
// tell remote my window size
|
|||
|
|
probe |= IKCP_ASK_TELL;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return byteBuf;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 1,判断是否有完整的包,如果有就抛给下一层
|
|||
|
|
* 2,整理消息接收队列,判断下一个包是否已经收到 收到放入rcvQueue
|
|||
|
|
* 3,判断接收窗口剩余是否改变,如果改变记录需要通知
|
|||
|
|
* @param bufList
|
|||
|
|
* @return
|
|||
|
|
*/
|
|||
|
|
public int recv(List<IByteBuffer> bufList)
|
|||
|
|
{
|
|||
|
|
if (rcvQueue.Count == 0)
|
|||
|
|
{
|
|||
|
|
return -1;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
int peek = peekSize();
|
|||
|
|
|
|||
|
|
if (peek < 0)
|
|||
|
|
{
|
|||
|
|
return -2;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
//接收队列长度大于接收窗口?比如接收窗口是32个包,目前已经满32个包了,需要在恢复的时候告诉对方
|
|||
|
|
bool recover = rcvQueue.Count >= rcvWnd;
|
|||
|
|
|
|||
|
|
// merge fragment
|
|||
|
|
int len = 0;
|
|||
|
|
|
|||
|
|
|
|||
|
|
var itr = rcvQueue.First;
|
|||
|
|
|
|||
|
|
while (itr != null)
|
|||
|
|
{
|
|||
|
|
Segment seg = itr.Value;
|
|||
|
|
var next = itr.Next;
|
|||
|
|
rcvQueue.Remove(itr);
|
|||
|
|
itr = next;
|
|||
|
|
|
|||
|
|
len += seg.Data.ReadableBytes;
|
|||
|
|
bufList.Add(seg.Data);
|
|||
|
|
|
|||
|
|
int fragment = seg.Frg;
|
|||
|
|
|
|||
|
|
seg.recycle(false);
|
|||
|
|
|
|||
|
|
if (fragment == 0)
|
|||
|
|
{
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// move available data from rcv_buf -> rcv_queue
|
|||
|
|
moveRcvData();
|
|||
|
|
|
|||
|
|
// fast recover接收队列长度小于接收窗口,说明还可以接数据,已经恢复了,在下次发包的时候告诉对方本方的窗口
|
|||
|
|
if (rcvQueue.Count < rcvWnd && recover)
|
|||
|
|
{
|
|||
|
|
// ready to send back IKCP_CMD_WINS in ikcp_flush
|
|||
|
|
// tell remote my window size
|
|||
|
|
probe |= IKCP_ASK_TELL;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return len;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* check the size of next message in the recv queue
|
|||
|
|
* 检查接收队列里面是否有完整的一个包,如果有返回该包的字节长度
|
|||
|
|
* @return -1 没有完整包, >0 一个完整包所含字节
|
|||
|
|
*/
|
|||
|
|
public int peekSize()
|
|||
|
|
{
|
|||
|
|
if (rcvQueue.Count == 0)
|
|||
|
|
{
|
|||
|
|
return -1;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
Segment seg = rcvQueue.First();
|
|||
|
|
//第一个包是一条应用层消息的最后一个分包?一条消息只有一个包的情况?
|
|||
|
|
if (seg.Frg == 0)
|
|||
|
|
{
|
|||
|
|
return seg.Data.ReadableBytes;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
//接收队列长度小于应用层消息分包数量?接收队列空间不够用于接收完整的一个消息?
|
|||
|
|
if (rcvQueue.Count < seg.Frg + 1)
|
|||
|
|
{
|
|||
|
|
// Some segments have not arrived yet
|
|||
|
|
return -1;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
int len = 0;
|
|||
|
|
var itr = rcvQueue.First;
|
|||
|
|
while (itr != null)
|
|||
|
|
{
|
|||
|
|
var s = itr.Value;
|
|||
|
|
len += s.Data.ReadableBytes;
|
|||
|
|
if (s.Frg == 0)
|
|||
|
|
{
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
itr = itr.Next;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return len;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 判断一条消息是否完整收全了
|
|||
|
|
* @return
|
|||
|
|
*/
|
|||
|
|
public bool canRecv()
|
|||
|
|
{
|
|||
|
|
if (rcvQueue.Count == 0)
|
|||
|
|
{
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
Segment seg = rcvQueue.First();
|
|||
|
|
if (seg.Frg == 0)
|
|||
|
|
{
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (rcvQueue.Count < seg.Frg + 1)
|
|||
|
|
{
|
|||
|
|
// Some segments have not arrived yet
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
public int send(IByteBuffer buf)
|
|||
|
|
{
|
|||
|
|
int len = buf.ReadableBytes;
|
|||
|
|
if (len == 0)
|
|||
|
|
{
|
|||
|
|
return -1;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// append to previous segment in streaming mode (if possible)
|
|||
|
|
if (stream)
|
|||
|
|
{
|
|||
|
|
if (sndQueue.Count > 0)
|
|||
|
|
{
|
|||
|
|
Segment last = sndQueue.Last();
|
|||
|
|
IByteBuffer lastData = last.Data;
|
|||
|
|
int lastLen = lastData.ReadableBytes;
|
|||
|
|
if (lastLen < mss)
|
|||
|
|
{
|
|||
|
|
int capacity = mss - lastLen;
|
|||
|
|
int extend = len < capacity ? len : capacity;
|
|||
|
|
if (lastData.MaxWritableBytes < extend)
|
|||
|
|
{
|
|||
|
|
// extend
|
|||
|
|
IByteBuffer newBuf = byteBufAllocator.DirectBuffer(lastLen + extend);
|
|||
|
|
newBuf.WriteBytes(lastData);
|
|||
|
|
lastData.Release();
|
|||
|
|
lastData = last.Data = newBuf;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
lastData.WriteBytes(buf, extend);
|
|||
|
|
|
|||
|
|
len = buf.ReadableBytes;
|
|||
|
|
if (len == 0)
|
|||
|
|
{
|
|||
|
|
return 0;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
int count;
|
|||
|
|
if (len <= mss)
|
|||
|
|
{
|
|||
|
|
count = 1;
|
|||
|
|
}
|
|||
|
|
else
|
|||
|
|
{
|
|||
|
|
count = (len + mss - 1) / mss;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (count > 255)
|
|||
|
|
{
|
|||
|
|
// Maybe don't need the conditon in stream mode
|
|||
|
|
return -2;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (count == 0)
|
|||
|
|
{
|
|||
|
|
// impossible
|
|||
|
|
count = 1;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// segment
|
|||
|
|
for (int i = 0; i < count; i++)
|
|||
|
|
{
|
|||
|
|
int size = len > mss ? mss : len;
|
|||
|
|
Segment seg = Segment.createSegment(buf.ReadRetainedSlice(size));
|
|||
|
|
seg.Frg = (short) (stream ? 0 : count - i - 1);
|
|||
|
|
sndQueue.Enqueue(seg);
|
|||
|
|
// sndQueue.add(seg);
|
|||
|
|
len = buf.ReadableBytes;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return 0;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* update ack.
|
|||
|
|
* parse ack根据RTT计算SRTT和RTO即重传超时
|
|||
|
|
* @param rtt
|
|||
|
|
*/
|
|||
|
|
private void updateAck(int rtt)
|
|||
|
|
{
|
|||
|
|
if (rxSrtt == 0)
|
|||
|
|
{
|
|||
|
|
rxSrtt = rtt;
|
|||
|
|
rxRttval = rtt >> 2;
|
|||
|
|
}
|
|||
|
|
else
|
|||
|
|
{
|
|||
|
|
int delta = rtt - rxSrtt;
|
|||
|
|
rxSrtt += delta >> 3;
|
|||
|
|
delta = Math.Abs(delta);
|
|||
|
|
if (rtt < rxSrtt - rxRttval)
|
|||
|
|
{
|
|||
|
|
rxRttval += (delta - rxRttval) >> 5;
|
|||
|
|
}
|
|||
|
|
else
|
|||
|
|
{
|
|||
|
|
rxRttval += (delta - rxRttval) >> 2;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
//int delta = rtt - rxSrtt;
|
|||
|
|
//if (delta < 0) {
|
|||
|
|
// delta = -delta;
|
|||
|
|
//}
|
|||
|
|
//rxRttval = (3 * rxRttval + delta) / 4;
|
|||
|
|
//rxSrtt = (7 * rxSrtt + rtt) / 8;
|
|||
|
|
//if (rxSrtt < 1) {
|
|||
|
|
// rxSrtt = 1;
|
|||
|
|
//}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
int rto = rxSrtt + Math.Max(interval, rxRttval << 2);
|
|||
|
|
rxRto = ibound(rxMinrto, rto, IKCP_RTO_MAX);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private void shrinkBuf()
|
|||
|
|
{
|
|||
|
|
if (sndBuf.Count > 0)
|
|||
|
|
{
|
|||
|
|
Segment seg = sndBuf.First();
|
|||
|
|
sndUna = seg.Sn;
|
|||
|
|
}
|
|||
|
|
else
|
|||
|
|
{
|
|||
|
|
sndUna = sndNxt;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private void parseAck(long sn)
|
|||
|
|
{
|
|||
|
|
if (itimediff(sn, sndUna) < 0 || itimediff(sn, sndNxt) >= 0)
|
|||
|
|
{
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
var itr = sndBuf.First;
|
|||
|
|
while (itr != null)
|
|||
|
|
{
|
|||
|
|
var next = itr.Next;
|
|||
|
|
Segment seg = itr.Value;
|
|||
|
|
if (sn == seg.Sn)
|
|||
|
|
{
|
|||
|
|
sndBuf.Remove(itr);
|
|||
|
|
seg.recycle(true);
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (itimediff(sn, seg.Sn) < 0)
|
|||
|
|
{
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
itr = next;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private void parseUna(long una)
|
|||
|
|
{
|
|||
|
|
var itr = sndBuf.First;
|
|||
|
|
while (itr != null)
|
|||
|
|
{
|
|||
|
|
var next = itr.Next;
|
|||
|
|
Segment seg = itr.Value;
|
|||
|
|
if (itimediff(una, seg.Sn) > 0)
|
|||
|
|
{
|
|||
|
|
sndBuf.Remove(itr);
|
|||
|
|
seg.recycle(true);
|
|||
|
|
}
|
|||
|
|
else
|
|||
|
|
{
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
itr = next;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private void parseAckMask(long una, long ackMask)
|
|||
|
|
{
|
|||
|
|
if (ackMask == 0)
|
|||
|
|
{
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
var itr = sndBuf.First;
|
|||
|
|
while (itr != null)
|
|||
|
|
{
|
|||
|
|
var next = itr.Next;
|
|||
|
|
Segment seg = itr.Value;
|
|||
|
|
int index = (int) (seg.Sn - una - 1);
|
|||
|
|
if (index < 0)
|
|||
|
|
{
|
|||
|
|
continue;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (index >= ackMaskSize)
|
|||
|
|
break;
|
|||
|
|
long mask = ackMask & 1 << index;
|
|||
|
|
if (mask != 0)
|
|||
|
|
{
|
|||
|
|
sndBuf.Remove(itr);
|
|||
|
|
seg.recycle(true);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
itr = next;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
private void parseFastack(long sn, long ts)
|
|||
|
|
{
|
|||
|
|
if (itimediff(sn, sndUna) < 0 || itimediff(sn, sndNxt) >= 0)
|
|||
|
|
{
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
foreach (var seg in sndBuf)
|
|||
|
|
{
|
|||
|
|
if (itimediff(sn, seg.Sn) < 0)
|
|||
|
|
{
|
|||
|
|
break;
|
|||
|
|
//根据时间判断 在当前包时间之前的包才能被认定是需要快速重传的
|
|||
|
|
}
|
|||
|
|
else if (sn != seg.Sn && itimediff(seg.Ts, ts) <= 0)
|
|||
|
|
{
|
|||
|
|
seg.Fastack++;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
private void ackPush(long sn, long ts)
|
|||
|
|
{
|
|||
|
|
int newSize = 2 * (ackcount + 1);
|
|||
|
|
|
|||
|
|
if (newSize > acklist.Count())
|
|||
|
|
{
|
|||
|
|
int newCapacity = acklist.Count() << 1; // double capacity
|
|||
|
|
|
|||
|
|
// if (newCapacity < 0) {
|
|||
|
|
// throw new OutOfMemoryError();
|
|||
|
|
// }
|
|||
|
|
|
|||
|
|
long[] newArray = new long[newCapacity];
|
|||
|
|
Array.Copy(acklist, 0, newArray, 0, acklist.Count());
|
|||
|
|
this.acklist = newArray;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
acklist[2 * ackcount] = sn;
|
|||
|
|
acklist[2 * ackcount + 1] = ts;
|
|||
|
|
ackcount++;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private bool parseData(Segment newSeg)
|
|||
|
|
{
|
|||
|
|
long sn = newSeg.Sn;
|
|||
|
|
|
|||
|
|
if (itimediff(sn, rcvNxt + rcvWnd) >= 0 || itimediff(sn, rcvNxt) < 0)
|
|||
|
|
{
|
|||
|
|
newSeg.recycle(true);
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
bool repeat = false;
|
|||
|
|
bool findPos = false;
|
|||
|
|
|
|||
|
|
var last = rcvBuf.Last;
|
|||
|
|
while (last != null)
|
|||
|
|
{
|
|||
|
|
var front = last.Previous;
|
|||
|
|
Segment seg = last.Value;
|
|||
|
|
if (seg.Sn == sn)
|
|||
|
|
{
|
|||
|
|
repeat = true;
|
|||
|
|
//Snmp.snmp.RepeatSegs.incrementAndGet();
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (itimediff(sn, seg.Sn) > 0)
|
|||
|
|
{
|
|||
|
|
findPos = true;
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (front == null)
|
|||
|
|
{
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
last = front;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (repeat)
|
|||
|
|
{
|
|||
|
|
newSeg.recycle(true);
|
|||
|
|
}
|
|||
|
|
else if (last == null)
|
|||
|
|
{
|
|||
|
|
rcvBuf.AddLast(newSeg);
|
|||
|
|
}
|
|||
|
|
else if(findPos)
|
|||
|
|
{
|
|||
|
|
rcvBuf.AddAfter(last, newSeg);
|
|||
|
|
}
|
|||
|
|
else
|
|||
|
|
{
|
|||
|
|
rcvBuf.AddFirst(newSeg);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// var firstSn = rcvBuf.First.Value.Sn;
|
|||
|
|
// foreach (var segment in rcvBuf)
|
|||
|
|
// {
|
|||
|
|
// if (segment.Sn == firstSn)
|
|||
|
|
// continue;
|
|||
|
|
// firstSn++;
|
|||
|
|
// if (firstSn != segment.Sn)
|
|||
|
|
// {
|
|||
|
|
// Console.WriteLine();
|
|||
|
|
// }
|
|||
|
|
//
|
|||
|
|
//
|
|||
|
|
// }
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
// move available data from rcv_buf -> rcv_queue
|
|||
|
|
moveRcvData(); // Invoke the method only if the segment is not repeat?
|
|||
|
|
return repeat;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private void moveRcvData()
|
|||
|
|
{
|
|||
|
|
var itr = rcvBuf.First;
|
|||
|
|
while (itr != null)
|
|||
|
|
{
|
|||
|
|
var next = itr.Next;
|
|||
|
|
Segment seg = itr.Value;
|
|||
|
|
if (seg.Sn == rcvNxt && rcvQueue.Count < rcvWnd)
|
|||
|
|
{
|
|||
|
|
rcvBuf.Remove(itr);
|
|||
|
|
rcvQueue.AddLast(seg);
|
|||
|
|
rcvNxt++;
|
|||
|
|
}
|
|||
|
|
else
|
|||
|
|
{
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
itr = next;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
public int input(IByteBuffer data, bool regular, long current)
|
|||
|
|
{
|
|||
|
|
long oldSndUna = sndUna;
|
|||
|
|
if (data == null || data.ReadableBytes < IKCP_OVERHEAD)
|
|||
|
|
{
|
|||
|
|
return -1;
|
|||
|
|
}
|
|||
|
|
// if (log.isDebugEnabled()) {
|
|||
|
|
// log.debug("{} [RI] {} bytes", this, data.readableBytes());
|
|||
|
|
// }
|
|||
|
|
|
|||
|
|
|
|||
|
|
long latest = 0; // latest packet
|
|||
|
|
bool flag = false;
|
|||
|
|
int inSegs = 0;
|
|||
|
|
|
|||
|
|
|
|||
|
|
long uintCurrent = long2Uint(current);
|
|||
|
|
|
|||
|
|
while (true)
|
|||
|
|
{
|
|||
|
|
int conv, len, wnd;
|
|||
|
|
long ts, sn, una, ackMask;
|
|||
|
|
byte cmd;
|
|||
|
|
short frg;
|
|||
|
|
Segment seg;
|
|||
|
|
|
|||
|
|
if (data.ReadableBytes < IKCP_OVERHEAD)
|
|||
|
|
{
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
conv = data.ReadIntLE();
|
|||
|
|
if (conv != this.conv)
|
|||
|
|
{
|
|||
|
|
return -4;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
cmd = data.ReadByte();
|
|||
|
|
frg = data.ReadByte();
|
|||
|
|
wnd = data.ReadUnsignedShortLE();
|
|||
|
|
ts = data.ReadUnsignedIntLE();
|
|||
|
|
sn = data.ReadUnsignedIntLE();
|
|||
|
|
una = data.ReadUnsignedIntLE();
|
|||
|
|
len = data.ReadIntLE();
|
|||
|
|
|
|||
|
|
|
|||
|
|
switch (ackMaskSize)
|
|||
|
|
{
|
|||
|
|
case 8:
|
|||
|
|
ackMask = data.ReadByte();
|
|||
|
|
break;
|
|||
|
|
case 16:
|
|||
|
|
ackMask = data.ReadUnsignedShortLE();
|
|||
|
|
break;
|
|||
|
|
case 32:
|
|||
|
|
ackMask = data.ReadUnsignedIntLE();
|
|||
|
|
break;
|
|||
|
|
case 64:
|
|||
|
|
//TODO need unsignedLongLe
|
|||
|
|
ackMask = data.ReadLongLE();
|
|||
|
|
break;
|
|||
|
|
default:
|
|||
|
|
ackMask = 0;
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
;
|
|||
|
|
|
|||
|
|
|
|||
|
|
if (data.ReadableBytes < len)
|
|||
|
|
{
|
|||
|
|
return -2;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (cmd != IKCP_CMD_PUSH && cmd != IKCP_CMD_ACK && cmd != IKCP_CMD_WASK && cmd != IKCP_CMD_WINS)
|
|||
|
|
{
|
|||
|
|
return -3;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
//最后收到的来计算远程窗口大小
|
|||
|
|
if (regular)
|
|||
|
|
{
|
|||
|
|
this.rmtWnd = wnd; //更新远端窗口大小删除已确认的包,una以前的包对方都收到了,可以把本地小于una的都删除掉
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
//this.rmtWnd = wnd;
|
|||
|
|
parseUna(una);
|
|||
|
|
shrinkBuf();
|
|||
|
|
|
|||
|
|
|
|||
|
|
bool readed = false;
|
|||
|
|
switch (cmd)
|
|||
|
|
{
|
|||
|
|
case IKCP_CMD_ACK:
|
|||
|
|
{
|
|||
|
|
parseAck(sn);
|
|||
|
|
parseFastack(sn, ts);
|
|||
|
|
flag = true;
|
|||
|
|
latest = ts;
|
|||
|
|
int rtt = itimediff(uintCurrent, ts);
|
|||
|
|
// Debug.Log(GetHashCode()+" input ack: sn="+sn+", rtt="+rtt+", rto="+rxRto+",regular="+regular);
|
|||
|
|
// if (log.isDebugEnabled()) {
|
|||
|
|
// log.debug("{} input ack: sn={}, rtt={}, rto={} ,regular={}", this, sn, rtt, rxRto,regular);
|
|||
|
|
// }
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
case IKCP_CMD_PUSH:
|
|||
|
|
{
|
|||
|
|
bool repeat = true;
|
|||
|
|
if (itimediff(sn, rcvNxt + rcvWnd) < 0)
|
|||
|
|
{
|
|||
|
|
ackPush(sn, ts);
|
|||
|
|
if (itimediff(sn, rcvNxt) >= 0)
|
|||
|
|
{
|
|||
|
|
if (len > 0)
|
|||
|
|
{
|
|||
|
|
seg = Segment.createSegment(data.ReadRetainedSlice(len));
|
|||
|
|
readed = true;
|
|||
|
|
}
|
|||
|
|
else
|
|||
|
|
{
|
|||
|
|
seg = Segment.createSegment(byteBufAllocator, 0);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
seg.Conv = conv;
|
|||
|
|
seg.Cmd = cmd;
|
|||
|
|
seg.Frg = frg;
|
|||
|
|
seg.Wnd = wnd;
|
|||
|
|
seg.Ts = ts;
|
|||
|
|
seg.Sn = sn;
|
|||
|
|
seg.Una = una;
|
|||
|
|
repeat = parseData(seg);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (regular && repeat)
|
|||
|
|
{
|
|||
|
|
Snmp.snmp.RepeatSegs++;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// if (log.isDebugEnabled()) {
|
|||
|
|
// log.debug("{} input push: sn={}, una={}, ts={},regular={}", this, sn, una, ts,regular);
|
|||
|
|
// }
|
|||
|
|
|
|||
|
|
// Console.WriteLine(GetHashCode()+" input push: sn="+sn+", una="+una+", ts="+ts+",regular="+regular);
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
case IKCP_CMD_WASK:
|
|||
|
|
{
|
|||
|
|
// ready to send back IKCP_CMD_WINS in ikcp_flush
|
|||
|
|
// tell remote my window size
|
|||
|
|
probe |= IKCP_ASK_TELL;
|
|||
|
|
// if (log.isDebugEnabled()) {
|
|||
|
|
// log.debug("{} input ask", this);
|
|||
|
|
// }
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
case IKCP_CMD_WINS:
|
|||
|
|
{
|
|||
|
|
// do nothing
|
|||
|
|
// if (log.isDebugEnabled()) {
|
|||
|
|
// log.debug("{} input tell: {}", this, wnd);
|
|||
|
|
// }
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
default:
|
|||
|
|
return -3;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
parseAckMask(una, ackMask);
|
|||
|
|
|
|||
|
|
if (!readed)
|
|||
|
|
{
|
|||
|
|
data.SkipBytes(len);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
inSegs++;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// if (data.ReadableBytes > 0)
|
|||
|
|
// {
|
|||
|
|
// Console.WriteLine("ReadableBytes"+data.ReadableBytes);
|
|||
|
|
// }
|
|||
|
|
|
|||
|
|
Snmp.snmp.InSegs += inSegs;
|
|||
|
|
|
|||
|
|
if (flag && regular)
|
|||
|
|
{
|
|||
|
|
int rtt = itimediff(uintCurrent, latest);
|
|||
|
|
if (rtt >= 0)
|
|||
|
|
{
|
|||
|
|
updateAck(rtt); //收到ack包,根据ack包的时间计算srtt和rto
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (!nocwnd)
|
|||
|
|
{
|
|||
|
|
if (itimediff(sndUna, oldSndUna) > 0)
|
|||
|
|
{
|
|||
|
|
if (cwnd < rmtWnd)
|
|||
|
|
{
|
|||
|
|
int mss = this.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 > rmtWnd)
|
|||
|
|
{
|
|||
|
|
cwnd = rmtWnd;
|
|||
|
|
incr = rmtWnd * mss;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
if (ackNoDelay && ackcount > 0)
|
|||
|
|
{
|
|||
|
|
// ack immediately
|
|||
|
|
flush(true, current);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return 0;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
private int wndUnused()
|
|||
|
|
{
|
|||
|
|
if (rcvQueue.Count < rcvWnd)
|
|||
|
|
{
|
|||
|
|
return rcvWnd - rcvQueue.Count;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return 0;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
private IByteBuffer makeSpace(IByteBuffer buffer, int space)
|
|||
|
|
{
|
|||
|
|
if (buffer.ReadableBytes + space > mtu)
|
|||
|
|
{
|
|||
|
|
outPut(buffer, this);
|
|||
|
|
buffer = createFlushByteBuf();
|
|||
|
|
buffer.SetWriterIndex(reserved);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return buffer;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private void flushBuffer(IByteBuffer buffer)
|
|||
|
|
{
|
|||
|
|
if (buffer.ReadableBytes > reserved)
|
|||
|
|
{
|
|||
|
|
outPut(buffer, this);
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
buffer.Release();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
private readonly long startTicks = DateTime.Now.Ticks;
|
|||
|
|
|
|||
|
|
public long currentMs()
|
|||
|
|
{
|
|||
|
|
long currentTicks = DateTime.Now.Ticks;
|
|||
|
|
return (currentTicks - startTicks) / TimeSpan.TicksPerMillisecond;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* ikcp_flush
|
|||
|
|
*/
|
|||
|
|
public long flush(bool ackOnly, long current)
|
|||
|
|
{
|
|||
|
|
// 'ikcp_update' haven't been called.
|
|||
|
|
//if (!updated) {
|
|||
|
|
// return;
|
|||
|
|
//}
|
|||
|
|
|
|||
|
|
//long current = this.current;
|
|||
|
|
//long uintCurrent = long2Uint(current);
|
|||
|
|
|
|||
|
|
Segment seg = Segment.createSegment(byteBufAllocator, 0);
|
|||
|
|
seg.Conv = conv;
|
|||
|
|
seg.Cmd = IKCP_CMD_ACK;
|
|||
|
|
seg.AckMaskSize = this.ackMaskSize;
|
|||
|
|
seg.Wnd = wndUnused(); //可接收数量
|
|||
|
|
seg.Una = rcvNxt; //已接收数量,下次要接收的包的sn,这sn之前的包都已经收到
|
|||
|
|
|
|||
|
|
IByteBuffer buffer = createFlushByteBuf();
|
|||
|
|
buffer.SetWriterIndex(reserved);
|
|||
|
|
|
|||
|
|
|
|||
|
|
//计算ackMask
|
|||
|
|
int count = ackcount;
|
|||
|
|
|
|||
|
|
if (lastRcvNxt != rcvNxt)
|
|||
|
|
{
|
|||
|
|
ackMask = 0;
|
|||
|
|
lastRcvNxt = rcvNxt;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
for (int i = 0; i < count; i++)
|
|||
|
|
{
|
|||
|
|
long sn = acklist[i * 2];
|
|||
|
|
if (sn < rcvNxt)
|
|||
|
|
continue;
|
|||
|
|
int index = (int) (sn - rcvNxt - 1);
|
|||
|
|
if (index >= ackMaskSize)
|
|||
|
|
break;
|
|||
|
|
if (index >= 0)
|
|||
|
|
{
|
|||
|
|
ackMask |= 1 << index;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
seg.AckMask = ackMask;
|
|||
|
|
|
|||
|
|
|
|||
|
|
// flush acknowledges有收到的包需要确认,则发确认包
|
|||
|
|
for (int i = 0; i < count; i++)
|
|||
|
|
{
|
|||
|
|
buffer = makeSpace(buffer, IKCP_OVERHEAD);
|
|||
|
|
long sn = acklist[i * 2];
|
|||
|
|
if (sn >= rcvNxt || count - 1 == i)
|
|||
|
|
{
|
|||
|
|
seg.Sn = sn;
|
|||
|
|
seg.Ts = acklist[i * 2 + 1];
|
|||
|
|
encodeSeg(buffer, seg);
|
|||
|
|
|
|||
|
|
// Console.WriteLine(GetHashCode()+"flush ack: sn="+seg.Sn+", ts="+seg.Ts+" ,count="+count+" Una"+seg.Una);
|
|||
|
|
|
|||
|
|
// if (log.isDebugEnabled()) {
|
|||
|
|
// log.debug("{} flush ack: sn={}, ts={} ,count={}", this, seg.sn, seg.ts,count);
|
|||
|
|
// }
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
ackcount = 0;
|
|||
|
|
|
|||
|
|
|
|||
|
|
if (ackOnly)
|
|||
|
|
{
|
|||
|
|
flushBuffer(buffer);
|
|||
|
|
seg.recycle(true);
|
|||
|
|
return interval;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// probe window size (if remote window size equals zero)
|
|||
|
|
//拥堵控制 如果对方可接受窗口大小为0 需要询问对方窗口大小
|
|||
|
|
if (rmtWnd == 0)
|
|||
|
|
{
|
|||
|
|
current = currentMs();
|
|||
|
|
if (probeWait == 0)
|
|||
|
|
{
|
|||
|
|
probeWait = IKCP_PROBE_INIT;
|
|||
|
|
tsProbe = current + probeWait;
|
|||
|
|
}
|
|||
|
|
else
|
|||
|
|
{
|
|||
|
|
if (itimediff(current, tsProbe) >= 0)
|
|||
|
|
{
|
|||
|
|
if (probeWait < IKCP_PROBE_INIT)
|
|||
|
|
{
|
|||
|
|
probeWait = IKCP_PROBE_INIT;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
probeWait += probeWait / 2;
|
|||
|
|
if (probeWait > IKCP_PROBE_LIMIT)
|
|||
|
|
{
|
|||
|
|
probeWait = IKCP_PROBE_LIMIT;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
tsProbe = current + probeWait;
|
|||
|
|
probe |= IKCP_ASK_SEND;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
else
|
|||
|
|
{
|
|||
|
|
tsProbe = 0;
|
|||
|
|
probeWait = 0;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// flush window probing commands
|
|||
|
|
if ((probe & IKCP_ASK_SEND) != 0)
|
|||
|
|
{
|
|||
|
|
seg.Cmd = IKCP_CMD_WASK;
|
|||
|
|
buffer = makeSpace(buffer, IKCP_OVERHEAD);
|
|||
|
|
encodeSeg(buffer, seg);
|
|||
|
|
// if (log.isDebugEnabled()) {
|
|||
|
|
// log.debug("{} flush ask", this);
|
|||
|
|
// }
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// flush window probing commands
|
|||
|
|
if ((probe & IKCP_ASK_TELL) != 0)
|
|||
|
|
{
|
|||
|
|
seg.Cmd = IKCP_CMD_WINS;
|
|||
|
|
buffer = makeSpace(buffer, IKCP_OVERHEAD);
|
|||
|
|
encodeSeg(buffer, seg);
|
|||
|
|
// if (log.isDebugEnabled()) {
|
|||
|
|
// log.debug("{} flush tell: wnd={}", this, seg.wnd);
|
|||
|
|
// }
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
probe = 0;
|
|||
|
|
|
|||
|
|
// calculate window size
|
|||
|
|
int cwnd0 = Math.Min(sndWnd, rmtWnd);
|
|||
|
|
if (!nocwnd)
|
|||
|
|
{
|
|||
|
|
cwnd0 = Math.Min(this.cwnd, cwnd0);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
int newSegsCount = 0;
|
|||
|
|
// move data from snd_queue to snd_buf
|
|||
|
|
while (itimediff(sndNxt, sndUna + cwnd0) < 0)
|
|||
|
|
{
|
|||
|
|
if (sndQueue.Count == 0)
|
|||
|
|
{
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
var newSeg = sndQueue.Dequeue();
|
|||
|
|
newSeg.Conv = conv;
|
|||
|
|
newSeg.Cmd = IKCP_CMD_PUSH;
|
|||
|
|
newSeg.Sn = sndNxt;
|
|||
|
|
sndBuf.AddLast(newSeg);
|
|||
|
|
// sndBuf.AddLast(newSeg);
|
|||
|
|
sndNxt++;
|
|||
|
|
newSegsCount++;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// calculate resent
|
|||
|
|
int resent = fastresend > 0 ? fastresend : 0x7fffffff;
|
|||
|
|
|
|||
|
|
// flush data segments
|
|||
|
|
current = currentMs();
|
|||
|
|
int change = 0;
|
|||
|
|
bool lost = false;
|
|||
|
|
int lostSegs = 0, fastRetransSegs = 0, earlyRetransSegs = 0;
|
|||
|
|
long minrto = interval;
|
|||
|
|
var itr = sndBuf.First;
|
|||
|
|
|
|||
|
|
while (itr != null)
|
|||
|
|
{
|
|||
|
|
var next = itr.Next;
|
|||
|
|
Segment segment = itr.Value;
|
|||
|
|
itr = next;
|
|||
|
|
|
|||
|
|
bool needsend = false;
|
|||
|
|
if (segment.Xmit == 0)
|
|||
|
|
{
|
|||
|
|
needsend = true;
|
|||
|
|
segment.Rto = rxRto;
|
|||
|
|
segment.Resendts = current + segment.Rto;
|
|||
|
|
// if (log.isDebugEnabled()) {
|
|||
|
|
// log.debug("{} flush data: sn={}, resendts={}", this, segment.sn, (segment.resendts - current));
|
|||
|
|
// }
|
|||
|
|
}
|
|||
|
|
else if (segment.Fastack >= resent)
|
|||
|
|
{
|
|||
|
|
needsend = true;
|
|||
|
|
segment.Fastack = 0;
|
|||
|
|
segment.Rto = rxRto;
|
|||
|
|
segment.Resendts = current + segment.Rto;
|
|||
|
|
change++;
|
|||
|
|
fastRetransSegs++;
|
|||
|
|
// if (log.isDebugEnabled()) {
|
|||
|
|
// log.debug("{} fastresend. sn={}, xmit={}, resendts={} ", this, segment.sn, segment.xmit, (segment
|
|||
|
|
// .resendts - current));
|
|||
|
|
// }
|
|||
|
|
}
|
|||
|
|
else if (segment.Fastack > 0 && newSegsCount == 0)
|
|||
|
|
{
|
|||
|
|
// early retransmit
|
|||
|
|
needsend = true;
|
|||
|
|
segment.Fastack = 0;
|
|||
|
|
segment.Rto = rxRto;
|
|||
|
|
segment.Resendts = current + segment.Rto;
|
|||
|
|
change++;
|
|||
|
|
earlyRetransSegs++;
|
|||
|
|
}
|
|||
|
|
else if (itimediff(current, segment.Resendts) >= 0)
|
|||
|
|
{
|
|||
|
|
needsend = true;
|
|||
|
|
if (!nodelay)
|
|||
|
|
{
|
|||
|
|
segment.Rto += rxRto;
|
|||
|
|
}
|
|||
|
|
else
|
|||
|
|
{
|
|||
|
|
segment.Rto += rxRto / 2;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
segment.Fastack = 0;
|
|||
|
|
segment.Resendts = current + segment.Rto;
|
|||
|
|
lost = true;
|
|||
|
|
lostSegs++;
|
|||
|
|
// if (log.isDebugEnabled()) {
|
|||
|
|
// log.debug("{} resend. sn={}, xmit={}, resendts={}", this, segment.sn, segment.xmit, (segment
|
|||
|
|
// .resendts - current));
|
|||
|
|
// }
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
if (needsend)
|
|||
|
|
{
|
|||
|
|
segment.Xmit++;
|
|||
|
|
segment.Ts = long2Uint(current);
|
|||
|
|
segment.Wnd = seg.Wnd;
|
|||
|
|
segment.Una = rcvNxt;
|
|||
|
|
segment.AckMaskSize = this.ackMaskSize;
|
|||
|
|
segment.AckMask = ackMask;
|
|||
|
|
|
|||
|
|
IByteBuffer segData = segment.Data;
|
|||
|
|
int segLen = segData.ReadableBytes;
|
|||
|
|
int need = IKCP_OVERHEAD + segLen;
|
|||
|
|
buffer = makeSpace(buffer, need);
|
|||
|
|
|
|||
|
|
//if (buffer.readableBytes() + need > mtu) {
|
|||
|
|
// output(buffer, this);
|
|||
|
|
// buffer = createFlushByteBuf();
|
|||
|
|
//}
|
|||
|
|
|
|||
|
|
encodeSeg(buffer, segment);
|
|||
|
|
|
|||
|
|
if (segLen > 0)
|
|||
|
|
{
|
|||
|
|
// don't increases data's readerIndex, because the data may be resend.
|
|||
|
|
buffer.WriteBytes(segData, segData.ReaderIndex, segLen);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (segment.Xmit >= deadLink)
|
|||
|
|
{
|
|||
|
|
state = -1;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// get the nearest rto
|
|||
|
|
long rto = itimediff(segment.Resendts, current);
|
|||
|
|
if (rto > 0 && rto < minrto)
|
|||
|
|
{
|
|||
|
|
minrto = rto;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// flash remain segments
|
|||
|
|
flushBuffer(buffer);
|
|||
|
|
seg.recycle(true);
|
|||
|
|
|
|||
|
|
int sum = lostSegs;
|
|||
|
|
if (lostSegs > 0)
|
|||
|
|
{
|
|||
|
|
Snmp.snmp.LostSegs += lostSegs;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (fastRetransSegs > 0)
|
|||
|
|
{
|
|||
|
|
Snmp.snmp.FastRetransSegs += fastRetransSegs;
|
|||
|
|
sum += fastRetransSegs;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (earlyRetransSegs > 0)
|
|||
|
|
{
|
|||
|
|
Snmp.snmp.EarlyRetransSegs += earlyRetransSegs;
|
|||
|
|
sum += earlyRetransSegs;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (sum > 0)
|
|||
|
|
{
|
|||
|
|
Snmp.snmp.RetransSegs += sum;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// update ssthresh
|
|||
|
|
if (!nocwnd)
|
|||
|
|
{
|
|||
|
|
if (change > 0)
|
|||
|
|
{
|
|||
|
|
int inflight = (int) (sndNxt - sndUna);
|
|||
|
|
ssthresh = inflight / 2;
|
|||
|
|
if (ssthresh < IKCP_THRESH_MIN)
|
|||
|
|
{
|
|||
|
|
ssthresh = IKCP_THRESH_MIN;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
cwnd = ssthresh + resent;
|
|||
|
|
incr = cwnd * mss;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (lost)
|
|||
|
|
{
|
|||
|
|
ssthresh = cwnd0 / 2;
|
|||
|
|
if (ssthresh < IKCP_THRESH_MIN)
|
|||
|
|
{
|
|||
|
|
ssthresh = IKCP_THRESH_MIN;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
cwnd = 1;
|
|||
|
|
incr = mss;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (cwnd < 1)
|
|||
|
|
{
|
|||
|
|
cwnd = 1;
|
|||
|
|
incr = mss;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return minrto;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* update getState (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.
|
|||
|
|
*
|
|||
|
|
* @param current
|
|||
|
|
*/
|
|||
|
|
public void update(long current)
|
|||
|
|
{
|
|||
|
|
if (!updated)
|
|||
|
|
{
|
|||
|
|
updated = true;
|
|||
|
|
tsFlush = current;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
int slap = itimediff(current, tsFlush);
|
|||
|
|
|
|||
|
|
if (slap >= 10000 || slap < -10000)
|
|||
|
|
{
|
|||
|
|
tsFlush = current;
|
|||
|
|
slap = 0;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/*if (slap >= 0) {
|
|||
|
|
tsFlush += setInterval;
|
|||
|
|
if (itimediff(this.current, tsFlush) >= 0) {
|
|||
|
|
tsFlush = this.current + setInterval;
|
|||
|
|
}
|
|||
|
|
flush();
|
|||
|
|
}*/
|
|||
|
|
|
|||
|
|
if (slap >= 0)
|
|||
|
|
{
|
|||
|
|
tsFlush += interval;
|
|||
|
|
if (itimediff(current, tsFlush) >= 0)
|
|||
|
|
{
|
|||
|
|
tsFlush = current + interval;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
else
|
|||
|
|
{
|
|||
|
|
tsFlush = current + interval;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
flush(false, current);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 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)
|
|||
|
|
*
|
|||
|
|
* @param current
|
|||
|
|
* @return
|
|||
|
|
*/
|
|||
|
|
public long check(long current)
|
|||
|
|
{
|
|||
|
|
if (!updated)
|
|||
|
|
{
|
|||
|
|
return current;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
long tsFlush = this.tsFlush;
|
|||
|
|
int slap = itimediff(current, tsFlush);
|
|||
|
|
if (slap >= 10000 || slap < -10000)
|
|||
|
|
{
|
|||
|
|
tsFlush = current;
|
|||
|
|
slap = 0;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (slap >= 0)
|
|||
|
|
{
|
|||
|
|
return current;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
int tmFlush = itimediff(tsFlush, current);
|
|||
|
|
int tmPacket = 0x7fffffff;
|
|||
|
|
|
|||
|
|
foreach (var seg in sndBuf)
|
|||
|
|
{
|
|||
|
|
int diff = itimediff(seg.Resendts, current);
|
|||
|
|
if (diff <= 0)
|
|||
|
|
{
|
|||
|
|
return current;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (diff < tmPacket)
|
|||
|
|
{
|
|||
|
|
tmPacket = diff;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
int minimal = tmPacket < tmFlush ? tmPacket : tmFlush;
|
|||
|
|
if (minimal >= interval)
|
|||
|
|
{
|
|||
|
|
minimal = interval;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return current + minimal;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
public bool checkFlush()
|
|||
|
|
{
|
|||
|
|
if (ackcount > 0)
|
|||
|
|
{
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (probe != 0)
|
|||
|
|
{
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (sndBuf.Count > 0)
|
|||
|
|
{
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (sndQueue.Count > 0)
|
|||
|
|
{
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public int setMtu(int mtu)
|
|||
|
|
{
|
|||
|
|
if (mtu < IKCP_OVERHEAD || mtu < 50)
|
|||
|
|
{
|
|||
|
|
return -1;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (reserved >= mtu - IKCP_OVERHEAD || reserved < 0)
|
|||
|
|
{
|
|||
|
|
return -1;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
this.mtu = mtu;
|
|||
|
|
this.mss = mtu - IKCP_OVERHEAD - reserved;
|
|||
|
|
return 0;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
public int setInterval(int interval)
|
|||
|
|
{
|
|||
|
|
if (interval > 5000)
|
|||
|
|
{
|
|||
|
|
interval = 5000;
|
|||
|
|
}
|
|||
|
|
else if (interval < 10)
|
|||
|
|
{
|
|||
|
|
interval = 10;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
this.interval = interval;
|
|||
|
|
|
|||
|
|
return 0;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public int initNodelay(bool nodelay, int interval, int resend, bool nc)
|
|||
|
|
{
|
|||
|
|
this.nodelay = nodelay;
|
|||
|
|
if (nodelay)
|
|||
|
|
{
|
|||
|
|
this.rxMinrto = IKCP_RTO_NDL;
|
|||
|
|
}
|
|||
|
|
else
|
|||
|
|
{
|
|||
|
|
this.rxMinrto = IKCP_RTO_MIN;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (interval >= 0)
|
|||
|
|
{
|
|||
|
|
if (interval > 5000)
|
|||
|
|
{
|
|||
|
|
interval = 5000;
|
|||
|
|
}
|
|||
|
|
else if (interval < 10)
|
|||
|
|
{
|
|||
|
|
interval = 10;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
this.interval = interval;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (resend >= 0)
|
|||
|
|
{
|
|||
|
|
fastresend = resend;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
this.nocwnd = nc;
|
|||
|
|
|
|||
|
|
return 0;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public int waitSnd()
|
|||
|
|
{
|
|||
|
|
return this.sndBuf.Count + this.sndQueue.Count;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public void SetNodelay(bool nodelay)
|
|||
|
|
{
|
|||
|
|
this.nodelay = nodelay;
|
|||
|
|
if (nodelay)
|
|||
|
|
{
|
|||
|
|
this.rxMinrto = IKCP_RTO_NDL;
|
|||
|
|
}
|
|||
|
|
else
|
|||
|
|
{
|
|||
|
|
this.rxMinrto = IKCP_RTO_MIN;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
public void setAckMaskSize(int ackMaskSize)
|
|||
|
|
{
|
|||
|
|
this.ackMaskSize = ackMaskSize;
|
|||
|
|
this.IKCP_OVERHEAD += (ackMaskSize / 8);
|
|||
|
|
this.mss = mtu - IKCP_OVERHEAD - reserved;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public void setReserved(int reserved)
|
|||
|
|
{
|
|||
|
|
this.reserved = reserved;
|
|||
|
|
this.mss = mtu - IKCP_OVERHEAD - reserved;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
public int Conv
|
|||
|
|
{
|
|||
|
|
get => conv;
|
|||
|
|
set => conv = value;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public int Mtu
|
|||
|
|
{
|
|||
|
|
get => mtu;
|
|||
|
|
set => mtu = value;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public int Mss
|
|||
|
|
{
|
|||
|
|
get => mss;
|
|||
|
|
set => mss = value;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public long SndUna
|
|||
|
|
{
|
|||
|
|
get => sndUna;
|
|||
|
|
set => sndUna = value;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public long SndNxt
|
|||
|
|
{
|
|||
|
|
get => sndNxt;
|
|||
|
|
set => sndNxt = value;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public long RcvNxt
|
|||
|
|
{
|
|||
|
|
get => rcvNxt;
|
|||
|
|
set => rcvNxt = value;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public bool AckNoDelay
|
|||
|
|
{
|
|||
|
|
get => ackNoDelay;
|
|||
|
|
set => ackNoDelay = value;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public object User
|
|||
|
|
{
|
|||
|
|
get => user;
|
|||
|
|
set => user = value;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public int Fastresend
|
|||
|
|
{
|
|||
|
|
get => fastresend;
|
|||
|
|
set => fastresend = value;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public bool Nocwnd
|
|||
|
|
{
|
|||
|
|
get => nocwnd;
|
|||
|
|
set => nocwnd = value;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public bool Stream
|
|||
|
|
{
|
|||
|
|
get => stream;
|
|||
|
|
set => stream = value;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public int RcvWnd
|
|||
|
|
{
|
|||
|
|
get => rcvWnd;
|
|||
|
|
set => rcvWnd = value;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public int SndWnd
|
|||
|
|
{
|
|||
|
|
get => sndWnd;
|
|||
|
|
set => sndWnd = value;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public int RxMinrto
|
|||
|
|
{
|
|||
|
|
get => rxMinrto;
|
|||
|
|
set => rxMinrto = value;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public KcpOutput Output
|
|||
|
|
{
|
|||
|
|
get => output;
|
|||
|
|
set => output = value;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public int Interval
|
|||
|
|
{
|
|||
|
|
get => interval;
|
|||
|
|
set => interval = value;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public bool Nodelay
|
|||
|
|
{
|
|||
|
|
get => nodelay;
|
|||
|
|
set => nodelay = value;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public int DeadLink
|
|||
|
|
{
|
|||
|
|
get => deadLink;
|
|||
|
|
set => deadLink = value;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public IByteBufferAllocator ByteBufAllocator
|
|||
|
|
{
|
|||
|
|
get => byteBufAllocator;
|
|||
|
|
set => byteBufAllocator = value;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public int State
|
|||
|
|
{
|
|||
|
|
get => state;
|
|||
|
|
set => state = value;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|