예제 #1
0
        // 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()
        {
            var current = time;

            if (0 == updated)
            {
                updated  = 1;
                ts_flush = current;
            }

            var slap = KcpTool.Subtract(current, ts_flush);

            if (slap >= 10000 || slap < -10000)
            {
                ts_flush = current;
                slap     = 0;
            }

            if (slap >= 0)
            {
                ts_flush += interval;
                if (KcpTool.Subtract(current, ts_flush) >= 0)
                {
                    ts_flush = current + interval;
                }
                Flush(false);
            }
        }
예제 #2
0
        // update ack.
        void update_ack(Int32 rtt)
        {
            // https://tools.ietf.org/html/rfc6298
            if (0 == rx_srtt)
            {
                rx_srtt   = (uint)rtt;
                rx_rttval = (uint)rtt >> 1;
            }
            else
            {
                Int32 delta = (Int32)((uint)rtt - rx_srtt);
                rx_srtt += (uint)(delta >> 3);
                if (0 > delta)
                {
                    delta = -delta;
                }

                if (rtt < rx_srtt - rx_rttval)
                {
                    // if the new RTT sample is below the bottom of the range of
                    // what an RTT measurement is expected to be.
                    // give an 8x reduced weight versus its normal weighting
                    rx_rttval += (uint)((delta - rx_rttval) >> 5);
                }
                else
                {
                    rx_rttval += (uint)((delta - rx_rttval) >> 2);
                }
            }

            var rto = (int)(rx_srtt + KcpTool.Max(interval, rx_rttval << 2));

            rx_rto = KcpTool.Clamp(rx_minrto, (uint)rto, KcpTool.IKCP_RTO_MAX);
        }
예제 #3
0
        // 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 uint Check()
        {
            var ts_flush_ = ts_flush;
            var tm_flush_ = 0x7fffffff;
            var tm_packet = 0x7fffffff;
            var minimal   = 0;

            if (updated == 0)
            {
                return(time);
            }

            if (KcpTool.Subtract(time, ts_flush_) >= 10000 || KcpTool.Subtract(time, ts_flush_) < -10000)
            {
                ts_flush_ = time;
            }

            if (KcpTool.Subtract(time, ts_flush_) >= 0)
            {
                return(time);
            }

            tm_flush_ = (int)KcpTool.Subtract(ts_flush_, time);

            foreach (var seg in snd_buf)
            {
                var diff = KcpTool.Subtract(seg.resendts, time);
                if (diff <= 0)
                {
                    return(time);
                }
                if (diff < tm_packet)
                {
                    tm_packet = (int)diff;
                }
            }

            minimal = (int)tm_packet;
            if (tm_packet >= tm_flush_)
            {
                minimal = (int)tm_flush_;
            }
            if (minimal >= interval)
            {
                minimal = (int)interval;
            }

            return(time + (uint)minimal);
        }
예제 #4
0
            // encode a segment into buffer
            internal int Encode(byte[] ptr, int offset)
            {
                var offset_ = offset;

                offset += KcpTool.Encode(ptr, offset, conv);
                offset += KcpTool.Encode(ptr, offset, (byte)cmd);
                offset += KcpTool.Encode(ptr, offset, (byte)frg);
                offset += KcpTool.Encode(ptr, offset, (UInt16)wnd);
                offset += KcpTool.Encode(ptr, offset, ts);
                offset += KcpTool.Encode(ptr, offset, sn);
                offset += KcpTool.Encode(ptr, offset, una);
                offset += KcpTool.Encode(ptr, offset, (uint)data.canRead);

                return(offset - offset_);
            }
예제 #5
0
        void parse_fastack(uint sn, uint ts)
        {
            if (KcpTool.Subtract(sn, snd_una) < 0 || KcpTool.Subtract(sn, snd_nxt) >= 0)
            {
                return;
            }

            foreach (var seg in snd_buf)
            {
                if (KcpTool.Subtract(sn, seg.sn) < 0)
                {
                    break;
                }
                else if (sn != seg.sn && KcpTool.Subtract(seg.ts, ts) <= 0)
                {
                    seg.fastack++;
                }
            }
        }
예제 #6
0
        void parse_una(uint una)
        {
            var count = 0;

            foreach (var seg in snd_buf)
            {
                if (KcpTool.Subtract(una, seg.sn) > 0)
                {
                    count++;
                    Segment.Put(seg);
                }
                else
                {
                    break;
                }
            }

            if (count > 0)
            {
                snd_buf.RemoveRange(0, count);
            }
        }
