Browse Source

First commit.

justin.xing 4 years ago
commit
c322d17b76

+ 12 - 0
KcpClientTest/KcpClientTest.csproj

@@ -0,0 +1,12 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <OutputType>Exe</OutputType>
+    <TargetFramework>net5.0</TargetFramework>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <ProjectReference Include="..\KcpNet\KcpNet.csproj" />
+  </ItemGroup>
+
+</Project>

+ 88 - 0
KcpClientTest/Program.cs

@@ -0,0 +1,88 @@
+using System;
+using System.Net;
+using System.Text;
+using System.Threading.Tasks;
+using KcpNet;
+
+namespace KcpClientTest
+{
+    class Program
+    {
+        static void Main(string[] args)
+        {
+            var client = new KcpClient();
+            Task.Run(async () =>
+            {
+                client.Connect(new IPEndPoint(IPAddress.Parse("127.0.0.1"), 6000), 5000);
+                client.Connection.PingResponsed += (sender, eventArgs) =>
+                {
+                    Console.WriteLine("Ping responsed.");
+                };
+                var i = 0;
+                while (true)
+                {
+                    i++;
+                    try
+                    {
+                        await SendStringAsync(client, $"[{i}]哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈").ConfigureAwait(false);
+                        var result = await ReadStringAsync(client).ConfigureAwait(false);
+                        Console.WriteLine($"Client [{client.Connection.Id}] received: " + result);
+                    }
+                    catch (Exception ex)
+                    {
+                        Console.WriteLine(ex.Message);
+                        break;
+                    }
+
+                }
+            });
+            Console.Read();
+        }
+
+
+        private static async Task SendStringAsync(KcpClient client, string str)
+        {
+            var strData = Encoding.UTF8.GetBytes(str);
+            var lengthData = BitConverter.GetBytes(strData.Length);
+            await client.SendAsync(lengthData).ConfigureAwait(false);
+            await client.SendAsync(strData).ConfigureAwait(false);
+        }
+
+        private static async Task<string> ReadStringAsync(KcpClient client)
+        {
+            var lengthData = await ReadBytesAsync(client, 4).ConfigureAwait(false);
+            var length = BitConverter.ToInt32(lengthData);
+            var strData = await ReadBytesAsync(client, length);
+            return Encoding.UTF8.GetString(strData);
+        }
+
+        private static async Task<byte[]> ReadBytesAsync(KcpClient client, int size)
+        {
+            if (size > 0)
+            {
+                var bytes = new byte[size];
+                var length = size;
+                var startIndex = 0;
+                while (length > 0)
+                {
+                    var buffer = new byte[length];
+                    var bytesRead = await client.ReadAsync(buffer).ConfigureAwait(false);
+                    if (bytesRead == 0)
+                    {
+                        throw new InvalidOperationException("Connection closed.");
+                    }
+                    if (bytesRead > 0)
+                    {
+                        Array.Copy(buffer, 0, bytes, startIndex, bytesRead);
+                        startIndex += bytesRead;
+                        length -= bytesRead;
+                    }
+                }
+
+                return bytes;
+            }
+
+            return null;
+        }
+    }
+}

+ 37 - 0
KcpNet.sln

