├── bin └── Debug │ └── UnityEngine.dll ├── KCP_PInvoke ├── kcp_native_dll │ ├── ikcp.c │ ├── stdafx.h │ ├── dllmain.cpp │ ├── stdafx.cpp │ ├── targetver.h │ ├── x86 │ │ ├── ikcp.dll │ │ ├── ikcp.pdb │ │ ├── ikcp.pdb.meta │ │ └── ikcp.dll.meta │ ├── x86_64 │ │ ├── ikcp.dll │ │ ├── ikcp.pdb │ │ ├── ikcp.pdb.meta │ │ └── ikcp.dll.meta │ ├── ReadMe.txt │ ├── kcpdll.vcxproj.filters │ ├── ikcp.h │ └── kcpdll.vcxproj └── kcp_native.cs ├── KcpProject.csproj.user ├── Unity3D ├── KCPReceiveListener.cs ├── SwitchQueue.cs ├── KCP_H.cs ├── KCPProxy_BE.cs ├── KCPProxy_LE.cs ├── NetworkDebuger.cs ├── KCPSocket.cs ├── kcpUnity3DClientTest.cs └── KCP_LE.cs ├── common ├── switch_queue.cs └── kcp.cs ├── Properties └── AssemblyInfo.cs ├── Program.cs ├── KcpProject.csproj ├── v2 └── udp_socket_v2.cs ├── README.md └── v1 └── udp_socket_v1.cs /bin/Debug/UnityEngine.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RainsSoft/kcp-csharp/HEAD/bin/Debug/UnityEngine.dll -------------------------------------------------------------------------------- /KCP_PInvoke/kcp_native_dll/ikcp.c: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RainsSoft/kcp-csharp/HEAD/KCP_PInvoke/kcp_native_dll/ikcp.c -------------------------------------------------------------------------------- /KCP_PInvoke/kcp_native_dll/stdafx.h: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RainsSoft/kcp-csharp/HEAD/KCP_PInvoke/kcp_native_dll/stdafx.h -------------------------------------------------------------------------------- /KCP_PInvoke/kcp_native_dll/dllmain.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RainsSoft/kcp-csharp/HEAD/KCP_PInvoke/kcp_native_dll/dllmain.cpp -------------------------------------------------------------------------------- /KCP_PInvoke/kcp_native_dll/stdafx.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RainsSoft/kcp-csharp/HEAD/KCP_PInvoke/kcp_native_dll/stdafx.cpp -------------------------------------------------------------------------------- /KCP_PInvoke/kcp_native_dll/targetver.h: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RainsSoft/kcp-csharp/HEAD/KCP_PInvoke/kcp_native_dll/targetver.h -------------------------------------------------------------------------------- /KCP_PInvoke/kcp_native_dll/x86/ikcp.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RainsSoft/kcp-csharp/HEAD/KCP_PInvoke/kcp_native_dll/x86/ikcp.dll -------------------------------------------------------------------------------- /KCP_PInvoke/kcp_native_dll/x86/ikcp.pdb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RainsSoft/kcp-csharp/HEAD/KCP_PInvoke/kcp_native_dll/x86/ikcp.pdb -------------------------------------------------------------------------------- /KCP_PInvoke/kcp_native_dll/x86_64/ikcp.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RainsSoft/kcp-csharp/HEAD/KCP_PInvoke/kcp_native_dll/x86_64/ikcp.dll -------------------------------------------------------------------------------- /KCP_PInvoke/kcp_native_dll/x86_64/ikcp.pdb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RainsSoft/kcp-csharp/HEAD/KCP_PInvoke/kcp_native_dll/x86_64/ikcp.pdb -------------------------------------------------------------------------------- /KCP_PInvoke/kcp_native_dll/x86/ikcp.pdb.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 3c62416f57f0f4d449d21bce03809673 3 | timeCreated: 1525848235 4 | licenseType: Pro 5 | DefaultImporter: 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /KCP_PInvoke/kcp_native_dll/x86_64/ikcp.pdb.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: ea0a796fb109c154c9b0b68e032a1c52 3 | timeCreated: 1525848138 4 | licenseType: Pro 5 | DefaultImporter: 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /KcpProject.csproj.user: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | ShowAllFiles 5 | 6 | -------------------------------------------------------------------------------- /Unity3D/KCPReceiveListener.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Net; 5 | using System.Text; 6 | 7 | namespace Network_Kcp 8 | { 9 | public delegate void KCPReceiveListener(byte[] buff, int size, IPEndPoint remotePoint); 10 | 11 | } 12 | -------------------------------------------------------------------------------- /KCP_PInvoke/kcp_native_dll/ReadMe.txt: -------------------------------------------------------------------------------- 1 | ======================================================================== 2 | 动态链接库:kcpdll 项目概述 3 | ======================================================================== 4 | 5 | 应用程序向导已为您创建了此 kcp_native_dll DLL。 6 | 7 | 本文件概要介绍组成 kcpdll 应用程序的 8 | 的每个文件的内容。 9 | 10 | 11 | kcpdll.vcproj 12 | 这是使用应用程序向导生成的 VC++ 项目的主项目文件, 13 | 其中包含生成该文件的 Visual C++ 的版本信息,以及有关使用应用程序向导选择的平台、配置和项目功能的信息。 14 | 15 | kcpdll.cpp 16 | 这是主 DLL 源文件。 17 | 18 | 此 DLL 在创建时不导出任何符号。因此,在生成此 DLL 时 19 | 将不会产生 .lib 文件。如果希望此项目 20 | 成为其他某个项目的项目依赖项,则需要 21 | 添加代码以从 DLL 导出某些符号, 22 | 以便产生一个导出库,或者,也可以在项目“属性页”对话框中的 23 | “链接器”文件夹中,将“常规”属性页上的 24 | “忽略输入库”属性设置为“是”。 25 | 26 | ///////////////////////////////////////////////////////////////////////////// 27 | 其他标准文件: 28 | 29 | StdAfx.h, StdAfx.cpp 30 | 这些文件用于生成名为 kcpdll.pch 的预编译头 (PCH) 文件和名为 StdAfx.obj 的预编译类型文件。 31 | 32 | ///////////////////////////////////////////////////////////////////////////// 33 | 其他注释: 34 | 35 | 应用程序向导使用“TODO:”注释来指示应添加或自定义的源代码部分。 36 | 37 | ///////////////////////////////////////////////////////////////////////////// -------------------------------------------------------------------------------- /common/switch_queue.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | 4 | public class Utility { 5 | 6 | public static void Swap< QT >( ref QT t1, ref QT t2 ) { 7 | 8 | QT temp = t1; 9 | t1 = t2; 10 | t2 = temp; 11 | } 12 | } 13 | 14 | public class SwitchQueue where T : class { 15 | 16 | private Queue mConsumeQueue; 17 | private Queue mProduceQueue; 18 | 19 | public SwitchQueue() { 20 | mConsumeQueue = new Queue(16); 21 | mProduceQueue = new Queue(16); 22 | } 23 | 24 | public SwitchQueue(int capcity) { 25 | mConsumeQueue = new Queue(capcity); 26 | mProduceQueue = new Queue(capcity); 27 | } 28 | 29 | // producer 30 | public void Push( T obj ) { 31 | lock (mProduceQueue) 32 | { 33 | mProduceQueue.Enqueue( obj ); 34 | } 35 | } 36 | 37 | // consumer. 38 | public T Pop() { 39 | 40 | return (T)mConsumeQueue.Dequeue(); 41 | } 42 | 43 | public bool Empty() { 44 | return 0 == mConsumeQueue.Count; 45 | } 46 | 47 | public void Switch() { 48 | lock (mProduceQueue) 49 | { 50 | Utility.Swap ( ref mConsumeQueue, ref mProduceQueue ); 51 | } 52 | } 53 | 54 | public void Clear() { 55 | lock (mProduceQueue) 56 | { 57 | mConsumeQueue.Clear(); 58 | mProduceQueue.Clear(); 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /Unity3D/SwitchQueue.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | namespace Network_Kcp 4 | { 5 | public class SwitchQueue where T : class 6 | { 7 | 8 | private Queue mConsumeQueue; 9 | private Queue mProduceQueue; 10 | 11 | public SwitchQueue() { 12 | mConsumeQueue = new Queue(16); 13 | mProduceQueue = new Queue(16); 14 | } 15 | 16 | public SwitchQueue(int capcity) { 17 | mConsumeQueue = new Queue(capcity); 18 | mProduceQueue = new Queue(capcity); 19 | } 20 | 21 | // producer 22 | public void Push(T obj) { 23 | lock (mProduceQueue) { 24 | mProduceQueue.Enqueue(obj); 25 | } 26 | } 27 | 28 | // consumer. 29 | public T Pop() { 30 | 31 | return (T)mConsumeQueue.Dequeue(); 32 | } 33 | 34 | public bool Empty() { 35 | return 0 == mConsumeQueue.Count; 36 | } 37 | 38 | public void Switch() { 39 | lock (mProduceQueue) { 40 | Swap(ref mConsumeQueue, ref mProduceQueue); 41 | } 42 | } 43 | 44 | public void Clear() { 45 | lock (mProduceQueue) { 46 | mConsumeQueue.Clear(); 47 | mProduceQueue.Clear(); 48 | } 49 | } 50 | 51 | public static void Swap(ref QT t1, ref QT t2) { 52 | 53 | QT temp = t1; 54 | t1 = t2; 55 | t2 = temp; 56 | } 57 | } 58 | } -------------------------------------------------------------------------------- /KCP_PInvoke/kcp_native_dll/kcpdll.vcxproj.filters: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {4FC737F1-C7A5-4376-A066-2A32D752A2FF} 6 | cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx 7 | 8 | 9 | {93995380-89BD-4b04-88EB-625FBE52EBFB} 10 | h;hpp;hxx;hm;inl;inc;xsd 11 | 12 | 13 | {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} 14 | rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav 15 | 16 | 17 | 18 | 19 | 源文件 20 | 21 | 22 | 源文件 23 | 24 | 25 | 源文件 26 | 27 | 28 | 29 | 30 | 头文件 31 | 32 | 33 | 头文件 34 | 35 | 36 | 头文件 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("KcpProject")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("Microsoft")] 12 | [assembly: AssemblyProduct("KcpProject")] 13 | [assembly: AssemblyCopyright("Copyright © Microsoft 2016")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("5ae62a35-f172-411a-b95b-502af9dfd222")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /KCP_PInvoke/kcp_native_dll/x86_64/ikcp.dll.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: e1f796eb30ea0e04697ebd2c2e86f8e5 3 | timeCreated: 1510752959 4 | licenseType: Pro 5 | PluginImporter: 6 | serializedVersion: 1 7 | iconMap: {} 8 | executionOrder: {} 9 | isPreloaded: 0 10 | isOverridable: 0 11 | platformData: 12 | Android: 13 | enabled: 0 14 | settings: 15 | CPU: ARMv7 16 | Any: 17 | enabled: 0 18 | settings: 19 | Exclude Android: 1 20 | Exclude Editor: 0 21 | Exclude Linux: 0 22 | Exclude Linux64: 0 23 | Exclude LinuxUniversal: 0 24 | Exclude OSXIntel: 0 25 | Exclude OSXIntel64: 0 26 | Exclude OSXUniversal: 0 27 | Exclude WebGL: 1 28 | Exclude Win: 0 29 | Exclude Win64: 0 30 | Editor: 31 | enabled: 1 32 | settings: 33 | CPU: x86_64 34 | DefaultValueInitialized: true 35 | OS: Windows 36 | Linux: 37 | enabled: 1 38 | settings: 39 | CPU: None 40 | Linux64: 41 | enabled: 1 42 | settings: 43 | CPU: x86_64 44 | LinuxUniversal: 45 | enabled: 1 46 | settings: 47 | CPU: AnyCPU 48 | OSXIntel: 49 | enabled: 1 50 | settings: 51 | CPU: None 52 | OSXIntel64: 53 | enabled: 1 54 | settings: 55 | CPU: AnyCPU 56 | OSXUniversal: 57 | enabled: 1 58 | settings: 59 | CPU: AnyCPU 60 | Win: 61 | enabled: 1 62 | settings: 63 | CPU: None 64 | Win64: 65 | enabled: 1 66 | settings: 67 | CPU: AnyCPU 68 | userData: 69 | assetBundleName: 70 | assetBundleVariant: 71 | -------------------------------------------------------------------------------- /KCP_PInvoke/kcp_native_dll/x86/ikcp.dll.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 45a3d2a0bb67fb340ab88e832e378e71 3 | timeCreated: 1510752959 4 | licenseType: Pro 5 | PluginImporter: 6 | serializedVersion: 1 7 | iconMap: {} 8 | executionOrder: {} 9 | isPreloaded: 0 10 | isOverridable: 0 11 | platformData: 12 | Android: 13 | enabled: 0 14 | settings: 15 | CPU: ARMv7 16 | Any: 17 | enabled: 0 18 | settings: 19 | Exclude Android: 1 20 | Exclude Editor: 0 21 | Exclude Linux: 0 22 | Exclude Linux64: 0 23 | Exclude LinuxUniversal: 0 24 | Exclude OSXIntel: 0 25 | Exclude OSXIntel64: 0 26 | Exclude OSXUniversal: 0 27 | Exclude WebGL: 1 28 | Exclude Win: 0 29 | Exclude Win64: 0 30 | Editor: 31 | enabled: 1 32 | settings: 33 | CPU: x86 34 | DefaultValueInitialized: true 35 | OS: Windows 36 | Linux: 37 | enabled: 1 38 | settings: 39 | CPU: x86 40 | Linux64: 41 | enabled: 1 42 | settings: 43 | CPU: None 44 | LinuxUniversal: 45 | enabled: 1 46 | settings: 47 | CPU: AnyCPU 48 | OSXIntel: 49 | enabled: 1 50 | settings: 51 | CPU: AnyCPU 52 | OSXIntel64: 53 | enabled: 1 54 | settings: 55 | CPU: None 56 | OSXUniversal: 57 | enabled: 1 58 | settings: 59 | CPU: AnyCPU 60 | WebGL: 61 | enabled: 0 62 | settings: {} 63 | Win: 64 | enabled: 1 65 | settings: 66 | CPU: AnyCPU 67 | Win64: 68 | enabled: 1 69 | settings: 70 | CPU: None 71 | userData: 72 | assetBundleName: 73 | assetBundleVariant: 74 | -------------------------------------------------------------------------------- /Unity3D/KCP_H.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Text; 6 | namespace Network_Kcp 7 | { 8 | /// 9 | /// 小端数据模式 10 | /// 11 | public partial class KCP_LE 12 | { 13 | //上下文地址 14 | //public class IQUEUEHEAD 15 | //{ 16 | // public IQUEUEHEAD next; 17 | // public IQUEUEHEAD prev; 18 | //} 19 | //public IQUEUEHEAD iqueue_head; 20 | #region 自己加的方法 21 | public void Dispose() { 22 | output = null; 23 | } 24 | public void FastSet() { 25 | rx_minrto = 10; 26 | fastresend = 1; 27 | } 28 | public UInt32 GetConv() { 29 | return conv; 30 | } 31 | public void ResetData() { 32 | Segment[] snd_queue = new Segment[0]; 33 | Segment[] rcv_queue = new Segment[0]; 34 | Segment[] snd_buf = new Segment[0]; 35 | Segment[] rcv_buf = new Segment[0]; 36 | 37 | UInt32[] acklist = new UInt32[0]; 38 | } 39 | /// 40 | /// reset array buffer size 41 | /// 42 | /// 43 | /// 44 | /// 45 | /// 46 | public static byte[] Recapacity(byte[] self, int length, bool copyData = false) { 47 | byte[] newBytes = new byte[length]; 48 | //if (self.Length != length) { 49 | // newBytes = new byte[length]; 50 | if (copyData) { 51 | Array.Copy(self, 0, newBytes, 0, length <= self.Length ? length : self.Length); 52 | } 53 | //} 54 | return newBytes; 55 | } 56 | #endregion 57 | } 58 | /// 59 | /// 大端数据模式 60 | /// 61 | public partial class KCP_BE { 62 | #region 自己添加的方法 63 | public void Dispose() { 64 | output = null; 65 | } 66 | #endregion 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | 7 | class Program 8 | { 9 | static void test_v1(string host, UInt16 port) 10 | { 11 | var wait_response = true; 12 | 13 | KcpProject.v1.UdpSocket client = null; 14 | 15 | client = new KcpProject.v1.UdpSocket((KcpProject.v1.UdpSocket.cliEvent ev, byte[] buf, string err) => 16 | { 17 | wait_response = false; 18 | 19 | switch (ev) 20 | { 21 | case KcpProject.v1.UdpSocket.cliEvent.Connected: 22 | Console.WriteLine("connected."); 23 | client.Send("Hello KCP."); 24 | break; 25 | case KcpProject.v1.UdpSocket.cliEvent.ConnectFailed: 26 | Console.WriteLine("connect failed. {0}", err); 27 | break; 28 | case KcpProject.v1.UdpSocket.cliEvent.Disconnect: 29 | Console.WriteLine("disconnect. {0}", err); 30 | break; 31 | case KcpProject.v1.UdpSocket.cliEvent.RcvMsg: 32 | Console.WriteLine("recv message: {0}", System.Text.ASCIIEncoding.ASCII.GetString(buf) ); 33 | break; 34 | } 35 | }); 36 | 37 | client.Connect(host, port); 38 | 39 | while (wait_response) 40 | { 41 | client.Update(); 42 | System.Threading.Thread.Sleep(10); 43 | } 44 | } 45 | 46 | static void test_v2(string host, UInt16 port) 47 | { 48 | var wait_response = true; 49 | 50 | KcpProject.v2.UdpSocket client = null; 51 | 52 | // 创建一个实例 53 | client = new KcpProject.v2.UdpSocket((byte[] buf) => 54 | { 55 | wait_response = false; 56 | Console.WriteLine("recv message: {0}", System.Text.ASCIIEncoding.ASCII.GetString(buf)); 57 | }); 58 | 59 | // 绑定端口 60 | client.Connect(host, port); 61 | 62 | // 发送消息 63 | client.Send("Hello KCP."); 64 | 65 | // update. 66 | while (wait_response) 67 | { 68 | client.Update(); 69 | System.Threading.Thread.Sleep(10); 70 | } 71 | } 72 | 73 | static void Main(string[] args) 74 | { 75 | // 测试v1版本,有握手过程,服务器决定conv的分配 76 | //test_v1("192.168.1.2", 4444); 77 | 78 | //Console.WriteLine("**********************************************************"); 79 | 80 | // 测试v2版本,没有握手过程,客户端自行决定conv的分配 81 | // 适合配合 kcp-go 82 | test_v2("192.168.1.2", 4455); 83 | } 84 | } 85 | 86 | -------------------------------------------------------------------------------- /KcpProject.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {8C02F001-6134-4BC1-AD24-B6F3B458C929} 8 | Exe 9 | Properties 10 | KcpProject 11 | KcpProject 12 | v3.5 13 | 512 14 | 15 | 16 | AnyCPU 17 | true 18 | full 19 | false 20 | bin\Debug\ 21 | DEBUG;TRACE 22 | prompt 23 | 4 24 | true 25 | 26 | 27 | AnyCPU 28 | pdbonly 29 | true 30 | bin\Release\ 31 | TRACE 32 | prompt 33 | 4 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | C:\Program Files\Unity5.5.0\Editor\Data\Managed\UnityEngine.dll 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 74 | -------------------------------------------------------------------------------- /v2/udp_socket_v2.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Net.Sockets; 6 | using System.Net; 7 | 8 | namespace KcpProject.v2 9 | { 10 | // 客户端随机生成conv并作为后续与服务器通信 11 | public class UdpSocket 12 | { 13 | private static readonly DateTime utc_time = new DateTime(1970, 1, 1); 14 | 15 | public static UInt32 iclock() 16 | { 17 | return (UInt32)(Convert.ToInt64(DateTime.UtcNow.Subtract(utc_time).TotalMilliseconds) & 0xffffffff); 18 | } 19 | 20 | private UdpClient mUdpClient; 21 | private IPEndPoint mIPEndPoint; 22 | private IPEndPoint mSvrEndPoint; 23 | private Action evHandler; 24 | private KCP mKcp; 25 | private bool mNeedUpdateFlag; 26 | private UInt32 mNextUpdateTime; 27 | 28 | private SwitchQueue mRecvQueue = new SwitchQueue(128); 29 | 30 | public UdpSocket(Action handler) 31 | { 32 | evHandler = handler; 33 | } 34 | 35 | public void Connect(string host, UInt16 port) 36 | { 37 | mSvrEndPoint = new IPEndPoint(IPAddress.Parse(host), port); 38 | mUdpClient = new UdpClient(host, port); 39 | mUdpClient.Connect(mSvrEndPoint); 40 | 41 | init_kcp((UInt32)new Random((int)DateTime.Now.Ticks).Next(1, Int32.MaxValue)); 42 | 43 | mUdpClient.BeginReceive(ReceiveCallback, this); 44 | } 45 | 46 | void init_kcp(UInt32 conv) 47 | { 48 | mKcp = new KCP(conv, (byte[] buf, int size) => 49 | { 50 | mUdpClient.Send(buf, size); 51 | }); 52 | 53 | // fast mode. 54 | mKcp.NoDelay(1, 10, 2, 1); 55 | mKcp.WndSize(128, 128); 56 | } 57 | 58 | void ReceiveCallback(IAsyncResult ar) 59 | { 60 | Byte[] data = (mIPEndPoint == null) ? 61 | mUdpClient.Receive(ref mIPEndPoint) : 62 | mUdpClient.EndReceive(ar, ref mIPEndPoint); 63 | 64 | if (null != data) 65 | { 66 | // push udp packet to switch queue. 67 | mRecvQueue.Push(data); 68 | } 69 | 70 | if (mUdpClient != null) 71 | { 72 | // try to receive again. 73 | mUdpClient.BeginReceive(ReceiveCallback, this); 74 | } 75 | } 76 | 77 | public void Send(byte[] buf) 78 | { 79 | mKcp.Send(buf); 80 | mNeedUpdateFlag = true; 81 | } 82 | 83 | public void Send(string str) 84 | { 85 | Send(System.Text.ASCIIEncoding.ASCII.GetBytes(str)); 86 | } 87 | 88 | public void Update() 89 | { 90 | update(iclock()); 91 | } 92 | 93 | public void Close() 94 | { 95 | mUdpClient.Close(); 96 | } 97 | 98 | void process_recv_queue() 99 | { 100 | mRecvQueue.Switch(); 101 | 102 | while (!mRecvQueue.Empty()) 103 | { 104 | var buf = mRecvQueue.Pop(); 105 | 106 | mKcp.Input(buf); 107 | mNeedUpdateFlag = true; 108 | 109 | for (var size = mKcp.PeekSize(); size > 0; size = mKcp.PeekSize()) 110 | { 111 | var buffer = new byte[size]; 112 | if (mKcp.Recv(buffer) > 0) 113 | { 114 | evHandler(buffer); 115 | } 116 | } 117 | } 118 | } 119 | 120 | void update(UInt32 current) 121 | { 122 | process_recv_queue(); 123 | 124 | if (mNeedUpdateFlag || current >= mNextUpdateTime) 125 | { 126 | mKcp.Update(current); 127 | mNextUpdateTime = mKcp.Check(current); 128 | mNeedUpdateFlag = false; 129 | } 130 | } 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /Unity3D/KCPProxy_BE.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Net; 5 | using System.Net.Sockets; 6 | using System.Text; 7 | 8 | namespace Network_Kcp 9 | { 10 | 11 | public class KCPProxy_BE 12 | { 13 | #region 工具函数 14 | public static IPEndPoint IPEP_Any = new IPEndPoint(IPAddress.Any, 0); 15 | public static IPEndPoint IPEP_IPv6Any = new IPEndPoint(IPAddress.IPv6Any, 0); 16 | public static IPEndPoint GetIPEndPointAny(AddressFamily family, int port) { 17 | if (family == AddressFamily.InterNetwork) { 18 | if (port == 0) { 19 | return IPEP_Any; 20 | } 21 | 22 | return new IPEndPoint(IPAddress.Any, port); 23 | } 24 | else if (family == AddressFamily.InterNetworkV6) { 25 | if (port == 0) { 26 | return IPEP_IPv6Any; 27 | } 28 | 29 | return new IPEndPoint(IPAddress.IPv6Any, port); 30 | } 31 | return null; 32 | } 33 | 34 | 35 | private static readonly DateTime UTCTimeBegin = new DateTime(1970, 1, 1); 36 | 37 | public static long GetClockMS() { 38 | return (Convert.ToInt64(DateTime.UtcNow.Subtract(UTCTimeBegin).TotalMilliseconds) & 0xffffffff); 39 | } 40 | 41 | 42 | 43 | #endregion 44 | 45 | 46 | //private KCP m_Kcp; 47 | private KCP_BE m_Kcp; 48 | private bool m_NeedKcpUpdateFlag = false; 49 | private long m_NextKcpUpdateTime = 0; 50 | private SwitchQueue m_RecvQueue = new SwitchQueue(128); 51 | 52 | private IPEndPoint m_RemotePoint; 53 | private Socket m_Socket; 54 | private KCPReceiveListener m_Listener; 55 | 56 | public IPEndPoint RemotePoint { get { return m_RemotePoint; } } 57 | 58 | 59 | 60 | public KCPProxy_BE(uint key, IPEndPoint remotePoint, Socket socket) { 61 | m_Socket = socket; 62 | m_RemotePoint = remotePoint; 63 | 64 | //m_Kcp = new KCP(key, HandleKcpSend); 65 | m_Kcp = new KCP_BE(key, HandleKcpSend); 66 | m_Kcp.NoDelay(1, 10, 2, 1); 67 | m_Kcp.WndSize(128, 128); 68 | 69 | } 70 | 71 | public void Dispose() { 72 | m_Socket = null; 73 | 74 | if (m_Kcp != null) { 75 | m_Kcp.Dispose(); 76 | m_Kcp = null; 77 | } 78 | 79 | m_Listener = null; 80 | } 81 | 82 | //--------------------------------------------- 83 | private void HandleKcpSend(byte[] buff, int size) { 84 | //KCP输出回调 85 | if (m_Socket != null) { 86 | m_Socket.SendTo(buff, 0, size, SocketFlags.None, m_RemotePoint); 87 | } 88 | } 89 | 90 | private void HandleKcpSend_Hook(byte[] buff, int size) { 91 | if (m_Socket != null) { 92 | m_Socket.SendTo(buff, 0, size, SocketFlags.None, m_RemotePoint); 93 | } 94 | } 95 | 96 | public bool DoSend(byte[] buff, int size) { 97 | m_NeedKcpUpdateFlag = true; 98 | byte[] dst = new byte[size]; 99 | Buffer.BlockCopy(buff, 0, dst, 0, size); 100 | return m_Kcp.Send(dst) >= 0; 101 | } 102 | public bool DoSend(byte[] buff) { 103 | m_NeedKcpUpdateFlag = true; 104 | return m_Kcp.Send(buff) >= 0; 105 | } 106 | //--------------------------------------------- 107 | 108 | public void AddReceiveListener(KCPReceiveListener listener) { 109 | m_Listener += listener; 110 | } 111 | 112 | public void RemoveReceiveListener(KCPReceiveListener listener) { 113 | m_Listener -= listener; 114 | } 115 | 116 | 117 | 118 | public void DoReceiveInThread(byte[] buffer, int size) { 119 | byte[] dst = new byte[size]; 120 | Buffer.BlockCopy(buffer, 0, dst, 0, size); 121 | m_RecvQueue.Push(dst); 122 | } 123 | 124 | private void HandleRecvQueue() { 125 | m_RecvQueue.Switch(); 126 | while (!m_RecvQueue.Empty()) { 127 | var recvBufferRaw = m_RecvQueue.Pop(); 128 | int ret = m_Kcp.Input(recvBufferRaw); 129 | 130 | //收到的不是一个正确的KCP包 131 | if (ret < 0) { 132 | if (m_Listener != null) { 133 | m_Listener(recvBufferRaw, recvBufferRaw.Length, m_RemotePoint); 134 | } 135 | return; 136 | } 137 | 138 | m_NeedKcpUpdateFlag = true; 139 | 140 | for (int size = m_Kcp.PeekSize(); size > 0; size = m_Kcp.PeekSize()) { 141 | var recvBuffer = new byte[size]; 142 | if (m_Kcp.Recv(recvBuffer) > 0) { 143 | if (m_Listener != null) { 144 | m_Listener(recvBuffer, size, m_RemotePoint); 145 | } 146 | } 147 | } 148 | } 149 | } 150 | 151 | //--------------------------------------------- 152 | public void Update(long currentTimeMS) { 153 | HandleRecvQueue(); 154 | 155 | if (m_NeedKcpUpdateFlag || currentTimeMS >= m_NextKcpUpdateTime) { 156 | m_Kcp.Update(currentTimeMS); 157 | m_NextKcpUpdateTime = m_Kcp.Check(currentTimeMS); 158 | m_NeedKcpUpdateFlag = false; 159 | } 160 | } 161 | 162 | //--------------------------------------------- 163 | 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /Unity3D/KCPProxy_LE.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Net; 5 | using System.Net.Sockets; 6 | using System.Text; 7 | 8 | namespace Network_Kcp 9 | { 10 | 11 | public class KCPProxy_LE 12 | { 13 | #region 工具函数 14 | public static IPEndPoint IPEP_Any = new IPEndPoint(IPAddress.Any, 0); 15 | public static IPEndPoint IPEP_IPv6Any = new IPEndPoint(IPAddress.IPv6Any, 0); 16 | public static IPEndPoint GetIPEndPointAny(AddressFamily family, int port) { 17 | if (family == AddressFamily.InterNetwork) { 18 | if (port == 0) { 19 | return IPEP_Any; 20 | } 21 | 22 | return new IPEndPoint(IPAddress.Any, port); 23 | } 24 | else if (family == AddressFamily.InterNetworkV6) { 25 | if (port == 0) { 26 | return IPEP_IPv6Any; 27 | } 28 | 29 | return new IPEndPoint(IPAddress.IPv6Any, port); 30 | } 31 | return null; 32 | } 33 | 34 | 35 | private static readonly DateTime UTCTimeBegin = new DateTime(1970, 1, 1); 36 | 37 | public static long GetClockMS() { 38 | return (Convert.ToInt64(DateTime.UtcNow.Subtract(UTCTimeBegin).TotalMilliseconds) & 0xffffffff); 39 | } 40 | 41 | 42 | 43 | #endregion 44 | 45 | 46 | private KCP_LE m_Kcp; 47 | //private KCP_BE m_Kcp; 48 | private bool m_NeedKcpUpdateFlag = false; 49 | private long m_NextKcpUpdateTime = 0; 50 | private SwitchQueue m_RecvQueue = new SwitchQueue(128); 51 | 52 | private IPEndPoint m_RemotePoint; 53 | private Socket m_Socket; 54 | private KCPReceiveListener m_Listener; 55 | 56 | public IPEndPoint RemotePoint { get { return m_RemotePoint; } } 57 | 58 | 59 | 60 | public KCPProxy_LE(uint key, IPEndPoint remotePoint, Socket socket) { 61 | m_Socket = socket; 62 | m_RemotePoint = remotePoint; 63 | 64 | m_Kcp = new KCP_LE(key, HandleKcpSend); 65 | //m_Kcp = new KCP_BE(key, HandleKcpSend); 66 | m_Kcp.NoDelay(1, 10, 2, 1); 67 | m_Kcp.WndSize(128, 128); 68 | 69 | } 70 | 71 | public void Dispose() { 72 | m_Socket = null; 73 | 74 | if (m_Kcp != null) { 75 | m_Kcp.Dispose(); 76 | m_Kcp = null; 77 | } 78 | 79 | m_Listener = null; 80 | } 81 | 82 | //--------------------------------------------- 83 | private void HandleKcpSend(byte[] buff, int size) { 84 | //KCP输出回调 85 | if (m_Socket != null) { 86 | m_Socket.SendTo(buff, 0, size, SocketFlags.None, m_RemotePoint); 87 | } 88 | } 89 | 90 | private void HandleKcpSend_Hook(byte[] buff, int size) { 91 | if (m_Socket != null) { 92 | m_Socket.SendTo(buff, 0, size, SocketFlags.None, m_RemotePoint); 93 | } 94 | } 95 | 96 | public bool DoSend(byte[] buff, int size) { 97 | m_NeedKcpUpdateFlag = true; 98 | byte[] dst = new byte[size]; 99 | Buffer.BlockCopy(buff, 0, dst, 0, size); 100 | return m_Kcp.Send(dst) >= 0; 101 | } 102 | public bool DoSend(byte[] buff) { 103 | m_NeedKcpUpdateFlag = true; 104 | return m_Kcp.Send(buff) >= 0; 105 | } 106 | //--------------------------------------------- 107 | 108 | public void AddReceiveListener(KCPReceiveListener listener) { 109 | m_Listener += listener; 110 | } 111 | 112 | public void RemoveReceiveListener(KCPReceiveListener listener) { 113 | m_Listener -= listener; 114 | } 115 | 116 | 117 | 118 | public void DoReceiveInThread(byte[] buffer, int size) { 119 | byte[] dst = new byte[size]; 120 | Buffer.BlockCopy(buffer, 0, dst, 0, size); 121 | m_RecvQueue.Push(dst); 122 | } 123 | 124 | private void HandleRecvQueue() { 125 | m_RecvQueue.Switch(); 126 | while (!m_RecvQueue.Empty()) { 127 | var recvBufferRaw = m_RecvQueue.Pop(); 128 | int ret = m_Kcp.Input(recvBufferRaw); 129 | 130 | //收到的不是一个正确的KCP包 131 | if (ret < 0) { 132 | if (m_Listener != null) { 133 | m_Listener(recvBufferRaw, recvBufferRaw.Length, m_RemotePoint); 134 | } 135 | return; 136 | } 137 | 138 | m_NeedKcpUpdateFlag = true; 139 | 140 | for (int size = m_Kcp.PeekSize(); size > 0; size = m_Kcp.PeekSize()) { 141 | var recvBuffer = new byte[size]; 142 | if (m_Kcp.Recv(recvBuffer) > 0) { 143 | if (m_Listener != null) { 144 | m_Listener(recvBuffer, size, m_RemotePoint); 145 | } 146 | } 147 | } 148 | } 149 | } 150 | 151 | //--------------------------------------------- 152 | public void Update(long currentTimeMS) { 153 | HandleRecvQueue(); 154 | 155 | if (m_NeedKcpUpdateFlag || currentTimeMS >= m_NextKcpUpdateTime) { 156 | m_Kcp.Update(currentTimeMS); 157 | m_NextKcpUpdateTime = m_Kcp.Check(currentTimeMS); 158 | m_NeedKcpUpdateFlag = false; 159 | } 160 | } 161 | 162 | //--------------------------------------------- 163 | 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # kcp-csharp 2 | KCP - A Fast and Reliable ARQ Protocol 3 | 4 | ref: 5 | c: skywind3000 [KCP](https://github.com/skywind3000/kcp) 6 | go: xtaci [kcp-go](https://github.com/xtaci/kcp-go) 7 | 8 | 9 | 10 | KCP - A Fast and Reliable ARQ Protocol 11 | ====================================== 12 | 13 | # 简介 14 | 15 | KCP是一个快速可靠协议,能以比 TCP浪费10%-20%的带宽的代价,换取平均延迟降低 30%-40%,且最大延迟降低三倍的传输效果。纯算法实现,并不负责底层协议(如UDP)的收发,需要使用者自己定义下层数据包的发送方式,以 callback的方式提供给 KCP。 连时钟都需要外部传递进来,内部不会有任何一次系统调用。 16 | 17 | 整个协议只有 ikcp.h, ikcp.c两个源文件,可以方便的集成到用户自己的协议栈中。也许你实现了一个P2P,或者某个基于 UDP的协议,而缺乏一套完善的ARQ可靠协议实现,那么简单的拷贝这两个文件到现有项目中,稍微编写两行代码,即可使用。 18 | 19 | 20 | # 技术特性 21 | 22 | TCP是为流量设计的(每秒内可以传输多少KB的数据),讲究的是充分利用带宽。而 KCP是为流速设计的(单个数据包从一端发送到一端需要多少时间),以10%-20%带宽浪费的代价换取了比 TCP快30%-40%的传输速度。TCP信道是一条流速很慢,但每秒流量很大的大运河,而KCP是水流湍急的小激流。KCP有正常模式和快速模式两种,通过以下策略达到提高流速的结果: 23 | 24 | #### RTO翻倍vs不翻倍: 25 | 26 | TCP超时计算是RTOx2,这样连续丢三次包就变成RTOx8了,十分恐怖,而KCP启动快速模式后不x2,只是x1.5(实验证明1.5这个值相对比较好),提高了传输速度。 27 | 28 | #### 选择性重传 vs 全部重传: 29 | 30 | TCP丢包时会全部重传从丢的那个包开始以后的数据,KCP是选择性重传,只重传真正丢失的数据包。 31 | 32 | #### 快速重传: 33 | 34 | 发送端发送了1,2,3,4,5几个包,然后收到远端的ACK: 1, 3, 4, 5,当收到ACK3时,KCP知道2被跳过1次,收到ACK4时,知道2被跳过了2次,此时可以认为2号丢失,不用等超时,直接重传2号包,大大改善了丢包时的传输速度。 35 | 36 | #### 延迟ACK vs 非延迟ACK: 37 | 38 | TCP为了充分利用带宽,延迟发送ACK(NODELAY都没用),这样超时计算会算出较大 RTT时间,延长了丢包时的判断过程。KCP的ACK是否延迟发送可以调节。 39 | 40 | #### UNA vs ACK+UNA: 41 | 42 | ARQ模型响应有两种,UNA(此编号前所有包已收到,如TCP)和ACK(该编号包已收到),光用UNA将导致全部重传,光用ACK则丢失成本太高,以往协议都是二选其一,而 KCP协议中,除去单独的 ACK包外,所有包都有UNA信息。 43 | 44 | #### 非退让流控: 45 | 46 | KCP正常模式同TCP一样使用公平退让法则,即发送窗口大小由:发送缓存大小、接收端剩余接收缓存大小、丢包退让及慢启动这四要素决定。但传送及时性要求很高的小数据时,可选择通过配置跳过后两步,仅用前两项来控制发送频率。以牺牲部分公平性及带宽利用率之代价,换取了开着BT都能流畅传输的效果。 47 | 48 | 49 | # 基本使用 50 | 51 | 1. 创建 KCP对象: 52 | 53 | ```cpp 54 | // 初始化 kcp对象,conv为一个表示会话编号的整数,和tcp的 conv一样,通信双 55 | // 方需保证 conv相同,相互的数据包才能够被认可,user是一个给回调函数的指针 56 | ikcpcb *kcp = ikcp_create(conv, user); 57 | ``` 58 | 59 | 2. 设置回调函数: 60 | 61 | ```cpp 62 | // KCP的下层协议输出函数,KCP需要发送数据时会调用它 63 | // buf/len 表示缓存和长度 64 | // user指针为 kcp对象创建时传入的值,用于区别多个 KCP对象 65 | int udp_output(const char *buf, int len, ikcpcb *kcp, void *user) 66 | { 67 | .... 68 | } 69 | // 设置回调函数 70 | kcp->output = udp_output; 71 | ``` 72 | 73 | 3. 循环调用 update: 74 | 75 | ```cpp 76 | // 以一定频率调用 ikcp_update来更新 kcp状态,并且传入当前时钟(毫秒单位) 77 | // 如 10ms调用一次,或用 ikcp_check确定下次调用 update的时间不必每次调用 78 | ikcp_update(kcp, millisec); 79 | ``` 80 | 81 | 4. 输入一个下层数据包: 82 | 83 | ```cpp 84 | // 收到一个下层数据包(比如UDP包)时需要调用: 85 | ikcp_input(kcp, received_udp_packet, received_udp_size); 86 | ``` 87 | 处理了下层协议的输出/输入后 KCP协议就可以正常工作了,使用 ikcp_send 来向 88 | 远端发送数据。而另一端使用 ikcp_recv(kcp, ptr, size)来接收数据。 89 | 90 | 91 | # 协议配置 92 | 93 | 协议默认模式是一个标准的 ARQ,需要通过配置打开各项加速开关: 94 | 95 | 1. 工作模式: 96 | ```cpp 97 | int ikcp_nodelay(ikcpcb *kcp, int nodelay, int interval, int resend, int nc) 98 | ``` 99 | 100 | > nodelay :是否启用 nodelay模式,0不启用;1启用。 101 | > interval :协议内部工作的 interval,单位毫秒,比如 10ms或者 20ms 102 | > resend :快速重传模式,默认0关闭,可以设置2(2次ACK跨越将会直接重传) 103 | > nc :是否关闭流控,默认是0代表不关闭,1代表关闭。 104 | > 普通模式:`ikcp_nodelay(kcp, 0, 40, 0, 0); 105 | > 极速模式: ikcp_nodelay(kcp, 1, 10, 2, 1); 106 | 107 | 2. 最大窗口: 108 | ```cpp 109 | int ikcp_wndsize(ikcpcb *kcp, int sndwnd, int rcvwnd); 110 | ``` 111 | 该调用将会设置协议的最大发送窗口和最大接收窗口大小,默认为32. 这个可以理解为 TCP的 SND_BUF 和 RCV_BUF,只不过单位不一样 SND/RCV_BUF 单位是字节,这个单位是包。 112 | 113 | 3. 最大传输单元: 114 | 115 | 纯算法协议并不负责探测 MTU,默认 mtu是1400字节,可以使用ikcp_setmtu来设置该值。该值将会影响数据包归并及分片时候的最大传输单元。 116 | 117 | 4. 最小RTO: 118 | 119 | 不管是 TCP还是 KCP计算 RTO时都有最小 RTO的限制,即便计算出来RTO为40ms,由于默认的 RTO是100ms,协议只有在100ms后才能检测到丢包,快速模式下为30ms,可以手动更改该值: 120 | ```cpp 121 | kcp->rx_minrto = 10; 122 | ``` 123 | 124 | 125 | # 文档索引 126 | 127 | 协议的使用和配置都是很简单的,大部分情况看完上面的内容基本可以使用了。如果你需要进一步进行精细的控制,比如改变 KCP的内存分配器,或者你需要更有效的大规模调度 KCP链接(比如 3500个以上),或者如何更好的同 TCP结合,那么可以继续延伸阅读: 128 | 129 | - [Wiki Home](https://github.com/skywind3000/kcp/wiki) 130 | - [KCP 最佳实践](https://github.com/skywind3000/kcp/wiki/KCP-Best-Practice) 131 | - [同现有TCP服务器集成](https://github.com/skywind3000/kcp/wiki/Cooperate-With-Tcp-Server) 132 | - [传输数据加密](https://github.com/skywind3000/kcp/wiki/Network-Encryption) 133 | - [应用层流量控制](https://github.com/skywind3000/kcp/wiki/Flow-Control-for-Users) 134 | - [性能评测](https://github.com/skywind3000/kcp/wiki/KCP-Benchmark) 135 | 136 | 137 | # 相关应用 138 | 139 | - [dog-tunnel](https://github.com/vzex/dog-tunnel): GO开发的网络隧道,使用 KCP极大的改进了传输速度,并移植了一份 GO版本 KCP 140 | - [lua-kcp](https://github.com/linxiaolong/lua-kcp): KCP的 Lua扩展,用于 Lua服务器 141 | - [asio-kcp](https://github.com/libinzhangyuan/asio_kcp): 使用 KCP的完整 UDP网络库,完整实现了基于 UDP的链接状态管理,会话控制,KCP协议调度等 142 | - [kcp-go](https://github.com/xtaci/kcp-go): 另一份对 KCP更精简的 GO移植,同时包含简单的 UDP会话管理,可以直接使用 143 | - [kcp-csharp](https://github.com/limpo1989/kcp-csharp): kcp的csharp移植,同时包含一份回话管理,可以连接上面kcp-go的服务端。 144 | - [kcptun](https://github.com/xtaci/kcptun): 基于kcpgo做的远程端口转发,配合ssh -D,可以比 sslocal/ssserver 更流畅的看在线视频。 145 | - [kcpuv](https://github.com/elisaday/kcpuv): 使用 libuv开发的kcpuv库,目前还在 Demo阶段。 146 | 147 | # 协议比较 148 | 149 | 如果网络永远不卡,那 KCP/TCP 表现类似,但是网络本身就是不可靠的,丢包和抖动无法避免(否则还要各种可靠协议干嘛)。在内网这种几乎理想的环境里直接比较,大家都差不多,但是放到公网上,放到3G/4G网络情况下,或者使用内网丢包模拟,差距就很明显了。公网在高峰期有平均接近10%的丢包,wifi/3g/4g下更糟糕,这些都会让传输变卡。 150 | 151 | 感谢 [asio-kcp](https://github.com/libinzhangyuan/asio_kcp) 的作者 [zhangyuan](https://github.com/libinzhangyuan) 对 KCP 与 enet, udt做过的一次横向评测,结论如下: 152 | 153 | - ASIO-KCP **has good performace in wifi and phone network(3G, 4G)**. 154 | - The kcp is the **first choice for realtime pvp game**. 155 | - The lag is less than 1 second when network lag happen. **3 times better than enet** when lag happen. 156 | - The enet is a good choice if your game allow 2 second lag. 157 | - **UDT is a bad idea**. It always sink into badly situation of more than serval seconds lag. And the recovery is not expected. 158 | - enet has the problem of lack of doc. And it has lots of functions that you may intrest. 159 | - kcp's doc is chinese. Good thing is the function detail which is writen in code is english. And you can use asio_kcp which is a good wrap. 160 | - The kcp is a simple thing. You will write more code if you want more feature. 161 | - UDT has a perfect doc. UDT may has more bug than others as I feeling. 162 | 163 | 具体见:[横向比较](https://github.com/libinzhangyuan/reliable_udp_bench_mark) 和 [评测数据](https://github.com/skywind3000/kcp/wiki/KCP-Benchmark),为犹豫选择的人提供了更多指引。 164 | -------------------------------------------------------------------------------- /Unity3D/NetworkDebuger.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Text; 6 | using UnityEngine; 7 | 8 | namespace Network_Kcp 9 | { 10 | public class NetworkDebuger 11 | { 12 | public static bool EnableLog; 13 | public static bool EnableTime = false; 14 | public static bool EnableSave = false; 15 | public static bool EnableStack = false; 16 | public static string LogFileDir = Application.persistentDataPath + "/NetworkLog/"; 17 | public static string LogFileName = ""; 18 | public static string Prefix = "> "; 19 | public static StreamWriter LogFileWriter = null; 20 | 21 | 22 | public static void Log(object message) { 23 | if (!NetworkDebuger.EnableLog) { 24 | return; 25 | } 26 | 27 | string msg = GetLogTime() + message; 28 | 29 | //Debug.Log(Prefix + msg, null); 30 | LogToFile("[I]" + msg); 31 | } 32 | 33 | //public static void Log(object message, object context) { 34 | // if (!NetworkDebuger.EnableLog) { 35 | // return; 36 | // } 37 | 38 | // string msg = GetLogTime() + message; 39 | 40 | // //Debug.Log(Prefix + msg, context); 41 | // LogToFile("[I]" + msg); 42 | //} 43 | 44 | public static void LogError(object message) { 45 | string msg = GetLogTime() + message; 46 | 47 | Debug.LogError(Prefix + msg, null); 48 | LogToFile("[E]" + msg, true); 49 | } 50 | 51 | //public static void LogError(object message, object context) { 52 | // string msg = GetLogTime() + message; 53 | 54 | // Debug.LogError(Prefix + msg, context); 55 | // LogToFile("[E]" + msg, true); 56 | //} 57 | 58 | public static void LogWarning(object message) { 59 | string msg = GetLogTime() + message; 60 | 61 | Debug.LogWarning(Prefix + msg, null); 62 | LogToFile("[W]" + msg); 63 | } 64 | 65 | //public static void LogWarning(object message, Object context) { 66 | // string msg = GetLogTime() + message; 67 | 68 | // Debug.LogWarning(Prefix + msg, context); 69 | // LogToFile("[W]" + msg); 70 | //} 71 | 72 | 73 | //---------------------------------------------------------------------- 74 | 75 | public static void Log(string tag, string message) { 76 | if (!NetworkDebuger.EnableLog) { 77 | return; 78 | } 79 | 80 | message = GetLogText(tag, message); 81 | //Debug.Log(Prefix + message); 82 | LogToFile("[I]" + message); 83 | } 84 | 85 | public static void Log(string tag, string format, params object[] args) { 86 | if (!NetworkDebuger.EnableLog) { 87 | return; 88 | } 89 | 90 | string message = GetLogText(tag, string.Format(format, args)); 91 | //Debug.Log(Prefix + message); 92 | LogToFile("[I]" + message); 93 | } 94 | 95 | public static void LogError(string tag, string message) { 96 | message = GetLogText(tag, message); 97 | Debug.LogError(Prefix + message); 98 | LogToFile("[E]" + message, true); 99 | } 100 | 101 | public static void LogError(string tag, string format, params object[] args) { 102 | string message = GetLogText(tag, string.Format(format, args)); 103 | Debug.LogError(Prefix + message); 104 | LogToFile("[E]" + message, true); 105 | } 106 | 107 | 108 | public static void LogWarning(string tag, string message) { 109 | message = GetLogText(tag, message); 110 | Debug.LogWarning(Prefix + message); 111 | LogToFile("[W]" + message); 112 | } 113 | 114 | public static void LogWarning(string tag, string format, params object[] args) { 115 | string message = GetLogText(tag, string.Format(format, args)); 116 | Debug.LogWarning(Prefix + message); 117 | LogToFile("[W]" + message); 118 | } 119 | 120 | 121 | //---------------------------------------------------------------------- 122 | private static string GetLogText(string tag, string message) { 123 | string str = ""; 124 | if (NetworkDebuger.EnableTime) { 125 | DateTime now = DateTime.Now; 126 | str = now.ToString("HH:mm:ss.fff") + " "; 127 | } 128 | 129 | str = str + tag + "::" + message; 130 | return str; 131 | } 132 | 133 | private static string GetLogTime() { 134 | string str = ""; 135 | if (NetworkDebuger.EnableTime) { 136 | DateTime now = DateTime.Now; 137 | str = now.ToString("HH:mm:ss.fff") + " "; 138 | } 139 | return str; 140 | } 141 | 142 | 143 | 144 | private static void LogToFile(string message, bool EnableStack = false) { 145 | if (!EnableSave) { 146 | if (LogFileWriter != null) { 147 | LogFileWriter.Flush(); 148 | LogFileWriter.Close(); 149 | LogFileWriter = null; 150 | } 151 | return; 152 | } 153 | 154 | if (LogFileWriter == null) { 155 | DateTime now = DateTime.Now; 156 | LogFileName = now.GetDateTimeFormats('s')[0].ToString();//2005-11-05T14:06:25 157 | LogFileName = LogFileName.Replace("-", "_"); 158 | LogFileName = LogFileName.Replace(":", "_"); 159 | LogFileName = LogFileName.Replace(" ", ""); 160 | LogFileName += ".log"; 161 | 162 | string fullpath = LogFileDir + LogFileName; 163 | try { 164 | if (!Directory.Exists(LogFileDir)) { 165 | Directory.CreateDirectory(LogFileDir); 166 | } 167 | 168 | LogFileWriter = File.AppendText(fullpath); 169 | LogFileWriter.AutoFlush = true; 170 | } 171 | catch (Exception e) { 172 | LogFileWriter = null; 173 | Debug.LogError("LogToCache() " + e.Message + e.StackTrace); 174 | return; 175 | } 176 | } 177 | 178 | if (LogFileWriter != null) { 179 | try { 180 | LogFileWriter.WriteLine(message); 181 | if (EnableStack || NetworkDebuger.EnableStack) { 182 | LogFileWriter.WriteLine(StackTraceUtility.ExtractStackTrace()); 183 | } 184 | } 185 | catch (Exception) { 186 | return; 187 | } 188 | } 189 | } 190 | } 191 | } 192 | -------------------------------------------------------------------------------- /v1/udp_socket_v1.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Net; 5 | using System.Net.Sockets; 6 | using System.Text; 7 | 8 | namespace KcpProject.v1 9 | { 10 | 11 | // 模拟TCP客户端主动发送握手数据 12 | // 服务器下发conv 13 | public class UdpSocket 14 | { 15 | 16 | private static readonly DateTime utc_time = new DateTime(1970, 1, 1); 17 | 18 | public static UInt32 iclock() 19 | { 20 | return (UInt32)(Convert.ToInt64(DateTime.UtcNow.Subtract(utc_time).TotalMilliseconds) & 0xffffffff); 21 | } 22 | 23 | public enum cliEvent 24 | { 25 | Connected = 0, 26 | ConnectFailed = 1, 27 | Disconnect = 2, 28 | RcvMsg = 3, 29 | } 30 | 31 | private const UInt32 CONNECT_TIMEOUT = 5000; 32 | private const UInt32 RESEND_CONNECT = 500; 33 | 34 | private UdpClient mUdpClient; 35 | private IPEndPoint mIPEndPoint; 36 | private IPEndPoint mSvrEndPoint; 37 | private Action evHandler; 38 | private KCP mKcp; 39 | private bool mNeedUpdateFlag; 40 | private UInt32 mNextUpdateTime; 41 | 42 | private bool mInConnectStage; 43 | private bool mConnectSucceed; 44 | private UInt32 mConnectStartTime; 45 | private UInt32 mLastSendConnectTime; 46 | 47 | private SwitchQueue mRecvQueue = new SwitchQueue(128); 48 | 49 | public UdpSocket(Action handler) 50 | { 51 | evHandler = handler; 52 | } 53 | 54 | public void Connect(string host, UInt16 port) 55 | { 56 | mSvrEndPoint = new IPEndPoint(IPAddress.Parse(host), port); 57 | mUdpClient = new UdpClient(host, port); 58 | mUdpClient.Connect(mSvrEndPoint); 59 | 60 | reset_state(); 61 | 62 | mInConnectStage = true; 63 | mConnectStartTime = iclock(); 64 | 65 | mUdpClient.BeginReceive(ReceiveCallback, this); 66 | } 67 | 68 | void ReceiveCallback(IAsyncResult ar) 69 | { 70 | Byte[] data = (mIPEndPoint == null) ? 71 | mUdpClient.Receive(ref mIPEndPoint) : 72 | mUdpClient.EndReceive(ar, ref mIPEndPoint); 73 | 74 | if (null != data) 75 | OnData(data); 76 | 77 | if (mUdpClient != null) 78 | { 79 | // try to receive again. 80 | mUdpClient.BeginReceive(ReceiveCallback, this); 81 | } 82 | } 83 | 84 | void OnData(byte[] buf) 85 | { 86 | mRecvQueue.Push(buf); 87 | } 88 | 89 | void reset_state() 90 | { 91 | mNeedUpdateFlag = false; 92 | mNextUpdateTime = 0; 93 | 94 | mInConnectStage = false; 95 | mConnectSucceed = false; 96 | mConnectStartTime = 0; 97 | mLastSendConnectTime = 0; 98 | mRecvQueue.Clear(); 99 | mKcp = null; 100 | } 101 | 102 | string dump_bytes(byte[] buf, int size) 103 | { 104 | var sb = new StringBuilder(size * 2); 105 | for (var i = 0; i < size; i++) 106 | { 107 | sb.Append(buf[i]); 108 | sb.Append(" "); 109 | } 110 | return sb.ToString(); 111 | } 112 | 113 | void init_kcp(UInt32 conv) 114 | { 115 | mKcp = new KCP(conv, (byte[] buf, int size) => 116 | { 117 | mUdpClient.Send(buf, size); 118 | }); 119 | 120 | mKcp.NoDelay(1, 10, 2, 1); 121 | } 122 | 123 | public void Send(byte[] buf) 124 | { 125 | mKcp.Send(buf); 126 | mNeedUpdateFlag = true; 127 | } 128 | 129 | public void Send(string str) 130 | { 131 | Send(System.Text.ASCIIEncoding.ASCII.GetBytes(str)); 132 | } 133 | 134 | public void Update() 135 | { 136 | update(iclock()); 137 | } 138 | 139 | public void Close() 140 | { 141 | mUdpClient.Close(); 142 | evHandler(cliEvent.Disconnect, null, "Closed"); 143 | } 144 | 145 | void process_connect_packet() 146 | { 147 | mRecvQueue.Switch(); 148 | 149 | if (!mRecvQueue.Empty()) 150 | { 151 | var buf = mRecvQueue.Pop(); 152 | 153 | UInt32 conv = 0; 154 | KCP.ikcp_decode32u(buf, 0, ref conv); 155 | 156 | if (conv <= 0) 157 | throw new Exception("inlvaid connect back packet"); 158 | 159 | init_kcp(conv); 160 | 161 | mInConnectStage = false; 162 | mConnectSucceed = true; 163 | 164 | evHandler(cliEvent.Connected, null, null); 165 | } 166 | } 167 | 168 | void process_recv_queue() 169 | { 170 | mRecvQueue.Switch(); 171 | 172 | while (!mRecvQueue.Empty()) 173 | { 174 | var buf = mRecvQueue.Pop(); 175 | 176 | mKcp.Input(buf); 177 | mNeedUpdateFlag = true; 178 | 179 | for (var size = mKcp.PeekSize(); size > 0; size = mKcp.PeekSize()) 180 | { 181 | var buffer = new byte[size]; 182 | if (mKcp.Recv(buffer) > 0) 183 | { 184 | evHandler(cliEvent.RcvMsg, buffer, null); 185 | } 186 | } 187 | } 188 | } 189 | 190 | bool connect_timeout(UInt32 current) 191 | { 192 | return current - mConnectStartTime > CONNECT_TIMEOUT; 193 | } 194 | 195 | bool need_send_connect_packet(UInt32 current) 196 | { 197 | return current - mLastSendConnectTime > RESEND_CONNECT; 198 | } 199 | 200 | void update(UInt32 current) 201 | { 202 | if (mInConnectStage) 203 | { 204 | if (connect_timeout(current)) 205 | { 206 | evHandler(cliEvent.ConnectFailed, null, "Timeout"); 207 | mInConnectStage = false; 208 | return; 209 | } 210 | 211 | if (need_send_connect_packet(current)) 212 | { 213 | mLastSendConnectTime = current; 214 | mUdpClient.Send(new byte[4] { 0, 0, 0, 0 }, 4); 215 | } 216 | 217 | process_connect_packet(); 218 | 219 | return; 220 | } 221 | 222 | if (mConnectSucceed) 223 | { 224 | process_recv_queue(); 225 | 226 | if (mNeedUpdateFlag || current >= mNextUpdateTime) 227 | { 228 | mKcp.Update(current); 229 | mNextUpdateTime = mKcp.Check(current); 230 | mNeedUpdateFlag = false; 231 | } 232 | } 233 | } 234 | } 235 | 236 | } -------------------------------------------------------------------------------- /Unity3D/KCPSocket.cs: -------------------------------------------------------------------------------- 1 | //#define BigEndian 2 | #define LittleEndian 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Net; 6 | using System.Net.Sockets; 7 | using System.Text; 8 | using System.Threading; 9 | using UnityEngine; 10 | #if BigEndian 11 | using KCPProxy = Network_Kcp.KCPProxy_BE; 12 | #else 13 | using KCPProxy = Network_Kcp.KCPProxy_LE; 14 | #endif 15 | namespace Network_Kcp 16 | { 17 | public class KCPSocket 18 | { 19 | 20 | 21 | 22 | public string LOG_TAG = "KCPSocket"; 23 | 24 | private bool m_IsRunning = false; 25 | private Socket m_SystemSocket; 26 | private IPEndPoint m_LocalEndPoint; 27 | private AddressFamily m_AddrFamily; 28 | private Thread m_ThreadRecv; 29 | private byte[] m_RecvBufferTemp = new byte[4096]; 30 | 31 | //KCP参数 32 | private List m_ListKcp; 33 | private uint m_KcpKey = 0; 34 | private KCPReceiveListener m_AnyEPListener; 35 | 36 | //================================================================================= 37 | #region 构造和析构 38 | 39 | public KCPSocket(int bindPort, uint kcpKey, AddressFamily family = AddressFamily.InterNetwork) { 40 | m_AddrFamily = family; 41 | m_KcpKey = kcpKey; 42 | m_ListKcp = new List(); 43 | 44 | m_SystemSocket = new Socket(m_AddrFamily, SocketType.Dgram, ProtocolType.Udp); 45 | IPEndPoint ipep = KCPProxy.GetIPEndPointAny(m_AddrFamily, bindPort); 46 | m_SystemSocket.Bind(ipep); 47 | 48 | bindPort = (m_SystemSocket.LocalEndPoint as IPEndPoint).Port; 49 | LOG_TAG = "KCPSocket[" + bindPort + "-" + kcpKey + "]"; 50 | 51 | m_IsRunning = true; 52 | m_ThreadRecv = new Thread(Thread_Recv) { IsBackground = true }; 53 | m_ThreadRecv.Start(); 54 | 55 | 56 | 57 | #if UNITY_EDITOR_WIN 58 | uint IOC_IN = 0x80000000; 59 | uint IOC_VENDOR = 0x18000000; 60 | uint SIO_UDP_CONNRESET = IOC_IN | IOC_VENDOR | 12; 61 | m_SystemSocket.IOControl((int)SIO_UDP_CONNRESET, new byte[] { Convert.ToByte(false) }, null); 62 | #endif 63 | 64 | 65 | #if UNITY_EDITOR 66 | UnityEditor.EditorApplication.playmodeStateChanged -= OnEditorPlayModeChanged; 67 | UnityEditor.EditorApplication.playmodeStateChanged += OnEditorPlayModeChanged; 68 | #endif 69 | } 70 | 71 | 72 | #if UNITY_EDITOR 73 | private void OnEditorPlayModeChanged() 74 | { 75 | if (Application.isPlaying == false) 76 | { 77 | this.Log("OnEditorPlayModeChanged()"); 78 | UnityEditor.EditorApplication.playmodeStateChanged -= OnEditorPlayModeChanged; 79 | Dispose(); 80 | } 81 | } 82 | #endif 83 | 84 | public void Dispose() { 85 | m_IsRunning = false; 86 | m_AnyEPListener = null; 87 | 88 | if (m_ThreadRecv != null) { 89 | m_ThreadRecv.Interrupt(); 90 | m_ThreadRecv = null; 91 | } 92 | 93 | int cnt = m_ListKcp.Count; 94 | for (int i = 0; i < cnt; i++) { 95 | m_ListKcp[i].Dispose(); 96 | } 97 | m_ListKcp.Clear(); 98 | 99 | if (m_SystemSocket != null) { 100 | try { 101 | m_SystemSocket.Shutdown(SocketShutdown.Both); 102 | } 103 | catch (Exception e) { 104 | NetworkDebuger.LogWarning("Close() " + e.Message + e.StackTrace); 105 | } 106 | 107 | m_SystemSocket.Close(); 108 | m_SystemSocket = null; 109 | } 110 | } 111 | 112 | 113 | public int LocalPort { 114 | get { return (m_SystemSocket.LocalEndPoint as IPEndPoint).Port; } 115 | } 116 | 117 | public string LocalIP { 118 | get { return UnityEngine.Network.player.ipAddress; } 119 | } 120 | 121 | public IPEndPoint LocalEndPoint { 122 | get { 123 | if (m_LocalEndPoint == null || 124 | m_LocalEndPoint.Address.ToString() != UnityEngine.Network.player.ipAddress) { 125 | IPAddress ip = IPAddress.Parse(LocalIP); 126 | m_LocalEndPoint = new IPEndPoint(ip, LocalPort); 127 | } 128 | 129 | return m_LocalEndPoint; 130 | } 131 | } 132 | 133 | public Socket SystemSocket { get { return m_SystemSocket; } } 134 | 135 | #endregion 136 | 137 | //================================================================================= 138 | 139 | public bool EnableBroadcast { 140 | get { return m_SystemSocket.EnableBroadcast; } 141 | set { m_SystemSocket.EnableBroadcast = value; } 142 | } 143 | 144 | //================================================================================= 145 | #region 管理KCP 146 | 147 | private KCPProxy GetKcp(IPEndPoint ipep) { 148 | if (ipep == null || ipep.Port == 0 || 149 | ipep.Address.Equals(IPAddress.Any) || 150 | ipep.Address.Equals(IPAddress.IPv6Any)) { 151 | return null; 152 | } 153 | 154 | KCPProxy proxy; 155 | int cnt = m_ListKcp.Count; 156 | for (int i = 0; i < cnt; i++) { 157 | proxy = m_ListKcp[i]; 158 | if (proxy.RemotePoint.Equals(ipep)) { 159 | return proxy; 160 | } 161 | } 162 | 163 | proxy = new KCPProxy(m_KcpKey, ipep, m_SystemSocket); 164 | proxy.AddReceiveListener(OnReceiveAny); 165 | m_ListKcp.Add(proxy); 166 | return proxy; 167 | } 168 | 169 | #endregion 170 | 171 | //================================================================================= 172 | #region 发送逻辑 173 | public bool SendTo(byte[] buffer, int size, IPEndPoint remotePoint) { 174 | if (remotePoint.Address == IPAddress.Broadcast) { 175 | int cnt = m_SystemSocket.SendTo(buffer, size, SocketFlags.None, remotePoint); 176 | return cnt > 0; 177 | } 178 | else { 179 | KCPProxy proxy = GetKcp(remotePoint); 180 | if (proxy != null) { 181 | return proxy.DoSend(buffer, size); 182 | } 183 | } 184 | 185 | return false; 186 | } 187 | 188 | public bool SendTo(string message, IPEndPoint remotePoint) { 189 | byte[] buffer = Encoding.UTF8.GetBytes(message); 190 | return SendTo(buffer, buffer.Length, remotePoint); 191 | } 192 | 193 | #endregion 194 | 195 | 196 | //================================================================================= 197 | #region 主线程驱动 198 | ushort keepHeartbeat = 0; 199 | const string HeartbeatMsg = "Heartbeat"; 200 | byte[] HeartbeatMsgBuffer = Encoding.UTF8.GetBytes(HeartbeatMsg); 201 | public void SendKeepHeartbeat(IPEndPoint remotePoint) { 202 | keepHeartbeat++; 203 | if (keepHeartbeat > 500) { 204 | keepHeartbeat = 0; 205 | SendTo(HeartbeatMsgBuffer, HeartbeatMsgBuffer.Length, remotePoint); 206 | } 207 | } 208 | public void Update() { 209 | 210 | if (m_IsRunning) { 211 | //获取时钟 212 | long current = KCPProxy.GetClockMS(); 213 | 214 | int cnt = m_ListKcp.Count; 215 | for (int i = 0; i < cnt; i++) { 216 | KCPProxy proxy = m_ListKcp[i]; 217 | proxy.Update(current); 218 | } 219 | } 220 | } 221 | 222 | #endregion 223 | 224 | //================================================================================= 225 | #region 接收逻辑 226 | 227 | public void AddReceiveListener(IPEndPoint remotePoint, KCPReceiveListener listener) { 228 | KCPProxy proxy = GetKcp(remotePoint); 229 | if (proxy != null) { 230 | proxy.AddReceiveListener(listener); 231 | } 232 | else { 233 | m_AnyEPListener += listener; 234 | } 235 | } 236 | 237 | public void RemoveReceiveListener(IPEndPoint remotePoint, KCPReceiveListener listener) { 238 | KCPProxy proxy = GetKcp(remotePoint); 239 | if (proxy != null) { 240 | proxy.RemoveReceiveListener(listener); 241 | } 242 | else { 243 | m_AnyEPListener -= listener; 244 | } 245 | } 246 | 247 | public void AddReceiveListener(KCPReceiveListener listener) { 248 | m_AnyEPListener += listener; 249 | } 250 | 251 | public void RemoveReceiveListener(KCPReceiveListener listener) { 252 | m_AnyEPListener -= listener; 253 | } 254 | 255 | 256 | private void OnReceiveAny(byte[] buffer, int size, IPEndPoint remotePoint) { 257 | if (m_AnyEPListener != null) { 258 | m_AnyEPListener(buffer, size, remotePoint); 259 | } 260 | } 261 | 262 | #endregion 263 | 264 | //================================================================================= 265 | #region 接收线程 266 | 267 | private void Thread_Recv() { 268 | NetworkDebuger.Log("Thread_Recv() Begin ......"); 269 | 270 | while (m_IsRunning) { 271 | try { 272 | DoReceive(); 273 | } 274 | catch (Exception e) { 275 | NetworkDebuger.LogError("Thread_Recv() " + e.Message + "\n" + e.StackTrace); 276 | Thread.Sleep(10); 277 | } 278 | } 279 | 280 | NetworkDebuger.Log("Thread_Recv() End!"); 281 | } 282 | 283 | private void DoReceive() { 284 | if (m_SystemSocket.Available <= 0) { 285 | return; 286 | } 287 | 288 | EndPoint remotePoint = new IPEndPoint(IPAddress.Any, 0); 289 | int cnt = m_SystemSocket.ReceiveFrom(m_RecvBufferTemp, m_RecvBufferTemp.Length, 290 | SocketFlags.None, ref remotePoint); 291 | 292 | if (cnt > 0) { 293 | KCPProxy proxy = GetKcp((IPEndPoint)remotePoint); 294 | if (proxy != null) { 295 | proxy.DoReceiveInThread(m_RecvBufferTemp, cnt); 296 | } 297 | } 298 | 299 | } 300 | 301 | #endregion 302 | } 303 | 304 | 305 | 306 | 307 | 308 | } -------------------------------------------------------------------------------- /KCP_PInvoke/kcp_native.cs: -------------------------------------------------------------------------------- 1 | 2 | //KCP_NATIVE 使用P/Invoke调用KCP协议相关处理方法,这种方式尽量规避了KCP源代码C->C#改写过程中造成的问题。 3 | //目前C#版KCP在长时间运行后,IOPS会下降,原因尚不明确。 4 | //简单测试阶段,可以使用C#版 5 | 6 | using System; 7 | using System.Collections.Generic; 8 | using System.Linq; 9 | using System.Runtime.InteropServices; 10 | using System.Text; 11 | using System.Threading; 12 | 13 | namespace KCP.NetWrapper { 14 | class kcp_native { 15 | #if UNITY_IOS 16 | const string KCPDLL="__Internal"; 17 | #elif UNITY_ANDROID 18 | const string KCPDLL="libikcp"; 19 | #elif UNITY_EDITOR || UNITY_STANDALONE 20 | const string KCPDLL = "ikcp"; 21 | #else 22 | const string KCPDLL = "ikcp"; 23 | #endif 24 | [DllImport(KCPDLL, CallingConvention = CallingConvention.Cdecl)] 25 | public static extern IntPtr ikcp_create(int conv, IntPtr user); 26 | 27 | [DllImport(KCPDLL, CallingConvention = CallingConvention.Cdecl)] 28 | public static extern void ikcp_release(IntPtr kcp); 29 | 30 | [DllImport(KCPDLL, CallingConvention = CallingConvention.Cdecl)] 31 | public static extern void ikcp_setoutput(IntPtr kcp, Delegate output); 32 | 33 | [DllImport(KCPDLL, CallingConvention = CallingConvention.Cdecl)] 34 | public static extern int ikcp_recv(IntPtr kcp, IntPtr buffer, int len); 35 | 36 | [DllImport(KCPDLL, CallingConvention = CallingConvention.Cdecl)] 37 | public static extern int ikcp_send(IntPtr kcp, IntPtr buffer, int len); 38 | 39 | [DllImport(KCPDLL, CallingConvention = CallingConvention.Cdecl)] 40 | public static extern void ikcp_update(IntPtr kcp, uint current); 41 | 42 | [DllImport(KCPDLL, CallingConvention = CallingConvention.Cdecl)] 43 | public static extern uint ikcp_check(IntPtr kcp, uint current); 44 | 45 | [DllImport(KCPDLL, CallingConvention = CallingConvention.Cdecl)] 46 | public static extern int ikcp_input(IntPtr kcp, IntPtr data, long size); 47 | 48 | [DllImport(KCPDLL, CallingConvention = CallingConvention.Cdecl)] 49 | public static extern int ikcp_peeksize(IntPtr kcp); 50 | 51 | [DllImport(KCPDLL, CallingConvention = CallingConvention.Cdecl)] 52 | public static extern int ikcp_setmtu(IntPtr kcp, int mtu); 53 | 54 | [DllImport(KCPDLL, CallingConvention = CallingConvention.Cdecl)] 55 | public static extern uint ikcp_getconv(IntPtr buf); 56 | 57 | 58 | [DllImport(KCPDLL, CallingConvention = CallingConvention.Cdecl)] 59 | public static extern int ikcp_get_interval(IntPtr buf); 60 | /// 61 | /// 设置内存分配和释放方法 62 | /// 63 | /// 64 | /// 65 | [DllImport(KCPDLL, CallingConvention = CallingConvention.Cdecl)] 66 | public static extern void ikcp_allocator(Delegate bufalloc_callback, Delegate buffree_callbak); 67 | 68 | 69 | //参考:https://github.com/skywind3000/kcp/wiki/Flow-Control-for-Users 70 | [DllImport(KCPDLL, CallingConvention = CallingConvention.Cdecl)] 71 | public static extern int ikcp_waitsnd(IntPtr kcp); 72 | 73 | 74 | [DllImport(KCPDLL, CallingConvention = CallingConvention.Cdecl)] 75 | public static extern int ikcp_wndsize(IntPtr kcp, int sndwnd, int rcvwnd); 76 | 77 | 78 | 79 | [DllImport(KCPDLL, CallingConvention = CallingConvention.Cdecl)] 80 | public static extern int ikcp_nodelay(IntPtr kcp, int nodelay, int interval, int resend, int nc); 81 | 82 | [DllImport(KCPDLL, CallingConvention = CallingConvention.Cdecl)] 83 | public static extern int ikcp_flush(IntPtr kcp); 84 | 85 | [DllImport(KCPDLL, CallingConvention = CallingConvention.Cdecl)] 86 | public static extern void ikcp_setminrto(IntPtr kcp, int minrto); 87 | [DllImport(KCPDLL, CallingConvention = CallingConvention.Cdecl)] 88 | public static extern int ikcp_getminrto(IntPtr kcp); 89 | } 90 | 91 | public partial class KCPLib { 92 | // encode 8 bits unsigned int 93 | public static int ikcp_encode8u(byte[] p, int offset, byte c) { 94 | p[0 + offset] = c; 95 | return 1; 96 | } 97 | 98 | // decode 8 bits unsigned int 99 | public static int ikcp_decode8u(byte[] p, int offset, ref byte c) { 100 | c = p[0 + offset]; 101 | return 1; 102 | } 103 | 104 | /* encode 16 bits unsigned int (lsb) */ 105 | public static int ikcp_encode16u(byte[] p, int offset, UInt16 w) { 106 | p[0 + offset] = (byte)(w >> 0); 107 | p[1 + offset] = (byte)(w >> 8); 108 | return 2; 109 | } 110 | 111 | /* decode 16 bits unsigned int (lsb) */ 112 | public static int ikcp_decode16u(byte[] p, int offset, ref UInt16 c) { 113 | UInt16 result = 0; 114 | result |= (UInt16)p[0 + offset]; 115 | result |= (UInt16)(p[1 + offset] << 8); 116 | c = result; 117 | return 2; 118 | } 119 | 120 | /* encode 32 bits unsigned int (lsb) */ 121 | public static int ikcp_encode32u(byte[] p, int offset, UInt32 l) { 122 | p[0 + offset] = (byte)(l >> 0); 123 | p[1 + offset] = (byte)(l >> 8); 124 | p[2 + offset] = (byte)(l >> 16); 125 | p[3 + offset] = (byte)(l >> 24); 126 | return 4; 127 | } 128 | 129 | /* decode 32 bits unsigned int (lsb) */ 130 | public static int ikcp_decode32u(byte[] p, int offset, ref UInt32 c) { 131 | UInt32 result = 0; 132 | result |= (UInt32)p[0 + offset]; 133 | result |= (UInt32)(p[1 + offset] << 8); 134 | result |= (UInt32)(p[2 + offset] << 16); 135 | result |= (UInt32)(p[3 + offset] << 24); 136 | c = result; 137 | return 4; 138 | } 139 | 140 | 141 | //BufferChunk m_bufChunk;// = new BufferChunk(16 * 1024, 64 * 1024 * 1024); 142 | IntPtr BufAlloc(int size) { 143 | if(size > 16 * 1024) { 144 | throw new Exception("BufAlloc size过大 size:" + size.ToString()); 145 | } 146 | return BufferPoolForNative.GetBuf(); 147 | } 148 | void BufFree(IntPtr p) { 149 | BufferPoolForNative.Return(p); 150 | } 151 | internal delegate IntPtr BufAllocCallback(int size); 152 | internal delegate void BufFreeCallback(IntPtr p); 153 | public delegate void OutputCallback(IntPtr buf, int len, IntPtr kcp, IntPtr user); 154 | 155 | BufAllocCallback m_BufAlloc; 156 | BufFreeCallback m_BufFree; 157 | 158 | IntPtr m_kcp; 159 | bool m_useNativeBufpool = false; 160 | /// 161 | /// 162 | /// 163 | /// 164 | /// 165 | /// 166 | /// 是否使用内存池,如果true,则 167 | /// 内存池单元尺寸,默认为4K 168 | /// 内存池总尺寸,默认8MB 169 | public KCPLib(uint conv_, object user, OutputCallback output_, bool useNativeBufPool = false) { 170 | if(useNativeBufPool) { 171 | if(BufferPoolForNative.m_inited == false) { 172 | throw new Exception("内存池BufferChunk尚未初始化,请先调用BufferChunk.Init"); 173 | } 174 | m_BufAlloc = BufAlloc; 175 | m_BufFree = BufFree; 176 | kcp_native.ikcp_allocator(m_BufAlloc, m_BufFree); 177 | } 178 | m_useNativeBufpool = useNativeBufPool; 179 | //kcp_native.ikcp_allocator(m_BufAlloc, m_BufFree); 180 | m_kcp = kcp_native.ikcp_create((int)conv_, IntPtr.Zero); 181 | if(m_kcp == IntPtr.Zero) { 182 | throw new Exception("初始化KCP失败"); 183 | } 184 | kcp_native.ikcp_setoutput(m_kcp, output_); 185 | } 186 | 187 | public unsafe int Input(byte[] buffer, int offset, int size) { 188 | fixed (byte* p = buffer) { 189 | return kcp_native.ikcp_input(m_kcp, new IntPtr(p + offset), size); 190 | } 191 | //IntPtr p = BufAlloc(size); 192 | //Marshal.Copy(buffer, offset, p, size); 193 | //int ret = kcp_native.ikcp_input(m_kcp, p, size); 194 | //BufFree(p); 195 | //return ret; 196 | } 197 | 198 | // check the size of next message in the recv queue 199 | public int PeekSize() { 200 | return kcp_native.ikcp_peeksize(m_kcp); 201 | } 202 | 203 | // user/upper level recv: returns size, returns below zero for EAGAIN 204 | public unsafe int Recv(byte[] buffer, int offset, int size) { 205 | 206 | fixed (byte* p = buffer) { 207 | int recvbytes = kcp_native.ikcp_recv(m_kcp, (IntPtr)(p + offset), size); 208 | return recvbytes; 209 | } 210 | 211 | //IntPtr p = BufAlloc(size); 212 | //int recvbytes = kcp_native.ikcp_recv(m_kcp, p, size); 213 | //Marshal.Copy(p, buffer, offset, size); 214 | //BufFree(p); 215 | //return recvbytes; 216 | } 217 | 218 | 219 | 220 | public unsafe int Send(byte[] buffer, int offset, int bufsize) { 221 | 222 | //IntPtr p = BufAlloc(bufsize); 223 | //Marshal.Copy(buffer, offset, p, bufsize); 224 | 225 | //int ret = kcp_native.ikcp_send(m_kcp, p, bufsize); 226 | //BufFree(p); 227 | //return ret; 228 | 229 | int send = 0; 230 | fixed (byte* p = buffer) { 231 | send = kcp_native.ikcp_send(m_kcp, new IntPtr(p + offset), bufsize); 232 | } 233 | //https://github.com/skywind3000/kcp/issues/10 234 | //https://github.com/skywind3000/kcp/wiki/Flow-Control-for-Users 235 | //https://github.com/skywind3000/kcp/issues/4 236 | kcp_native.ikcp_flush(m_kcp); 237 | return send; 238 | } 239 | 240 | 241 | 242 | // user/upper level send, returns below zero for error 243 | public int Send(byte[] buffer) { 244 | return Send(buffer, 0, buffer.Length); 245 | } 246 | 247 | public void Update(UInt32 current_) { 248 | kcp_native.ikcp_update(m_kcp, current_); 249 | } 250 | public UInt32 Check(UInt32 current_) { 251 | return kcp_native.ikcp_check(m_kcp, current_); 252 | } 253 | 254 | public int NoDelay(int nodelay, int interval, int resend, int nc) { 255 | return kcp_native.ikcp_nodelay(m_kcp, nodelay, interval, resend, nc); 256 | } 257 | public int GetInterval() { 258 | return kcp_native.ikcp_get_interval(m_kcp); 259 | } 260 | 261 | public int WaitSnd() { 262 | //https://github.com/skywind3000/kcp/wiki/Flow-Control-for-Users 263 | return kcp_native.ikcp_waitsnd(m_kcp); 264 | } 265 | public int WndSize(int sndwnd, int rcvwnd) { 266 | return kcp_native.ikcp_wndsize(m_kcp, sndwnd, rcvwnd); 267 | } 268 | public void SetMtu(int mtu) { 269 | kcp_native.ikcp_setmtu(m_kcp, mtu);//ikcp_setmtu 270 | } 271 | public void SetMinRto(int minrto) { 272 | kcp_native.ikcp_setminrto(m_kcp, minrto); 273 | } 274 | public int GetMinRto() { 275 | return kcp_native.ikcp_getminrto(m_kcp); 276 | } 277 | public void Dispose() { 278 | kcp_native.ikcp_release(m_kcp); 279 | } 280 | } 281 | 282 | public static class BufferPoolForNative { 283 | public static string Name; 284 | public static Byte[] Buffer; 285 | public static Stack AddrIndex; 286 | public static GCHandle BufferHandle; 287 | internal static IntPtr BufferAddr; 288 | private static object m_locker = new object(); 289 | internal static bool m_inited = false; 290 | public static IntPtr GetBuf() { 291 | lock(m_locker) { 292 | if(AddrIndex.Count == 0) { 293 | throw new Exception("没有充足的空间.ChunkName:" + Name); 294 | } 295 | long addr = AddrIndex.Pop(); 296 | Interlocked.Increment(ref InUse); 297 | return new IntPtr(addr); 298 | } 299 | } 300 | public static int InUse = 0; 301 | private static int m_unitBytes; 302 | public static void Init(int unitBytes, int totalSize) { 303 | Buffer = new byte[totalSize]; 304 | BufferHandle = GCHandle.Alloc(Buffer, GCHandleType.Pinned); 305 | BufferAddr = BufferHandle.AddrOfPinnedObject(); 306 | AddrIndex = new Stack(); 307 | long start = BufferAddr.ToInt64(); 308 | for(int j = 0; j < totalSize; j += unitBytes) { 309 | long add = start + j; 310 | AddrIndex.Push(add); 311 | } 312 | m_unitBytes = unitBytes; 313 | m_inited = true; 314 | } 315 | public static void Return(IntPtr buf) { 316 | long p = buf.ToInt64(); 317 | long l = p % m_unitBytes; 318 | 319 | long addr = p - l;// (int)((buf.ToInt64() - BufferAddr.ToInt64()) / m_unitBytes); 320 | lock(m_locker) { 321 | AddrIndex.Push(addr); 322 | } 323 | Interlocked.Decrement(ref InUse); 324 | } 325 | 326 | 327 | public static void Close() { 328 | BufferHandle.Free(); 329 | } 330 | 331 | 332 | } 333 | 334 | } 335 | 336 | -------------------------------------------------------------------------------- /Unity3D/kcpUnity3DClientTest.cs: -------------------------------------------------------------------------------- 1 | //#define BigEndian 2 | #define LittleEndian 3 | using System; 4 | using System.Collections; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using System.Text; 8 | using System.Net; 9 | using System.Net.Sockets; 10 | using UnityEngine; 11 | using Network_Kcp; 12 | #if BigEndian 13 | using KCPProxy = Network_Kcp.KCPProxy_BE; 14 | #else 15 | using KCPProxy = Network_Kcp.KCPProxy_LE; 16 | #endif 17 | namespace Assets.Script.Test 18 | { 19 | public class kcpUnity3DClientTest : MonoBehaviour 20 | { 21 | private KCPPlayer p1, p2; 22 | 23 | void Awake() { 24 | 25 | 26 | } 27 | private bool m_initKcp = false; 28 | private bool m_stopKcpSendMsg = true; 29 | private IEnumerator Start() { 30 | yield return new WaitForSecondsRealtime(3f); 31 | NetworkDebuger.EnableLog = true; 32 | NetworkDebuger.EnableSave = true; 33 | NetworkDebuger.Log("Awake()"); 34 | 35 | p1 = new KCPPlayer(); 36 | p1.Init("Player1", 12345, 12346); 37 | 38 | p2 = new KCPPlayer(); 39 | p2.Init("Player2", 12346, 12345); 40 | m_initKcp = true; 41 | // 42 | StartCoroutine(sendMsgLoop()); 43 | } 44 | private IEnumerator sendMsgLoop() { 45 | if (!m_initKcp) yield return null; 46 | while (true) { 47 | if(!m_stopKcpSendMsg) { 48 | //for (int i = 0; i < 10; i++) { 49 | p1.SendMessage(); 50 | p2.SendMessage(); 51 | //} 52 | yield return null; 53 | } 54 | yield return null; 55 | } 56 | } 57 | 58 | void Update() { 59 | if (m_initKcp) { 60 | p1.OnUpdate(); 61 | p2.OnUpdate(); 62 | } 63 | } 64 | private void FixedUpdate() { 65 | if (m_initKcp) { 66 | p1.OnFixedUpdate(); 67 | p2.OnFixedUpdate(); 68 | } 69 | } 70 | void OnGUI() { 71 | if (!m_initKcp) return; 72 | if (GUILayout.Button("Player1 SendMessage")) { 73 | p1.SendMessage(); 74 | } 75 | 76 | if (GUILayout.Button("Player2 SendMessage")) { 77 | p2.SendMessage(); 78 | } 79 | if (GUILayout.Button("stop/continue SendMessage")) { 80 | m_stopKcpSendMsg = !m_stopKcpSendMsg; 81 | //StartCoroutine(sendMsgLoop()); 82 | } 83 | 84 | } 85 | private void OnApplicationQuit() { 86 | NetworkDebuger.EnableSave = false; 87 | p1.Dispose(); 88 | p2.Dispose(); 89 | } 90 | 91 | public class KCPPlayer 92 | { 93 | public string LOG_TAG = "KCPPlayer"; 94 | 95 | private KCPSocket m_Socket; 96 | private string m_Name; 97 | private int m_MsgId = 0; 98 | private IPEndPoint m_RemotePoint; 99 | public void Dispose() { 100 | m_Socket.Dispose(); 101 | m_Socket = null; 102 | } 103 | public void Init(string name, int localPort, int remotePort) { 104 | m_Name = name; 105 | LOG_TAG = "KCPPlayer[" + m_Name + "]"; 106 | 107 | IPAddress ipa = IPAddress.Parse(UnityEngine.Network.player.ipAddress); 108 | m_RemotePoint = new IPEndPoint(ipa, remotePort); 109 | 110 | m_Socket = new KCPSocket(localPort, 1, AddressFamily.InterNetwork); 111 | m_Socket.AddReceiveListener(KCPProxy.IPEP_Any, OnReceiveAny); 112 | m_Socket.AddReceiveListener(m_RemotePoint, OnReceive); 113 | 114 | NetworkDebuger.Log("Init() name:{0}, localPort:{1}, remotePort:{2}", name, localPort, remotePort); 115 | } 116 | 117 | private void OnReceiveAny(byte[] buffer, int size, IPEndPoint remotePoint) { 118 | string str = Encoding.UTF8.GetString(buffer, 0, size); 119 | NetworkDebuger.Log("OnReceiveAny() " + remotePoint + ":" + str); 120 | } 121 | 122 | private void OnReceive(byte[] buffer, int size, IPEndPoint remotePoint) { 123 | string str = Encoding.UTF8.GetString(buffer, 0, size); 124 | NetworkDebuger.Log("OnReceive() " + remotePoint + ":" + str); 125 | } 126 | 127 | public void OnUpdate() { 128 | if (m_Socket != null) { 129 | m_Socket.Update(); 130 | } 131 | } 132 | 133 | public void OnFixedUpdate() { 134 | if (m_Socket != null) { 135 | m_Socket.SendKeepHeartbeat(m_RemotePoint); 136 | } 137 | } 138 | string msgContent = @" 139 | LICENSE SYSTEM [2017918 10:58:53] Next license update check is after 2025-06-30T00:00:00 140 | 141 | Built from '5.5/release' branch; Version is '5.5.0f3 (38b4efef76f0) revision 3716335'; Using compiler version '160040219' 142 | OS: 'Windows 7 Service Pack 1 (6.1.7601) 64bit' Language: 'zh' Physical Memory: 16224 MB 143 | BatchMode: 0, IsHumanControllingUs: 1, StartBugReporterOnCrash: 1, Is64bit: 1, IsPro: 1 144 | Initialize mono 145 | Mono path[0] = 'C:/Program Files/Unity5.5.0/Editor/Data/Managed' 146 | Mono path[1] = 'C:/Program Files/Unity5.5.0/Editor/Data/Mono/lib/mono/2.0' 147 | Mono path[2] = 'C:/Program Files/Unity5.5.0/Editor/Data/UnityScript' 148 | Mono config path = 'C:/Program Files/Unity5.5.0/Editor/Data/Mono/etc' 149 | Using monoOptions --debugger-agent=transport=dt_socket,embedding=1,defer=y,address=0.0.0.0:56392 150 | IsTimeToCheckForNewEditor: Update time 1505705705 current 1505703540 151 | C:/work/irobotqv2.0_dev/irobotqv2.0_app 152 | Loading GUID <-> Path mappings...0.000281 seconds 153 | Loading Asset Database...0.015599 seconds 154 | Audio: FMOD Profiler initialized on port 54900 155 | AssetDatabase consistency checks...0.019115 seconds 156 | Initialize engine version: 5.5.0f3 (38b4efef76f0) 157 | GfxDevice: creating device client; threaded=1 158 | Direct3D: 159 | Version: Direct3D 11.0 [level 11.0] 160 | Renderer: AMD Radeon HD 6670 (ID=0x6758) 161 | Vendor: ATI 162 | VRAM: 4418 MB 163 | Driver: 14.100.0.0 164 | Begin MonoManager ReloadAssembly1 165 | Platform assembly: C:\Program Files\Unity5.5.0\Editor\Data\Managed\UnityEngine.dll (this message is harmless) 166 | Platform assembly: C:\Program Files\Unity5.5.0\Editor\Data\Managed\UnityEditor.dll (this message is harmless) 167 | Platform assembly: C:\Program Files\Unity5.5.0\Editor\Data\Managed\Unity.Locator.dll (this message is harmless) 168 | Refreshing native plugins compatible for Editor in 9.17 ms, found 3 plugins. 169 | Platform assembly: C:\Program Files\Unity5.5.0\Editor\Data\Mono\lib\mono\2.0\I18N.dll (this message is harmless) 170 | Platform assembly: C:\Program Files\Unity5.5.0\Editor\Data\Mono\lib\mono\2.0\I18N.CJK.dll (this message is harmless) 171 | Platform assembly: C:\Program Files\Unity5.5.0\Editor\Data\Managed\Unity.DataContract.dll (this message is harmless) 172 | Platform assembly: C:\Program Files\Unity5.5.0\Editor\Data\Mono\lib\mono\2.0\System.Core.dll (this message is harmless) 173 | Platform assembly: C:\Program Files\Unity5.5.0\Editor\Data\Managed\Unity.IvyParser.dll (this message is harmless) 174 | Platform assembly: C:\Program Files\Unity5.5.0\Editor\Data\Mono\lib\mono\2.0\System.dll (this message is harmless) 175 | Platform assembly: C:\Program Files\Unity5.5.0\Editor\Data\Mono\lib\mono\2.0\System.Xml.dll (this message is harmless) 176 | Platform assembly: C:\Program Files\Unity5.5.0\Editor\Data\Mono\lib\mono\2.0\System.Configuration.dll (this message is harmless) 177 | Begin MonoManager ReloadAssembly2 178 | Platform assembly: C:\Program Files\Unity5.5.0\Editor\Data\Managed\UnityEngine.dll (this message is harmless) 179 | Platform assembly: C:\Program Files\Unity5.5.0\Editor\Data\Managed\UnityEditor.dll (this message is harmless) 180 | Platform assembly: C:\Program Files\Unity5.5.0\Editor\Data\Managed\Unity.Locator.dll (this message is harmless) 181 | Refreshing native plugins compatible for Editor in 9.17 ms, found 3 plugins. 182 | Platform assembly: C:\Program Files\Unity5.5.0\Editor\Data\Mono\lib\mono\2.0\I18N.dll (this message is harmless) 183 | Platform assembly: C:\Program Files\Unity5.5.0\Editor\Data\Mono\lib\mono\2.0\I18N.CJK.dll (this message is harmless) 184 | Platform assembly: C:\Program Files\Unity5.5.0\Editor\Data\Managed\Unity.DataContract.dll (this message is harmless) 185 | Platform assembly: C:\Program Files\Unity5.5.0\Editor\Data\Mono\lib\mono\2.0\System.Core.dll (this message is harmless) 186 | Platform assembly: C:\Program Files\Unity5.5.0\Editor\Data\Managed\Unity.IvyParser.dll (this message is harmless) 187 | Platform assembly: C:\Program Files\Unity5.5.0\Editor\Data\Mono\lib\mono\2.0\System.dll (this message is harmless) 188 | Platform assembly: C:\Program Files\Unity5.5.0\Editor\Data\Mono\lib\mono\2.0\System.Xml.dll (this message is harmless) 189 | Platform assembly: C:\Program Files\Unity5.5.0\Editor\Data\Mono\lib\mono\2.0\System.Configuration.dll (this message is harmless) 190 | Begin MonoManager ReloadAssembly3 191 | Platform assembly: C:\Program Files\Unity5.5.0\Editor\Data\Managed\UnityEngine.dll (this message is harmless) 192 | Platform assembly: C:\Program Files\Unity5.5.0\Editor\Data\Managed\UnityEditor.dll (this message is harmless) 193 | Platform assembly: C:\Program Files\Unity5.5.0\Editor\Data\Managed\Unity.Locator.dll (this message is harmless) 194 | Refreshing native plugins compatible for Editor in 9.17 ms, found 3 plugins. 195 | Platform assembly: C:\Program Files\Unity5.5.0\Editor\Data\Mono\lib\mono\2.0\I18N.dll (this message is harmless) 196 | Platform assembly: C:\Program Files\Unity5.5.0\Editor\Data\Mono\lib\mono\2.0\I18N.CJK.dll (this message is harmless) 197 | Platform assembly: C:\Program Files\Unity5.5.0\Editor\Data\Managed\Unity.DataContract.dll (this message is harmless) 198 | Platform assembly: C:\Program Files\Unity5.5.0\Editor\Data\Mono\lib\mono\2.0\System.Core.dll (this message is harmless) 199 | Platform assembly: C:\Program Files\Unity5.5.0\Editor\Data\Managed\Unity.IvyParser.dll (this message is harmless) 200 | Platform assembly: C:\Program Files\Unity5.5.0\Editor\Data\Mono\lib\mono\2.0\System.dll (this message is harmless) 201 | Platform assembly: C:\Program Files\Unity5.5.0\Editor\Data\Mono\lib\mono\2.0\System.Xml.dll (this message is harmless) 202 | Platform assembly: C:\Program Files\Unity5.5.0\Editor\Data\Mono\lib\mono\2.0\System.Configuration.dll (this message is harmless) 203 | Begin MonoManager ReloadAssembly4 204 | Platform assembly: C:\Program Files\Unity5.5.0\Editor\Data\Managed\UnityEngine.dll (this message is harmless) 205 | Platform assembly: C:\Program Files\Unity5.5.0\Editor\Data\Managed\UnityEditor.dll (this message is harmless) 206 | Platform assembly: C:\Program Files\Unity5.5.0\Editor\Data\Managed\Unity.Locator.dll (this message is harmless) 207 | Refreshing native plugins compatible for Editor in 9.17 ms, found 3 plugins. 208 | Platform assembly: C:\Program Files\Unity5.5.0\Editor\Data\Mono\lib\mono\2.0\I18N.dll (this message is harmless) 209 | Platform assembly: C:\Program Files\Unity5.5.0\Editor\Data\Mono\lib\mono\2.0\I18N.CJK.dll (this message is harmless) 210 | Platform assembly: C:\Program Files\Unity5.5.0\Editor\Data\Managed\Unity.DataContract.dll (this message is harmless) 211 | Platform assembly: C:\Program Files\Unity5.5.0\Editor\Data\Mono\lib\mono\2.0\System.Core.dll (this message is harmless) 212 | Platform assembly: C:\Program Files\Unity5.5.0\Editor\Data\Managed\Unity.IvyParser.dll (this message is harmless) 213 | Platform assembly: C:\Program Files\Unity5.5.0\Editor\Data\Mono\lib\mono\2.0\System.dll (this message is harmless) 214 | Platform assembly: C:\Program Files\Unity5.5.0\Editor\Data\Mono\lib\mono\2.0\System.Xml.dll (this message is harmless) 215 | Platform assembly: C:\Program Files\Unity5.5.0\Editor\Data\Mono\lib\mono\2.0\System.Configuration.dll (this message is harmless) 216 | Begin MonoManager ReloadAssembly5 217 | Platform assembly: C:\Program Files\Unity5.5.0\Editor\Data\Managed\UnityEngine.dll (this message is harmless) 218 | Platform assembly: C:\Program Files\Unity5.5.0\Editor\Data\Managed\UnityEditor.dll (this message is harmless) 219 | Platform assembly: C:\Program Files\Unity5.5.0\Editor\Data\Managed\Unity.Locator.dll (this message is harmless) 220 | Refreshing native plugins compatible for Editor in 9.17 ms, found 3 plugins. 221 | Platform assembly: C:\Program Files\Unity5.5.0\Editor\Data\Mono\lib\mono\2.0\I18N.dll (this message is harmless) 222 | Platform assembly: C:\Program Files\Unity5.5.0\Editor\Data\Mono\lib\mono\2.0\I18N.CJK.dll (this message is harmless) 223 | Platform assembly: C:\Program Files\Unity5.5.0\Editor\Data\Managed\Unity.DataContract.dll (this message is harmless) 224 | Platform assembly: C:\Program Files\Unity5.5.0\Editor\Data\Mono\lib\mono\2.0\System.Core.dll (this message is harmless) 225 | Platform assembly: C:\Program Files\Unity5.5.0\Editor\Data\Managed\Unity.IvyParser.dll (this message is harmless) 226 | Platform assembly: C:\Program Files\Unity5.5.0\Editor\Data\Mono\lib\mono\2.0\System.dll (this message is harmless) 227 | Platform assembly: C:\Program Files\Unity5.5.0\Editor\Data\Mono\lib\mono\2.0\System.Xml.dll (this message is harmless) 228 | Platform assembly: C:\Program Files\Unity5.5.0\Editor\Data\Mono\lib\mono\2.0\System.Configuration.dll (this message is harmless)"; 229 | 230 | 231 | public void SendMessage() { 232 | if (m_Socket != null) { 233 | m_MsgId++; 234 | m_Socket.SendTo(m_Name + "_" + "Message" + m_MsgId+" [size]"+msgContent, m_RemotePoint); 235 | } 236 | } 237 | } 238 | } 239 | 240 | 241 | } 242 | -------------------------------------------------------------------------------- /KCP_PInvoke/kcp_native_dll/ikcp.h: -------------------------------------------------------------------------------- 1 | //===================================================================== 2 | // 3 | // KCP - A Better ARQ Protocol Implementation 4 | // skywind3000 (at) gmail.com, 2010-2011 5 | // 6 | // Features: 7 | // + Average RTT reduce 30% - 40% vs traditional ARQ like tcp. 8 | // + Maximum RTT reduce three times vs tcp. 9 | // + Lightweight, distributed as a single source file. 10 | // 11 | //===================================================================== 12 | #ifndef __IKCP_H__ 13 | #define __IKCP_H__ 14 | 15 | #include 16 | #include 17 | #include 18 | 19 | 20 | //===================================================================== 21 | // 32BIT INTEGER DEFINITION 22 | //===================================================================== 23 | #ifndef __INTEGER_32_BITS__ 24 | #define __INTEGER_32_BITS__ 25 | #if defined(_WIN64) || defined(WIN64) || defined(__amd64__) || \ 26 | defined(__x86_64) || defined(__x86_64__) || defined(_M_IA64) || \ 27 | defined(_M_AMD64) 28 | typedef unsigned int ISTDUINT32; 29 | typedef int ISTDINT32; 30 | #elif defined(_WIN32) || defined(WIN32) || defined(__i386__) || \ 31 | defined(__i386) || defined(_M_X86) 32 | typedef unsigned long ISTDUINT32; 33 | typedef long ISTDINT32; 34 | #elif defined(__MACOS__) 35 | typedef UInt32 ISTDUINT32; 36 | typedef SInt32 ISTDINT32; 37 | #elif defined(__APPLE__) && defined(__MACH__) 38 | #include 39 | typedef u_int32_t ISTDUINT32; 40 | typedef int32_t ISTDINT32; 41 | #elif defined(__BEOS__) 42 | #include 43 | typedef u_int32_t ISTDUINT32; 44 | typedef int32_t ISTDINT32; 45 | #elif (defined(_MSC_VER) || defined(__BORLANDC__)) && (!defined(__MSDOS__)) 46 | typedef unsigned __int32 ISTDUINT32; 47 | typedef __int32 ISTDINT32; 48 | #elif defined(__GNUC__) 49 | #include 50 | typedef uint32_t ISTDUINT32; 51 | typedef int32_t ISTDINT32; 52 | #else 53 | typedef unsigned long ISTDUINT32; 54 | typedef long ISTDINT32; 55 | #endif 56 | #endif 57 | 58 | 59 | //===================================================================== 60 | // Integer Definition 61 | //===================================================================== 62 | #ifndef __IINT8_DEFINED 63 | #define __IINT8_DEFINED 64 | typedef char IINT8; 65 | #endif 66 | 67 | #ifndef __IUINT8_DEFINED 68 | #define __IUINT8_DEFINED 69 | typedef unsigned char IUINT8; 70 | #endif 71 | 72 | #ifndef __IUINT16_DEFINED 73 | #define __IUINT16_DEFINED 74 | typedef unsigned short IUINT16; 75 | #endif 76 | 77 | #ifndef __IINT16_DEFINED 78 | #define __IINT16_DEFINED 79 | typedef short IINT16; 80 | #endif 81 | 82 | #ifndef __IINT32_DEFINED 83 | #define __IINT32_DEFINED 84 | typedef ISTDINT32 IINT32; 85 | #endif 86 | 87 | #ifndef __IUINT32_DEFINED 88 | #define __IUINT32_DEFINED 89 | typedef ISTDUINT32 IUINT32; 90 | #endif 91 | 92 | #ifndef __IINT64_DEFINED 93 | #define __IINT64_DEFINED 94 | #if defined(_MSC_VER) || defined(__BORLANDC__) 95 | typedef __int64 IINT64; 96 | #else 97 | typedef long long IINT64; 98 | #endif 99 | #endif 100 | 101 | #ifndef __IUINT64_DEFINED 102 | #define __IUINT64_DEFINED 103 | #if defined(_MSC_VER) || defined(__BORLANDC__) 104 | typedef unsigned __int64 IUINT64; 105 | #else 106 | typedef unsigned long long IUINT64; 107 | #endif 108 | #endif 109 | 110 | #ifndef INLINE 111 | #if defined(__GNUC__) 112 | 113 | #if (__GNUC__ > 3) || ((__GNUC__ == 3) && (__GNUC_MINOR__ >= 1)) 114 | #define INLINE __inline__ __attribute__((always_inline)) 115 | #else 116 | #define INLINE __inline__ 117 | #endif 118 | 119 | #elif (defined(_MSC_VER) || defined(__BORLANDC__) || defined(__WATCOMC__)) 120 | #define INLINE __inline 121 | #else 122 | #define INLINE 123 | #endif 124 | #endif 125 | 126 | #if (!defined(__cplusplus)) && (!defined(inline)) 127 | #define inline INLINE 128 | #endif 129 | 130 | 131 | //===================================================================== 132 | // QUEUE DEFINITION 133 | //===================================================================== 134 | #ifndef __IQUEUE_DEF__ 135 | #define __IQUEUE_DEF__ 136 | 137 | struct IQUEUEHEAD { 138 | struct IQUEUEHEAD *next, *prev; 139 | }; 140 | 141 | typedef struct IQUEUEHEAD iqueue_head; 142 | 143 | 144 | //--------------------------------------------------------------------- 145 | // queue init 146 | //--------------------------------------------------------------------- 147 | #define IQUEUE_HEAD_INIT(name) { &(name), &(name) } 148 | #define IQUEUE_HEAD(name) \ 149 | struct IQUEUEHEAD name = IQUEUE_HEAD_INIT(name) 150 | 151 | #define IQUEUE_INIT(ptr) ( \ 152 | (ptr)->next = (ptr), (ptr)->prev = (ptr)) 153 | 154 | #define IOFFSETOF(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER) 155 | 156 | #define ICONTAINEROF(ptr, type, member) ( \ 157 | (type*)( ((char*)((type*)ptr)) - IOFFSETOF(type, member)) ) 158 | 159 | #define IQUEUE_ENTRY(ptr, type, member) ICONTAINEROF(ptr, type, member) 160 | 161 | 162 | //--------------------------------------------------------------------- 163 | // queue operation 164 | //--------------------------------------------------------------------- 165 | #define IQUEUE_ADD(node, head) ( \ 166 | (node)->prev = (head), (node)->next = (head)->next, \ 167 | (head)->next->prev = (node), (head)->next = (node)) 168 | 169 | #define IQUEUE_ADD_TAIL(node, head) ( \ 170 | (node)->prev = (head)->prev, (node)->next = (head), \ 171 | (head)->prev->next = (node), (head)->prev = (node)) 172 | 173 | #define IQUEUE_DEL_BETWEEN(p, n) ((n)->prev = (p), (p)->next = (n)) 174 | 175 | #define IQUEUE_DEL(entry) (\ 176 | (entry)->next->prev = (entry)->prev, \ 177 | (entry)->prev->next = (entry)->next, \ 178 | (entry)->next = 0, (entry)->prev = 0) 179 | 180 | #define IQUEUE_DEL_INIT(entry) do { \ 181 | IQUEUE_DEL(entry); IQUEUE_INIT(entry); } while (0) 182 | 183 | #define IQUEUE_IS_EMPTY(entry) ((entry) == (entry)->next) 184 | 185 | #define iqueue_init IQUEUE_INIT 186 | #define iqueue_entry IQUEUE_ENTRY 187 | #define iqueue_add IQUEUE_ADD 188 | #define iqueue_add_tail IQUEUE_ADD_TAIL 189 | #define iqueue_del IQUEUE_DEL 190 | #define iqueue_del_init IQUEUE_DEL_INIT 191 | #define iqueue_is_empty IQUEUE_IS_EMPTY 192 | 193 | #define IQUEUE_FOREACH(iterator, head, TYPE, MEMBER) \ 194 | for ((iterator) = iqueue_entry((head)->next, TYPE, MEMBER); \ 195 | &((iterator)->MEMBER) != (head); \ 196 | (iterator) = iqueue_entry((iterator)->MEMBER.next, TYPE, MEMBER)) 197 | 198 | #define iqueue_foreach(iterator, head, TYPE, MEMBER) \ 199 | IQUEUE_FOREACH(iterator, head, TYPE, MEMBER) 200 | 201 | #define iqueue_foreach_entry(pos, head) \ 202 | for( (pos) = (head)->next; (pos) != (head) ; (pos) = (pos)->next ) 203 | 204 | 205 | #define __iqueue_splice(list, head) do { \ 206 | iqueue_head *first = (list)->next, *last = (list)->prev; \ 207 | iqueue_head *at = (head)->next; \ 208 | (first)->prev = (head), (head)->next = (first); \ 209 | (last)->next = (at), (at)->prev = (last); } while (0) 210 | 211 | #define iqueue_splice(list, head) do { \ 212 | if (!iqueue_is_empty(list)) __iqueue_splice(list, head); } while (0) 213 | 214 | #define iqueue_splice_init(list, head) do { \ 215 | iqueue_splice(list, head); iqueue_init(list); } while (0) 216 | 217 | 218 | #ifdef _MSC_VER 219 | #pragma warning(disable:4311) 220 | #pragma warning(disable:4312) 221 | #pragma warning(disable:4996) 222 | #endif 223 | 224 | #endif 225 | 226 | 227 | //--------------------------------------------------------------------- 228 | // WORD ORDER 229 | //--------------------------------------------------------------------- 230 | #ifndef IWORDS_BIG_ENDIAN 231 | #ifdef _BIG_ENDIAN_ 232 | #if _BIG_ENDIAN_ 233 | #define IWORDS_BIG_ENDIAN 1 234 | #endif 235 | #endif 236 | #ifndef IWORDS_BIG_ENDIAN 237 | #if defined(__hppa__) || \ 238 | defined(__m68k__) || defined(mc68000) || defined(_M_M68K) || \ 239 | (defined(__MIPS__) && defined(__MIPSEB__)) || \ 240 | defined(__ppc__) || defined(__POWERPC__) || defined(_M_PPC) || \ 241 | defined(__sparc__) || defined(__powerpc__) || \ 242 | defined(__mc68000__) || defined(__s390x__) || defined(__s390__) 243 | #define IWORDS_BIG_ENDIAN 1 244 | #endif 245 | #endif 246 | #ifndef IWORDS_BIG_ENDIAN 247 | #define IWORDS_BIG_ENDIAN 0 248 | #endif 249 | #endif 250 | 251 | 252 | 253 | //===================================================================== 254 | // SEGMENT 255 | //===================================================================== 256 | struct IKCPSEG 257 | { 258 | struct IQUEUEHEAD node; 259 | IUINT32 conv; 260 | IUINT32 cmd; 261 | IUINT32 frg; 262 | IUINT32 wnd; 263 | IUINT32 ts; 264 | IUINT32 sn; 265 | IUINT32 una; 266 | IUINT32 len; 267 | IUINT32 resendts; 268 | IUINT32 rto; 269 | IUINT32 fastack; 270 | IUINT32 xmit; 271 | char data[1]; 272 | }; 273 | 274 | 275 | //--------------------------------------------------------------------- 276 | // IKCPCB 277 | //--------------------------------------------------------------------- 278 | struct IKCPCB 279 | { 280 | IUINT32 conv, mtu, mss, state; 281 | IUINT32 snd_una, snd_nxt, rcv_nxt; 282 | IUINT32 ts_recent, ts_lastack, ssthresh; 283 | IINT32 rx_rttval, rx_srtt, rx_rto, rx_minrto; 284 | IUINT32 snd_wnd, rcv_wnd, rmt_wnd, cwnd, probe; 285 | IUINT32 current, interval, ts_flush, xmit; 286 | IUINT32 nrcv_buf, nsnd_buf; 287 | IUINT32 nrcv_que, nsnd_que; 288 | IUINT32 nodelay, updated; 289 | IUINT32 ts_probe, probe_wait; 290 | IUINT32 dead_link, incr; 291 | struct IQUEUEHEAD snd_queue; 292 | struct IQUEUEHEAD rcv_queue; 293 | struct IQUEUEHEAD snd_buf; 294 | struct IQUEUEHEAD rcv_buf; 295 | IUINT32 *acklist; 296 | IUINT32 ackcount; 297 | IUINT32 ackblock; 298 | void *user; 299 | char *buffer; 300 | int fastresend; 301 | int nocwnd, stream; 302 | int logmask; 303 | int (*output)(const char *buf, int len, struct IKCPCB *kcp, void *user); 304 | void (*writelog)(const char *log, struct IKCPCB *kcp, void *user); 305 | }; 306 | 307 | 308 | typedef struct IKCPCB ikcpcb; 309 | 310 | #define IKCP_LOG_OUTPUT 1 311 | #define IKCP_LOG_INPUT 2 312 | #define IKCP_LOG_SEND 4 313 | #define IKCP_LOG_RECV 8 314 | #define IKCP_LOG_IN_DATA 16 315 | #define IKCP_LOG_IN_ACK 32 316 | #define IKCP_LOG_IN_PROBE 64 317 | #define IKCP_LOG_IN_WINS 128 318 | #define IKCP_LOG_OUT_DATA 256 319 | #define IKCP_LOG_OUT_ACK 512 320 | #define IKCP_LOG_OUT_PROBE 1024 321 | #define IKCP_LOG_OUT_WINS 2048 322 | 323 | #ifdef __cplusplus 324 | extern "C" { 325 | #endif 326 | 327 | //--------------------------------------------------------------------- 328 | // interface 329 | //--------------------------------------------------------------------- 330 | 331 | // create a new kcp control object, 'conv' must equal in two endpoint 332 | // from the same connection. 'user' will be passed to the output callback 333 | // output callback can be setup like this: 'kcp->output = my_udp_output' 334 | #if defined(_MSC_VER) 335 | // create a new kcp control object, 'conv' must equal in two endpoint 336 | // from the same connection. 'user' will be passed to the output callback 337 | // output callback can be setup like this: 'kcp->output = my_udp_output' 338 | __declspec(dllexport) ikcpcb* ikcp_create(IUINT32 conv, void *user); 339 | 340 | // release kcp control object 341 | __declspec(dllexport) void ikcp_release(ikcpcb *kcp); 342 | 343 | // set output callback, which will be invoked by kcp 344 | __declspec(dllexport) void ikcp_setoutput(ikcpcb *kcp, int (*output)(const char *buf, int len, 345 | ikcpcb *kcp, void *user)); 346 | 347 | // user/upper level recv: returns size, returns below zero for EAGAIN 348 | __declspec(dllexport) int ikcp_recv(ikcpcb *kcp, char *buffer, int len); 349 | 350 | // user/upper level send, returns below zero for error 351 | __declspec(dllexport) int ikcp_send(ikcpcb *kcp, const char *buffer, int len); 352 | 353 | // update state (call it repeatedly, every 10ms-100ms), or you can ask 354 | // ikcp_check when to call it again (without ikcp_input/_send calling). 355 | // 'current' - current timestamp in millisec. 356 | __declspec(dllexport) void ikcp_update(ikcpcb *kcp, IUINT32 current); 357 | 358 | // Determine when should you invoke ikcp_update: 359 | // returns when you should invoke ikcp_update in millisec, if there 360 | // is no ikcp_input/_send calling. you can call ikcp_update in that 361 | // time, instead of call update repeatly. 362 | // Important to reduce unnacessary ikcp_update invoking. use it to 363 | // schedule ikcp_update (eg. implementing an epoll-like mechanism, 364 | // or optimize ikcp_update when handling massive kcp connections) 365 | __declspec(dllexport) IUINT32 ikcp_check(const ikcpcb *kcp, IUINT32 current); 366 | 367 | // when you received a low level packet (eg. UDP packet), call it 368 | __declspec(dllexport) int ikcp_input(ikcpcb *kcp, const char *data, long size); 369 | 370 | // flush pending data 371 | __declspec(dllexport) void ikcp_flush(ikcpcb *kcp); 372 | 373 | // check the size of next message in the recv queue 374 | __declspec(dllexport) int ikcp_peeksize(const ikcpcb *kcp); 375 | 376 | // change MTU size, default is 1400 377 | __declspec(dllexport) int ikcp_setmtu(ikcpcb *kcp, int mtu); 378 | 379 | // set maximum window size: sndwnd=32, rcvwnd=32 by default 380 | __declspec(dllexport) int ikcp_wndsize(ikcpcb *kcp, int sndwnd, int rcvwnd); 381 | 382 | // get how many packet is waiting to be sent 383 | __declspec(dllexport) int ikcp_waitsnd(const ikcpcb *kcp); 384 | 385 | // fastest: ikcp_nodelay(kcp, 1, 20, 2, 1) 386 | // nodelay: 0:disable(default), 1:enable 387 | // interval: internal update timer interval in millisec, default is 100ms 388 | // resend: 0:disable fast resend(default), 1:enable fast resend 389 | // nc: 0:normal congestion control(default), 1:disable congestion control 390 | __declspec(dllexport) int ikcp_nodelay(ikcpcb *kcp, int nodelay, int interval, int resend, int nc); 391 | 392 | __declspec(dllexport) int ikcp_getminrto(ikcpcb *kcp); 393 | __declspec(dllexport) void ikcp_setminrto(ikcpcb *kcp, int minrto); 394 | 395 | __declspec(dllexport) int ikcp_get_interval(ikcpcb *kcp); 396 | 397 | __declspec(dllexport) int ikcp_rcvbuf_count(const ikcpcb *kcp); 398 | __declspec(dllexport) int ikcp_sndbuf_count(const ikcpcb *kcp); 399 | 400 | __declspec(dllexport) void ikcp_log(ikcpcb *kcp, int mask, const char *fmt, ...); 401 | 402 | // setup allocator 403 | __declspec(dllexport) void ikcp_allocator(void* (*new_malloc)(size_t), void (*new_free)(void*)); 404 | 405 | // read conv 406 | __declspec(dllexport) IUINT32 ikcp_getconv(const void *ptr); 407 | 408 | #else 409 | ikcpcb* ikcp_create(IUINT32 conv, void *user); 410 | 411 | // release kcp control object 412 | void ikcp_release(ikcpcb *kcp); 413 | 414 | // set output callback, which will be invoked by kcp 415 | void ikcp_setoutput(ikcpcb *kcp, int (*output)(const char *buf, int len, 416 | ikcpcb *kcp, void *user)); 417 | 418 | // user/upper level recv: returns size, returns below zero for EAGAIN 419 | int ikcp_recv(ikcpcb *kcp, char *buffer, int len); 420 | 421 | // user/upper level send, returns below zero for error 422 | int ikcp_send(ikcpcb *kcp, const char *buffer, int len); 423 | 424 | // update state (call it repeatedly, every 10ms-100ms), or you can ask 425 | // ikcp_check when to call it again (without ikcp_input/_send calling). 426 | // 'current' - current timestamp in millisec. 427 | void ikcp_update(ikcpcb *kcp, IUINT32 current); 428 | 429 | // Determine when should you invoke ikcp_update: 430 | // returns when you should invoke ikcp_update in millisec, if there 431 | // is no ikcp_input/_send calling. you can call ikcp_update in that 432 | // time, instead of call update repeatly. 433 | // Important to reduce unnacessary ikcp_update invoking. use it to 434 | // schedule ikcp_update (eg. implementing an epoll-like mechanism, 435 | // or optimize ikcp_update when handling massive kcp connections) 436 | IUINT32 ikcp_check(const ikcpcb *kcp, IUINT32 current); 437 | 438 | // when you received a low level packet (eg. UDP packet), call it 439 | int ikcp_input(ikcpcb *kcp, const char *data, long size); 440 | 441 | // flush pending data 442 | void ikcp_flush(ikcpcb *kcp); 443 | 444 | // check the size of next message in the recv queue 445 | int ikcp_peeksize(const ikcpcb *kcp); 446 | 447 | // change MTU size, default is 1400 448 | int ikcp_setmtu(ikcpcb *kcp, int mtu); 449 | 450 | // set maximum window size: sndwnd=32, rcvwnd=32 by default 451 | int ikcp_wndsize(ikcpcb *kcp, int sndwnd, int rcvwnd); 452 | 453 | // get how many packet is waiting to be sent 454 | int ikcp_waitsnd(const ikcpcb *kcp); 455 | 456 | // fastest: ikcp_nodelay(kcp, 1, 20, 2, 1) 457 | // nodelay: 0:disable(default), 1:enable 458 | // interval: internal update timer interval in millisec, default is 100ms 459 | // resend: 0:disable fast resend(default), 1:enable fast resend 460 | // nc: 0:normal congestion control(default), 1:disable congestion control 461 | int ikcp_nodelay(ikcpcb *kcp, int nodelay, int interval, int resend, int nc); 462 | 463 | 464 | int ikcp_get_interval(ikcpcb *kcp); 465 | 466 | void ikcp_log(ikcpcb *kcp, int mask, const char *fmt, ...); 467 | 468 | // setup allocator 469 | void ikcp_allocator(void* (*new_malloc)(size_t), void (*new_free)(void*)); 470 | 471 | // read conv 472 | IUINT32 ikcp_getconv(const void *ptr); 473 | 474 | #endif 475 | #ifdef __cplusplus 476 | } 477 | #endif 478 | 479 | #endif 480 | 481 | 482 | -------------------------------------------------------------------------------- /KCP_PInvoke/kcp_native_dll/kcpdll.vcxproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug_x64 6 | Win32 7 | 8 | 9 | Debug_x64 10 | x64 11 | 12 | 13 | Debug 14 | Win32 15 | 16 | 17 | Debug 18 | x64 19 | 20 | 21 | Release_x64 22 | Win32 23 | 24 | 25 | Release_x64 26 | x64 27 | 28 | 29 | Release 30 | Win32 31 | 32 | 33 | Release 34 | x64 35 | 36 | 37 | 38 | ikcp 39 | {2A9A14A1-0AFD-42E7-AEA4-D7A2F58C0391} 40 | kcpdll 41 | Win32Proj 42 | 43 | 44 | 45 | DynamicLibrary 46 | v140 47 | Unicode 48 | true 49 | 50 | 51 | DynamicLibrary 52 | v140 53 | Unicode 54 | 55 | 56 | DynamicLibrary 57 | v140 58 | Unicode 59 | true 60 | 61 | 62 | DynamicLibrary 63 | v140 64 | Unicode 65 | 66 | 67 | DynamicLibrary 68 | v140 69 | Unicode 70 | true 71 | 72 | 73 | DynamicLibrary 74 | v140 75 | Unicode 76 | 77 | 78 | DynamicLibrary 79 | v140 80 | Unicode 81 | true 82 | 83 | 84 | DynamicLibrary 85 | v140 86 | Unicode 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | <_ProjectFileVersion>14.0.25420.1 118 | 119 | 120 | $(SolutionDir)$(Configuration)\ 121 | $(Configuration)\ 122 | true 123 | 124 | 125 | $(SolutionDir)$(Platform)\$(Configuration)\ 126 | $(Platform)\$(Configuration)\ 127 | true 128 | 129 | 130 | $(SolutionDir)$(Configuration)\ 131 | $(Configuration)\ 132 | false 133 | 134 | 135 | $(SolutionDir)$(Platform)\$(Configuration)\ 136 | $(Platform)\$(Configuration)\ 137 | false 138 | 139 | 140 | $(SolutionDir)$(Configuration)\ 141 | $(Configuration)\ 142 | true 143 | 144 | 145 | $(SolutionDir)$(Platform)\$(Configuration)\ 146 | $(Platform)\$(Configuration)\ 147 | true 148 | 149 | 150 | $(SolutionDir)$(Configuration)\ 151 | $(Configuration)\ 152 | false 153 | 154 | 155 | $(SolutionDir)$(Platform)\$(Configuration)\ 156 | $(Platform)\$(Configuration)\ 157 | false 158 | 159 | 160 | 161 | Disabled 162 | WIN32;_DEBUG;_WINDOWS;_USRDLL;KCPDLL_EXPORTS;%(PreprocessorDefinitions) 163 | true 164 | EnableFastChecks 165 | MultiThreadedDebugDLL 166 | Use 167 | Level3 168 | EditAndContinue 169 | CompileAsC 170 | 171 | 172 | true 173 | Windows 174 | MachineX86 175 | 176 | 177 | 178 | 179 | X64 180 | 181 | 182 | Disabled 183 | WIN32;_DEBUG;_WINDOWS;_USRDLL;KCPDLL_EXPORTS;%(PreprocessorDefinitions) 184 | true 185 | EnableFastChecks 186 | MultiThreadedDebugDLL 187 | Use 188 | Level3 189 | ProgramDatabase 190 | CompileAsC 191 | 192 | 193 | true 194 | Windows 195 | MachineX64 196 | 197 | 198 | 199 | 200 | MaxSpeed 201 | true 202 | WIN32;NDEBUG;_WINDOWS;_USRDLL;KCPDLL_EXPORTS;%(PreprocessorDefinitions) 203 | MultiThreadedDLL 204 | true 205 | 206 | Level3 207 | ProgramDatabase 208 | CompileAsC 209 | 210 | 211 | true 212 | Windows 213 | true 214 | true 215 | MachineX86 216 | 217 | 218 | 219 | 220 | X64 221 | 222 | 223 | MaxSpeed 224 | true 225 | WIN32;NDEBUG;_WINDOWS;_USRDLL;KCPDLL_EXPORTS;%(PreprocessorDefinitions) 226 | MultiThreadedDLL 227 | true 228 | NotUsing 229 | Level3 230 | ProgramDatabase 231 | 232 | 233 | true 234 | Windows 235 | true 236 | true 237 | MachineX64 238 | 239 | 240 | 241 | 242 | Disabled 243 | WIN32;_DEBUG;_WINDOWS;_USRDLL;KCPDLL_EXPORTS;%(PreprocessorDefinitions) 244 | true 245 | EnableFastChecks 246 | MultiThreadedDebugDLL 247 | 248 | Level3 249 | EditAndContinue 250 | CompileAsC 251 | 252 | 253 | true 254 | Windows 255 | MachineX86 256 | 257 | 258 | 259 | 260 | X64 261 | 262 | 263 | Disabled 264 | WIN32;_DEBUG;_WINDOWS;_USRDLL;KCPDLL_EXPORTS;%(PreprocessorDefinitions) 265 | true 266 | EnableFastChecks 267 | MultiThreadedDebugDLL 268 | Use 269 | Level3 270 | ProgramDatabase 271 | CompileAsC 272 | 273 | 274 | true 275 | Windows 276 | MachineX64 277 | 278 | 279 | 280 | 281 | MaxSpeed 282 | true 283 | WIN32;NDEBUG;_WINDOWS;_USRDLL;KCPDLL_EXPORTS;%(PreprocessorDefinitions) 284 | MultiThreadedDLL 285 | true 286 | 287 | Level3 288 | ProgramDatabase 289 | 290 | 291 | true 292 | Windows 293 | true 294 | true 295 | MachineX86 296 | 297 | 298 | 299 | 300 | X64 301 | 302 | 303 | MaxSpeed 304 | true 305 | WIN32;NDEBUG;_WINDOWS;_USRDLL;KCPDLL_EXPORTS;%(PreprocessorDefinitions) 306 | MultiThreadedDLL 307 | true 308 | NotUsing 309 | Level3 310 | ProgramDatabase 311 | CompileAsC 312 | 313 | 314 | true 315 | Windows 316 | true 317 | true 318 | MachineX64 319 | 320 | 321 | 322 | 323 | 324 | 325 | false 326 | 327 | 328 | false 329 | 330 | 331 | false 332 | 333 | 334 | false 335 | 336 | 337 | false 338 | 339 | 340 | false 341 | 342 | 343 | false 344 | 345 | 346 | false 347 | 348 | 349 | 350 | Create 351 | Create 352 | Create 353 | Create 354 | Create 355 | Create 356 | Create 357 | Create 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | -------------------------------------------------------------------------------- /common/kcp.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Text; 6 | 7 | 8 | public class KCP 9 | { 10 | public const int IKCP_RTO_NDL = 30; // no delay min rto 11 | public const int IKCP_RTO_MIN = 100; // normal min rto 12 | public const int IKCP_RTO_DEF = 200; 13 | public const int IKCP_RTO_MAX = 60000; 14 | public const int IKCP_CMD_PUSH = 81; // cmd: push data 15 | public const int IKCP_CMD_ACK = 82; // cmd: ack 16 | public const int IKCP_CMD_WASK = 83; // cmd: window probe (ask) 17 | public const int IKCP_CMD_WINS = 84; // cmd: window size (tell) 18 | public const int IKCP_ASK_SEND = 1; // need to send IKCP_CMD_WASK 19 | public const int IKCP_ASK_TELL = 2; // need to send IKCP_CMD_WINS 20 | public const int IKCP_WND_SND = 32; 21 | public const int IKCP_WND_RCV = 32; 22 | public const int IKCP_MTU_DEF = 1400; 23 | public const int IKCP_ACK_FAST = 3; 24 | public const int IKCP_INTERVAL = 100; 25 | public const int IKCP_OVERHEAD = 24; 26 | public const int IKCP_DEADLINK = 10; 27 | public const int IKCP_THRESH_INIT = 2; 28 | public const int IKCP_THRESH_MIN = 2; 29 | public const int IKCP_PROBE_INIT = 7000; // 7 secs to probe window size 30 | public const int IKCP_PROBE_LIMIT = 120000; // up to 120 secs to probe window 31 | 32 | 33 | // encode 8 bits unsigned int 34 | public static int ikcp_encode8u(byte[] p, int offset, byte c) 35 | { 36 | p[0 + offset] = c; 37 | return 1; 38 | } 39 | 40 | // decode 8 bits unsigned int 41 | public static int ikcp_decode8u(byte[] p, int offset, ref byte c) 42 | { 43 | c = p[0 + offset]; 44 | return 1; 45 | } 46 | 47 | /* encode 16 bits unsigned int (lsb) */ 48 | public static int ikcp_encode16u(byte[] p, int offset, UInt16 w) 49 | { 50 | p[0 + offset] = (byte)(w >> 0); 51 | p[1 + offset] = (byte)(w >> 8); 52 | return 2; 53 | } 54 | 55 | /* decode 16 bits unsigned int (lsb) */ 56 | public static int ikcp_decode16u(byte[] p, int offset, ref UInt16 c) 57 | { 58 | UInt16 result = 0; 59 | result |= (UInt16)p[0 + offset]; 60 | result |= (UInt16)(p[1 + offset] << 8); 61 | c = result; 62 | return 2; 63 | } 64 | 65 | /* encode 32 bits unsigned int (lsb) */ 66 | public static int ikcp_encode32u(byte[] p, int offset, UInt32 l) 67 | { 68 | p[0 + offset] = (byte)(l >> 0); 69 | p[1 + offset] = (byte)(l >> 8); 70 | p[2 + offset] = (byte)(l >> 16); 71 | p[3 + offset] = (byte)(l >> 24); 72 | return 4; 73 | } 74 | 75 | /* decode 32 bits unsigned int (lsb) */ 76 | public static int ikcp_decode32u(byte[] p, int offset, ref UInt32 c) 77 | { 78 | UInt32 result = 0; 79 | result |= (UInt32)p[0 + offset]; 80 | result |= (UInt32)(p[1 + offset] << 8); 81 | result |= (UInt32)(p[2 + offset] << 16); 82 | result |= (UInt32)(p[3 + offset] << 24); 83 | c = result; 84 | return 4; 85 | } 86 | 87 | public static byte[] slice(byte[] p, int start, int stop) { 88 | var bytes = new byte[stop - start]; 89 | Array.Copy(p, start, bytes, 0, bytes.Length); 90 | return bytes; 91 | } 92 | 93 | public static T[] slice(T[] p, int start, int stop) { 94 | var arr = new T[stop - start]; 95 | var index = 0; 96 | for (var i = start; i < stop; i++) 97 | { 98 | arr[index] = p[i]; 99 | index++; 100 | } 101 | 102 | return arr; 103 | } 104 | 105 | public static byte[] append(byte[] p, byte c) { 106 | var bytes = new byte[p.Length + 1]; 107 | Array.Copy(p, bytes, p.Length); 108 | bytes[p.Length] = c; 109 | return bytes; 110 | } 111 | 112 | public static T[] append(T[] p, T c) { 113 | var arr = new T[p.Length + 1]; 114 | for (var i = 0; i < p.Length; i++) 115 | arr[i] = p[i]; 116 | arr[p.Length] = c; 117 | return arr; 118 | } 119 | 120 | public static T[] append(T[] p, T[] cs) 121 | { 122 | var arr = new T[p.Length + cs.Length]; 123 | for (var i = 0; i < p.Length; i++) 124 | arr[i] = p[i]; 125 | for (var i = 0; i < cs.Length; i++ ) 126 | arr[p.Length+i] = cs[i]; 127 | return arr; 128 | } 129 | 130 | static UInt32 _imin_(UInt32 a, UInt32 b) 131 | { 132 | return a <= b ? a : b; 133 | } 134 | 135 | static UInt32 _imax_(UInt32 a, UInt32 b) 136 | { 137 | return a >= b ? a : b; 138 | } 139 | 140 | static UInt32 _ibound_(UInt32 lower, UInt32 middle, UInt32 upper) 141 | { 142 | return _imin_(_imax_(lower, middle), upper); 143 | } 144 | 145 | static Int32 _itimediff(UInt32 later, UInt32 earlier) 146 | { 147 | return ((Int32)(later - earlier)); 148 | } 149 | 150 | // KCP Segment Definition 151 | internal class Segment { 152 | internal UInt32 conv = 0; 153 | internal UInt32 cmd = 0; 154 | internal UInt32 frg = 0; 155 | internal UInt32 wnd = 0; 156 | internal UInt32 ts = 0; 157 | internal UInt32 sn = 0; 158 | internal UInt32 una = 0; 159 | internal UInt32 resendts = 0; 160 | internal UInt32 rto = 0; 161 | internal UInt32 fastack = 0; 162 | internal UInt32 xmit = 0; 163 | internal byte[] data; 164 | 165 | internal Segment(int size) 166 | { 167 | this.data = new byte[size]; 168 | } 169 | 170 | // encode a segment into buffer 171 | internal int encode(byte[] ptr, int offset) { 172 | 173 | var offset_ = offset; 174 | 175 | offset += ikcp_encode32u(ptr, offset, conv); 176 | offset += ikcp_encode8u(ptr, offset, (byte)cmd); 177 | offset += ikcp_encode8u(ptr, offset, (byte)frg); 178 | offset += ikcp_encode16u(ptr, offset, (UInt16)wnd); 179 | offset += ikcp_encode32u(ptr, offset, ts); 180 | offset += ikcp_encode32u(ptr, offset, sn); 181 | offset += ikcp_encode32u(ptr, offset, una); 182 | offset += ikcp_encode32u(ptr, offset, (UInt32)data.Length); 183 | 184 | return offset - offset_; 185 | } 186 | } 187 | 188 | // kcp members. 189 | UInt32 conv; UInt32 mtu; UInt32 mss; UInt32 state; 190 | UInt32 snd_una; UInt32 snd_nxt; UInt32 rcv_nxt; 191 | UInt32 ts_recent; UInt32 ts_lastack; UInt32 ssthresh; 192 | UInt32 rx_rttval; UInt32 rx_srtt; UInt32 rx_rto; UInt32 rx_minrto; 193 | UInt32 snd_wnd; UInt32 rcv_wnd; UInt32 rmt_wnd; UInt32 cwnd; UInt32 probe; 194 | UInt32 current; UInt32 interval; UInt32 ts_flush; UInt32 xmit; 195 | UInt32 nodelay; UInt32 updated; 196 | UInt32 ts_probe; UInt32 probe_wait; 197 | UInt32 dead_link; UInt32 incr; 198 | 199 | Segment[] snd_queue = new Segment[0]; 200 | Segment[] rcv_queue = new Segment[0]; 201 | Segment[] snd_buf = new Segment[0]; 202 | Segment[] rcv_buf = new Segment[0]; 203 | 204 | UInt32[] acklist = new UInt32[0]; 205 | 206 | byte[] buffer; 207 | Int32 fastresend; 208 | Int32 nocwnd; 209 | Int32 logmask; 210 | // buffer, size 211 | Action output; 212 | 213 | // create a new kcp control object, 'conv' must equal in two endpoint 214 | // from the same connection. 215 | public KCP(UInt32 conv_, Action output_) { 216 | conv = conv_; 217 | snd_wnd = IKCP_WND_SND; 218 | rcv_wnd = IKCP_WND_RCV; 219 | rmt_wnd = IKCP_WND_RCV; 220 | mtu = IKCP_MTU_DEF; 221 | mss = mtu - IKCP_OVERHEAD; 222 | 223 | rx_rto = IKCP_RTO_DEF; 224 | rx_minrto = IKCP_RTO_MIN; 225 | interval = IKCP_INTERVAL; 226 | ts_flush = IKCP_INTERVAL; 227 | ssthresh = IKCP_THRESH_INIT; 228 | dead_link = IKCP_DEADLINK; 229 | buffer = new byte[(mtu+IKCP_OVERHEAD)*3]; 230 | output = output_; 231 | } 232 | 233 | // check the size of next message in the recv queue 234 | public int PeekSize() { 235 | 236 | if (0 == rcv_queue.Length) return -1; 237 | 238 | var seq = rcv_queue[0]; 239 | 240 | if (0 == seq.frg) return seq.data.Length; 241 | 242 | if (rcv_queue.Length < seq.frg + 1) return -1; 243 | 244 | int length = 0; 245 | 246 | foreach (var item in rcv_queue) { 247 | length += item.data.Length; 248 | if (0 == item.frg) 249 | break; 250 | } 251 | 252 | return length; 253 | } 254 | 255 | // user/upper level recv: returns size, returns below zero for EAGAIN 256 | public int Recv(byte[] buffer) { 257 | 258 | if (0 == rcv_queue.Length) return -1; 259 | 260 | var peekSize = PeekSize(); 261 | if (0 > peekSize) return -2; 262 | 263 | if (peekSize > buffer.Length) return -3; 264 | 265 | var fast_recover = false; 266 | if (rcv_queue.Length >= rcv_wnd) fast_recover = true; 267 | 268 | // merge fragment. 269 | var count = 0; 270 | var n = 0; 271 | foreach (var seg in rcv_queue) { 272 | Array.Copy(seg.data, 0, buffer, n, seg.data.Length); 273 | n += seg.data.Length; 274 | count++; 275 | if (0 == seg.frg) break; 276 | } 277 | 278 | if (0 < count) { 279 | rcv_queue = slice(rcv_queue, count, rcv_queue.Length); 280 | } 281 | 282 | // move available data from rcv_buf -> rcv_queue 283 | count = 0; 284 | foreach (var seg in rcv_buf) { 285 | if (seg.sn == rcv_nxt && rcv_queue.Length < rcv_wnd) { 286 | rcv_queue = append(rcv_queue, seg); 287 | rcv_nxt++; 288 | count++; 289 | } else { 290 | break; 291 | } 292 | } 293 | 294 | if(0 < count) rcv_buf = slice(rcv_buf, count, rcv_buf.Length); 295 | 296 | // fast recover 297 | if (rcv_queue.Length < rcv_wnd && fast_recover) { 298 | // ready to send back IKCP_CMD_WINS in ikcp_flush 299 | // tell remote my window size 300 | probe |= IKCP_ASK_TELL; 301 | } 302 | 303 | return n; 304 | } 305 | 306 | // user/upper level send, returns below zero for error 307 | public int Send(byte[] buffer) { 308 | 309 | if (0 == buffer.Length) return -1; 310 | 311 | var count = 0; 312 | 313 | if (buffer.Length < mss) 314 | count = 1; 315 | else 316 | count = (int)(buffer.Length + mss - 1) / (int)mss; 317 | 318 | if (255 < count) return -2; 319 | 320 | if (0 == count) count = 1; 321 | 322 | var offset = 0; 323 | 324 | for (var i = 0; i < count; i++) { 325 | var size = 0; 326 | if (buffer.Length - offset > mss) 327 | size = (int)mss; 328 | else 329 | size = buffer.Length - offset; 330 | 331 | var seg = new Segment(size); 332 | Array.Copy(buffer, offset, seg.data, 0, size); 333 | offset += size; 334 | seg.frg = (UInt32)(count - i - 1); 335 | snd_queue = append(snd_queue, seg); 336 | } 337 | 338 | return 0; 339 | } 340 | 341 | // update ack. 342 | void update_ack(Int32 rtt) 343 | { 344 | if (0 == rx_srtt) 345 | { 346 | rx_srtt = (UInt32)rtt; 347 | rx_rttval = (UInt32)rtt / 2; 348 | } 349 | else 350 | { 351 | Int32 delta = (Int32)((UInt32)rtt - rx_srtt); 352 | if (0 > delta) delta = -delta; 353 | 354 | rx_rttval = (3 * rx_rttval + (uint)delta) / 4; 355 | rx_srtt = (UInt32)((7 * rx_srtt + rtt) / 8); 356 | if (rx_srtt < 1) rx_srtt = 1; 357 | } 358 | 359 | var rto = (int)(rx_srtt + _imax_(1, 4 * rx_rttval)); 360 | rx_rto = _ibound_(rx_minrto, (UInt32)rto, IKCP_RTO_MAX); 361 | } 362 | 363 | void shrink_buf() { 364 | if (snd_buf.Length > 0) 365 | snd_una = snd_buf[0].sn; 366 | else 367 | snd_una = snd_nxt; 368 | } 369 | 370 | void parse_ack(UInt32 sn) { 371 | 372 | if (_itimediff(sn, snd_una) < 0 || _itimediff(sn, snd_nxt) >= 0) return; 373 | 374 | var index = 0; 375 | foreach (var seg in snd_buf) { 376 | if (sn == seg.sn) 377 | { 378 | snd_buf = append(slice(snd_buf, 0, index), slice(snd_buf, index + 1, snd_buf.Length)); 379 | break; 380 | } 381 | else 382 | { 383 | seg.fastack++; 384 | } 385 | 386 | index++; 387 | } 388 | } 389 | 390 | void parse_una(UInt32 una) { 391 | var count = 0; 392 | foreach (var seg in snd_buf) { 393 | if (_itimediff(una, seg.sn) > 0) 394 | count++; 395 | else 396 | break; 397 | } 398 | 399 | if (0 < count) snd_buf = slice(snd_buf, count, snd_buf.Length); 400 | } 401 | 402 | void ack_push(UInt32 sn, UInt32 ts) { 403 | acklist = append(acklist, new UInt32[2]{sn, ts}); 404 | } 405 | 406 | void ack_get(int p, ref UInt32 sn, ref UInt32 ts) { 407 | sn = acklist[p * 2 + 0]; 408 | ts = acklist[p * 2 + 1]; 409 | } 410 | 411 | void parse_data(Segment newseg) { 412 | var sn = newseg.sn; 413 | if (_itimediff(sn, rcv_nxt + rcv_wnd) >= 0 || _itimediff(sn, rcv_nxt) < 0) return; 414 | 415 | var n = rcv_buf.Length - 1; 416 | var after_idx = -1; 417 | var repeat = false; 418 | for (var i = n; i >= 0; i--) { 419 | var seg = rcv_buf[i]; 420 | if (seg.sn == sn) { 421 | repeat = true; 422 | break; 423 | } 424 | 425 | if (_itimediff(sn, seg.sn) > 0) { 426 | after_idx = i; 427 | break; 428 | } 429 | } 430 | 431 | if (!repeat) { 432 | if (after_idx == -1) 433 | rcv_buf = append(new Segment[1] { newseg }, rcv_buf); 434 | else 435 | rcv_buf = append(slice(rcv_buf, 0, after_idx + 1), append(new Segment[1] { newseg }, slice(rcv_buf, after_idx + 1, rcv_buf.Length))); 436 | } 437 | 438 | // move available data from rcv_buf -> rcv_queue 439 | var count = 0; 440 | foreach (var seg in rcv_buf) { 441 | if (seg.sn == rcv_nxt && rcv_queue.Length < rcv_wnd) 442 | { 443 | rcv_queue = append(rcv_queue, seg); 444 | rcv_nxt++; 445 | count++; 446 | } 447 | else 448 | { 449 | break; 450 | } 451 | } 452 | 453 | if (0 < count) { 454 | rcv_buf = slice(rcv_buf, count, rcv_buf.Length); 455 | } 456 | } 457 | 458 | // when you received a low level packet (eg. UDP packet), call it 459 | public int Input(byte[] data) { 460 | 461 | var s_una = snd_una; 462 | if (data.Length < IKCP_OVERHEAD) return 0; 463 | 464 | var offset = 0; 465 | 466 | while (true) 467 | { 468 | UInt32 ts = 0; 469 | UInt32 sn = 0; 470 | UInt32 length = 0; 471 | UInt32 una = 0; 472 | UInt32 conv_ = 0; 473 | 474 | UInt16 wnd = 0; 475 | 476 | byte cmd = 0; 477 | byte frg = 0; 478 | 479 | if (data.Length - offset < IKCP_OVERHEAD) break; 480 | 481 | offset += ikcp_decode32u(data, offset, ref conv_); 482 | 483 | if (conv != conv_) return -1; 484 | 485 | offset += ikcp_decode8u(data, offset, ref cmd); 486 | offset += ikcp_decode8u(data, offset, ref frg); 487 | offset += ikcp_decode16u(data, offset, ref wnd); 488 | offset += ikcp_decode32u(data, offset, ref ts); 489 | offset += ikcp_decode32u(data, offset, ref sn); 490 | offset += ikcp_decode32u(data, offset, ref una); 491 | offset += ikcp_decode32u(data, offset, ref length); 492 | 493 | if (data.Length - offset < length) return -2; 494 | 495 | switch (cmd) { 496 | case IKCP_CMD_PUSH: 497 | case IKCP_CMD_ACK: 498 | case IKCP_CMD_WASK: 499 | case IKCP_CMD_WINS: 500 | break; 501 | default: 502 | return -3; 503 | } 504 | 505 | rmt_wnd = (UInt32)wnd; 506 | parse_una(una); 507 | shrink_buf(); 508 | 509 | if (IKCP_CMD_ACK == cmd) { 510 | if (_itimediff(current, ts) >= 0) { 511 | update_ack(_itimediff(current, ts)); 512 | } 513 | parse_ack(sn); 514 | shrink_buf(); 515 | } 516 | else if (IKCP_CMD_PUSH == cmd) { 517 | if (_itimediff(sn, rcv_nxt + rcv_wnd) < 0) { 518 | ack_push(sn, ts); 519 | if (_itimediff(sn, rcv_nxt) >= 0) { 520 | var seg = new Segment((int)length); 521 | seg.conv = conv_; 522 | seg.cmd = (UInt32)cmd; 523 | seg.frg = (UInt32)frg; 524 | seg.wnd = (UInt32)wnd; 525 | seg.ts = ts; 526 | seg.sn = sn; 527 | seg.una = una; 528 | 529 | if (length > 0) Array.Copy(data, offset, seg.data, 0, length); 530 | 531 | parse_data(seg); 532 | } 533 | } 534 | } 535 | else if (IKCP_CMD_WASK == cmd) { 536 | // ready to send back IKCP_CMD_WINS in Ikcp_flush 537 | // tell remote my window size 538 | probe |= IKCP_ASK_TELL; 539 | } 540 | else if (IKCP_CMD_WINS == cmd) 541 | { 542 | // do nothing 543 | } 544 | else { 545 | return -3; 546 | } 547 | 548 | offset += (int)length; 549 | } 550 | 551 | if (_itimediff(snd_una, s_una) > 0) { 552 | if (cwnd < rmt_wnd) { 553 | var mss_ = mss; 554 | if (cwnd < ssthresh) 555 | { 556 | cwnd++; 557 | incr += mss_; 558 | } 559 | else { 560 | if(incr < mss_) { 561 | incr = mss_; 562 | } 563 | incr += (mss_ * mss_) / incr + (mss_ / 16); 564 | if ((cwnd + 1) * mss_ <= incr) cwnd++; 565 | } 566 | if (cwnd > rmt_wnd) { 567 | cwnd = rmt_wnd; 568 | incr = rmt_wnd * mss_; 569 | } 570 | } 571 | } 572 | 573 | return 0; 574 | } 575 | 576 | Int32 wnd_unused() { 577 | if (rcv_queue.Length < rcv_wnd) 578 | return (Int32)(int)rcv_wnd - rcv_queue.Length; 579 | return 0; 580 | } 581 | 582 | // flush pending data 583 | void flush() { 584 | var current_ = current; 585 | var buffer_ = buffer; 586 | var change = 0; 587 | var lost = 0; 588 | 589 | if (0 == updated) return; 590 | 591 | var seg = new Segment(0); 592 | seg.conv = conv; 593 | seg.cmd = IKCP_CMD_ACK; 594 | seg.wnd = (UInt32)wnd_unused(); 595 | seg.una = rcv_nxt; 596 | 597 | // flush acknowledges 598 | var count = acklist.Length / 2; 599 | var offset = 0; 600 | for (var i = 0; i < count; i++) { 601 | if (offset + IKCP_OVERHEAD > mtu) 602 | { 603 | output(buffer, offset); 604 | //Array.Clear(buffer, 0, offset); 605 | offset = 0; 606 | } 607 | ack_get(i, ref seg.sn, ref seg.ts); 608 | offset += seg.encode(buffer, offset); 609 | } 610 | acklist = new UInt32[0]; 611 | 612 | // probe window size (if remote window size equals zero) 613 | if (0 == rmt_wnd) 614 | { 615 | if (0 == probe_wait) 616 | { 617 | probe_wait = IKCP_PROBE_INIT; 618 | ts_probe = current + probe_wait; 619 | } 620 | else 621 | { 622 | if (_itimediff(current, ts_probe) >= 0) 623 | { 624 | if (probe_wait < IKCP_PROBE_INIT) 625 | probe_wait = IKCP_PROBE_INIT; 626 | probe_wait += probe_wait / 2; 627 | if (probe_wait > IKCP_PROBE_LIMIT) 628 | probe_wait = IKCP_PROBE_LIMIT; 629 | ts_probe = current + probe_wait; 630 | probe |= IKCP_ASK_SEND; 631 | } 632 | } 633 | } 634 | else { 635 | ts_probe = 0; 636 | probe_wait = 0; 637 | } 638 | 639 | // flush window probing commands 640 | if ((probe & IKCP_ASK_SEND) != 0) { 641 | seg.cmd = IKCP_CMD_WASK; 642 | if (offset + IKCP_OVERHEAD > (int)mtu) { 643 | output(buffer, offset); 644 | //Array.Clear(buffer, 0, offset); 645 | offset = 0; 646 | } 647 | offset += seg.encode(buffer, offset); 648 | } 649 | 650 | probe = 0; 651 | 652 | // calculate window size 653 | var cwnd_ = _imin_(snd_wnd, rmt_wnd); 654 | if (0 == nocwnd) 655 | cwnd_ = _imin_(cwnd, cwnd_); 656 | 657 | count = 0; 658 | for (var k = 0; k < snd_queue.Length; k++ ) 659 | { 660 | if (_itimediff(snd_nxt, snd_una + cwnd_) >= 0) break; 661 | 662 | var newseg = snd_queue[k]; 663 | newseg.conv = conv; 664 | newseg.cmd = IKCP_CMD_PUSH; 665 | newseg.wnd = seg.wnd; 666 | newseg.ts = current_; 667 | newseg.sn = snd_nxt; 668 | newseg.una = rcv_nxt; 669 | newseg.resendts = current_; 670 | newseg.rto = rx_rto; 671 | newseg.fastack = 0; 672 | newseg.xmit = 0; 673 | snd_buf = append(snd_buf, newseg); 674 | snd_nxt++; 675 | count++; 676 | } 677 | 678 | if (0 < count) { 679 | snd_queue = slice(snd_queue, count, snd_queue.Length); 680 | } 681 | 682 | // calculate resent 683 | var resent = (UInt32)fastresend; 684 | if (fastresend <= 0) resent = 0xffffffff; 685 | var rtomin = rx_rto >> 3; 686 | if(nodelay != 0) rtomin = 0; 687 | 688 | // flush data segments 689 | foreach (var segment in snd_buf) { 690 | var needsend = false; 691 | var debug = _itimediff(current_, segment.resendts); 692 | if (0 == segment.xmit) { 693 | needsend = true; 694 | segment.xmit++; 695 | segment.rto = rx_rto; 696 | segment.resendts = current_ + segment.rto + rtomin; 697 | } 698 | else if (_itimediff(current_, segment.resendts) >= 0) { 699 | needsend = true; 700 | segment.xmit++; 701 | xmit++; 702 | if (0 == nodelay) 703 | segment.rto += rx_rto; 704 | else 705 | segment.rto += rx_rto / 2; 706 | segment.resendts = current_ + segment.rto; 707 | lost = 1; 708 | } 709 | else if (segment.fastack >= resent) { 710 | needsend = true; 711 | segment.xmit++; 712 | segment.fastack = 0; 713 | segment.resendts = current_ + segment.rto; 714 | change++; 715 | } 716 | 717 | if (needsend) { 718 | segment.ts = current_; 719 | segment.wnd = seg.wnd; 720 | segment.una = rcv_nxt; 721 | 722 | var need = IKCP_OVERHEAD + segment.data.Length; 723 | if (offset + need > mtu) { 724 | output(buffer, offset); 725 | //Array.Clear(buffer, 0, offset); 726 | offset = 0; 727 | } 728 | 729 | offset += segment.encode(buffer, offset); 730 | if (segment.data.Length > 0) { 731 | Array.Copy(segment.data, 0, buffer, offset, segment.data.Length); 732 | offset += segment.data.Length; 733 | } 734 | 735 | if (segment.xmit >= dead_link) { 736 | state = 0; 737 | } 738 | } 739 | } 740 | 741 | // flash remain segments 742 | if (offset > 0) { 743 | output(buffer, offset); 744 | //Array.Clear(buffer, 0, offset); 745 | offset = 0; 746 | } 747 | 748 | // update ssthresh 749 | if (change != 0) { 750 | var inflight = snd_nxt - snd_una; 751 | ssthresh = inflight / 2; 752 | if (ssthresh < IKCP_THRESH_MIN) 753 | ssthresh = IKCP_THRESH_MIN; 754 | cwnd = ssthresh + resent; 755 | incr = cwnd * mss; 756 | } 757 | 758 | if (lost != 0) { 759 | ssthresh = cwnd / 2; 760 | if (ssthresh < IKCP_THRESH_MIN) 761 | ssthresh = IKCP_THRESH_MIN; 762 | cwnd = 1; 763 | incr = mss; 764 | } 765 | 766 | if (cwnd < 1) { 767 | cwnd = 1; 768 | incr = mss; 769 | } 770 | } 771 | 772 | // update state (call it repeatedly, every 10ms-100ms), or you can ask 773 | // ikcp_check when to call it again (without ikcp_input/_send calling). 774 | // 'current' - current timestamp in millisec. 775 | public void Update(UInt32 current_) 776 | { 777 | 778 | current = current_; 779 | 780 | if (0 == updated) { 781 | updated = 1; 782 | ts_flush = current; 783 | } 784 | 785 | var slap = _itimediff(current, ts_flush); 786 | 787 | if (slap >= 10000 || slap < -10000) { 788 | ts_flush = current; 789 | slap = 0; 790 | } 791 | 792 | if (slap >= 0) { 793 | ts_flush += interval; 794 | if (_itimediff(current, ts_flush) >= 0) 795 | ts_flush = current + interval; 796 | flush(); 797 | } 798 | } 799 | 800 | // Determine when should you invoke ikcp_update: 801 | // returns when you should invoke ikcp_update in millisec, if there 802 | // is no ikcp_input/_send calling. you can call ikcp_update in that 803 | // time, instead of call update repeatly. 804 | // Important to reduce unnacessary ikcp_update invoking. use it to 805 | // schedule ikcp_update (eg. implementing an epoll-like mechanism, 806 | // or optimize ikcp_update when handling massive kcp connections) 807 | public UInt32 Check(UInt32 current_) 808 | { 809 | 810 | if (0 == updated) return current_; 811 | 812 | var ts_flush_ = ts_flush; 813 | var tm_flush_ = 0x7fffffff; 814 | var tm_packet = 0x7fffffff; 815 | var minimal = 0; 816 | 817 | if (_itimediff(current_, ts_flush_) >= 10000 || _itimediff(current_, ts_flush_) < -10000) 818 | { 819 | ts_flush_ = current_; 820 | } 821 | 822 | if (_itimediff(current_, ts_flush_) >= 0) return current_; 823 | 824 | tm_flush_ = (int)_itimediff(ts_flush_, current_); 825 | 826 | foreach (var seg in snd_buf) { 827 | var diff = _itimediff(seg.resendts, current_); 828 | if (diff <= 0) return current_; 829 | if (diff < tm_packet) tm_packet = (int)diff; 830 | } 831 | 832 | minimal = (int)tm_packet; 833 | if (tm_packet >= tm_flush_) minimal = (int)tm_flush_; 834 | if (minimal >= interval) minimal = (int)interval; 835 | 836 | return current_ + (UInt32)minimal; 837 | } 838 | 839 | // change MTU size, default is 1400 840 | public int SetMtu(Int32 mtu_) 841 | { 842 | if (mtu_ < 50 || mtu_ < (Int32)IKCP_OVERHEAD) return -1; 843 | 844 | var buffer_ = new byte[(mtu_ + IKCP_OVERHEAD) * 3]; 845 | if (null == buffer_) return -2; 846 | 847 | mtu = (UInt32)mtu_; 848 | mss = mtu - IKCP_OVERHEAD; 849 | buffer = buffer_; 850 | return 0; 851 | } 852 | 853 | public int Interval(Int32 interval_) 854 | { 855 | if (interval_ > 5000) { 856 | interval_ = 5000; 857 | } 858 | else if (interval_ < 10) { 859 | interval_ = 10; 860 | } 861 | interval = (UInt32)interval_; 862 | return 0; 863 | } 864 | 865 | // fastest: ikcp_nodelay(kcp, 1, 20, 2, 1) 866 | // nodelay: 0:disable(default), 1:enable 867 | // interval: internal update timer interval in millisec, default is 100ms 868 | // resend: 0:disable fast resend(default), 1:enable fast resend 869 | // nc: 0:normal congestion control(default), 1:disable congestion control 870 | public int NoDelay(int nodelay_, int interval_, int resend_, int nc_) 871 | { 872 | 873 | if (nodelay_ > 0) { 874 | nodelay = (UInt32)nodelay_; 875 | if (nodelay_ != 0) 876 | rx_minrto = IKCP_RTO_NDL; 877 | else 878 | rx_minrto = IKCP_RTO_MIN; 879 | } 880 | 881 | if (interval_ >= 0) { 882 | if (interval_ > 5000) 883 | { 884 | interval_ = 5000; 885 | } 886 | else if (interval_ < 10) 887 | { 888 | interval_ = 10; 889 | } 890 | interval = (UInt32)interval_; 891 | } 892 | 893 | if (resend_ >= 0) fastresend = resend_; 894 | 895 | if (nc_ >= 0) nocwnd = nc_; 896 | 897 | return 0; 898 | } 899 | 900 | // set maximum window size: sndwnd=32, rcvwnd=32 by default 901 | public int WndSize(int sndwnd, int rcvwnd) 902 | { 903 | if (sndwnd > 0) 904 | snd_wnd = (UInt32)sndwnd; 905 | 906 | if (rcvwnd > 0) 907 | rcv_wnd = (UInt32)rcvwnd; 908 | return 0; 909 | } 910 | 911 | // get how many packet is waiting to be sent 912 | public int WaitSnd() 913 | { 914 | return snd_buf.Length + snd_queue.Length; 915 | } 916 | } 917 | -------------------------------------------------------------------------------- /Unity3D/KCP_LE.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Text; 6 | namespace Network_Kcp 7 | { 8 | /// 9 | /// c#实现的默认是 小端的数据 10 | /// 11 | public partial class KCP_LE 12 | { 13 | public const int IKCP_RTO_NDL = 30; // no delay min rto 14 | public const int IKCP_RTO_MIN = 100; // normal min rto 15 | public const int IKCP_RTO_DEF = 200; 16 | public const int IKCP_RTO_MAX = 60000; 17 | public const int IKCP_CMD_PUSH = 81; // cmd: push data 18 | public const int IKCP_CMD_ACK = 82; // cmd: ack 19 | public const int IKCP_CMD_WASK = 83; // cmd: window probe (ask) 20 | public const int IKCP_CMD_WINS = 84; // cmd: window size (tell) 21 | public const int IKCP_ASK_SEND = 1; // need to send IKCP_CMD_WASK 22 | public const int IKCP_ASK_TELL = 2; // need to send IKCP_CMD_WINS 23 | public const int IKCP_WND_SND = 32; 24 | public const int IKCP_WND_RCV = 32; 25 | public const int IKCP_MTU_DEF = 1400; 26 | public const int IKCP_ACK_FAST = 3; 27 | public const int IKCP_INTERVAL = 100; 28 | public const int IKCP_OVERHEAD = 24; 29 | public const int IKCP_DEADLINK = 10; 30 | public const int IKCP_THRESH_INIT = 2; 31 | public const int IKCP_THRESH_MIN = 2; 32 | public const int IKCP_PROBE_INIT = 7000; // 7 secs to probe window size 33 | public const int IKCP_PROBE_LIMIT = 120000; // up to 120 secs to probe window 34 | 35 | 36 | // encode 8 bits unsigned int 37 | public static int ikcp_encode8u(byte[] p, int offset, byte c) { 38 | p[0 + offset] = c; 39 | return 1; 40 | } 41 | 42 | // decode 8 bits unsigned int 43 | public static int ikcp_decode8u(byte[] p, int offset, ref byte c) { 44 | c = p[0 + offset]; 45 | return 1; 46 | } 47 | 48 | /* encode 16 bits unsigned int (lsb) */ 49 | public static int ikcp_encode16u(byte[] p, int offset, UInt16 w) { 50 | p[0 + offset] = (byte)(w >> 0); 51 | p[1 + offset] = (byte)(w >> 8); 52 | return 2; 53 | } 54 | 55 | /* decode 16 bits unsigned int (lsb) */ 56 | public static int ikcp_decode16u(byte[] p, int offset, ref UInt16 c) { 57 | UInt16 result = 0; 58 | result |= (UInt16)p[0 + offset]; 59 | result |= (UInt16)(p[1 + offset] << 8); 60 | c = result; 61 | return 2; 62 | } 63 | 64 | /* encode 32 bits unsigned int (lsb) */ 65 | public static int ikcp_encode32u(byte[] p, int offset, UInt32 l) { 66 | p[0 + offset] = (byte)(l >> 0); 67 | p[1 + offset] = (byte)(l >> 8); 68 | p[2 + offset] = (byte)(l >> 16); 69 | p[3 + offset] = (byte)(l >> 24); 70 | return 4; 71 | } 72 | 73 | /* decode 32 bits unsigned int (lsb) */ 74 | public static int ikcp_decode32u(byte[] p, int offset, ref UInt32 c) { 75 | UInt32 result = 0; 76 | result |= (UInt32)p[0 + offset]; 77 | result |= (UInt32)(p[1 + offset] << 8); 78 | result |= (UInt32)(p[2 + offset] << 16); 79 | result |= (UInt32)(p[3 + offset] << 24); 80 | c = result; 81 | return 4; 82 | } 83 | 84 | public static byte[] slice(byte[] p, int start, int stop) { 85 | var bytes = new byte[stop - start]; 86 | Array.Copy(p, start, bytes, 0, bytes.Length); 87 | return bytes; 88 | } 89 | 90 | public static T[] slice(T[] p, int start, int stop) { 91 | var arr = new T[stop - start]; 92 | var index = 0; 93 | for (var i = start; i < stop; i++) { 94 | arr[index] = p[i]; 95 | index++; 96 | } 97 | 98 | return arr; 99 | } 100 | 101 | public static byte[] append(byte[] p, byte c) { 102 | var bytes = new byte[p.Length + 1]; 103 | Array.Copy(p, bytes, p.Length); 104 | bytes[p.Length] = c; 105 | return bytes; 106 | } 107 | 108 | public static T[] append(T[] p, T c) { 109 | var arr = new T[p.Length + 1]; 110 | for (var i = 0; i < p.Length; i++) 111 | arr[i] = p[i]; 112 | arr[p.Length] = c; 113 | return arr; 114 | } 115 | 116 | public static T[] append(T[] p, T[] cs) { 117 | var arr = new T[p.Length + cs.Length]; 118 | for (var i = 0; i < p.Length; i++) 119 | arr[i] = p[i]; 120 | for (var i = 0; i < cs.Length; i++) 121 | arr[p.Length + i] = cs[i]; 122 | return arr; 123 | } 124 | 125 | static UInt32 _imin_(UInt32 a, UInt32 b) { 126 | return a <= b ? a : b; 127 | } 128 | 129 | static UInt32 _imax_(UInt32 a, UInt32 b) { 130 | return a >= b ? a : b; 131 | } 132 | 133 | static UInt32 _ibound_(UInt32 lower, UInt32 middle, UInt32 upper) { 134 | return _imin_(_imax_(lower, middle), upper); 135 | } 136 | 137 | static Int32 _itimediff(UInt32 later, UInt32 earlier) { 138 | return ((Int32)(later - earlier)); 139 | } 140 | 141 | // KCP Segment Definition 142 | internal class Segment 143 | { 144 | internal UInt32 conv = 0; 145 | internal UInt32 cmd = 0; 146 | internal UInt32 frg = 0; 147 | internal UInt32 wnd = 0; 148 | internal UInt32 ts = 0; 149 | internal UInt32 sn = 0; 150 | internal UInt32 una = 0; 151 | internal UInt32 resendts = 0; 152 | internal UInt32 rto = 0; 153 | internal UInt32 fastack = 0; 154 | internal UInt32 xmit = 0; 155 | internal byte[] data; 156 | 157 | internal Segment(int size) { 158 | this.data = new byte[size]; 159 | } 160 | 161 | // encode a segment into buffer 162 | internal int encode(byte[] ptr, int offset) { 163 | 164 | var offset_ = offset; 165 | 166 | offset += ikcp_encode32u(ptr, offset, conv); 167 | offset += ikcp_encode8u(ptr, offset, (byte)cmd); 168 | offset += ikcp_encode8u(ptr, offset, (byte)frg); 169 | offset += ikcp_encode16u(ptr, offset, (UInt16)wnd); 170 | offset += ikcp_encode32u(ptr, offset, ts); 171 | offset += ikcp_encode32u(ptr, offset, sn); 172 | offset += ikcp_encode32u(ptr, offset, una); 173 | offset += ikcp_encode32u(ptr, offset, (UInt32)data.Length); 174 | 175 | return offset - offset_; 176 | } 177 | } 178 | 179 | // kcp members. 180 | UInt32 conv; UInt32 mtu; UInt32 mss; UInt32 state; 181 | UInt32 snd_una; UInt32 snd_nxt; UInt32 rcv_nxt; 182 | UInt32 ts_recent; UInt32 ts_lastack; UInt32 ssthresh; 183 | UInt32 rx_rttval; UInt32 rx_srtt; UInt32 rx_rto; UInt32 rx_minrto; 184 | UInt32 snd_wnd; UInt32 rcv_wnd; UInt32 rmt_wnd; UInt32 cwnd; UInt32 probe; 185 | UInt32 current; UInt32 interval; UInt32 ts_flush; UInt32 xmit; 186 | UInt32 nodelay; UInt32 updated; 187 | UInt32 ts_probe; UInt32 probe_wait; 188 | UInt32 dead_link; UInt32 incr; 189 | 190 | Segment[] snd_queue = new Segment[0]; 191 | Segment[] rcv_queue = new Segment[0]; 192 | Segment[] snd_buf = new Segment[0]; 193 | Segment[] rcv_buf = new Segment[0]; 194 | 195 | UInt32[] acklist = new UInt32[0]; 196 | 197 | byte[] buffer; 198 | Int32 fastresend; 199 | Int32 nocwnd; 200 | Int32 logmask; 201 | // buffer, size 202 | Action output; 203 | 204 | // create a new kcp control object, 'conv' must equal in two endpoint 205 | // from the same connection. 206 | public KCP_LE(UInt32 conv_, Action output_) { 207 | conv = conv_; 208 | snd_wnd = IKCP_WND_SND; 209 | rcv_wnd = IKCP_WND_RCV; 210 | rmt_wnd = IKCP_WND_RCV; 211 | mtu = IKCP_MTU_DEF; 212 | mss = mtu - IKCP_OVERHEAD; 213 | 214 | rx_rto = IKCP_RTO_DEF; 215 | rx_minrto = IKCP_RTO_MIN; 216 | interval = IKCP_INTERVAL; 217 | ts_flush = IKCP_INTERVAL; 218 | ssthresh = IKCP_THRESH_INIT; 219 | dead_link = IKCP_DEADLINK; 220 | buffer = new byte[(mtu + IKCP_OVERHEAD) * 3]; 221 | output = output_; 222 | } 223 | 224 | // check the size of next message in the recv queue 225 | public int PeekSize() { 226 | 227 | if (0 == rcv_queue.Length) return -1; 228 | 229 | var seq = rcv_queue[0]; 230 | 231 | if (0 == seq.frg) return seq.data.Length; 232 | 233 | if (rcv_queue.Length < seq.frg + 1) return -1; 234 | 235 | int length = 0; 236 | 237 | foreach (var item in rcv_queue) { 238 | length += item.data.Length; 239 | if (0 == item.frg) 240 | break; 241 | } 242 | 243 | return length; 244 | } 245 | 246 | // user/upper level recv: returns size, returns below zero for EAGAIN 247 | public int Recv(byte[] buffer) { 248 | 249 | if (0 == rcv_queue.Length) return -1; 250 | 251 | var peekSize = PeekSize(); 252 | if (0 > peekSize) return -2; 253 | 254 | if (peekSize > buffer.Length) return -3; 255 | 256 | var fast_recover = false; 257 | if (rcv_queue.Length >= rcv_wnd) fast_recover = true; 258 | 259 | // merge fragment. 260 | var count = 0; 261 | var n = 0; 262 | foreach (var seg in rcv_queue) { 263 | Array.Copy(seg.data, 0, buffer, n, seg.data.Length); 264 | n += seg.data.Length; 265 | count++; 266 | if (0 == seg.frg) break; 267 | } 268 | 269 | if (0 < count) { 270 | rcv_queue = slice(rcv_queue, count, rcv_queue.Length); 271 | } 272 | 273 | // move available data from rcv_buf -> rcv_queue 274 | count = 0; 275 | foreach (var seg in rcv_buf) { 276 | if (seg.sn == rcv_nxt && rcv_queue.Length < rcv_wnd) { 277 | rcv_queue = append(rcv_queue, seg); 278 | rcv_nxt++; 279 | count++; 280 | } 281 | else { 282 | break; 283 | } 284 | } 285 | 286 | if (0 < count) rcv_buf = slice(rcv_buf, count, rcv_buf.Length); 287 | 288 | // fast recover 289 | if (rcv_queue.Length < rcv_wnd && fast_recover) { 290 | // ready to send back IKCP_CMD_WINS in ikcp_flush 291 | // tell remote my window size 292 | probe |= IKCP_ASK_TELL; 293 | } 294 | 295 | return n; 296 | } 297 | 298 | // user/upper level send, returns below zero for error 299 | public int Send(byte[] buffer) { 300 | int bufferSize = buffer.Length; 301 | if (0 == bufferSize) return -1; 302 | 303 | var count = 0; 304 | 305 | if (bufferSize < mss) 306 | count = 1; 307 | else 308 | count = (int)(bufferSize + mss - 1) / (int)mss; 309 | 310 | if (255 < count) return -2; 311 | 312 | if (0 == count) count = 1; 313 | 314 | var offset = 0; 315 | 316 | for (var i = 0; i < count; i++) { 317 | var size = 0; 318 | if (bufferSize - offset > mss) 319 | size = (int)mss; 320 | else 321 | size = bufferSize - offset; 322 | 323 | var seg = new Segment(size); 324 | Array.Copy(buffer, offset, seg.data, 0, size); 325 | offset += size; 326 | seg.frg = (UInt32)(count - i - 1); 327 | snd_queue = append(snd_queue, seg); 328 | } 329 | 330 | return 0; 331 | } 332 | 333 | // update ack. 334 | void update_ack(Int32 rtt) { 335 | if (0 == rx_srtt) { 336 | rx_srtt = (UInt32)rtt; 337 | rx_rttval = (UInt32)rtt / 2; 338 | } 339 | else { 340 | Int32 delta = (Int32)((UInt32)rtt - rx_srtt); 341 | if (0 > delta) delta = -delta; 342 | 343 | rx_rttval = (3 * rx_rttval + (uint)delta) / 4; 344 | rx_srtt = (UInt32)((7 * rx_srtt + rtt) / 8); 345 | if (rx_srtt < 1) rx_srtt = 1; 346 | } 347 | 348 | var rto = (int)(rx_srtt + _imax_(1, 4 * rx_rttval)); 349 | rx_rto = _ibound_(rx_minrto, (UInt32)rto, IKCP_RTO_MAX); 350 | } 351 | 352 | void shrink_buf() { 353 | if (snd_buf.Length > 0) 354 | snd_una = snd_buf[0].sn; 355 | else 356 | snd_una = snd_nxt; 357 | } 358 | 359 | void parse_ack(UInt32 sn) { 360 | 361 | if (_itimediff(sn, snd_una) < 0 || _itimediff(sn, snd_nxt) >= 0) return; 362 | 363 | var index = 0; 364 | foreach (var seg in snd_buf) { 365 | if (sn == seg.sn) { 366 | snd_buf = append(slice(snd_buf, 0, index), slice(snd_buf, index + 1, snd_buf.Length)); 367 | break; 368 | } 369 | else { 370 | seg.fastack++; 371 | } 372 | 373 | index++; 374 | } 375 | } 376 | 377 | void parse_una(UInt32 una) { 378 | var count = 0; 379 | foreach (var seg in snd_buf) { 380 | if (_itimediff(una, seg.sn) > 0) 381 | count++; 382 | else 383 | break; 384 | } 385 | 386 | if (0 < count) snd_buf = slice(snd_buf, count, snd_buf.Length); 387 | } 388 | 389 | void ack_push(UInt32 sn, UInt32 ts) { 390 | acklist = append(acklist, new UInt32[2] { sn, ts }); 391 | } 392 | 393 | void ack_get(int p, ref UInt32 sn, ref UInt32 ts) { 394 | sn = acklist[p * 2 + 0]; 395 | ts = acklist[p * 2 + 1]; 396 | } 397 | 398 | void parse_data(Segment newseg) { 399 | var sn = newseg.sn; 400 | if (_itimediff(sn, rcv_nxt + rcv_wnd) >= 0 || _itimediff(sn, rcv_nxt) < 0) return; 401 | 402 | var n = rcv_buf.Length - 1; 403 | var after_idx = -1; 404 | var repeat = false; 405 | for (var i = n; i >= 0; i--) { 406 | var seg = rcv_buf[i]; 407 | if (seg.sn == sn) { 408 | repeat = true; 409 | break; 410 | } 411 | 412 | if (_itimediff(sn, seg.sn) > 0) { 413 | after_idx = i; 414 | break; 415 | } 416 | } 417 | 418 | if (!repeat) { 419 | if (after_idx == -1) 420 | rcv_buf = append(new Segment[1] { newseg }, rcv_buf); 421 | else 422 | rcv_buf = append(slice(rcv_buf, 0, after_idx + 1), append(new Segment[1] { newseg }, slice(rcv_buf, after_idx + 1, rcv_buf.Length))); 423 | } 424 | 425 | // move available data from rcv_buf -> rcv_queue 426 | var count = 0; 427 | foreach (var seg in rcv_buf) { 428 | if (seg.sn == rcv_nxt && rcv_queue.Length < rcv_wnd) { 429 | rcv_queue = append(rcv_queue, seg); 430 | rcv_nxt++; 431 | count++; 432 | } 433 | else { 434 | break; 435 | } 436 | } 437 | 438 | if (0 < count) { 439 | rcv_buf = slice(rcv_buf, count, rcv_buf.Length); 440 | } 441 | } 442 | 443 | // when you received a low level packet (eg. UDP packet), call it 444 | public int Input(byte[] data) { 445 | 446 | var s_una = snd_una; 447 | if (data.Length < IKCP_OVERHEAD) return 0; 448 | 449 | var offset = 0; 450 | 451 | while (true) { 452 | UInt32 ts = 0; 453 | UInt32 sn = 0; 454 | UInt32 length = 0; 455 | UInt32 una = 0; 456 | UInt32 conv_ = 0; 457 | 458 | UInt16 wnd = 0; 459 | 460 | byte cmd = 0; 461 | byte frg = 0; 462 | 463 | if (data.Length - offset < IKCP_OVERHEAD) break; 464 | 465 | offset += ikcp_decode32u(data, offset, ref conv_); 466 | 467 | if (conv != conv_) return -1; 468 | 469 | offset += ikcp_decode8u(data, offset, ref cmd); 470 | offset += ikcp_decode8u(data, offset, ref frg); 471 | offset += ikcp_decode16u(data, offset, ref wnd); 472 | offset += ikcp_decode32u(data, offset, ref ts); 473 | offset += ikcp_decode32u(data, offset, ref sn); 474 | offset += ikcp_decode32u(data, offset, ref una); 475 | offset += ikcp_decode32u(data, offset, ref length); 476 | 477 | if (data.Length - offset < length) return -2; 478 | 479 | switch (cmd) { 480 | case IKCP_CMD_PUSH: 481 | case IKCP_CMD_ACK: 482 | case IKCP_CMD_WASK: 483 | case IKCP_CMD_WINS: 484 | break; 485 | default: 486 | return -3; 487 | } 488 | 489 | rmt_wnd = (UInt32)wnd; 490 | parse_una(una); 491 | shrink_buf(); 492 | 493 | if (IKCP_CMD_ACK == cmd) { 494 | if (_itimediff(current, ts) >= 0) { 495 | update_ack(_itimediff(current, ts)); 496 | } 497 | parse_ack(sn); 498 | shrink_buf(); 499 | } 500 | else if (IKCP_CMD_PUSH == cmd) { 501 | if (_itimediff(sn, rcv_nxt + rcv_wnd) < 0) { 502 | ack_push(sn, ts); 503 | if (_itimediff(sn, rcv_nxt) >= 0) { 504 | var seg = new Segment((int)length); 505 | seg.conv = conv_; 506 | seg.cmd = (UInt32)cmd; 507 | seg.frg = (UInt32)frg; 508 | seg.wnd = (UInt32)wnd; 509 | seg.ts = ts; 510 | seg.sn = sn; 511 | seg.una = una; 512 | 513 | if (length > 0) Array.Copy(data, offset, seg.data, 0, length); 514 | 515 | parse_data(seg); 516 | } 517 | } 518 | } 519 | else if (IKCP_CMD_WASK == cmd) { 520 | // ready to send back IKCP_CMD_WINS in Ikcp_flush 521 | // tell remote my window size 522 | probe |= IKCP_ASK_TELL; 523 | } 524 | else if (IKCP_CMD_WINS == cmd) { 525 | // do nothing 526 | } 527 | else { 528 | return -3; 529 | } 530 | 531 | offset += (int)length; 532 | } 533 | 534 | if (_itimediff(snd_una, s_una) > 0) { 535 | if (cwnd < rmt_wnd) { 536 | var mss_ = mss; 537 | if (cwnd < ssthresh) { 538 | cwnd++; 539 | incr += mss_; 540 | } 541 | else { 542 | if (incr < mss_) { 543 | incr = mss_; 544 | } 545 | incr += (mss_ * mss_) / incr + (mss_ / 16); 546 | if ((cwnd + 1) * mss_ <= incr) cwnd++; 547 | } 548 | if (cwnd > rmt_wnd) { 549 | cwnd = rmt_wnd; 550 | incr = rmt_wnd * mss_; 551 | } 552 | } 553 | } 554 | 555 | return 0; 556 | } 557 | 558 | Int32 wnd_unused() { 559 | if (rcv_queue.Length < rcv_wnd) 560 | return (Int32)(int)rcv_wnd - rcv_queue.Length; 561 | return 0; 562 | } 563 | 564 | // flush pending data 565 | void flush() { 566 | var current_ = current; 567 | var buffer_ = buffer; 568 | var change = 0; 569 | var lost = 0; 570 | 571 | if (0 == updated) return; 572 | 573 | var seg = new Segment(0); 574 | seg.conv = conv; 575 | seg.cmd = IKCP_CMD_ACK; 576 | seg.wnd = (UInt32)wnd_unused(); 577 | seg.una = rcv_nxt; 578 | 579 | // flush acknowledges 580 | var count = acklist.Length / 2; 581 | var offset = 0; 582 | for (var i = 0; i < count; i++) { 583 | if (offset + IKCP_OVERHEAD > mtu) { 584 | output(buffer, offset); 585 | //Array.Clear(buffer, 0, offset); 586 | offset = 0; 587 | } 588 | ack_get(i, ref seg.sn, ref seg.ts); 589 | offset += seg.encode(buffer, offset); 590 | } 591 | acklist = new UInt32[0]; 592 | 593 | // probe window size (if remote window size equals zero) 594 | if (0 == rmt_wnd) { 595 | if (0 == probe_wait) { 596 | probe_wait = IKCP_PROBE_INIT; 597 | ts_probe = current + probe_wait; 598 | } 599 | else { 600 | if (_itimediff(current, ts_probe) >= 0) { 601 | if (probe_wait < IKCP_PROBE_INIT) 602 | probe_wait = IKCP_PROBE_INIT; 603 | probe_wait += probe_wait / 2; 604 | if (probe_wait > IKCP_PROBE_LIMIT) 605 | probe_wait = IKCP_PROBE_LIMIT; 606 | ts_probe = current + probe_wait; 607 | probe |= IKCP_ASK_SEND; 608 | } 609 | } 610 | } 611 | else { 612 | ts_probe = 0; 613 | probe_wait = 0; 614 | } 615 | 616 | // flush window probing commands 617 | if ((probe & IKCP_ASK_SEND) != 0) { 618 | seg.cmd = IKCP_CMD_WASK; 619 | if (offset + IKCP_OVERHEAD > (int)mtu) { 620 | output(buffer, offset); 621 | //Array.Clear(buffer, 0, offset); 622 | offset = 0; 623 | } 624 | offset += seg.encode(buffer, offset); 625 | } 626 | 627 | probe = 0; 628 | 629 | // calculate window size 630 | var cwnd_ = _imin_(snd_wnd, rmt_wnd); 631 | if (0 == nocwnd) 632 | cwnd_ = _imin_(cwnd, cwnd_); 633 | 634 | count = 0; 635 | for (var k = 0; k < snd_queue.Length; k++) { 636 | if (_itimediff(snd_nxt, snd_una + cwnd_) >= 0) break; 637 | 638 | var newseg = snd_queue[k]; 639 | newseg.conv = conv; 640 | newseg.cmd = IKCP_CMD_PUSH; 641 | newseg.wnd = seg.wnd; 642 | newseg.ts = current_; 643 | newseg.sn = snd_nxt; 644 | newseg.una = rcv_nxt; 645 | newseg.resendts = current_; 646 | newseg.rto = rx_rto; 647 | newseg.fastack = 0; 648 | newseg.xmit = 0; 649 | snd_buf = append(snd_buf, newseg); 650 | snd_nxt++; 651 | count++; 652 | } 653 | 654 | if (0 < count) { 655 | snd_queue = slice(snd_queue, count, snd_queue.Length); 656 | } 657 | 658 | // calculate resent 659 | var resent = (UInt32)fastresend; 660 | if (fastresend <= 0) resent = 0xffffffff; 661 | var rtomin = rx_rto >> 3; 662 | if (nodelay != 0) rtomin = 0; 663 | 664 | // flush data segments 665 | foreach (var segment in snd_buf) { 666 | var needsend = false; 667 | var debug = _itimediff(current_, segment.resendts); 668 | if (0 == segment.xmit) { 669 | needsend = true; 670 | segment.xmit++; 671 | segment.rto = rx_rto; 672 | segment.resendts = current_ + segment.rto + rtomin; 673 | } 674 | else if (_itimediff(current_, segment.resendts) >= 0) { 675 | needsend = true; 676 | segment.xmit++; 677 | xmit++; 678 | if (0 == nodelay) 679 | segment.rto += rx_rto; 680 | else 681 | segment.rto += rx_rto / 2; 682 | segment.resendts = current_ + segment.rto; 683 | lost = 1; 684 | } 685 | else if (segment.fastack >= resent) { 686 | needsend = true; 687 | segment.xmit++; 688 | segment.fastack = 0; 689 | segment.resendts = current_ + segment.rto; 690 | change++; 691 | } 692 | 693 | if (needsend) { 694 | segment.ts = current_; 695 | segment.wnd = seg.wnd; 696 | segment.una = rcv_nxt; 697 | 698 | var need = IKCP_OVERHEAD + segment.data.Length; 699 | if (offset + need > mtu) { 700 | output(buffer, offset); 701 | //Array.Clear(buffer, 0, offset); 702 | offset = 0; 703 | } 704 | 705 | offset += segment.encode(buffer, offset); 706 | if (segment.data.Length > 0) { 707 | Array.Copy(segment.data, 0, buffer, offset, segment.data.Length); 708 | offset += segment.data.Length; 709 | } 710 | 711 | if (segment.xmit >= dead_link) { 712 | state = 0; 713 | } 714 | } 715 | } 716 | 717 | // flash remain segments 718 | if (offset > 0) { 719 | output(buffer, offset); 720 | //Array.Clear(buffer, 0, offset); 721 | offset = 0; 722 | } 723 | 724 | // update ssthresh 725 | if (change != 0) { 726 | var inflight = snd_nxt - snd_una; 727 | ssthresh = inflight / 2; 728 | if (ssthresh < IKCP_THRESH_MIN) 729 | ssthresh = IKCP_THRESH_MIN; 730 | cwnd = ssthresh + resent; 731 | incr = cwnd * mss; 732 | } 733 | 734 | if (lost != 0) { 735 | ssthresh = cwnd / 2; 736 | if (ssthresh < IKCP_THRESH_MIN) 737 | ssthresh = IKCP_THRESH_MIN; 738 | cwnd = 1; 739 | incr = mss; 740 | } 741 | 742 | if (cwnd < 1) { 743 | cwnd = 1; 744 | incr = mss; 745 | } 746 | } 747 | 748 | // update state (call it repeatedly, every 10ms-100ms), or you can ask 749 | // ikcp_check when to call it again (without ikcp_input/_send calling). 750 | // 'current' - current timestamp in millisec. 751 | public void Update(long current_L_) { 752 | UInt32 current_ = (UInt32)current_L_; 753 | 754 | current = current_; 755 | 756 | if (0 == updated) { 757 | updated = 1; 758 | ts_flush = current; 759 | } 760 | 761 | var slap = _itimediff(current, ts_flush); 762 | 763 | if (slap >= 10000 || slap < -10000) { 764 | ts_flush = current; 765 | slap = 0; 766 | } 767 | 768 | if (slap >= 0) { 769 | ts_flush += interval; 770 | if (_itimediff(current, ts_flush) >= 0) 771 | ts_flush = current + interval; 772 | flush(); 773 | } 774 | } 775 | 776 | // Determine when should you invoke ikcp_update: 777 | // returns when you should invoke ikcp_update in millisec, if there 778 | // is no ikcp_input/_send calling. you can call ikcp_update in that 779 | // time, instead of call update repeatly. 780 | // Important to reduce unnacessary ikcp_update invoking. use it to 781 | // schedule ikcp_update (eg. implementing an epoll-like mechanism, 782 | // or optimize ikcp_update when handling massive kcp connections) 783 | public UInt32 Check(long current_L_) { 784 | UInt32 current_ = (UInt32)current_L_; 785 | 786 | if (0 == updated) return current_; 787 | 788 | var ts_flush_ = ts_flush; 789 | var tm_flush_ = 0x7fffffff; 790 | var tm_packet = 0x7fffffff; 791 | var minimal = 0; 792 | 793 | if (_itimediff(current_, ts_flush_) >= 10000 || _itimediff(current_, ts_flush_) < -10000) { 794 | ts_flush_ = current_; 795 | } 796 | 797 | if (_itimediff(current_, ts_flush_) >= 0) return current_; 798 | 799 | tm_flush_ = (int)_itimediff(ts_flush_, current_); 800 | 801 | foreach (var seg in snd_buf) { 802 | var diff = _itimediff(seg.resendts, current_); 803 | if (diff <= 0) return current_; 804 | if (diff < tm_packet) tm_packet = (int)diff; 805 | } 806 | 807 | minimal = (int)tm_packet; 808 | if (tm_packet >= tm_flush_) minimal = (int)tm_flush_; 809 | if (minimal >= interval) minimal = (int)interval; 810 | 811 | return current_ + (UInt32)minimal; 812 | } 813 | 814 | // change MTU size, default is 1400 815 | public int SetMtu(Int32 mtu_) { 816 | if (mtu_ < 50 || mtu_ < (Int32)IKCP_OVERHEAD) return -1; 817 | 818 | var buffer_ = new byte[(mtu_ + IKCP_OVERHEAD) * 3]; 819 | if (null == buffer_) return -2; 820 | 821 | mtu = (UInt32)mtu_; 822 | mss = mtu - IKCP_OVERHEAD; 823 | buffer = buffer_; 824 | return 0; 825 | } 826 | 827 | public int Interval(Int32 interval_) { 828 | if (interval_ > 5000) { 829 | interval_ = 5000; 830 | } 831 | else if (interval_ < 10) { 832 | interval_ = 10; 833 | } 834 | interval = (UInt32)interval_; 835 | return 0; 836 | } 837 | 838 | // fastest: ikcp_nodelay(kcp, 1, 20, 2, 1) 839 | // nodelay: 0:disable(default), 1:enable 840 | // interval: internal update timer interval in millisec, default is 100ms 841 | // resend: 0:disable fast resend(default), 1:enable fast resend 842 | // nc: 0:normal congestion control(default), 1:disable congestion control 843 | public int NoDelay(int nodelay_, int interval_, int resend_, int nc_) { 844 | 845 | if (nodelay_ > 0) { 846 | nodelay = (UInt32)nodelay_; 847 | if (nodelay_ != 0) 848 | rx_minrto = IKCP_RTO_NDL; 849 | else 850 | rx_minrto = IKCP_RTO_MIN; 851 | } 852 | 853 | if (interval_ >= 0) { 854 | if (interval_ > 5000) { 855 | interval_ = 5000; 856 | } 857 | else if (interval_ < 10) { 858 | interval_ = 10; 859 | } 860 | interval = (UInt32)interval_; 861 | } 862 | 863 | if (resend_ >= 0) fastresend = resend_; 864 | 865 | if (nc_ >= 0) nocwnd = nc_; 866 | 867 | return 0; 868 | } 869 | 870 | // set maximum window size: sndwnd=32, rcvwnd=32 by default 871 | public int WndSize(int sndwnd, int rcvwnd) { 872 | if (sndwnd > 0) 873 | snd_wnd = (UInt32)sndwnd; 874 | 875 | if (rcvwnd > 0) 876 | rcv_wnd = (UInt32)rcvwnd; 877 | return 0; 878 | } 879 | 880 | // get how many packet is waiting to be sent 881 | public int WaitSnd() { 882 | return snd_buf.Length + snd_queue.Length; 883 | } 884 | } 885 | 886 | } --------------------------------------------------------------------------------