예제 #7
0
        void parse_ack(uint sn)
        {
            if (KcpTool.Subtract(sn, snd_una) < 0 || KcpTool.Subtract(sn, snd_nxt) >= 0)
            {
                return;
            }

            foreach (var seg in snd_buf)
            {
                if (sn == seg.sn)
                {
                    // mark and free space, but leave the segment here,
                    // and wait until `una` to delete this, then we don't
                    // have to shift the segments behind forward,
                    // which is an expensive operation for large window
                    seg.acked = 1;
                    break;
                }
                if (KcpTool.Subtract(sn, seg.sn) < 0)
                {
                    break;
                }
            }
        }
예제 #8
0
        // flush pending data
        public uint Flush(bool ackOnly)
        {
            var seg = Segment.Get(32);

            seg.conv = conv;
            seg.cmd  = KcpTool.IKCP_CMD_ACK;
            seg.wnd  = (uint)wnd_unused();
            seg.una  = rcv_nxt;

            var writeIndex = reserved;

            Action <int> makeSpace = (space) =>
            {
                if (writeIndex + space > mtu)
                {
                    _sender.Send(buffer, writeIndex);
                    writeIndex = reserved;
                }
            };

            Action flushBuffer = () =>
            {
                if (writeIndex > reserved)
                {
                    _sender.Send(buffer, writeIndex);
                }
            };

            // flush acknowledges
            for (var i = 0; i < acklist.Count; i++)
            {
                makeSpace(KcpTool.IKCP_OVERHEAD);
                var ack = acklist[i];
                if (KcpTool.Subtract(ack.sn, rcv_nxt) >= 0 || acklist.Count - 1 == i)
                {
                    seg.sn      = ack.sn;
                    seg.ts      = ack.ts;
                    writeIndex += seg.Encode(buffer, writeIndex);
                }
            }
            acklist.Clear();

            // flash remain ack segments
            if (ackOnly)
            {
                flushBuffer();
                return(interval);
            }

            uint current = 0;

            // probe window size (if remote window size equals zero)
            if (0 == rmt_wnd)
            {
                current = time;
                if (0 == probe_wait)
                {
                    probe_wait = KcpTool.IKCP_PROBE_INIT;
                    ts_probe   = current + probe_wait;
                }
                else
                {
                    if (KcpTool.Subtract(current, ts_probe) >= 0)
                    {
                        if (probe_wait < KcpTool.IKCP_PROBE_INIT)
                        {
                            probe_wait = KcpTool.IKCP_PROBE_INIT;
                        }
                        probe_wait += probe_wait / 2;
                        if (probe_wait > KcpTool.IKCP_PROBE_LIMIT)
                        {
                            probe_wait = KcpTool.IKCP_PROBE_LIMIT;
                        }
                        ts_probe = current + probe_wait;
                        probe   |= KcpTool.IKCP_ASK_SEND;
                    }
                }
            }
            else
            {
                ts_probe   = 0;
                probe_wait = 0;
            }

            // flush window probing commands
            if ((probe & KcpTool.IKCP_ASK_SEND) != 0)
            {
                seg.cmd = KcpTool.IKCP_CMD_WASK;
                makeSpace(KcpTool.IKCP_OVERHEAD);
                writeIndex += seg.Encode(buffer, writeIndex);
            }

            if ((probe & KcpTool.IKCP_ASK_TELL) != 0)
            {
                seg.cmd = KcpTool.IKCP_CMD_WINS;
                makeSpace(KcpTool.IKCP_OVERHEAD);
                writeIndex += seg.Encode(buffer, writeIndex);
            }

            probe = 0;

            // calculate window size
            var cwnd_ = KcpTool.Min(snd_wnd, rmt_wnd);

            if (!nocwnd)
            {
                cwnd_ = KcpTool.Min(cwnd, cwnd_);
            }

            // sliding window, controlled by snd_nxt && sna_una+cwnd
            var newSegsCount = 0;

            for (var k = 0; k < snd_queue.Count; k++)
            {
                if (KcpTool.Subtract(snd_nxt, snd_una + cwnd_) >= 0)
                {
                    break;
                }

                var newseg = snd_queue[k];
                newseg.conv = conv;
                newseg.cmd  = KcpTool.IKCP_CMD_PUSH;
                newseg.sn   = snd_nxt;
                snd_buf.Add(newseg);
                snd_nxt++;
                newSegsCount++;
            }

            if (newSegsCount > 0)
            {
                snd_queue.RemoveRange(0, newSegsCount);
            }

            // calculate resent
            var resent = (uint)fastresend;

            if (fastresend <= 0)
            {
                resent = 0xffffffff;
            }

            // check for retransmissions
            current = time;
            UInt64 change = 0; UInt64 lostSegs = 0; UInt64 fastRetransSegs = 0; UInt64 earlyRetransSegs = 0;
            var    minrto = (Int32)interval;

            for (var k = 0; k < snd_buf.Count; k++)
            {
                var segment  = snd_buf[k];
                var needsend = false;
                if (segment.acked == 1)
                {
                    continue;
                }
                if (segment.xmit == 0)  // initial transmit
                {
                    needsend         = true;
                    segment.rto      = rx_rto;
                    segment.resendts = current + segment.rto;
                }
                else if (segment.fastack >= resent) // fast retransmit
                {
                    needsend         = true;
                    segment.fastack  = 0;
                    segment.rto      = rx_rto;
                    segment.resendts = current + segment.rto;
                    change++;
                    fastRetransSegs++;
                }
                else if (segment.fastack > 0 && newSegsCount == 0) // early retransmit
                {
                    needsend         = true;
                    segment.fastack  = 0;
                    segment.rto      = rx_rto;
                    segment.resendts = current + segment.rto;
                    change++;
                    earlyRetransSegs++;
                }
                else if (KcpTool.Subtract(current, segment.resendts) >= 0) // RTO
                {
                    needsend = true;
                    if (nodelay == 0)
                    {
                        segment.rto += rx_rto;
                    }
                    else
                    {
                        segment.rto += rx_rto / 2;
                    }
                    segment.fastack  = 0;
                    segment.resendts = current + segment.rto;
                    lostSegs++;
                }

                if (needsend)
                {
                    current = time;
                    segment.xmit++;
                    segment.ts  = current;
                    segment.wnd = seg.wnd;
                    segment.una = seg.una;

                    var need = KcpTool.IKCP_OVERHEAD + segment.data.canRead;
                    makeSpace(need);
                    writeIndex += segment.Encode(buffer, writeIndex);
                    Buffer.BlockCopy(segment.data.buffer, segment.data.reader, buffer, writeIndex, segment.data.canRead);
                    writeIndex += segment.data.canRead;

                    if (segment.xmit >= dead_link)
                    {
                        state = 0xFFFFFFFF;
                    }
                }

                // get the nearest rto
                var _rto = KcpTool.Subtract(segment.resendts, current);
                if (_rto > 0 && _rto < minrto)
                {
                    minrto = _rto;
                }
            }

            // flash remain segments
            flushBuffer();

            // cwnd update
            if (!nocwnd)
            {
                // update ssthresh
                // rate halving, https://tools.ietf.org/html/rfc6937
                if (change > 0)
                {
                    var inflght = snd_nxt - snd_una;
                    ssthresh = inflght / 2;
                    if (ssthresh < KcpTool.IKCP_THRESH_MIN)
                    {
                        ssthresh = KcpTool.IKCP_THRESH_MIN;
                    }
                    cwnd = ssthresh + resent;
                    incr = cwnd * mss;
                }

                // congestion control, https://tools.ietf.org/html/rfc5681
                if (lostSegs > 0)
                {
                    ssthresh = cwnd / 2;
                    if (ssthresh < KcpTool.IKCP_THRESH_MIN)
                    {
                        ssthresh = KcpTool.IKCP_THRESH_MIN;
                    }
                    cwnd = 1;
                    incr = mss;
                }

                if (cwnd < 1)
                {
                    cwnd = 1;
                    incr = mss;
                }
            }

            return((uint)minrto);
        }