@@ -0,0 +1,37 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 16
+VisualStudioVersion = 16.0.30717.126
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "KcpServerTest", "KcpServerTest\KcpServerTest.csproj", "{D98A6983-F6C1-4702-ADDD-B2DF8857D5E0}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "KcpClientTest", "KcpClientTest\KcpClientTest.csproj", "{917253A8-6E16-4519-B7CF-24CDFDB315EA}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "KcpNet", "KcpNet\KcpNet.csproj", "{2E76987D-5FA4-4A20-BBB4-9F4F537D1446}"
+EndProject
+Global
+	GlobalSection(SolutionConfigurationPlatforms) = preSolution
+		Debug|Any CPU = Debug|Any CPU
+		Release|Any CPU = Release|Any CPU
+	EndGlobalSection
+	GlobalSection(ProjectConfigurationPlatforms) = postSolution
+		{D98A6983-F6C1-4702-ADDD-B2DF8857D5E0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{D98A6983-F6C1-4702-ADDD-B2DF8857D5E0}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{D98A6983-F6C1-4702-ADDD-B2DF8857D5E0}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{D98A6983-F6C1-4702-ADDD-B2DF8857D5E0}.Release|Any CPU.Build.0 = Release|Any CPU
+		{917253A8-6E16-4519-B7CF-24CDFDB315EA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{917253A8-6E16-4519-B7CF-24CDFDB315EA}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{917253A8-6E16-4519-B7CF-24CDFDB315EA}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{917253A8-6E16-4519-B7CF-24CDFDB315EA}.Release|Any CPU.Build.0 = Release|Any CPU
+		{2E76987D-5FA4-4A20-BBB4-9F4F537D1446}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{2E76987D-5FA4-4A20-BBB4-9F4F537D1446}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{2E76987D-5FA4-4A20-BBB4-9F4F537D1446}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{2E76987D-5FA4-4A20-BBB4-9F4F537D1446}.Release|Any CPU.Build.0 = Release|Any CPU
+	EndGlobalSection
+	GlobalSection(SolutionProperties) = preSolution
+		HideSolutionNode = FALSE
+	EndGlobalSection
+	GlobalSection(ExtensibilityGlobals) = postSolution
+		SolutionGuid = {71EEDF95-B3B9-4C99-A2D7-B4E95D2C6635}
+	EndGlobalSection
+EndGlobal

+ 66 - 0
KcpNet/FakeKcpIO.cs

@@ -0,0 +1,66 @@
+using System;
+using System.Buffers;
+using System.Threading.Tasks;
+
+namespace KcpNet
+{
+    /// <summary>
+    /// 用于调试的KCP IO 类,没有Kcp功能
+    /// </summary>
+    public class FakeKcpIO : IKcpIO
+    {
+        SimplePipeQueue<byte[]> recv = new SimplePipeQueue<byte[]>();
+        public int Input(ReadOnlySpan<byte> span)
+        {
+            byte[] buffer = new byte[span.Length];
+            span.CopyTo(buffer);
+            recv.Write(buffer);
+            return 0;
+        }
+
+        public int Input(ReadOnlySequence<byte> span)
+        {
+            byte[] buffer = new byte[span.Length];
+            span.CopyTo(buffer);
+            return Input(buffer);
+        }
+
+        public async ValueTask Recv(IBufferWriter<byte> writer, object option = null)
+        {
+            var buffer = await recv.ReadAsync().ConfigureAwait(false);
+            var target = writer.GetMemory(buffer.Length);
+            buffer.AsSpan().CopyTo(target.Span);
+            writer.Advance(buffer.Length);
+        }
+
+
+        SimplePipeQueue<byte[]> send = new SimplePipeQueue<byte[]>();
+        public int Send(ReadOnlySpan<byte> span, object option = null)
+        {
+            byte[] buffer = new byte[span.Length];
+            span.CopyTo(buffer);
+            send.Write(buffer);
+            return 0;
+        }
+
+        public int Send(ReadOnlySequence<byte> span, object option = null)
+        {
+            byte[] buffer = new byte[span.Length];
+            span.CopyTo(buffer);
+            return Send(buffer);
+        }
+
+        public async ValueTask Output(IBufferWriter<byte> writer, object option = null)
+        {
+            var buffer = await send.ReadAsync().ConfigureAwait(false);
+            Write(writer, buffer);
+        }
+
+        private static void Write(IBufferWriter<byte> writer, byte[] buffer)
+        {
+            var span = writer.GetSpan(buffer.Length);
+            buffer.AsSpan().CopyTo(span);
+            writer.Advance(buffer.Length);
+        }
+    }
+}

+ 8 - 0
KcpNet/HandshakeLevel.cs

@@ -0,0 +1,8 @@
+namespace KcpNet
+{
+    internal enum HandshakeLevel
+    {
+        S01,
+        S02,
+    }
+}

+ 45 - 0
KcpNet/IKcpInterface.cs

@@ -0,0 +1,45 @@
+using System;
+using BufferOwner = System.Buffers.IMemoryOwner<byte>;
+
+namespace KcpNet
+{
+    /// <summary>
+    /// Kcp回调
+    /// </summary>
+    public interface IKcpCallback
+    {
+        /// <summary>
+        /// kcp 发送方向输出
+        /// </summary>
+        /// <param name="buffer">kcp 交出发送缓冲区控制权,缓冲区来自<see cref="RentBuffer(int)"/></param>
+        /// <param name="avalidLength">数据的有效长度</param>
+        /// <returns>不需要返回值</returns>
+        /// <remarks>通过增加 avalidLength 能够在协议栈中有效的减少数据拷贝</remarks>
+        void Output(BufferOwner buffer, int avalidLength);
+    }
+
+
+    /// <summary>
+    /// 外部提供缓冲区,可以在外部链接一个内存池
+    /// </summary>
+    public interface IRentable
+    {
+        /// <summary>
+        /// 外部提供缓冲区,可以在外部链接一个内存池
+        /// </summary>
+        BufferOwner RentBuffer(int length);
+    }
+
+    public interface IKcpSetting
+    {
+        int Interval(int interval_);
+        int NoDelay(int nodelay_, int interval_, int resend_, int nc_);
+        int SetMtu(int mtu_);
+        int WndSize(int sndwnd, int rcvwnd);
+    }
+
+    public interface IKcpUpdate
+    {
+        void Update(in DateTime time);
+    }
+}

+ 18 - 0
KcpNet/IKcpIo.cs

@@ -0,0 +1,18 @@
+using System;
+
+namespace KcpNet
+{
+    public interface IKcpIo
+    {
+        event EventHandler<byte[]> Received;
+
+        void Output(byte[] buffer);
+
+        void Output(Memory<byte> memory, int size);
+
+        void Input(byte[] buffer);
+
+        void Input(Memory<byte> memory, int size);
+    }
+
+}

+ 7 - 0
KcpNet/IKcpIoContainer.cs

@@ -0,0 +1,7 @@
+namespace KcpNet
+{
+    internal interface IKcpIoContainer
+    {
+        IKcpIo Io { get; }
+    }
+}

+ 33 - 0
KcpNet/IKcpSegment.cs

@@ -0,0 +1,33 @@
+using System;
+
+namespace KcpNet
+{
+    public interface IKcpSegment
+    {
+        byte cmd { get; set; }
+        uint conv { get; set; }
+        Span<byte> data { get; }
+        uint fastack { get; set; }
+        byte frg { get; set; }
+        uint len { get; }
+        uint resendts { get; set; }
+        uint rto { get; set; }
+        uint sn { get; set; }
+        uint ts { get; set; }
+        uint una { get; set; }
+        ushort wnd { get; set; }
+        uint xmit { get; set; }
+
+        int Encode(Span<byte> buffer);
+    }
+
+    public interface ISegmentManager<Segment> where Segment : IKcpSegment
+    {
+        Segment Alloc(int appendDateSize);
+        void Free(Segment seg);
+    }
+
+}
+
+
+

+ 541 - 0
KcpNet/Kcp.cs

@@ -0,0 +1,541 @@
+using System;
+using System.Buffers.Binary;
+using BufferOwner = System.Buffers.IMemoryOwner<byte>;
+
+namespace KcpNet
+{
+    public class Kcp<Segment> : KcpCore<Segment>
+        where Segment:IKcpSegment
+    {
+        /// <summary>
+        /// create a new kcp control object, 'conv' must equal in two endpoint
+        /// from the same connection.
+        /// </summary>
+        /// <param name="conv_"></param>
+        /// <param name="callback"></param>
+        /// <param name="rentable">可租用内存的回调</param>
+        public Kcp(uint conv_, IKcpCallback callback, IRentable rentable = null)
+            :base(conv_)
+        {
+            callbackHandle = callback;
+            this.rentable = rentable;
+        }      
+
+
+        //extension 重构和新增加的部分============================================
+
+        IRentable rentable;
+        /// <summary>
+        /// 如果外部能够提供缓冲区则使用外部缓冲区,否则new byte[]
+        /// </summary>
+        /// <param name="needSize"></param>
+        /// <returns></returns>
+        protected internal override BufferOwner CreateBuffer(int needSize)
+        {
+            var res = rentable?.RentBuffer(needSize);
+            if (res == null)
+            {
+                return base.CreateBuffer(needSize);
+            }
+            else
+            {
+                if (res.Memory.Length < needSize)
+                {
+                    throw new ArgumentException($"{nameof(rentable.RentBuffer)} 指定的委托不符合标准,返回的" +
+                        $"BufferOwner.Memory.Length 小于 {nameof(needSize)}");
+                }
+            }
+
+            return res;
+        }
+
+        public (BufferOwner buffer, int avalidLength) TryRecv()
+        {
+            if (rcv_queue.Count == 0)
+            {
+                ///没有可用包
+                return (null, -1);
+            }
+
+            var peekSize = -1;
+            var seq = rcv_queue[0];
+
+            if (seq.frg == 0)
+            {
+                peekSize = (int)seq.len;
+            }
+
+            if (rcv_queue.Count < seq.frg + 1)
+            {
+                ///没有足够的包
+                return (null, -1);
+            }
+
+            lock (rcv_queueLock)
+            {
+                uint length = 0;
+
+                foreach (var item in rcv_queue)
+                {
+                    length += item.len;
+                    if (item.frg == 0)
+                    {
+                        break;
+                    }
+                }
+
+                peekSize = (int)length;
+            }
+
+            if (peekSize <= 0)
+            {
+                return (null, -2);
+            }
+
+            var buffer = CreateBuffer(peekSize);
+            var recvlength = UncheckRecv(buffer.Memory.Span);
+            return (buffer, recvlength);
+        }
+
+        /// <summary>
+        /// user/upper level recv: returns size, returns below zero for EAGAIN
+        /// </summary>
+        /// <param name="buffer"></param>
+        /// <returns></returns>
+        public int Recv(Span<byte> buffer)
+        {
+            if (0 == rcv_queue.Count)
+            {
+                return -1;
+            }
+
+            var peekSize = PeekSize();
+            if (peekSize < 0)
+            {
+                return -2;
+            }
+
+            if (peekSize > buffer.Length)
+            {
+                return -3;
+            }
+
+            /// 拆分函数
+            var recvLength = UncheckRecv(buffer);
+
+            return recvLength;
+        }
+
+        /// <summary>
+        /// 这个函数不检查任何参数
+        /// </summary>
+        /// <param name="buffer"></param>
+        /// <returns></returns>
+        int UncheckRecv(Span<byte> buffer)
+        {
+            var recover = false;
+            if (rcv_queue.Count >= rcv_wnd)
+            {
+                recover = true;
+            }
+
+            #region merge fragment.
+            /// merge fragment.
+
+            var recvLength = 0;
+            lock (rcv_queueLock)
+            {
+                var count = 0;
+                foreach (var seg in rcv_queue)
+                {
+                    seg.data.CopyTo(buffer.Slice(recvLength));
+                    recvLength += (int)seg.len;
+
+                    count++;
+                    int frg = seg.frg;
+
+                    SegmentManager.Free(seg);
+                    if (frg == 0)
+                    {
+                        break;
+                    }
+                }
+
+                if (count > 0)
+                {
+                    rcv_queue.RemoveRange(0, count);
+                }
+            }
+
+            #endregion
+
+            Move_Rcv_buf_2_Rcv_queue();
+
+            #region fast recover
+            /// fast recover
+            if (rcv_queue.Count < rcv_wnd && recover)
+            {
+                // ready to send back IKCP_CMD_WINS in ikcp_flush
+                // tell remote my window size
+                probe |= IKCP_ASK_TELL;
+            }
+            #endregion
+            return recvLength;
+        }
+
+        /// <summary>
+        /// check the size of next message in the recv queue
+        /// </summary>
+        /// <returns></returns>
+        public int PeekSize()
+        {
+
+            if (rcv_queue.Count == 0)
+            {
+                ///没有可用包
+                return -1;
+            }
+
+            var seq = rcv_queue[0];
+
+            if (seq.frg == 0)
+            {
+                return (int)seq.len;
+            }
+
+            if (rcv_queue.Count < seq.frg + 1)
+            {
+                ///没有足够的包
+                return -1;
+            }
+
+            lock (rcv_queueLock)
+            {
+                uint length = 0;
+
+                foreach (var item in rcv_queue)
+                {
+                    length += item.len;
+                    if (item.frg == 0)
+                    {
+                        break;
+                    }
+                }
+
+                return (int)length;
+            }
+        }
+
+        /// <summary>
+        /// user/upper level send, returns below zero for error
+        /// </summary>
+        /// <param name="buffer"></param>
+        /// <returns></returns>
+        public int Send(Span<byte> buffer)
+        {
+            if (CheckDispose())
+            {
+                //检查释放
+                return -4;
+            }
+
+            if (mss <= 0)
+            {
+                throw new InvalidOperationException($" mss <= 0 ");
+            }
+
+
+            if (buffer.Length == 0)
+            {
+                return -1;
+            }
+            var offset = 0;
+            var count = 0;
+
+            #region append to previous segment in streaming mode (if possible)
+            /// 基于线程安全和数据结构的等原因,移除了追加数据到最后一个包行为。
+            #endregion
+
+            #region fragment
+
+            if (buffer.Length <= mss)
+            {
+                count = 1;
+            }
+            else
+            {
+                count = (int)(buffer.Length + mss - 1) / (int)mss;
+            }
+
+            if (count > IKCP_WND_RCV)
+            {
+                return -2;
+            }
+
+            if (count == 0)
+            {
+                count = 1;
+            }
+
+            for (var i = 0; i < count; i++)
+            {
+                var size = 0;
+                if (buffer.Length - offset > mss)
+                {
+                    size = (int)mss;
+                }
+                else
+                {
+                    size = buffer.Length - offset;
+                }
+
+                var seg = SegmentManager.Alloc(size);
+                buffer.Slice(offset, size).CopyTo(seg.data);
+                offset += size;
+                seg.frg = (byte)(count - i - 1);
+                snd_queue.Enqueue(seg);
+            }
+
+            #endregion
+
+
+            return 0;
+        }
+
+        /// <summary>
+        /// when you received a low level packet (eg. UDP packet), call it
+        /// </summary>
+        /// <param name="data"></param>
+        /// <returns></returns>
+        public int Input(Span<byte> data)
+        {
+            if (CheckDispose())
+            {
+                //检查释放
+                return -4;
+            }
+
+            uint temp_una = snd_una;
+
+            if (data.Length < IKCP_OVERHEAD)
+            {
+                return -1;
+            }
+
+            var offset = 0;
+            int flag = 0;
+            uint maxack = 0;
+            while (true)
+            {
+                uint ts = 0;
+                uint sn = 0;
+                uint length = 0;
+                uint una = 0;
+                uint conv_ = 0;
+                ushort wnd = 0;
+                byte cmd = 0;
+                byte frg = 0;
+
+                if (data.Length - offset < IKCP_OVERHEAD)
+                {
+                    break;
+                }
+
+                if (IsLittleEndian)
+                {
+                    conv_ = BinaryPrimitives.ReadUInt32LittleEndian(data.Slice(offset));
+                    offset += 4;
+
+                    if (conv != conv_)
+                    {
+                        return -1;
+                    }
+
+                    cmd = data[offset];
+                    offset += 1;
+                    frg = data[offset];
+                    offset += 1;
+                    wnd = BinaryPrimitives.ReadUInt16LittleEndian(data.Slice(offset));
+                    offset += 2;
+
+                    ts = BinaryPrimitives.ReadUInt32LittleEndian(data.Slice(offset));
+                    offset += 4;
+                    sn = BinaryPrimitives.ReadUInt32LittleEndian(data.Slice(offset));
+                    offset += 4;
+                    una = BinaryPrimitives.ReadUInt32LittleEndian(data.Slice(offset));
+                    offset += 4;
+                    length = BinaryPrimitives.ReadUInt32LittleEndian(data.Slice(offset));
+                    offset += 4;
+                }
+                else
+                {
+                    conv_ = BinaryPrimitives.ReadUInt32BigEndian(data.Slice(offset));
+                    offset += 4;
+
+                    if (conv != conv_)
+                    {
+                        return -1;
+                    }
+
+                    cmd = data[offset];
+                    offset += 1;
+                    frg = data[offset];
+                    offset += 1;
+                    wnd = BinaryPrimitives.ReadUInt16BigEndian(data.Slice(offset));
+                    offset += 2;
+
+                    ts = BinaryPrimitives.ReadUInt32BigEndian(data.Slice(offset));
+                    offset += 4;
+                    sn = BinaryPrimitives.ReadUInt32BigEndian(data.Slice(offset));
+                    offset += 4;
+                    una = BinaryPrimitives.ReadUInt32BigEndian(data.Slice(offset));
+                    offset += 4;
+                    length = BinaryPrimitives.ReadUInt32BigEndian(data.Slice(offset));
+                    offset += 4;
+                }
+
+
+                if (data.Length - offset < length || (int)length < 0)
+                {
+                    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;
+                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 (flag == 0)
+                    {
+                        flag = 1;
+                        maxack = sn;
+                    }
+                    else if (Itimediff(sn, maxack) > 0)
+                    {
+                        maxack = sn;
+                    }
+
+                }
+                else if (IKCP_CMD_PUSH == cmd)
+                {
+                    if (Itimediff(sn, rcv_nxt + rcv_wnd) < 0)
+                    {
+                        ///instead of ikcp_ack_push
+                        acklist.Enqueue((sn, ts));
+
+                        if (Itimediff(sn, rcv_nxt) >= 0)
+                        {
+                            var seg = SegmentManager.Alloc((int)length);
+                            seg.conv = conv_;
+                            seg.cmd = cmd;
+                            seg.frg = frg;
+                            seg.wnd = wnd;
+                            seg.ts = ts;
+                            seg.sn = sn;
+                            seg.una = una;
+                            //seg.len = length;  长度在分配时确定,不能改变
+
+                            if (length > 0)
+                            {
+                                data.Slice(offset, (int)length).CopyTo(seg.data);
+                            }
+
+                            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 (flag != 0)
+            {
+                Parse_fastack(maxack);
+            }
+
+            if (Itimediff(this.snd_una, temp_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)
+                        {
+                            cwnd++;
+                        }
+                    }
+                    if (cwnd > rmt_wnd)
+                    {
+                        cwnd = rmt_wnd;
+                        incr = rmt_wnd * mss_;
+                    }
+                }
+            }
+
+            return 0;
+        }
+    }
+
+    public class Kcp : Kcp<KcpSegment>
+    {
+        public Kcp(uint conv_, IKcpCallback callback, IRentable rentable = null) 
+            : base(conv_, callback, rentable)
+        {
+            SegmentManager = SimpleSegManager.Default;
+        }
+    }
+}
+
+
+
+
+
+
+
+
+
+

+ 149 - 0
KcpNet/KcpByteBuffer.cs

@@ -0,0 +1,149 @@
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+using System.Threading.Tasks.Dataflow;
+
+namespace KcpNet
+{
+    internal class KcpByteBuffer
+    {
+        private readonly BufferBlock<byte[]> _bufferQueue;
+
+        private int _bufferOffset;
+        private byte[] _buffer;
+
+        public KcpByteBuffer()
+        {
+            _bufferQueue = new BufferBlock<byte[]>();
+        }
+
+        public void Write(byte[] bytes)
+        {
+            if (!_bufferQueue.Post(bytes))
+            {
+                throw new InvalidOperationException("Write bytes error.");
+            }
+        }
+
+        public async Task WriteAsync(byte[] value)
+        {
+            if (!await _bufferQueue.SendAsync(value).ConfigureAwait(false))
+            {
+                throw new InvalidOperationException("Write bytes error.");
+            }
+        }
+
+
+        public async Task<int> ReadAsync(byte[] buffer, CancellationToken cancellationToken)
+        {
+            try
+            {
+                if (_buffer != null)
+                {
+                    var bufferLength = _buffer.Length - _bufferOffset;
+                    if (buffer.Length >= bufferLength)
+                    {
+                        var length = bufferLength;
+                        Array.Copy(_buffer, _bufferOffset, buffer, 0, length);
+                        _buffer = null;
+                        _bufferOffset = 0;
+                        return length;
+                    }
+
+                    Array.Copy(_buffer, _bufferOffset, buffer, 0, buffer.Length);
+                    _bufferOffset += buffer.Length;
+                    return buffer.Length;
+
+                }
+
+                _buffer = await _bufferQueue.ReceiveAsync(cancellationToken).ConfigureAwait(false);
+                if (_buffer != null)
+                {
+                    if (_buffer.Length <= buffer.Length)
+                    {
+                        var length = _buffer.Length;
+                        Array.Copy(_buffer, buffer, length);
+                        _buffer = null;
+                        return length;
+                    }
+
+                    if (_buffer.Length > buffer.Length)
+                    {
+                        Array.Copy(_buffer, buffer, buffer.Length);
+                        _bufferOffset += buffer.Length;
+                        return buffer.Length;
+                    }
+                }
+            }
+            catch (Exception)
+            {
+                //This could be the connection is closed.
+            }
+
+            return 0;
+        }
+
+
+        public async Task<int> ReadAsync(byte[] buffer)
+        {
+            return await ReadAsync(buffer, CancellationToken.None).ConfigureAwait(false);
+        }
+
+        public int Read(byte[] buffer)
+        {
+            try
+            {
+                if (_buffer != null)
+                {
+                    var bufferLength = _buffer.Length - _bufferOffset;
+                    if (buffer.Length >= bufferLength)
+                    {
+                        var length = bufferLength;
+                        Array.Copy(_buffer, _bufferOffset, buffer, 0,length);
+                        _buffer = null;
+                        _bufferOffset = 0;
+                        return length;
+                    }
+
+                    Array.Copy(_buffer, _bufferOffset, buffer,0, buffer.Length);
+                    _bufferOffset += buffer.Length;
+                    return buffer.Length;
+
+                }
+
+                _buffer = _bufferQueue.Receive();
+                if (_buffer != null)
+                {
+                    if (_buffer.Length <= buffer.Length)
+                    {
+                        var length = _buffer.Length;
+                        Array.Copy(_buffer, buffer, length);
+                        _buffer = null;
+                        return length;
+                    }
+
+                    if (_buffer.Length > buffer.Length)
+                    {
+                        Array.Copy(_buffer, buffer, buffer.Length);
+                        _bufferOffset += buffer.Length;
+                        return buffer.Length;
+                    }
+                }
+            }
+            catch (Exception)
+            {
+                //This could be the connection is closed.
+            }
+
+            return 0;
+        }
+
+        public void Close()
+        {
+            _bufferQueue.TryReceiveAll(out _);
+            _bufferQueue.Complete();
+            _buffer = null;
+            _bufferOffset = 0;
+        }
+    }
+}

+ 251 - 0
KcpNet/KcpClient.cs

@@ -0,0 +1,251 @@
+using System;
+using System.IO;
+using System.Linq;
+using System.Net;
+using System.Net.Sockets;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace KcpNet
+{
+    public class KcpClient : KcpNetBase
+    {
+        private readonly UdpClient _client;
+
+        private bool _connected;
+
+        private IPEndPoint _remoteEndPoint;
+
+        private KcpConnection _connection;
+
+        private Thread _processThread;
+        private Thread _updateTread;
+        private Thread _autoPingThread;
+
+        public TimeSpan ConnectionTimeout { get; set; } = TimeSpan.FromSeconds(5);
+
+        public TimeSpan PingInterval { get; set; } = TimeSpan.FromSeconds(2);
+
+        public bool AutoPing { get; set; } = true;
+
+        public KcpConnection Connection => _connection;
+
+        public KcpClient()
+        {
+            _client = new UdpClient();
+        }
+
+        public void Connect(IPEndPoint remoteEndPoint, int timeout = Timeout.Infinite)
+        {
+            _remoteEndPoint = remoteEndPoint;
+            var handshakeTask = HandshakeAsync();
+            var timeoutTask = Task.Delay(timeout);
+            var tasks = new[] { handshakeTask, timeoutTask };
+            var resultTask = Task.WhenAny(tasks).Result;
+            if (resultTask == handshakeTask)
+            {
+                _connection = handshakeTask.Result;
+                if (_connection != null)
+                {
+                    _connection.Closed += OnConnectionClosed;
+                    _connected = true;
+                    _processThread = new Thread(() =>
+                    {
+                        while (_connected)
+                        {
+                            try
+                            {
+                                _connection.ProcessOnce();
+                                Thread.Sleep(1);
+                            }
+                            catch (Exception)
+                            {
+                                _connection.Close();
+                                break;
+                            }
+
+                        }
+                    });
+                    _updateTread = new Thread(() =>
+                    {
+                        while (_connected)
+                        {
+                            try
+                            {
+                                _connection.Update();
+                                var now = DateTime.UtcNow;
+                                if (now - _connection.LastPing > ConnectionTimeout)
+                                {
+                                    _connection.Close();
+                                }
+
+                                Thread.Sleep(10);
+                            }
+                            catch (Exception)
+                            {
+                                _connection.Close();
+                                break;
+                            }
+
+                        }
+                    });
+                    _processThread.Start();
+                    _updateTread.Start();
+
+                    if (AutoPing)
+                    {
+                        _autoPingThread = new Thread(() =>
+                        {
+                            while (_connected)
+                            {
+                                try
+                                {
+                                    _connection.PingRequest();
+                                    Thread.Sleep(PingInterval);
+                                }
+                                catch (Exception)
+                                {
+                                    _connection.Close();
+                                    break;
+                                }
+                            }
+
+                        });
+                        _autoPingThread.Start();
+                    }
+
+                    Task.Run(async () =>
+                    {
+                        while (_connected)
+                        {
+                            var result = await _client.ReceiveAsync().ConfigureAwait(false);
+                            if (result.RemoteEndPoint.Equals(_remoteEndPoint))
+                            {
+                                ((IKcpIoContainer)_connection).Io.Input(result.Buffer);
+                            }
+                        }
+                    });
+                }
+                else
+                {
+                    throw new InvalidOperationException("Connect to server failed.");
+                }
+            }
+            else
+            {
+                _client.Close();
+                _client.Dispose();
+                throw new TimeoutException("Connect to server timeout.");
+            }
+        }
+
+        private void OnConnectionClosed(object sender, EventArgs e)
+        {
+            _connection.Closed -= OnConnectionClosed;
+            _connected = false;
+        }
+
+        public void Close()
+        {
+            if (_connected)
+            {
+                _connection?.Close();
+                _processThread?.Join();
+                _updateTread?.Join();
+                _autoPingThread?.Join();
+            }
+        }
+
+
+        public async Task SendAsync(byte[] buffer)
+        {
+            if (_connection != null)
+            {
+                await _connection.SendAsync(buffer).ConfigureAwait(false);
+            }
+            else
+            {
+                throw new InvalidOperationException("TODO");
+            }
+        }
+
+        public async Task<int> ReadAsync(byte[] buffer)
+        {
+            if (_connection != null)
+            {
+                return await _connection.ReadAsync(buffer).ConfigureAwait(false);
+            }
+
+            throw new InvalidOperationException("TODO");
+        }
+
+
+        private async Task<KcpConnection> HandshakeAsync()
+        {
+            try
+            {
+                var random = new Random();
+                var sb01 = (byte) random.Next(0, 255);
+                var sb02 = (byte) random.Next(0, 255);
+                var su03 = (ushort) (sb01 * sb02);
+                var su03Buffer = BitConverter.GetBytes(su03);
+                var sendBuffer = new byte[8];
+                Array.Copy(ProtocolHeader, 0, sendBuffer, 0, 3);
+                sendBuffer[3] = sb01;
+                sendBuffer[4] = sb02;
+                Array.Copy(su03Buffer, 0, sendBuffer, 5, 2);
+                sendBuffer[7] = (byte) HandshakeLevel.S01;
+
+                await _client.SendAsync(sendBuffer, sendBuffer.Length, _remoteEndPoint).ConfigureAwait(false);
+
+                var result = await _client.ReceiveAsync().ConfigureAwait(false);
+                if (result.RemoteEndPoint.Equals(_remoteEndPoint))
+                {
+                    await using var stream = new MemoryStream(result.Buffer);
+                    var header = await ReadBytesAsync(stream, 3).ConfigureAwait(false);
+                    if (!header.SequenceEqual(ProtocolHeader))
+                    {
+                        throw new InvalidDataException("Error header for handshake01.");
+                    }
+
+                    var rb1 = await ReadByteAsync(stream).ConfigureAwait(false);
+                    var rb2 = await ReadByteAsync(stream).ConfigureAwait(false);
+                    var ru = await ReadUInt16Async(stream).ConfigureAwait(false);
+                    if (rb1 * rb2 != ru)
+                    {
+                        throw new InvalidDataException("Error RBU data for handshake.");
+                    }
+
+                    var level = (HandshakeLevel) await ReadByteAsync(stream).ConfigureAwait(false);
+                    if (level != HandshakeLevel.S01)
+                    {
+                        throw new InvalidDataException("Error level data for handshake.");
+                    }
+
+                    var connectionId = await ReadUInt32Async(stream).ConfigureAwait(false);
+
+                    sendBuffer = new byte[12];
+                    var sb11 = (byte) random.Next(0, 255);
+                    var sb12 = (byte) random.Next(0, 255);
+                    var su13 = (ushort) (sb11 * sb12);
+                    var su13Buffer = BitConverter.GetBytes(su13);
+                    Array.Copy(ProtocolHeader, 0, sendBuffer, 0, 3);
+                    sendBuffer[3] = sb11;
+                    sendBuffer[4] = sb12;
+                    Array.Copy(su13Buffer, 0, sendBuffer, 5, 2);
+                    sendBuffer[7] = (byte) HandshakeLevel.S02;
+                    var idData = BitConverter.GetBytes(connectionId);
+                    Array.Copy(idData, 0, sendBuffer, 8, 4);
+                    await _client.SendAsync(sendBuffer, sendBuffer.Length, _remoteEndPoint).ConfigureAwait(false);
+                    return new KcpConnection(connectionId, new KcpUdpIo(_client, _remoteEndPoint),(IPEndPoint) _client.Client.LocalEndPoint, _remoteEndPoint);
+                }
+            }
+            catch
+            {
+                //
+            }
+            return null;
+        }
+        
+    }
+}

+ 249 - 0
KcpNet/KcpConnection.cs

@@ -0,0 +1,249 @@
+using System;
+using System.IO;
+using System.Net;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace KcpNet
+{
+    public class KcpConnection : IKcpIoContainer
+    {
+        private static readonly IRentable MemoryPool = new KcpMemoryPool();
+
+        private readonly IKcpIo _io;
+        private readonly Kcp _kcp;
+
+        private readonly Thread _readPacketThread;
+
+        private readonly KcpByteBuffer _receivedByteBuffer;
+        private readonly KcpDataBuffer _receivedDataBuffer;
+        private readonly KcpPriorityPacketBuffer _sendPacketBuffer;
+
+        private bool _closed;
+
+        IKcpIo IKcpIoContainer.Io => _io;
+
+        public uint Id { get; }
+
+        public IPEndPoint LocalEndPoint { get; }
+
+
+        public IPEndPoint RemoteEndPoint { get; }
+
+
+        public DateTime LastPing { get; private set; }
+
+
+        public event EventHandler PingRequested;
+
+
+        public event EventHandler PingResponsed;
+
+
+        public event EventHandler Closed;
+
+
+        internal KcpConnection(uint connectionId, IKcpIo io, IPEndPoint localEndPoint, IPEndPoint remoteEndPoint)
+        {
+            LastPing = DateTime.UtcNow;
+            Id = connectionId;
+            _io = io;
+            _io.Received += OnIoReceived;
+            LocalEndPoint = localEndPoint;
+            RemoteEndPoint = remoteEndPoint;
+            _receivedByteBuffer = new KcpByteBuffer();
+            _receivedDataBuffer = new KcpDataBuffer();
+            _sendPacketBuffer = new KcpPriorityPacketBuffer();
+            var callback = new KcpOutputCallback(_io);
+            _kcp = new Kcp(Id, callback, MemoryPool);
+            _kcp.NoDelay(1, 10, 2, 1);
+            _kcp.WndSize(64, 64);
+            _kcp.SetMtu(512);
+            _closed = false;
+            _readPacketThread = new Thread(() =>
+            {
+                try
+                {
+                    while (!_closed)
+                    {
+                        ReadPacketOnce();
+                        Thread.Sleep(10);
+                    }
+                }
+                catch (Exception)
+                {
+                    //Exception will be catch when the connection is closed.
+                }
+            });
+            _readPacketThread.Start();
+        }
+
+
+
+        private void ProcessReceive()
+        {
+            int size;
+            while ((size = _kcp.PeekSize()) > 0)
+            {
+                var buffer = new byte[size];
+                var result = _kcp.Recv(buffer);
+                if ( result >= 0)
+                {
+                    _receivedByteBuffer.Write(buffer);
+                }
+                else
+                {
+                    throw new InvalidOperationException($"Receive kcp message error:{result}.");
+                }
+            }
+        }
+
+
+        private void ProcessSend()
+        {
+            var packets = _sendPacketBuffer.ReadPackets();
+            foreach (var packet in packets)
+            {
+                var result = _kcp.Send(packet.GetBuffer());
+                if (result < 0)
+                {
+                    throw new InvalidOperationException($"Send kcp data error: {result}");
+                }
+            }
+        }
+
+
+
+        internal void ProcessOnce()
+        {
+            if (_closed) throw new InvalidOperationException("Connection closed.");
+            ProcessReceive();
+            ProcessSend();
+        }
+
+
+        /// <summary>
+        /// This method should run in a standalone thread.
+        /// </summary>
+        private void ReadPacketOnce()
+        {
+            var typeData = new byte[1];
+            var result = _receivedByteBuffer.Read(typeData);
+            if (result == 0)
+            {
+                throw new InvalidOperationException("Receive packet type error.");
+            }
+
+            var type = (KcpPacketType) typeData[0];
+            switch (type)
+            {
+                case KcpPacketType.PingRequest:
+                    PingResponse();
+                    LastPing = DateTime.UtcNow;
+                    PingRequested?.Invoke(this, EventArgs.Empty);
+                    break;
+                case KcpPacketType.PingResponse:
+                    LastPing = DateTime.UtcNow;
+                    PingResponsed?.Invoke(this, EventArgs.Empty);
+                    break;
+                case KcpPacketType.Close:
+                    Close();
+                    break;
+                case KcpPacketType.Data:
+                    var lengthData = new byte[4];
+                    result = _receivedByteBuffer.Read(lengthData);
+                    if (result == 0)
+                    {
+                        throw new InvalidOperationException("Receive length data error.");
+                    }
+                    var length = BitConverter.ToInt32(lengthData);
+                    var data = new byte[length];
+                    result = _receivedByteBuffer.Read(data);
+                    if (result == 0)
+                    {
+                        throw new InvalidOperationException("Receive data error.");
+                    }
+
+                    var kcpData = new KcpData(data);
+                    _receivedDataBuffer.Write(kcpData);
+                    break;
+                default:
+                    throw new InvalidDataException("Unknown packet type.");
+            }
+        }
+
+
+        internal void Update()
+        {
+            if (_closed) throw new InvalidOperationException("Connection closed.");
+            _kcp.Update(DateTime.UtcNow);
+        }
+
+
+        private void OnIoReceived(object sender, byte[] e)
+        {
+            _kcp.Input(e);
+        }
+
+        public async Task SendAsync(byte[] buffer)
+        {
+            await SendAsync(buffer, CancellationToken.None).ConfigureAwait(false);
+        }
+
+        public async Task SendAsync(byte[] buffer, CancellationToken cancellationToken)
+        {
+            if (_closed) throw new InvalidOperationException("Connection closed.");
+            var data = new KcpData(buffer);
+            await _sendPacketBuffer.WritePacketAsync(data, PacketPriority.Middle, cancellationToken);
+        }
+
+        private void SendClose()
+        {
+            if (_closed) throw new InvalidOperationException("Connection closed.");
+            var close = new KcpClose();
+            _kcp.Input(close.GetBuffer());
+            _kcp.Update(DateTime.UtcNow);
+        }
+
+        internal void PingResponse()
+        {
+            if (_closed) throw new InvalidOperationException("Connection closed.");
+            var response = new KcpPingResponse();
+            _sendPacketBuffer.WritePacket(response, PacketPriority.High);
+        }
+
+        internal void PingRequest()
+        {
+            if (_closed) throw new InvalidOperationException("Connection closed.");
+            var request = new KcpPingRequest();
+            _sendPacketBuffer.WritePacket(request, PacketPriority.High);
+        }
+
+        public async Task<int> ReadAsync(byte[] buffer)
+        {
+            return await ReadAsync(buffer, CancellationToken.None).ConfigureAwait(false);
+        }
+
+        public async Task<int> ReadAsync(byte[] buffer, CancellationToken cancellationToken)
+        {
+            if (_closed) throw new InvalidOperationException("Connection closed.");
+            return await _receivedDataBuffer.ReadAsync(buffer,cancellationToken).ConfigureAwait(false);
+        }
+
+        public void Close()
+        {
+            if (!_closed)
+            {
+                SendClose();
+                _closed = true;
+                _receivedDataBuffer.Close();
+                _receivedByteBuffer.Close();
+                _sendPacketBuffer.Close();
+                _readPacketThread.Join();
+                _io.Received -= OnIoReceived;
+                _kcp.Dispose();
+                Closed?.Invoke(this, EventArgs.Empty);
+            }
+        }
+    }
+}

+ 1107 - 0
KcpNet/KcpCore.cs

@@ -0,0 +1,1107 @@
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Runtime.CompilerServices;
+using static System.Math;
+using BufferOwner = System.Buffers.IMemoryOwner<byte>;
+
+namespace KcpNet
+{
+    /// <summary>
+    /// https://github.com/skywind3000/kcp/wiki/Network-Layer
+    /// <para>外部buffer ----拆分拷贝----等待列表 -----移动----发送列表----拷贝----发送buffer---output</para>
+    /// https://github.com/skywind3000/kcp/issues/118#issuecomment-338133930
+    /// </summary>
+    public class KcpCore<Segment>: IKcpSetting, IKcpUpdate, IDisposable
+        where Segment:IKcpSegment
+    {
+        // 为了减少阅读难度,变量名尽量于 C版 统一
+        /*
+        conv 会话ID
+        mtu 最大传输单元
+        mss 最大分片大小
+        state 连接状态(0xFFFFFFFF表示断开连接)
+        snd_una 第一个未确认的包
+        snd_nxt 待发送包的序号
+        rcv_nxt 待接收消息序号
+        ssthresh 拥塞窗口阈值
+        rx_rttvar ack接收rtt浮动值
+        rx_srtt ack接收rtt静态值
+        rx_rto 由ack接收延迟计算出来的复原时间
+        rx_minrto 最小复原时间
+        snd_wnd 发送窗口大小
+        rcv_wnd 接收窗口大小
+        rmt_wnd,	远端接收窗口大小
+        cwnd, 拥塞窗口大小
+        probe 探查变量,IKCP_ASK_TELL表示告知远端窗口大小。IKCP_ASK_SEND表示请求远端告知窗口大小
+        interval    内部flush刷新间隔
+        ts_flush 下次flush刷新时间戳
+        nodelay 是否启动无延迟模式
+        updated 是否调用过update函数的标识
+        ts_probe, 下次探查窗口的时间戳
+        probe_wait 探查窗口需要等待的时间
+        dead_link 最大重传次数
+        incr 可发送的最大数据量
+        fastresend 触发快速重传的重复ack个数
+        nocwnd 取消拥塞控制
+        stream 是否采用流传输模式
+
+        snd_queue 发送消息的队列
+        rcv_queue 接收消息的队列
+        snd_buf 发送消息的缓存
+        rcv_buf 接收消息的缓存
+        acklist 待发送的ack列表
+        buffer 存储消息字节流的内存
+        output udp发送消息的回调函数
+        */
+
+        #region Const
+
+        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; // must >= max fragment size
+        public const int IKCP_MTU_DEF = 1400;
+        public const int IKCP_ACK_FAST = 3;
+        public const int IKCP_INTERVAL = 100;
+        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 const int IKCP_FASTACK_LIMIT = 5;		// max times to trigger fastack
+        #endregion
+
+        #region kcp members
+        /// <summary>
+        /// 频道号
+        /// </summary>
+        public uint conv { get; protected set; }
+        /// <summary>
+        /// 最大传输单元(Maximum Transmission Unit,MTU)
+        /// </summary>
+        protected uint mtu;
+
+        /// <summary>
+        /// 缓冲区最小大小
+        /// </summary>
+        protected int BufferNeedSize
+        {
+            [MethodImpl(MethodImplOptions.AggressiveInlining)]
+            get
+            {
+                return (int)((mtu/* + IKCP_OVERHEAD*/) /** 3*/);
+            }
+        }
+
+        /// <summary>
+        /// 最大报文段长度
+        /// </summary>
+        protected uint mss;
+        protected int state;
+        protected uint snd_una;
+        protected uint snd_nxt;
+        /// <summary>
+        /// 下一个等待接收消息ID
+        /// </summary>
+        protected uint rcv_nxt;
+        protected uint ts_recent;
+        protected uint ts_lastack;
+        protected uint ssthresh;
+        protected uint rx_rttval;
+        protected uint rx_srtt;
+        protected uint rx_rto;
+        protected uint rx_minrto;
+        protected uint snd_wnd;
+        protected uint rcv_wnd;
+        protected uint rmt_wnd;
+        protected uint cwnd;
+        protected uint probe;
+        protected uint current;
+        protected uint interval;
+        protected uint ts_flush;
+        protected uint xmit;
+        protected uint nodelay;
+        protected uint updated;
+        protected uint ts_probe;
+        protected uint probe_wait;
+        protected uint dead_link;
+        protected uint incr;
+        protected int fastresend;
+        protected int fastlimit;
+        protected int nocwnd;
+        protected int logmask;
+        public int stream;
+        protected BufferOwner buffer;
+
+        /// <summary>
+        /// <para>https://github.com/skywind3000/kcp/issues/53</para>
+        /// 按照 C版 设计,使用小端字节序
+        /// </summary>
+        public static bool IsLittleEndian = true;
+
+        #endregion
+
+        #region 锁和容器
+
+        protected readonly object snd_bufLock = new object();
+        protected readonly object rcv_queueLock = new object();
+        protected readonly object rcv_bufLock = new object();
+
+        /// <summary>
+        /// 发送 ack 队列 
+        /// </summary>
+        protected ConcurrentQueue<(uint sn, uint ts)> acklist = new ConcurrentQueue<(uint sn, uint ts)>();
+        /// <summary>
+        /// 发送等待队列
+        /// </summary>
+        internal ConcurrentQueue<Segment> snd_queue = new ConcurrentQueue<Segment>();
+        /// <summary>
+        /// 正在发送列表
+        /// </summary>
+        internal LinkedList<Segment> snd_buf = new LinkedList<Segment>();
+        /// <summary>
+        /// 正在等待触发接收回调函数消息列表
+        /// <para>需要执行的操作  添加 遍历 删除</para>
+        /// </summary>
+        internal List<Segment> rcv_queue = new List<Segment>();
+        /// <summary>
+        /// 正在等待重组消息列表
+        /// <para>需要执行的操作  添加 插入 遍历 删除</para>
+        /// </summary>
+        internal LinkedList<Segment> rcv_buf = new LinkedList<Segment>();
+
+        /// <summary>
+        /// get how many packet is waiting to be sent
+        /// </summary>
+        /// <returns></returns>
+        public int WaitSnd => snd_buf.Count + snd_queue.Count;
+
+        #endregion
+
+        public ISegmentManager<Segment> SegmentManager { get; set; }
+        public KcpCore(uint conv_)
+        {
+            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;
+            buffer = CreateBuffer(BufferNeedSize);
+
+            rx_rto = IKCP_RTO_DEF;
+            rx_minrto = IKCP_RTO_MIN;
+            interval = IKCP_INTERVAL;
+            ts_flush = IKCP_INTERVAL;
+            ssthresh = IKCP_THRESH_INIT;
+            fastlimit = IKCP_FASTACK_LIMIT;
+            dead_link = IKCP_DEADLINK;
+        }
+
+        #region IDisposable Support
+        private bool disposedValue = false; // 要检测冗余调用
+
+        /// <summary>
+        /// 是否正在释放
+        /// </summary>
+        private bool m_disposing = false;
+
+        protected bool CheckDispose()
+        {
+            if (m_disposing)
+            {
+                return true;
+            }
+
+            if (disposedValue)
+            {
+                throw new ObjectDisposedException(
+                    $"{nameof(Kcp)} [conv:{conv}]");
+            }
+
+            return false;
+        }
+
+        protected virtual void Dispose(bool disposing)
+        {
+            try
+            {
+                m_disposing = true;
+                if (!disposedValue)
+                {
+                    if (disposing)
+                    {
+                        // 释放托管状态(托管对象)。
+                        callbackHandle = null;
+                        acklist = null;
+                        buffer = null;
+                    }
+
+                    // 释放未托管的资源(未托管的对象)并在以下内容中替代终结器。
+                    // 将大型字段设置为 null。
+                    void FreeCollection(IEnumerable<Segment> collection)
+                    {
+                        if (collection == null)
+                        {
+                            return;
+                        }
+                        foreach (var item in collection)
+                        {
+                            try
+                            {
+                                SegmentManager.Free(item);
+                            }
+                            catch (Exception)
+                            {
+                                //理论上此处不会有任何异常
+                            }
+                        }
+                    }
+
+                    while (snd_queue != null &&
+                        (snd_queue.TryDequeue(out var segment)
+                        || !snd_queue.IsEmpty)
+                        )
+                    {
+                        try
+                        {
+                            SegmentManager.Free(segment);
+                        }
+                        catch (Exception)
+                        {
+                            //理论上这里没有任何异常;
+                        }
+                    }
+                    snd_queue = null;
+
+                    lock (snd_bufLock)
+                    {
+                        FreeCollection(snd_buf);
+                        snd_buf?.Clear();
+                        snd_buf = null;
+                    }
+
+                    lock (rcv_bufLock)
+                    {
+                        FreeCollection(rcv_buf);
+                        rcv_buf?.Clear();
+                        rcv_buf = null;
+                    }
+
+                    lock (rcv_queueLock)
+                    {
+                        FreeCollection(rcv_queue);
+                        rcv_queue?.Clear();
+                        rcv_queue = null;
+                    }
+
+
+                    disposedValue = true;
+                }
+            }
+            finally
+            {
+                m_disposing = false;
+            }
+
+        }
+
+        // 仅当以上 Dispose(bool disposing) 拥有用于释放未托管资源的代码时才替代终结器。
+        ~KcpCore()
+        {
+            // 请勿更改此代码。将清理代码放入以上 Dispose(bool disposing) 中。
+            Dispose(false);
+        }
+
+        // 添加此代码以正确实现可处置模式。
+        /// <summary>
+        /// 释放不是严格线程安全的,尽量使用和Update相同的线程调用,
+        /// 或者等待析构时自动释放。
+        /// </summary>
+        public void Dispose()
+        {
+            // 请勿更改此代码。将清理代码放入以上 Dispose(bool disposing) 中。
+            Dispose(true);
+            // 如果在以上内容中替代了终结器,则取消注释以下行。
+            GC.SuppressFinalize(this);
+        }
+
+        #endregion
+
+        internal protected IKcpCallback callbackHandle;
+
+        protected static uint Ibound(uint lower, uint middle, uint upper)
+        {
+            return Min(Max(lower, middle), upper);
+        }
+
+        protected static int Itimediff(uint later, uint earlier)
+        {
+            return ((int)(later - earlier));
+        }
+
+        internal protected virtual BufferOwner CreateBuffer(int needSize)
+        {
+            return new KcpInnerBuffer(needSize);
+        }
+
+        internal protected class KcpInnerBuffer : BufferOwner
+        {
+            private readonly Memory<byte> _memory;
+
+            public Memory<byte> Memory
+            {
+                get
+                {
+                    if (alreadyDisposed)
+                    {
+                        throw new ObjectDisposedException(nameof(KcpInnerBuffer));
+                    }
+                    return _memory;
+                }
+            }
+
+            public KcpInnerBuffer(int size)
+            {
+                _memory = new Memory<byte>(new byte[size]);
+            }
+
+            bool alreadyDisposed = false;
+            public void Dispose()
+            {
+                alreadyDisposed = true;
+            }
+        }
+
+
+        #region 功能逻辑
+
+        //功能函数
+
+        /// <summary>
+        /// 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.
+        /// <para></para>
+        /// 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)
+        /// <para></para>
+        /// </summary>
+        /// <param name="time"></param>
+        /// <returns></returns>
+        public DateTime Check(DateTime time)
+        {
+            if (CheckDispose())
+            {
+                //检查释放
+                return default;
+            }
+
+            if (updated == 0)
+            {
+                return time;
+            }
+
+            var current_ = time.ConvertTime();
+
+            var ts_flush_ = ts_flush;
+            var tm_flush_ = 0x7fffffff;
+            var tm_packet = 0x7fffffff;
+            var minimal = 0;
+
+            if (Itimediff(current_, ts_flush_) >= 10000 || Itimediff(current_, ts_flush_) < -10000)
+            {
+                ts_flush_ = current_;
+            }
+
+            if (Itimediff(current_, ts_flush_) >= 0)
+            {
+                return time;
+            }
+
+            tm_flush_ = Itimediff(ts_flush_, current_);
+
+            lock (snd_bufLock)
+            {
+                foreach (var seg in snd_buf)
+                {
+                    var diff = Itimediff(seg.resendts, current_);
+                    if (diff <= 0)
+                    {
+                        return time;
+                    }
+
+                    if (diff < tm_packet)
+                    {
+                        tm_packet = diff;
+                    }
+                }
+            }
+
+            minimal = tm_packet < tm_flush_ ? tm_packet : tm_flush_;
+            if (minimal >= interval) minimal = (int)interval;
+
+            return time + TimeSpan.FromMilliseconds(minimal);
+        }
+
+        /// <summary>
+        /// move available data from rcv_buf -> rcv_queue
+        /// </summary>
+        protected void Move_Rcv_buf_2_Rcv_queue()
+        {
+            lock (rcv_bufLock)
+            {
+                while (rcv_buf.Count > 0)
+                {
+                    var seg = rcv_buf.First.Value;
+                    if (seg.sn == rcv_nxt && rcv_queue.Count < rcv_wnd)
+                    {
+                        rcv_buf.RemoveFirst();
+                        lock (rcv_queueLock)
+                        {
+                            rcv_queue.Add(seg);
+                        }
+
+                        rcv_nxt++;
+                    }
+                    else
+                    {
+                        break;
+                    }
+                }
+            }
+        }
+
+        /// <summary>
+        /// update ack.
+        /// </summary>
+        /// <param name="rtt"></param>
+        protected void Update_ack(int rtt)
+        {
+            if (rx_srtt == 0)
+            {
+                rx_srtt = (uint)rtt;
+                rx_rttval = (uint)rtt / 2;
+            }
+            else
+            {
+                int delta = (int)((uint)rtt - rx_srtt);
+
+                if (delta < 0)
+                {
+                    delta = -delta;
+                }
+
+                rx_rttval = (3 * rx_rttval + (uint)delta) / 4;
+                rx_srtt = (uint)((7 * rx_srtt + rtt) / 8);
+
+                if (rx_srtt < 1)
+                {
+                    rx_srtt = 1;
+                }
+            }
+
+            var rto = rx_srtt + Max(interval, 4 * rx_rttval);
+
+            rx_rto = Ibound(rx_minrto, rto, IKCP_RTO_MAX);
+        }
+
+        protected void Shrink_buf()
+        {
+            lock (snd_bufLock)
+            {
+                snd_una = snd_buf.Count > 0 ? snd_buf.First.Value.sn : snd_nxt;
+            }
+        }
+
+        protected void Parse_ack(uint sn)
+        {
+            if (Itimediff(sn, snd_una) < 0 || Itimediff(sn, snd_nxt) >= 0)
+            {
+                return;
+            }
+
+            lock (snd_bufLock)
+            {
+                for (var p = snd_buf.First; p != null; p = p.Next)
+                {
+                    var seg = p.Value;
+                    if (sn == seg.sn)
+                    {
+                        snd_buf.Remove(p);
+                        SegmentManager.Free(seg);
+                        break;
+                    }
+
+                    if (Itimediff(sn, seg.sn) < 0)
+                    {
+                        break;
+                    }
+                }
+            }
+        }
+
+        protected void Parse_una(uint una)
+        {
+            /// 删除给定时间之前的片段。保留之后的片段
+            lock (snd_bufLock)
+            {
+                while (snd_buf.First != null)
+                {
+                    var seg = snd_buf.First.Value;
+                    if (Itimediff(una, seg.sn) > 0)
+                    {
+                        SegmentManager.Free(seg);
+                        snd_buf.RemoveFirst();
+                    }
+                    else
+                    {
+                        break;
+                    }
+                }
+            }
+
+        }
+
+        protected void Parse_fastack(uint sn)
+        {
+            if (Itimediff(sn, snd_una) < 0 || Itimediff(sn, snd_nxt) >= 0)
+            {
+                return;
+            }
+
+            lock (snd_bufLock)
+            {
+                foreach (var item in snd_buf)
+                {
+                    var seg = item;
+                    if (Itimediff(sn, seg.sn) < 0)
+                    {
+                        break;
+                    }
+                    else if (sn != seg.sn)
+                    {
+                        seg.fastack++;
+                    }
+                }
+            }
+        }
+
+        internal virtual void Parse_data(Segment newseg)
+        {
+            var sn = newseg.sn;
+
+            lock (rcv_bufLock)
+            {
+                if (Itimediff(sn, rcv_nxt + rcv_wnd) >= 0 || Itimediff(sn, rcv_nxt) < 0)
+                {
+                    SegmentManager.Free(newseg);
+                    return;
+                }
+
+                var repeat = false;
+
+                ///检查是否重复消息和插入位置
+                LinkedListNode<Segment> p;
+                for (p = rcv_buf.Last; p != null; p = p.Previous)
+                {
+                    var seg = p.Value;
+                    if (seg.sn == sn)
+                    {
+                        repeat = true;
+                        break;
+                    }
+
+                    if (Itimediff(sn, seg.sn) > 0)
+                    {
+                        break;
+                    }
+                }
+
+                if (!repeat)
+                {
+                    if (p == null)
+                    {
+                        rcv_buf.AddFirst(newseg);
+                    }
+                    else
+                    {
+                        rcv_buf.AddAfter(p, newseg);
+                    }
+
+                }
+                else
+                {
+                    SegmentManager.Free(newseg);
+                }
+            }
+
+            Move_Rcv_buf_2_Rcv_queue();
+        }
+
+        protected ushort Wnd_unused()
+        {
+            ///此处没有加锁,所以不要内联变量,否则可能导致 判断变量和赋值变量不一致
+            int waitCount = rcv_queue.Count;
+
+            if (waitCount < rcv_wnd)
+            {
+                /// fix https://github.com/skywind3000/kcp/issues/126
+                /// 实际上 rcv_wnd 不应该大于65535
+                var count = rcv_wnd - waitCount;
+                return (ushort)Min(count, ushort.MaxValue);
+            }
+
+            return 0;
+        }
+
+        /// <summary>
+        /// flush pending data
+        /// </summary>
+        protected void Flush()
+        {
+            var current_ = current;
+            var buffer_ = buffer;
+            var change = 0;
+            var lost = 0;
+            var offset = 0;
+
+            if (updated == 0)
+            {
+                return;
+            }
+
+            ushort wnd_ = Wnd_unused();
+
+            unsafe
+            {
+                ///在栈上分配这个segment,这个segment随用随销毁,不会被保存
+                const int len = KcpSegment.LocalOffset + KcpSegment.HeadOffset;
+                var ptr = stackalloc byte[len];
+                KcpSegment seg = new KcpSegment(ptr, 0);
+                //seg = KcpSegment.AllocHGlobal(0);
+
+                seg.conv = conv;
+                seg.cmd = IKCP_CMD_ACK;
+                //seg.frg = 0;
+                seg.wnd = wnd_;
+                seg.una = rcv_nxt;
+                //seg.len = 0;
+                //seg.sn = 0;
+                //seg.ts = 0;
+
+                #region flush acknowledges
+
+                if (CheckDispose())
+                {
+                    //检查释放
+                    return;
+                }
+
+                while (acklist.TryDequeue(out var temp))
+                {
+                    if (offset + IKCP_OVERHEAD > mtu)
+                    {
+                        callbackHandle.Output(buffer, offset);
+                        offset = 0;
+                        buffer = CreateBuffer(BufferNeedSize);
+                    }
+
+                    seg.sn = temp.sn;
+                    seg.ts = temp.ts;
+                    offset += seg.Encode(buffer.Memory.Span.Slice(offset));
+                }
+
+                #endregion
+
+                #region probe window size (if remote window size equals zero)
+                // probe window size (if remote window size equals zero)
+                if (rmt_wnd == 0)
+                {
+                    if (probe_wait == 0)
+                    {
+                        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;
+                }
+                #endregion
+
+                #region flush window probing commands
+                // flush window probing commands
+                if ((probe & IKCP_ASK_SEND) != 0)
+                {
+                    seg.cmd = IKCP_CMD_WASK;
+                    if (offset + IKCP_OVERHEAD > (int)mtu)
+                    {
+                        callbackHandle.Output(buffer, offset);
+                        offset = 0;
+                        buffer = CreateBuffer(BufferNeedSize);
+                    }
+                    offset += seg.Encode(buffer.Memory.Span.Slice(offset));
+                }
+
+                if ((probe & IKCP_ASK_TELL) != 0)
+                {
+                    seg.cmd = IKCP_CMD_WINS;
+                    if (offset + IKCP_OVERHEAD > (int)mtu)
+                    {
+                        callbackHandle.Output(buffer, offset);
+                        offset = 0;
+                        buffer = CreateBuffer(BufferNeedSize);
+                    }
+                    offset += seg.Encode(buffer.Memory.Span.Slice(offset));
+                }
+
+                probe = 0;
+                #endregion
+            }
+
+            #region 刷新,将发送等待列表移动到发送列表
+
+            // calculate window size
+            var cwnd_ = Min(snd_wnd, rmt_wnd);
+            if (nocwnd == 0)
+            {
+                cwnd_ = Min(cwnd, cwnd_);
+            }
+
+            while (Itimediff(snd_nxt, snd_una + cwnd_) < 0)
+            {
+                if (snd_queue.TryDequeue(out var newseg))
+                {
+                    newseg.conv = conv;
+                    newseg.cmd = IKCP_CMD_PUSH;
+                    newseg.wnd = wnd_;
+                    newseg.ts = current_;
+                    newseg.sn = snd_nxt;
+                    snd_nxt++;
+                    newseg.una = rcv_nxt;
+                    newseg.resendts = current_;
+                    newseg.rto = rx_rto;
+                    newseg.fastack = 0;
+                    newseg.xmit = 0;
+                    lock (snd_bufLock)
+                    {
+                        snd_buf.AddLast(newseg);
+                    }
+                }
+                else
+                {
+                    break;
+                }
+            }
+
+            #endregion
+
+            #region 刷新 发送列表,调用Output
+
+            // calculate resent
+            var resent = fastresend > 0 ? (uint)fastresend : 0xffffffff;
+            var rtomin = nodelay == 0 ? (rx_rto >> 3) : 0;
+
+            lock (snd_bufLock)
+            {
+                // flush data segments
+                foreach (var item in snd_buf)
+                {
+                    var segment = item;
+                    var needsend = false;
+                    var debug = Itimediff(current_, segment.resendts);
+                    if (segment.xmit == 0)
+                    {
+                        needsend = true;
+                        segment.xmit++;
+                        segment.rto = rx_rto;
+                        segment.resendts = current_ + rx_rto + rtomin;
+                    }
+                    else if (Itimediff(current_, segment.resendts) >= 0)
+                    {
+                        needsend = true;
+                        segment.xmit++;
+                        this.xmit++;
+                        if (nodelay == 0)
+                        {
+                            segment.rto += rx_rto;
+                        }
+                        else
+                        {
+                            segment.rto += rx_rto / 2;
+                        }
+
+                        segment.resendts = current_ + segment.rto;
+                        lost = 1;
+                    }
+                    else if (segment.fastack >= resent)
+                    {
+                        if (segment.xmit <= fastlimit
+                            || fastlimit <= 0)
+                        {
+                            needsend = true;
+                            segment.xmit++;
+                            segment.fastack = 0;
+                            segment.resendts = current_ + segment.rto;
+                            change++;
+                        }
+                    }
+
+                    if (needsend)
+                    {
+                        segment.ts = current_;
+                        segment.wnd = wnd_;
+                        segment.una = rcv_nxt;
+
+                        var need = IKCP_OVERHEAD + segment.len;
+                        if (offset + need > mtu)
+                        {
+                            callbackHandle.Output(buffer, offset);
+                            offset = 0;
+                            buffer = CreateBuffer(BufferNeedSize);
+                        }
+
+                        offset += segment.Encode(buffer.Memory.Span.Slice(offset));
+
+                        if (segment.xmit >= dead_link)
+                        {
+                            state = -1;
+                        }
+                    }
+                }
+            }
+
+
+            // flash remain segments
+            if (offset > 0)
+            {
+                callbackHandle.Output(buffer, offset);
+                offset = 0;
+                buffer = CreateBuffer(BufferNeedSize);
+            }
+
+            #endregion
+
+            #region update ssthresh
+            // update ssthresh
+            if (change != 0)
+            {
+                var 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;
+            }
+            #endregion
+
+        }
+
+        /// <summary>
+        /// update state (call it repeatedly, every 10ms-100ms), or you can ask
+        /// ikcp_check when to call it again (without ikcp_input/_send calling).
+        /// </summary>
+        /// <param name="time">DateTime.UtcNow</param>
+        public void Update(in DateTime time)
+        {
+            if (CheckDispose())
+            {
+                //检查释放
+                return;
+            }
+
+            current = time.ConvertTime();
+
+            if (updated == 0)
+            {
+                updated = 1;
+                ts_flush = current;
+            }
+
+            var 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();
+            }
+        }
+        #endregion
+
+        #region 设置控制
+
+        /// <summary>
+        /// change MTU size, default is 1400
+        /// <para>** 这个方法不是线程安全的。请在没有发送和接收时调用 。</para>
+        /// </summary>
+        /// <param name="mtu"></param>
+        /// <returns></returns>
+        public int SetMtu(int mtu)
+        {
+            if (mtu < 50 || mtu < IKCP_OVERHEAD)
+            {
+                return -1;
+            }
+
+            var buffer_ = CreateBuffer(BufferNeedSize);
+            if (null == buffer_)
+            {
+                return -2;
+            }
+
+            this.mtu = (uint)mtu;
+            mss = this.mtu - IKCP_OVERHEAD;
+            buffer.Dispose();
+            buffer = buffer_;
+            return 0;
+        }
+
+        /// <summary>
+        /// 
+        /// </summary>
+        /// <param name="interval_"></param>
+        /// <returns></returns>
+        public int Interval(int interval_)
+        {
+            if (interval_ > 5000)
+            {
+                interval_ = 5000;
+            }
+            else if (interval_ < 0)
+            {
+                /// 将最小值 10 改为 0;
+                ///在特殊形况下允许CPU满负荷运转;
+                interval_ = 0;
+            }
+            interval = (uint)interval_;
+            return 0;
+        }
+
+        /// <summary>
+        /// fastest: ikcp_nodelay(kcp, 1, 20, 2, 1)
+        /// </summary>
+        /// <param name="nodelay_">0:disable(default), 1:enable</param>
+        /// <param name="interval_">internal update timer interval in millisec, default is 100ms</param>
+        /// <param name="resend_">0:disable fast resend(default), 1:enable fast resend</param>
+        /// <param name="nc_">0:normal congestion control(default), 1:disable congestion control</param>
+        /// <returns></returns>
+        public int NoDelay(int nodelay_, int interval_, int resend_, int nc_)
+        {
+
+            if (nodelay_ > 0)
+            {
+                nodelay = (uint)nodelay_;
+                if (nodelay_ != 0)
+                {
+                    rx_minrto = IKCP_RTO_NDL;
+                }
+                else
+                {
+                    rx_minrto = IKCP_RTO_MIN;
+                }
+            }
+
+            if (resend_ >= 0)
+            {
+                fastresend = resend_;
+            }
+
+            if (nc_ >= 0)
+            {
+                nocwnd = nc_;
+            }
+
+            return Interval(interval_);
+        }
+
+        /// <summary>
+        /// set maximum window size: sndwnd=32, rcvwnd=32 by default
+        /// </summary>
+        /// <param name="sndwnd"></param>
+        /// <param name="rcvwnd"></param>
+        /// <returns></returns>
+        public int WndSize(int sndwnd, int rcvwnd)
+        {
+            if (sndwnd > 0)
+            {
+                snd_wnd = (uint)sndwnd;
+            }
+
+            if (rcvwnd > 0)
+            {
+                rcv_wnd = (uint)rcvwnd;
+            }
+
+            return 0;
+        }
+
+        #endregion
+    }
+}

+ 145 - 0
KcpNet/KcpDataBuffer.cs

@@ -0,0 +1,145 @@
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+using System.Threading.Tasks.Dataflow;
+
+namespace KcpNet
+{
+    internal class KcpDataBuffer
+    {
+        private readonly BufferBlock<KcpData> _bufferQueue;
+
+        private KcpData _buffer;
+
+        public KcpDataBuffer()
+        {
+            _bufferQueue = new BufferBlock<KcpData>();
+        }
+
+        public void Write(KcpData data)
+        {
+            if (!_bufferQueue.Post(data))
+            {
+                throw new InvalidOperationException("Write data error.");
+            }
+        }
+
+        public async Task WriteAsync(KcpData data)
+        {
+            if (!await _bufferQueue.SendAsync(data).ConfigureAwait(false))
+            {
+                throw new InvalidOperationException("Write data error.");
+            }
+        }
+
+
+        public async Task<int> ReadAsync(byte[] buffer, CancellationToken cancellationToken)
+        {
+            try
+            {
+                if (_buffer != null)
+                {
+                    if (buffer.Length >= _buffer.Length)
+                    {
+                        var length = _buffer.Length;
+                        Array.Copy(_buffer.GetData(), buffer, length);
+                        _buffer = null;
+                        return length;
+                    }
+
+                    Array.Copy(_buffer.GetData(), buffer, buffer.Length);
+                    _buffer.Offset += buffer.Length;
+                    return buffer.Length;
+
+                }
+
+                _buffer = await _bufferQueue.ReceiveAsync(cancellationToken).ConfigureAwait(false);
+                if (_buffer != null)
+                {
+                    if (_buffer.Length <= buffer.Length)
+                    {
+                        var length = _buffer.Length;
+                        Array.Copy(_buffer.GetData(), buffer, length);
+                        _buffer = null;
+                        return length;
+                    }
+
+                    if (_buffer.Length > buffer.Length)
+                    {
+                        Array.Copy(_buffer.GetData(), buffer, buffer.Length);
+                        _buffer.Offset += buffer.Length;
+                        return buffer.Length;
+                    }
+                }
+            }
+            catch (Exception)
+            {
+                //This could be the connection is closed.
+            }
+
+            return 0;
+        }
+
+
+        public async Task<int> ReadAsync(byte[] buffer)
+        {
+            return await ReadAsync(buffer, CancellationToken.None).ConfigureAwait(false);
+        }
+
+
+        public int Read(byte[] buffer)
+        {
+            try
+            {
+                if (_buffer != null)
+                {
+                    if (buffer.Length >= _buffer.Length)
+                    {
+                        var length = _buffer.Length;
+                        Array.Copy(_buffer.GetData(), buffer, length);
+                        _buffer = null;
+                        return length;
+                    }
+
+                    Array.Copy(_buffer.GetData(), buffer, buffer.Length);
+                    _buffer.Offset += buffer.Length;
+                    return buffer.Length;
+
+                }
+
+                _buffer = _bufferQueue.Receive();
+                if (_buffer != null)
+                {
+                    if (_buffer.Length <= buffer.Length)
+                    {
+                        var length = _buffer.Length;
+                        Array.Copy(_buffer.GetData(), buffer, length);
+                        _buffer = null;
+                        return length;
+                    }
+
+                    if (_buffer.Length > buffer.Length)
+                    {
+                        Array.Copy(_buffer.GetData(), buffer, buffer.Length);
+                        _buffer.Offset += buffer.Length;
+                        return buffer.Length;
+                    }
+                }
+            }
+            catch (Exception)
+            {
+                //This could be the connection is closed.
+            }
+
+            return 0;
+        }
+
+
+        public void Close()
+        {
+            _bufferQueue.TryReceiveAll(out _);
+            _bufferQueue.Complete();
+            _buffer = null;
+        }
+    }
+}

+ 615 - 0
KcpNet/KcpIO.cs

@@ -0,0 +1,615 @@
+using System;
+using System.Buffers;
+using System.Buffers.Binary;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using BufferOwner = System.Buffers.IMemoryOwner<byte>;
+
+namespace KcpNet
+{
+    /// <summary>
+    /// kcp协议输入输出标准接口
+    /// </summary>
+    public interface IKcpIO
+    {
+        /// <summary>
+        /// 下层收到数据后添加到kcp协议中
+        /// </summary>
+        /// <param name="span"></param>
+        int Input(ReadOnlySpan<byte> span);
+        /// <summary>
+        /// 下层收到数据后添加到kcp协议中
+        /// </summary>
+        /// <param name="span"></param>
+        int Input(ReadOnlySequence<byte> span);
+        /// <summary>
+        /// 从kcp中取出一个整合完毕的数据包
+        /// </summary>
+        /// <returns></returns>
+        ValueTask Recv(IBufferWriter<byte> writer, object option = null);
+
+        /// <summary>
+        /// 将要发送到网络的数据Send到kcp协议中
+        /// </summary>
+        /// <param name="span"></param>
+        /// <param name="option"></param>
+        int Send(ReadOnlySpan<byte> span, object option = null);
+        /// <summary>
+        /// 将要发送到网络的数据Send到kcp协议中
+        /// </summary>
+        /// <param name="span"></param>
+        /// <param name="option"></param>
+        int Send(ReadOnlySequence<byte> span, object option = null);
+        /// <summary>
+        /// 从kcp协议中取出需要发送到网络的数据。
+        /// </summary>
+        /// <param name="writer"></param>
+        /// <param name="option"></param>
+        /// <returns></returns>
+        ValueTask Output(IBufferWriter<byte> writer, object option = null);
+    }
+    /// <summary>
+    /// 异步缓存管道
+    /// <para/>也可以通过(bool isEnd,T value)元组,来实现终止信号
+    /// </summary>
+    /// <typeparam name="T"></typeparam>
+    internal class SimplePipeQueue<T> : Queue<T>
+    {
+        readonly object _innerLock = new object();
+        private TaskCompletionSource<T> source;
+
+        //线程同步上下文由Task机制保证,无需额外处理
+        //SynchronizationContext callbackContext;
+        //public bool UseSynchronizationContext { get; set; } = true;
+
+        public void Write(T item)
+        {
+            lock (_innerLock)
+            {
+                if (source == null)
+                {
+                    Enqueue(item);
+                }
+                else
+                {
+                    if (Count > 0)
+                    {
+                        throw new Exception("内部顺序错误,不应该出现,请联系作者");
+                    }
+
+                    var next = source;
+                    source = null;
+                    next.TrySetResult(item);
+                }
+            }
+        }
+
+        public ValueTask<T> ReadAsync()
+        {
+            lock (_innerLock)
+            {
+                if (this.Count > 0)
+                {
+                    var next = Dequeue();
+                    return new ValueTask<T>(next);
+                }
+                else
+                {
+                    source = new TaskCompletionSource<T>();
+                    return new ValueTask<T>(source.Task);
+                }
+            }
+        }
+    }
+
+
+    public class KcpIO<Segment> : KcpCore<Segment>, IKcpIO
+        where Segment : IKcpSegment
+    {
+        OutputQ outq;
+        public KcpIO(uint conv_) : base(conv_)
+        {
+            outq = new OutputQ();
+            callbackHandle = outq;
+        }
+
+        public int Input(ReadOnlySpan<byte> data)
+        {
+            if (CheckDispose())
+            {
+                //检查释放
+                return -4;
+            }
+
+            uint temp_una = snd_una;
+
+            if (data.Length < IKCP_OVERHEAD)
+            {
+                return -1;
+            }
+
+            var offset = 0;
+            int flag = 0;
+            uint maxack = 0;
+            while (true)
+            {
+                uint ts = 0;
+                uint sn = 0;
+                uint length = 0;
+                uint una = 0;
+                uint conv_ = 0;
+                ushort wnd = 0;
+                byte cmd = 0;
+                byte frg = 0;
+
+                if (data.Length - offset < IKCP_OVERHEAD)
+                {
+                    break;
+                }
+
+                if (IsLittleEndian)
+                {
+                    conv_ = BinaryPrimitives.ReadUInt32LittleEndian(data.Slice(offset));
+                    offset += 4;
+
+                    if (conv != conv_)
+                    {
+                        return -1;
+                    }
+
+                    cmd = data[offset];
+                    offset += 1;
+                    frg = data[offset];
+                    offset += 1;
+                    wnd = BinaryPrimitives.ReadUInt16LittleEndian(data.Slice(offset));
+                    offset += 2;
+
+                    ts = BinaryPrimitives.ReadUInt32LittleEndian(data.Slice(offset));
+                    offset += 4;
+                    sn = BinaryPrimitives.ReadUInt32LittleEndian(data.Slice(offset));
+                    offset += 4;
+                    una = BinaryPrimitives.ReadUInt32LittleEndian(data.Slice(offset));
+                    offset += 4;
+                    length = BinaryPrimitives.ReadUInt32LittleEndian(data.Slice(offset));
+                    offset += 4;
+                }
+                else
+                {
+                    conv_ = BinaryPrimitives.ReadUInt32BigEndian(data.Slice(offset));
+                    offset += 4;
+
+                    if (conv != conv_)
+                    {
+                        return -1;
+                    }
+
+                    cmd = data[offset];
+                    offset += 1;
+                    frg = data[offset];
+                    offset += 1;
+                    wnd = BinaryPrimitives.ReadUInt16BigEndian(data.Slice(offset));
+                    offset += 2;
+
+                    ts = BinaryPrimitives.ReadUInt32BigEndian(data.Slice(offset));
+                    offset += 4;
+                    sn = BinaryPrimitives.ReadUInt32BigEndian(data.Slice(offset));
+                    offset += 4;
+                    una = BinaryPrimitives.ReadUInt32BigEndian(data.Slice(offset));
+                    offset += 4;
+                    length = BinaryPrimitives.ReadUInt32BigEndian(data.Slice(offset));
+                    offset += 4;
+                }
+
+
+                if (data.Length - offset < length || (int)length < 0)
+                {
+                    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;
+                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 (flag == 0)
+                    {
+                        flag = 1;
+                        maxack = sn;
+                    }
+                    else if (Itimediff(sn, maxack) > 0)
+                    {
+                        maxack = sn;
+                    }
+
+                }
+                else if (IKCP_CMD_PUSH == cmd)
+                {
+                    if (Itimediff(sn, rcv_nxt + rcv_wnd) < 0)
+                    {
+                        ///instead of ikcp_ack_push
+                        acklist.Enqueue((sn, ts));
+
+                        if (Itimediff(sn, rcv_nxt) >= 0)
+                        {
+                            var seg = SegmentManager.Alloc((int)length);
+                            seg.conv = conv_;
+                            seg.cmd = cmd;
+                            seg.frg = frg;
+                            seg.wnd = wnd;
+                            seg.ts = ts;
+                            seg.sn = sn;
+                            seg.una = una;
+                            //seg.len = length;  长度在分配时确定,不能改变
+
+                            if (length > 0)
+                            {
+                                data.Slice(offset, (int)length).CopyTo(seg.data);
+                            }
+
+                            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 (flag != 0)
+            {
+                Parse_fastack(maxack);
+            }
+
+            if (Itimediff(this.snd_una, temp_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)
+                        {
+                            cwnd++;
+                        }
+                    }
+                    if (cwnd > rmt_wnd)
+                    {
+                        cwnd = rmt_wnd;
+                        incr = rmt_wnd * mss_;
+                    }
+                }
+            }
+
+            return 0;
+        }
+
+        public int Input(ReadOnlySequence<byte> sequence)
+        {
+            byte[] temp = ArrayPool<byte>.Shared.Rent((int)sequence.Length);
+            Span<byte> data = new Span<byte>(temp, 0, (int)sequence.Length);
+            sequence.CopyTo(data);
+
+            var ret = Input(data);
+
+            ArrayPool<byte>.Shared.Return(temp);
+            return ret;
+        }
+
+        internal override void Parse_data(Segment newseg)
+        {
+            base.Parse_data(newseg);
+            FastChechRecv();
+        }
+
+        SimplePipeQueue<List<Segment>> recvSignal = new SimplePipeQueue<List<Segment>>();
+        private void FastChechRecv()
+        {
+            if (rcv_queue.Count == 0)
+            {
+                ///没有可用包
+                return;
+            }
+
+            var seq = rcv_queue[0];
+
+            if (seq.frg == 0)
+            {
+                return;
+            }
+
+            if (rcv_queue.Count < seq.frg + 1)
+            {
+                ///没有足够的包
+                return;
+            }
+            else
+            {
+                ///至少含有一个完整消息
+
+                List<Segment> kcpSegments = new List<Segment>();
+
+                var recover = false;
+                if (rcv_queue.Count >= rcv_wnd)
+                {
+                    recover = true;
+                }
+
+                #region merge fragment.
+                /// merge fragment.
+
+                lock (rcv_queueLock)
+                {
+                    var count = 0;
+                    foreach (var seg in rcv_queue)
+                    {
+                        kcpSegments.Add(seg);
+
+                        count++;
+                        int frg = seg.frg;
+
+                        if (frg == 0)
+                        {
+                            break;
+                        }
+                    }
+
+                    if (count > 0)
+                    {
+                        rcv_queue.RemoveRange(0, count);
+                    }
+                }
+
+                #endregion
+
+                Move_Rcv_buf_2_Rcv_queue();
+
+                #region fast recover
+                /// fast recover
+                if (rcv_queue.Count < rcv_wnd && recover)
+                {
+                    // ready to send back IKCP_CMD_WINS in ikcp_flush
+                    // tell remote my window size
+                    probe |= IKCP_ASK_TELL;
+                }
+                #endregion
+
+                recvSignal.Write(kcpSegments);
+            }
+        }
+
+        public async ValueTask Recv(IBufferWriter<byte> writer, object option = null)
+        {
+            FastChechRecv();
+            var list = await recvSignal.ReadAsync().ConfigureAwait(false);
+            foreach (var seg in list)
+            {
+                WriteRecv(writer, seg);
+            }
+            list.Clear();
+        }
+
+        private void WriteRecv(IBufferWriter<byte> writer, Segment seg)
+        {
+            var curCount = (int)seg.len;
+            var target = writer.GetSpan(curCount);
+            seg.data.CopyTo(target);
+            SegmentManager.Free(seg);
+            writer.Advance(curCount);
+        }
+
+        public int Send(ReadOnlySpan<byte> span, object option = null)
+        {
+            if (CheckDispose())
+            {
+                //检查释放
+                return -4;
+            }
+
+            if (mss <= 0)
+            {
+                throw new InvalidOperationException($" mss <= 0 ");
+            }
+
+
+            if (span.Length == 0)
+            {
+                return -1;
+            }
+            var offset = 0;
+            int count;
+
+            #region append to previous segment in streaming mode (if possible)
+            /// 基于线程安全和数据结构的等原因,移除了追加数据到最后一个包行为。
+            #endregion
+
+            #region fragment
+
+            if (span.Length <= mss)
+            {
+                count = 1;
+            }
+            else
+            {
+                count = (int)(span.Length + mss - 1) / (int)mss;
+            }
+
+            if (count > IKCP_WND_RCV)
+            {
+                return -2;
+            }
+
+            if (count == 0)
+            {
+                count = 1;
+            }
+
+            for (var i = 0; i < count; i++)
+            {
+                int size;
+                if (span.Length - offset > mss)
+                {
+                    size = (int)mss;
+                }
+                else
+                {
+                    size = span.Length - offset;
+                }
+
+                var seg = SegmentManager.Alloc(size);
+                span.Slice(offset, size).CopyTo(seg.data);
+                offset += size;
+                seg.frg = (byte)(count - i - 1);
+                snd_queue.Enqueue(seg);
+            }
+
+            #endregion
+
+            return 0;
+        }
+
+        public int Send(ReadOnlySequence<byte> span, object option = null)
+        {
+            if (CheckDispose())
+            {
+                //检查释放
+                return -4;
+            }
+
+            if (mss <= 0)
+            {
+                throw new InvalidOperationException($" mss <= 0 ");
+            }
+
+
+            if (span.Length == 0)
+            {
+                return -1;
+            }
+            var offset = 0;
+            int count;
+
+            #region append to previous segment in streaming mode (if possible)
+            /// 基于线程安全和数据结构的等原因,移除了追加数据到最后一个包行为。
+            #endregion
+
+            #region fragment
+
+            if (span.Length <= mss)
+            {
+                count = 1;
+            }
+            else
+            {
+                count = (int)(span.Length + mss - 1) / (int)mss;
+            }
+
+            if (count > IKCP_WND_RCV)
+            {
+                return -2;
+            }
+
+            if (count == 0)
+            {
+                count = 1;
+            }
+
+            for (var i = 0; i < count; i++)
+            {
+                int size;
+                if (span.Length - offset > mss)
+                {
+                    size = (int)mss;
+                }
+                else
+                {
+                    size = (int)span.Length - offset;
+                }
+
+                var seg = SegmentManager.Alloc(size);
+                span.Slice(offset, size).CopyTo(seg.data);
+                offset += size;
+                seg.frg = (byte)(count - i - 1);
+                snd_queue.Enqueue(seg);
+            }
+
+            #endregion
+
+            return 0;
+        }
+
+        public async ValueTask Output(IBufferWriter<byte> writer, object option = null)
+        {
+            var (Owner, Count) = await outq.ReadAsync().ConfigureAwait(false);
+            WriteOut(writer, Owner, Count);
+        }
+
+        private static void WriteOut(IBufferWriter<byte> writer, BufferOwner Owner, int Count)
+        {
+            var target = writer.GetSpan(Count);
+            Owner.Memory.Span.Slice(0, Count).CopyTo(target);
+            writer.Advance(Count);
+            Owner.Dispose();
+        }
+
+        internal class OutputQ : SimplePipeQueue<(BufferOwner Owner, int Count)>,
+            IKcpCallback
+        {
+            public void Output(BufferOwner buffer, int avalidLength)
+            {
+                Write((buffer, avalidLength));
+            }
+        }
+    }
+
+    public class KcpIO : KcpIO<KcpSegment>
+    {
+        public KcpIO(uint conv_)
+            : base(conv_)
+        {
+            SegmentManager = SimpleSegManager.Default;
+        }
+    }
+
+}

+ 223 - 0
KcpNet/KcpListener.cs

@@ -0,0 +1,223 @@
+using System;
+using System.Collections.Concurrent;
+using System.IO;
+using System.Linq;
+using System.Net;
+using System.Net.Sockets;
+using System.Threading;
+using System.Threading.Tasks;
+using System.Threading.Tasks.Dataflow;
+
+namespace KcpNet
+{
+    public class KcpListener : KcpNetBase
+    {
+        private uint _id;
+
+        private Thread _processThread;
+        private Thread _updateThread;
+
+        private readonly IPEndPoint _localEndPoint;
+
+        private readonly BufferBlock<KcpConnection> _incomeConnections = new();
+
+        private readonly ConcurrentDictionary<EndPoint, KcpConnection> _connections = new();
+
+        private UdpClient _client;
+
+        private bool _closed;
+
+
+        public TimeSpan ConnectionTimeout { get; set; } = TimeSpan.FromSeconds(5);
+
+        public KcpListener(IPEndPoint localEndPoint)
+        {
+            _localEndPoint = localEndPoint;
+        }
+
+        public async Task<KcpConnection> GetConnectionAsync()
+        {
+            return await GetConnectionAsync(CancellationToken.None).ConfigureAwait(false);
+        }
+
+        public async Task<KcpConnection> GetConnectionAsync(CancellationToken cancellationToken)
+        {
+            try
+            {
+                var connection = await _incomeConnections.ReceiveAsync(cancellationToken).ConfigureAwait(false);
+                return connection;
+            }
+            catch
+            {
+                //
+            }
+
+            return null;
+        }
+
+        public void Start()
+        {
+            if (_closed)
+            {
+                throw new InvalidOperationException("Listener already closed.");
+            }
+            _client = new UdpClient(_localEndPoint);
+            _processThread = new Thread(() =>
+            {
+                while (!_closed)
+                {
+                    var connections = _connections.Values.ToArray();
+                    Parallel.ForEach(connections, connection =>
+                    {
+                        try
+                        {
+                            connection.ProcessOnce();
+                        }
+                        catch (Exception)
+                        {
+                            connection.Close();
+                        }
+                    });
+                    Thread.Sleep(1);
+                }
+            });
+            _updateThread = new Thread(() =>
+            {
+                while (!_closed)
+                {
+                    var connections = _connections.Values.ToArray();
+                    Parallel.ForEach(connections, connection =>
+                    {
+                        try
+                        {
+                            connection.Update();
+                            var now = DateTime.UtcNow;
+                            if (now - connection.LastPing > ConnectionTimeout)
+                            {
+                                connection.Close();
+                            }
+                        }
+                        catch (Exception)
+                        {
+                            connection.Close();
+                        }
+                    });
+                    Thread.Sleep(10);
+                }
+            });
+            _processThread.Start();
+            _updateThread.Start();
+
+            Task.Run(async () =>
+                {
+                    while (!_closed)
+                    {
+                        try
+                        {
+                            var result = await _client.ReceiveAsync().ConfigureAwait(false);
+                            if (_connections.TryGetValue(result.RemoteEndPoint, out var connection))
+                            {
+                                ((IKcpIoContainer) connection).Io.Input(result.Buffer);
+                            }
+                            else
+                            {
+                                connection = await HandshakeAsync(result.RemoteEndPoint, result.Buffer, result.Buffer.Length).ConfigureAwait(false);
+                                if (connection != null)
+                                {
+                                    connection.Closed += OnConnectionClosed;
+                                    _connections.AddOrUpdate(result.RemoteEndPoint, _ => connection, (_, exist) =>
+                                    {
+                                        exist.Close();
+                                        return connection;
+                                    });
+                                    await _incomeConnections.SendAsync(connection).ConfigureAwait(false);
+                                }
+                            }
+                        }
+                        catch (Exception)
+                        {
+                           //Could be the socket closed. 
+                        }
+                    }
+                }
+            );
+        }
+
+        private void OnConnectionClosed(object sender, EventArgs e)
+        {
+            var connection = (KcpConnection) sender;
+            connection.Closed -= OnConnectionClosed;
+            _connections.TryRemove(connection.RemoteEndPoint, out _);
+        }
+
+
+        private async Task<KcpConnection> HandshakeAsync(IPEndPoint remoteEndPoint, byte[] buffer, int bufferSize)
+        {
+            var handshakeBuffer = new byte[bufferSize];
+            Array.Copy(buffer,handshakeBuffer, bufferSize);
+            await using var stream = new MemoryStream(handshakeBuffer);
+            var random = new Random();
+            var header = await ReadBytesAsync(stream, 3).ConfigureAwait(false);
+            if (!header.SequenceEqual(ProtocolHeader))
+            {
+                throw new InvalidDataException("Error header for handshake01.");
+            }
+
+            var rb1 = await ReadByteAsync(stream).ConfigureAwait(false);
+            var rb2 = await ReadByteAsync(stream).ConfigureAwait(false);
+            var ru = await ReadUInt16Async(stream).ConfigureAwait(false);
+            if (rb1 * rb2 != ru)
+            {
+                throw new InvalidDataException("Error RBU data for handshake.");
+            }
+
+            var level = (HandshakeLevel)await ReadByteAsync(stream).ConfigureAwait(false);
+
+            if (level == HandshakeLevel.S01)
+            {
+                var sb01 = (byte)random.Next(0, 255);
+                var sb02 = (byte)random.Next(0, 255);
+                var su03 = (ushort)(sb01 * sb02);
+                var su03Buffer = BitConverter.GetBytes(su03);
+                var sendBuffer = new byte[12];
+                Array.Copy(ProtocolHeader, 0, sendBuffer, 0, 3);
+                sendBuffer[3] = sb01;
+                sendBuffer[4] = sb02;
+                Array.Copy(su03Buffer, 0, sendBuffer, 5, 2);
+                sendBuffer[7] = (byte)HandshakeLevel.S01;
+                var connectionId = Interlocked.Increment(ref _id);
+                var idData = BitConverter.GetBytes(connectionId);
+                Array.Copy(idData, 0, sendBuffer, 8, 4);
+                await _client.SendAsync(sendBuffer, sendBuffer.Length, remoteEndPoint);
+            }
+            else if (level == HandshakeLevel.S02)
+            {
+                var connectionId = await ReadUInt32Async(stream).ConfigureAwait(false);
+                var connection = new KcpConnection(connectionId, new KcpUdpIo(_client, remoteEndPoint), (IPEndPoint)_client.Client.LocalEndPoint, remoteEndPoint);
+                return connection;
+            }
+            else
+            {
+                throw new InvalidDataException("Error level data for handshake.");
+            }
+
+            return null;
+        }
+
+
+        public void Close()
+        {
+            _closed = true;
+            var connections = _connections.Values.ToArray();
+            Parallel.ForEach(connections, connection =>
+            {
+                connection.Close();
+            });
+            _processThread?.Join();
+            _updateThread?.Join();
+            _client.Close();
+            _incomeConnections.TryReceiveAll(out _);
+            _incomeConnections.Complete();
+        }
+    }
+}

+ 12 - 0
KcpNet/KcpMemoryPool.cs

@@ -0,0 +1,12 @@
+using System.Buffers;
+
+namespace KcpNet
+{
+    internal class KcpMemoryPool : IRentable
+    {
+        public IMemoryOwner<byte> RentBuffer(int length)
+        {
+            return MemoryPool<byte>.Shared.Rent(length);
+        }
+    }
+}

+ 15 - 0
KcpNet/KcpNet.csproj

@@ -0,0 +1,15 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <TargetFramework>net5.0</TargetFramework>
+  </PropertyGroup>
+
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
+    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
+  </PropertyGroup>
+
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
+    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
+  </PropertyGroup>
+
+</Project>

+ 53 - 0
KcpNet/KcpNetBase.cs

@@ -0,0 +1,53 @@
+using System;
+using System.IO;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace KcpNet
+{
+    public class KcpNetBase
+    {
+        protected static readonly byte[] ProtocolHeader = Encoding.ASCII.GetBytes("KCP");
+
+        protected async Task<byte[]> ReadBytesAsync(Stream stream, int count)
+        {
+            var result = new byte[count];
+            var bytesRead = 0;
+            while (count > 0)
+            {
+                var readCount = await stream.ReadAsync(result, bytesRead, count).ConfigureAwait(false);
+                if (readCount == 0)
+                {
+                    throw new InvalidOperationException("Stream is empty");
+                }
+                bytesRead += readCount;
+                count -= readCount;
+            }
+
+            if (bytesRead != result.Length)
+            {
+                throw new EndOfStreamException();
+            }
+            return result;
+        }
+
+        protected async Task<byte> ReadByteAsync(Stream stream)
+        {
+            var bytes = await ReadBytesAsync(stream, 1).ConfigureAwait(false);
+            return bytes[0];
+        }
+
+        protected async Task<ushort> ReadUInt16Async(Stream stream)
+        {
+            var bytes = await ReadBytesAsync(stream, 2).ConfigureAwait(false);
+            return BitConverter.ToUInt16(bytes);
+        }
+
+        protected async Task<uint> ReadUInt32Async(Stream stream)
+        {
+            var bytes = await ReadBytesAsync(stream, 4).ConfigureAwait(false);
+            return BitConverter.ToUInt32(bytes);
+        }
+
+    }
+}

+ 19 - 0
KcpNet/KcpOutputCallback.cs

@@ -0,0 +1,19 @@
+using System.Buffers;
+
+namespace KcpNet
+{
+    public class KcpOutputCallback : IKcpCallback
+    {
+        private readonly IKcpIo _outputIo;
+
+        public KcpOutputCallback(IKcpIo outputIo)
+        {
+            _outputIo = outputIo;
+        }
+
+        public void Output(IMemoryOwner<byte> buffer, int avalidLength)
+        {
+            _outputIo.Output(buffer.Memory, avalidLength);
+        }
+    }
+}

+ 163 - 0
KcpNet/KcpPacket.cs

@@ -0,0 +1,163 @@
+using System;
+using System.Numerics;
+
+namespace KcpNet
+{
+    internal class KcpPacket
+    {
+        protected readonly byte[] data;
+
+        protected readonly byte[] buffer;
+
+        public KcpPacketType Type { get; }
+
+        public KcpPacket(KcpPacketType type, byte[] value)
+        {
+            Type = type;
+            data = value;
+            if (data != null)
+            {
+                buffer =  new byte[5 + this.data.Length];
+                buffer[0] = (byte) Type;
+                Array.Copy(BitConverter.GetBytes(this.data.Length),0,buffer,1,4);
+                Array.Copy(this.data, 0, buffer, 5, this.data.Length);
+            }
+            else
+            {
+                buffer = new byte[1];
+                buffer[0] = (byte)Type;
+            }
+        }
+
+
+        public virtual byte[] GetData()
+        {
+            return data;
+        }
+
+        public byte[] GetBuffer()
+        {
+            return buffer;
+        }
+    }
+
+
+
+    internal class KcpCommand : KcpPacket, IEquatable<KcpCommand>
+    {
+        public KcpCommand(KcpPacketType type) : base(type, null)
+        {
+        }
+
+        public bool Equals(KcpCommand other)
+        {
+            if (other == null)
+            {
+                return false;
+            }
+            return Type == other.Type;
+        }
+
+        public override bool Equals(object obj)
+        {
+            if (ReferenceEquals(null, obj)) return false;
+            if (ReferenceEquals(this, obj)) return true;
+            if (obj.GetType() != GetType()) return false;
+            return Equals((KcpCommand)obj);
+        }
+
+        public override int GetHashCode()
+        {
+            return (int)Type;
+        }
+
+        public static bool operator ==(KcpCommand left, KcpCommand right)
+        {
+            return Equals(left, right);
+        }
+
+        public static bool operator !=(KcpCommand left, KcpCommand right)
+        {
+            return !Equals(left, right);
+        }
+    }
+
+    internal class KcpPingRequest : KcpCommand
+    {
+        public KcpPingRequest() : base(KcpPacketType.PingRequest)
+        {
+        }
+    }
+
+
+    internal class KcpPingResponse : KcpCommand
+    {
+        public KcpPingResponse() : base(KcpPacketType.PingResponse)
+        {
+        }
+    }
+
+
+    internal class KcpClose : KcpCommand
+    {
+        public KcpClose() : base(KcpPacketType.Close)
+        {
+        }
+    }
+
+
+    internal class KcpData : KcpPacket, IEquatable<KcpData>
+    {
+
+        private readonly int _hashCode;
+
+        private readonly ArraySegment<byte> _bufferSegment;
+
+        public int Offset { get; set; } =  0;
+
+        public int Length => data.Length - Offset;
+
+
+        public KcpData(byte[] value) : base(KcpPacketType.Data, value)
+        {
+            _bufferSegment = new ArraySegment<byte>(data);
+            var bigInt = new BigInteger(buffer);
+            _hashCode = bigInt.GetHashCode();
+        }
+
+        public override byte[] GetData()
+        {
+            return _bufferSegment.Slice(Offset).ToArray();
+        }
+
+        public bool Equals(KcpData other)
+        {
+            if (ReferenceEquals(null, other)) return false;
+            if (ReferenceEquals(this, other)) return true;
+            return _hashCode == other._hashCode;
+        }
+
+        public override bool Equals(object obj)
+        {
+            if (ReferenceEquals(null, obj)) return false;
+            if (ReferenceEquals(this, obj)) return true;
+            if (obj.GetType() != this.GetType()) return false;
+            return Equals((KcpData)obj);
+        }
+
+        public override int GetHashCode()
+        {
+            return _hashCode;
+        }
+
+        public static bool operator ==(KcpData left, KcpData right)
+        {
+            return Equals(left, right);
+        }
+
+        public static bool operator !=(KcpData left, KcpData right)
+        {
+            return !Equals(left, right);
+        }
+    }
+}

+ 10 - 0
KcpNet/KcpPacketType.cs

@@ -0,0 +1,10 @@
+namespace KcpNet
+{
+    internal enum KcpPacketType
+    {
+        PingRequest = 1,
+        PingResponse = 2,
+        Close = 3,
+        Data = 4,
+    }
+}

+ 118 - 0
KcpNet/KcpPriorityPacketBuffer.cs

@@ -0,0 +1,118 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using System.Threading.Tasks.Dataflow;
+
+namespace KcpNet
+{
+    internal enum PacketPriority
+    {
+        Low,
+        Middle,
+        High,
+    }
+
+    /// <summary>
+    /// Store packet into this buffer, get packet with priority.
+    /// </summary>
+    internal class KcpPriorityPacketBuffer
+    {
+        private readonly BufferBlock<KcpPacket> _lowBuffer;
+        private readonly BufferBlock<KcpPacket> _middleBuffer;
+        private readonly BufferBlock<KcpPacket> _highBuffer;
+
+        public KcpPriorityPacketBuffer()
+        {
+            _lowBuffer = new BufferBlock<KcpPacket>();
+            _middleBuffer = new BufferBlock<KcpPacket>();
+            _highBuffer = new BufferBlock<KcpPacket>();
+        }
+
+
+        public async Task WritePacketAsync(KcpPacket packet, PacketPriority priority)
+        {
+            await WritePacketAsync(packet, priority, CancellationToken.None).ConfigureAwait(false);
+        }
+
+        public async Task WritePacketAsync(KcpPacket packet, PacketPriority priority, CancellationToken cancellationToken)
+        {
+            bool result;
+            switch (priority)
+            {
+                case PacketPriority.Low:
+                    result = await _lowBuffer.SendAsync(packet, cancellationToken).ConfigureAwait(false);
+                    break;
+                case PacketPriority.Middle:
+                    result = await _middleBuffer.SendAsync(packet, cancellationToken).ConfigureAwait(false);
+                    break;
+                case PacketPriority.High:
+                    result = await _highBuffer.SendAsync(packet, cancellationToken).ConfigureAwait(false);
+                    break;
+                default:
+                    throw new ArgumentOutOfRangeException(nameof(priority), priority, null);
+            }
+
+            if (!result)
+            {
+                throw new InvalidOperationException($"Post packet with priority {priority} failed.");
+            }
+        }
+
+
+        public void WritePacket(KcpPacket packet, PacketPriority priority)
+        {
+            bool result;
+            switch (priority)
+            {
+                case PacketPriority.Low:
+                    result = _lowBuffer.Post(packet);
+                    break;
+                case PacketPriority.Middle:
+                    result = _middleBuffer.Post(packet);
+                    break;
+                case PacketPriority.High:
+                    result = _highBuffer.Post(packet);
+                    break;
+                default:
+                    throw new ArgumentOutOfRangeException(nameof(priority), priority, null);
+            }
+
+            if (!result)
+            {
+                throw new InvalidOperationException($"Post packet {packet.Type} with priority {priority} failed.");
+            }
+        }
+        
+
+        public IList<KcpPacket> ReadPackets(bool distinct = false)
+        {
+            var packets = new List<KcpPacket>();
+            if (_highBuffer.TryReceiveAll(out var highPackets))
+            {
+                packets.AddRange(highPackets);
+            }
+            if (_middleBuffer.TryReceiveAll(out var middlePackets))
+            {
+                packets.AddRange(middlePackets);
+            }
+            if (_lowBuffer.TryReceiveAll(out var lowPackets))
+            {
+                packets.AddRange(lowPackets);
+            }
+            return distinct? packets.Distinct().ToList(): packets;
+        }
+
+
+        public void Close()
+        {
+            _lowBuffer.TryReceiveAll(out _);
+            _middleBuffer.TryReceiveAll(out _);
+            _highBuffer.TryReceiveAll(out _);
+            _lowBuffer.Complete();
+            _middleBuffer.Complete();
+            _highBuffer.Complete();
+        }
+    }
+}

+ 403 - 0
KcpNet/KcpSegment.cs

@@ -0,0 +1,403 @@
+using System;
+using System.Buffers.Binary;
+using System.Runtime.InteropServices;
+
+namespace KcpNet
+{
+    /// <summary>
+    /// 调整了没存布局,直接拷贝块提升性能。
+    /// <para>结构体保存内容只有一个指针,不用担心参数传递过程中的性能</para>
+    /// https://github.com/skywind3000/kcp/issues/118#issuecomment-338133930
+    /// <para>不要对没有初始化的KcpSegment(内部指针为0,所有属性都将指向位置区域) 进行任何赋值操作,可能导致内存损坏。
+    /// 出于性能考虑,没有对此项进行安全检查。</para>
+    /// </summary>
+    public struct KcpSegment : IKcpSegment
+    {
+        internal readonly unsafe byte* ptr;
+        internal unsafe KcpSegment(byte* intPtr, uint appendDateSize)
+        {
+            this.ptr = intPtr;
+            len = appendDateSize;
+        }
+
+        /// <summary>
+        /// 使用完必须显示释放,否则内存泄漏
+        /// </summary>
+        /// <param name="appendDateSize"></param>
+        /// <returns></returns>
+        public static KcpSegment AllocHGlobal(int appendDateSize)
+        {
+            var total = LocalOffset + HeadOffset + appendDateSize;
+            IntPtr intPtr = Marshal.AllocHGlobal(total);
+            unsafe
+            {
+                ///清零    不知道是不是有更快的清0方法?
+                Span<byte> span = new Span<byte>(intPtr.ToPointer(), total);
+                span.Clear();
+
+                return new KcpSegment((byte*)intPtr.ToPointer(), (uint)appendDateSize);
+            }
+        }
+
+        /// <summary>
+        /// 释放非托管内存
+        /// </summary>
+        /// <param name="seg"></param>
+        public static void FreeHGlobal(KcpSegment seg)
+        {
+            unsafe
+            {
+                Marshal.FreeHGlobal((IntPtr)seg.ptr);
+            }
+        }
+
+        /// 以下为本机使用的参数
+        /// <summary>
+        /// offset = 0
+        /// </summary>
+        public uint resendts
+        {
+            get
+            {
+                unsafe
+                {
+                    return *(uint*)(ptr + 0);
+                }
+            }
+            set
+            {
+                unsafe
+                {
+                    *(uint*)(ptr + 0) = value;
+                }
+            }
+        }
+
+        /// <summary>
+        /// offset = 4
+        /// </summary>
+        public uint rto
+        {
+            get
+            {
+                unsafe
+                {
+                    return *(uint*)(ptr + 4);
+                }
+            }
+            set
+            {
+                unsafe
+                {
+                    *(uint*)(ptr + 4) = value;
+                }
+            }
+        }
+
+        /// <summary>
+        /// offset = 8
+        /// </summary>
+        public uint fastack
+        {
+            get
+            {
+                unsafe
+                {
+                    return *(uint*)(ptr + 8);
+                }
+            }
+            set
+            {
+                unsafe
+                {
+                    *(uint*)(ptr + 8) = value;
+                }
+            }
+        }
+
+        /// <summary>
+        /// offset = 12
+        /// </summary>
+        public uint xmit
+        {
+            get
+            {
+                unsafe
+                {
+                    return *(uint*)(ptr + 12);
+                }
+            }
+            set
+            {
+                unsafe
+                {
+                    *(uint*)(ptr + 12) = value;
+                }
+            }
+        }
+
+        ///以下为需要网络传输的参数
+        public const int LocalOffset = 4 * 4;
+        public const int HeadOffset = Kcp.IKCP_OVERHEAD;
+
+        /// <summary>
+        /// offset = <see cref="LocalOffset"/>
+        /// </summary>
+        /// https://github.com/skywind3000/kcp/issues/134
+        public uint conv
+        {
+            get
+            {
+                unsafe
+                {
+                    return *(uint*)(LocalOffset + 0 + ptr);
+                }
+            }
+            set
+            {
+                unsafe
+                {
+                    *(uint*)(LocalOffset + 0 + ptr) = value;
+                }
+            }
+        }
+
+        /// <summary>
+        /// offset = <see cref="LocalOffset"/> + 4
+        /// </summary>
+        public byte cmd
+        {
+            get
+            {
+                unsafe
+                {
+                    return *(LocalOffset + 4 + ptr);
+                }
+            }
+            set
+            {
+                unsafe
+                {
+                    *(LocalOffset + 4 + ptr) = value;
+                }
+            }
+        }
+
+        /// <summary>
+        /// offset = <see cref="LocalOffset"/> + 5
+        /// </summary>
+        public byte frg
+        {
+            get
+            {
+                unsafe
+                {
+                    return *(LocalOffset + 5 + ptr);
+                }
+            }
+            set
+            {
+                unsafe
+                {
+                    *(LocalOffset + 5 + ptr) = value;
+                }
+            }
+        }
+
+        /// <summary>
+        /// offset = <see cref="LocalOffset"/> + 6
+        /// </summary>
+        public ushort wnd
+        {
+            get
+            {
+                unsafe
+                {
+                    return *(ushort*)(LocalOffset + 6 + ptr);
+                }
+            }
+            set
+            {
+                unsafe
+                {
+                    *(ushort*)(LocalOffset + 6 + ptr) = value;
+                }
+            }
+        }
+
+        /// <summary>
+        /// offset = <see cref="LocalOffset"/> + 8
+        /// </summary>
+        public uint ts
+        {
+            get
+            {
+                unsafe
+                {
+                    return *(uint*)(LocalOffset + 8 + ptr);
+                }
+            }
+            set
+            {
+                unsafe
+                {
+                    *(uint*)(LocalOffset + 8 + ptr) = value;
+                }
+            }
+        }
+
+        /// <summary>
+        /// <para> SendNumber? </para>
+        /// offset = <see cref="LocalOffset"/> + 12
+        /// </summary>
+        public uint sn
+        {
+            get
+            {
+                unsafe
+                {
+                    return *(uint*)(LocalOffset + 12 + ptr);
+                }
+            }
+            set
+            {
+                unsafe
+                {
+                    *(uint*)(LocalOffset + 12 + ptr) = value;
+                }
+            }
+        }
+
+        /// <summary>
+        /// offset = <see cref="LocalOffset"/> + 16
+        /// </summary>
+        public uint una
+        {
+            get
+            {
+                unsafe
+                {
+                    return *(uint*)(LocalOffset + 16 + ptr);
+                }
+            }
+            set
+            {
+                unsafe
+                {
+                    *(uint*)(LocalOffset + 16 + ptr) = value;
+                }
+            }
+        }
+
+        /// <summary>
+        /// <para> AppendDateSize </para>
+        /// offset = <see cref="LocalOffset"/> + 20
+        /// </summary>
+        public uint len
+        {
+            get
+            {
+                unsafe
+                {
+                    return *(uint*)(LocalOffset + 20 + ptr);
+                }
+            }
+            private set
+            {
+                unsafe
+                {
+                    *(uint*)(LocalOffset + 20 + ptr) = value;
+                }
+            }
+        }
+
+        /// <summary>
+        /// 
+        /// </summary>
+        /// https://github.com/skywind3000/kcp/issues/35#issuecomment-263770736
+        public Span<byte> data
+        {
+            get
+            {
+                unsafe
+                {
+                    return new Span<byte>(LocalOffset + HeadOffset + ptr, (int)len);
+                }
+            }
+        }
+
+
+
+        /// <summary>
+        /// 将片段中的要发送的数据拷贝到指定缓冲区
+        /// </summary>
+        /// <param name="buffer"></param>
+        /// <returns></returns>
+        public int Encode(Span<byte> buffer)
+        {
+            var datelen = (int)(HeadOffset + len);
+
+            ///备用偏移值 现阶段没有使用
+            const int offset = 0;
+
+            if (Kcp.IsLittleEndian)
+            {
+                if (BitConverter.IsLittleEndian)
+                {
+                    ///小端可以一次拷贝
+                    unsafe
+                    {
+                        ///要发送的数据从LocalOffset开始。
+                        ///本结构体调整了要发送字段和单机使用字段的位置,让报头数据和数据连续,节约一次拷贝。
+                        Span<byte> sendDate = new Span<byte>(ptr + LocalOffset, datelen);
+                        sendDate.CopyTo(buffer);
+                    }
+                }
+                else
+                {
+                    BinaryPrimitives.WriteUInt32LittleEndian(buffer.Slice(offset), conv);
+                    buffer[offset + 4] = cmd;
+                    buffer[offset + 5] = frg;
+                    BinaryPrimitives.WriteUInt16LittleEndian(buffer.Slice(offset + 6), wnd);
+
+                    BinaryPrimitives.WriteUInt32LittleEndian(buffer.Slice(offset + 8), ts);
+                    BinaryPrimitives.WriteUInt32LittleEndian(buffer.Slice(offset + 12), sn);
+                    BinaryPrimitives.WriteUInt32LittleEndian(buffer.Slice(offset + 16), una);
+                    BinaryPrimitives.WriteUInt32LittleEndian(buffer.Slice(offset + 20), len);
+
+                    data.CopyTo(buffer.Slice(HeadOffset));
+                }
+            }
+            else
+            {
+                if (BitConverter.IsLittleEndian)
+                {
+                    BinaryPrimitives.WriteUInt32BigEndian(buffer.Slice(offset), conv);
+                    buffer[offset + 4] = cmd;
+                    buffer[offset + 5] = frg;
+                    BinaryPrimitives.WriteUInt16BigEndian(buffer.Slice(offset + 6), wnd);
+
+                    BinaryPrimitives.WriteUInt32BigEndian(buffer.Slice(offset + 8), ts);
+                    BinaryPrimitives.WriteUInt32BigEndian(buffer.Slice(offset + 12), sn);
+                    BinaryPrimitives.WriteUInt32BigEndian(buffer.Slice(offset + 16), una);
+                    BinaryPrimitives.WriteUInt32BigEndian(buffer.Slice(offset + 20), len);
+
+                    data.CopyTo(buffer.Slice(HeadOffset));
+                }
+                else
+                {
+                    ///大端可以一次拷贝
+                    unsafe
+                    {
+                        ///要发送的数据从LocalOffset开始。
+                        ///本结构体调整了要发送字段和单机使用字段的位置,让报头数据和数据连续,节约一次拷贝。
+                        Span<byte> sendDate = new Span<byte>(ptr + LocalOffset, datelen);
+                        sendDate.CopyTo(buffer);
+                    }
+                }
+            }
+
+            return datelen;
+        }
+    }
+}

+ 52 - 0
KcpNet/KcpUdpIo.cs

@@ -0,0 +1,52 @@
+using System;
+using System.Net;
+using System.Net.Sockets;
+
+namespace KcpNet
+{
+    public class KcpUdpIo : IKcpIo
+    {
+        private readonly UdpClient _client;
+
+        private readonly IPEndPoint _remoteEndPoint;
+
+        public event EventHandler<byte[]> Received;
+
+        public KcpUdpIo(UdpClient client, IPEndPoint remoteEndPoint)
+        {
+            _client = client;
+            _remoteEndPoint = remoteEndPoint;
+        }
+
+        public void Output(byte[] buffer)
+        {
+            var sentSize = _client.Send(buffer, buffer.Length, _remoteEndPoint);
+            if (sentSize != buffer.Length)
+            {
+                throw new InvalidOperationException("Send buffer failed.");
+            }
+        }
+
+        public void Output(Memory<byte> memory, int size)
+        {
+            var buffer = memory.Slice(0, size).ToArray();
+            var sentSize = _client.Send(buffer, buffer.Length, _remoteEndPoint);
+            if (sentSize != buffer.Length)
+            {
+                throw new InvalidOperationException("Send buffer failed.");
+            }
+        }
+
+        public void Input(byte[] buffer)
+        {
+            Received?.Invoke(this, buffer);
+        }
+
+
+        public void Input(Memory<byte> memory, int size)
+        {
+            var buffer = memory.Slice(0, size).ToArray();
+            Received?.Invoke(this, buffer);
+        }
+    }
+}

+ 101 - 0
KcpNet/SegManager.cs

@@ -0,0 +1,101 @@
+using System;
+using System.Collections.Generic;
+using System.Runtime.InteropServices;
+
+namespace KcpNet
+{
+    public class SimpleSegManager : ISegmentManager<KcpSegment>
+    {
+        public static SimpleSegManager Default { get; } = new SimpleSegManager();
+        public KcpSegment Alloc(int appendDateSize)
+        {
+            return KcpSegment.AllocHGlobal(appendDateSize);
+        }
+
+        public void Free(KcpSegment seg)
+        {
+            KcpSegment.FreeHGlobal(seg);
+        }
+    }
+
+    /// <summary>
+    /// 使用这个就不能SetMtu了,大小已经写死
+    /// </summary>
+    /// <remarks>需要大量测试</remarks>
+    public unsafe class UnSafeSegManager : ISegmentManager<KcpSegment>
+    {
+        public static UnSafeSegManager Default { get; } = new UnSafeSegManager();
+        /// <summary>
+        /// 因为默认mtu是1400,并且内存需要内存行/内存页对齐。这里直接512对齐。
+        /// </summary>
+        public const int blockSize = 512 * 3;
+        public HashSet<IntPtr> header = new HashSet<IntPtr>();
+        public Stack<IntPtr> blocks = new Stack<IntPtr>();
+        public readonly object locker = new object();
+        public UnSafeSegManager()
+        {
+            Alloc();
+        }
+
+        void Alloc()
+        {
+            int count = 50;
+            IntPtr intPtr = Marshal.AllocHGlobal(blockSize * count);
+            header.Add(intPtr);
+            for (int i = 0; i < count; i++)
+            {
+                blocks.Push(intPtr + blockSize * i);
+            }
+        }
+
+        ~UnSafeSegManager()
+        {
+            foreach (var item in header)
+            {
+                Marshal.FreeHGlobal(item);
+            }
+        }
+
+        public KcpSegment Alloc(int appendDateSize)
+        {
+            lock (locker)
+            {
+                var total = KcpSegment.LocalOffset + KcpSegment.HeadOffset + appendDateSize;
+                if (total > blockSize)
+                {
+                    throw new ArgumentOutOfRangeException();
+                }
+
+                if (blocks.Count > 0)
+                {
+
+                }
+                else
+                {
+                    Alloc();
+                }
+
+                var ptr = blocks.Pop();
+                Span<byte> span = new Span<byte>(ptr.ToPointer(), blockSize);
+                span.Clear();
+                return new KcpSegment((byte*)ptr.ToPointer(), (uint)appendDateSize);
+            }
+        }
+
+        public void Free(KcpSegment seg)
+        {
+            IntPtr ptr = (IntPtr)seg.ptr;
+            blocks.Push(ptr);
+        }
+
+        public class Kcp : Kcp<KcpSegment>
+        {
+            public Kcp(uint conv_, IKcpCallback callback, IRentable rentable = null)
+                : base(conv_, callback, rentable)
+            {
+                SegmentManager = Default;
+            }
+        }
+    }
+}
+

+ 15 - 0
KcpNet/Utility.cs

@@ -0,0 +1,15 @@
+using System;
+using System.Runtime.CompilerServices;
+
+namespace KcpNet
+{
+    internal static class KcpExtension_FDF71D0BC31D49C48EEA8FAA51F017D4
+    {
+        private static readonly DateTime utc_time = new DateTime(1970, 1, 1);
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public static uint ConvertTime(this in DateTime time)
+        {
+            return (uint)(Convert.ToInt64(time.Subtract(utc_time).TotalMilliseconds) & 0xffffffff);
+        }
+    }
+}

+ 12 - 0
KcpServerTest/KcpServerTest.csproj

@@ -0,0 +1,12 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <OutputType>Exe</OutputType>
+    <TargetFramework>net5.0</TargetFramework>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <ProjectReference Include="..\KcpNet\KcpNet.csproj" />
+  </ItemGroup>
+
+</Project>

+ 81 - 0
KcpServerTest/Program.cs

@@ -0,0 +1,81 @@
+using System;
+using System.Net;
+using System.Text;
+using System.Threading.Tasks;
+using KcpNet;
+
+namespace KcpServerTest
+{
+    class Program
+    {
+        static void Main(string[] args)
+        {
+            var server = new KcpListener(new IPEndPoint(IPAddress.Any, 6000));
+            server.Start();
+            Task.Run(async () =>
+            {
+                while (true)
+                {
+                    var connection = await server.GetConnectionAsync().ConfigureAwait(false);
+                    if (connection != null)
+                    {
+                        HandleServerConnection(connection);
+                    }
+                    else
+                    {
+                        break;
+                    }
+                }
+            });
+            Console.Read();
+        }
+
+        private static async Task<byte[]> ReadServerBytesAsync(KcpConnection connection, int size)
+        {
+            var bytes = new byte[size];
+            var length = size;
+            var startIndex = 0;
+            while (length > 0)
+            {
+                var buffer = new byte[length];
+                var bytesRead = await connection.ReadAsync(buffer).ConfigureAwait(false);
+                if (bytesRead == 0)
+                {
+                    throw new InvalidOperationException("Connection could be closed.");
+                }
+                Array.Copy(buffer, 0, bytes, startIndex, bytesRead);
+                startIndex += bytesRead;
+                length -= bytesRead;
+            }
+
+            return bytes;
+        }
+
+
+        private static void HandleServerConnection(KcpConnection connection)
+        {
+            Task.Run(async () =>
+            {
+                try
+                {
+
+                    while (true)
+                    {
+                        var lengthData = await ReadServerBytesAsync(connection, 4);
+                        var length = BitConverter.ToInt32(lengthData, 0);
+                        var data = await ReadServerBytesAsync(connection, length).ConfigureAwait(false);
+                        Console.WriteLine($"Server connection [{connection.Id}] received: " + Encoding.UTF8.GetString(data));
+                        var replyData = Encoding.UTF8.GetBytes("OK");
+                        await connection.SendAsync(BitConverter.GetBytes(replyData.Length)).ConfigureAwait(false);
+                        await connection.SendAsync(replyData).ConfigureAwait(false);
+                    }
+                }
+                catch (Exception ex)
+                {
+                    Console.WriteLine(ex.Message);
+                }
+            });
+
+        }
+    }
+}