예제 #9
0
        // Input when you received a low level packet (eg. UDP packet), call it
        // regular indicates a regular packet has received(not from FEC)
        //
        // 'ackNoDelay' will trigger immediate ACK, but surely it will not be efficient in bandwidth
        public int Input(byte[] data, int offset, int size, bool regular, bool ackNoDelay)
        {
            var s_una = snd_una;

            if (size < KcpTool.IKCP_OVERHEAD)
            {
                return(-1);
            }

            Int32  _offset = offset;
            uint   latest  = 0;
            int    flag    = 0;
            UInt64 inSegs  = 0;

            while (true)
            {
                uint ts     = 0;
                uint sn     = 0;
                uint length = 0;
                uint una    = 0;
                uint conv_  = 0;

                UInt16 wnd = 0;
                byte   cmd = 0;
                byte   frg = 0;

                if (size - (_offset - offset) < KcpTool.IKCP_OVERHEAD)
                {
                    break;
                }

                _offset += KcpTool.Decode(data, _offset, ref conv_);

                if (conv != conv_)
                {
                    return(-1);
                }

                _offset += KcpTool.Decode(data, _offset, ref cmd);
                _offset += KcpTool.Decode(data, _offset, ref frg);
                _offset += KcpTool.Decode(data, _offset, ref wnd);
                _offset += KcpTool.Decode(data, _offset, ref ts);
                _offset += KcpTool.Decode(data, _offset, ref sn);
                _offset += KcpTool.Decode(data, _offset, ref una);
                _offset += KcpTool.Decode(data, _offset, ref length);

                if (size - (_offset - offset) < length)
                {
                    return(-2);
                }
                switch (cmd)
                {
                case KcpTool.IKCP_CMD_PUSH:
                case KcpTool.IKCP_CMD_ACK:
                case KcpTool.IKCP_CMD_WASK:
                case KcpTool.IKCP_CMD_WINS:
                    break;

                default:
                    return(-3);
                }

                // only trust window updates from regular packets. i.e: latest update
                if (regular)
                {
                    rmt_wnd = wnd;
                }

                parse_una(una);
                shrink_buf();
                switch (cmd)
                {
                case KcpTool.IKCP_CMD_PUSH:
                    var repeat = true;
                    if (KcpTool.Subtract(sn, rcv_nxt + rcv_wnd) < 0)
                    {
                        ack_push(sn, ts);
                        if (KcpTool.Subtract(sn, rcv_nxt) >= 0)
                        {
                            var seg = Segment.Get((int)length);
                            seg.conv = conv_;
                            seg.cmd  = (uint)cmd;
                            seg.frg  = (uint)frg;
                            seg.wnd  = (uint)wnd;
                            seg.ts   = ts;
                            seg.sn   = sn;
                            seg.una  = una;
                            seg.data.WriteBytes(data, _offset, (int)length);
                            repeat = parse_data(seg);
                        }
                    }
                    break;

                case KcpTool.IKCP_CMD_ACK:
                    parse_ack(sn);
                    parse_fastack(sn, ts);
                    flag  |= 1;
                    latest = ts;
                    break;

                case KcpTool.IKCP_CMD_WASK:
                    probe |= KcpTool.IKCP_ASK_TELL;
                    break;

                case KcpTool.IKCP_CMD_WINS:
                    break;

                default:
                    return(-3);
                }
                inSegs++;
                _offset += (int)length;
            }

            // update rtt with the latest ts
            // ignore the FEC packet
            if (flag != 0 && regular)
            {
                var current = time;
                if (KcpTool.Subtract(current, latest) >= 0)
                {
                    update_ack(KcpTool.Subtract(current, latest));
                }
            }

            // cwnd update when packet arrived
            if (!nocwnd)
            {
                if (KcpTool.Subtract(snd_una, s_una) > 0)
                {
                    if (cwnd < rmt_wnd)
                    {
                        var _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)
                            {
                                if (_mss > 0)
                                {
                                    cwnd = (incr + _mss - 1) / _mss;
                                }
                                else
                                {
                                    cwnd = incr + _mss - 1;
                                }
                            }
                        }
                        if (cwnd > rmt_wnd)
                        {
                            cwnd = rmt_wnd;
                            incr = rmt_wnd * _mss;
                        }
                    }
                }
            }

            // ack immediately
            if (ackNoDelay && acklist.Count > 0)
            {
                Flush(true);
            }

            return(0);
        }
예제 #10
0
        bool parse_data(Segment newseg)
        {
            var sn = newseg.sn;

            if (KcpTool.Subtract(sn, rcv_nxt + rcv_wnd) >= 0 || KcpTool.Subtract(sn, rcv_nxt) < 0)
            {
                return(true);
            }

            var n          = rcv_buf.Count - 1;
            var insert_idx = 0;
            var repeat     = false;

            for (var i = n; i >= 0; i--)
            {
                var seg = rcv_buf[i];
                if (seg.sn == sn)
                {
                    repeat = true;
                    break;
                }

                if (KcpTool.Subtract(sn, seg.sn) > 0)
                {
                    insert_idx = i + 1;
                    break;
                }
            }

            if (!repeat)
            {
                if (insert_idx == n + 1)
                {
                    rcv_buf.Add(newseg);
                }
                else
                {
                    rcv_buf.Insert(insert_idx, newseg);
                }
            }

            // move available data from rcv_buf -> rcv_queue
            var count = 0;

            foreach (var seg in rcv_buf)
            {
                if (seg.sn == rcv_nxt && rcv_queue.Count + count < rcv_wnd)
                {
                    rcv_nxt++;
                    count++;
                }
                else
                {
                    break;
                }
            }

            if (count > 0)
            {
                for (var i = 0; i < count; i++)
                {
                    rcv_queue.Add(rcv_buf[i]);
                }
                rcv_buf.RemoveRange(0, count);
            }
            return(repeat);
        }