diff --git a/Assets/GameScripts/DotNet/Core/DataStructure/Collection/CircularBuffer.cs b/Assets/GameScripts/DotNet/Core/DataStructure/Collection/CircularBuffer.cs index 22c86792..d194f49e 100644 --- a/Assets/GameScripts/DotNet/Core/DataStructure/Collection/CircularBuffer.cs +++ b/Assets/GameScripts/DotNet/Core/DataStructure/Collection/CircularBuffer.cs @@ -96,6 +96,38 @@ namespace TEngine.DataStructure } } + public void Read(Memory memory, int count) + { + if (count > Length) + { + throw new Exception($"bufferList length < count, {Length} {count}"); + } + + var copyCount = 0; + while (copyCount < count) + { + var n = count - copyCount; + var asMemory = First.AsMemory(); + + if (ChunkSize - FirstIndex > n) + { + var slice = asMemory.Slice(FirstIndex, n); + slice.CopyTo(memory.Slice(copyCount, n)); + FirstIndex += n; + copyCount += n; + } + else + { + var length = ChunkSize - FirstIndex; + var slice = asMemory.Slice(FirstIndex, length); + slice.CopyTo(memory.Slice(copyCount, length)); + copyCount += ChunkSize - FirstIndex; + FirstIndex = 0; + RemoveFirst(); + } + } + } + public override int Read(byte[] buffer, int offset, int count) { if (buffer.Length < offset + count) @@ -124,7 +156,6 @@ namespace TEngine.DataStructure } Array.Copy(First, FirstIndex, buffer, copyCount + offset, ChunkSize - FirstIndex); - copyCount += ChunkSize - FirstIndex; FirstIndex = 0; diff --git a/Assets/GameScripts/DotNet/Core/Exporter/Excel/ServerConfigTableManage.cs b/Assets/GameScripts/DotNet/Core/Exporter/Excel/ServerConfigTableManage.cs index 5d020cc9..c8b7c2ea 100644 --- a/Assets/GameScripts/DotNet/Core/Exporter/Excel/ServerConfigTableManage.cs +++ b/Assets/GameScripts/DotNet/Core/Exporter/Excel/ServerConfigTableManage.cs @@ -33,7 +33,9 @@ namespace TEngine.Core { var configFile = GetConfigPath(dataConfig); var bytes = File.ReadAllBytes(configFile); - var data = (AProto) ProtoBufHelper.FromBytes(typeof(T), bytes, 0, bytes.Length); + // var data = (AProto) ProtoBufHelper.FromBytes(typeof(T), bytes, 0, bytes.Length); + // var data = ProtoBufHelper.FromBytes(bytes, 0, bytes.Length); + var data = (T)ProtoBufHelper.FromBytes(typeof(T), bytes, 0, bytes.Length); data.AfterDeserialization(); ConfigDic[dataConfig] = data; return (T)data; @@ -65,7 +67,7 @@ namespace TEngine.Core { var configFile = GetConfigPath(type.Name); var bytes = File.ReadAllBytes(configFile); - var data = (AProto) ProtoBufHelper.FromBytes(type, bytes, 0, bytes.Length); + var data = (AProto)ProtoBufHelper.FromBytes(type, bytes, 0, bytes.Length); data.AfterDeserialization(); ConfigDic[dataConfig] = data; return data; diff --git a/Assets/GameScripts/DotNet/Core/Helper/Mongo/MongoHelper.cs b/Assets/GameScripts/DotNet/Core/Helper/Mongo/MongoHelper.cs index 0a12670e..ed4869c8 100644 --- a/Assets/GameScripts/DotNet/Core/Helper/Mongo/MongoHelper.cs +++ b/Assets/GameScripts/DotNet/Core/Helper/Mongo/MongoHelper.cs @@ -95,11 +95,54 @@ public sealed class MongoHelper : Singleton { return BsonSerializer.Deserialize(str, type); } + + public object Deserialize(Span span, Type type) + { + using var stream = MemoryStreamHelper.GetRecyclableMemoryStream(); + stream.Write(span); + stream.Seek(0, SeekOrigin.Begin); + return BsonSerializer.Deserialize(stream, type); + } + + public object Deserialize(Memory memory, Type type) + { + using var stream = MemoryStreamHelper.GetRecyclableMemoryStream(); + stream.Write(memory.Span); + stream.Seek(0, SeekOrigin.Begin); + return BsonSerializer.Deserialize(stream, type); + } + + public object Deserialize(Span span) + { + using var stream = MemoryStreamHelper.GetRecyclableMemoryStream(); + stream.Write(span); + stream.Seek(0, SeekOrigin.Begin); + return BsonSerializer.Deserialize(stream); + } + + public object Deserialize(Memory memory) + { + using var stream = MemoryStreamHelper.GetRecyclableMemoryStream(); + stream.Seek(0, SeekOrigin.Begin); + return BsonSerializer.Deserialize(stream); + } + public T Deserialize(byte[] bytes) { return BsonSerializer.Deserialize(bytes); } + + public void SerializeTo(T t, Memory memory) + { + using var memoryStream = new MemoryStream(); + using (var writer = new BsonBinaryWriter(memoryStream, BsonBinaryWriterSettings.Defaults)) + { + BsonSerializer.Serialize(writer, typeof(T), t); + } + + memoryStream.GetBuffer().AsMemory().CopyTo(memory); + } public object Deserialize(byte[] bytes, Type type) { diff --git a/Assets/GameScripts/DotNet/Core/Helper/Proto/ProtoBufHelper.cs b/Assets/GameScripts/DotNet/Core/Helper/Proto/ProtoBufHelper.cs index 0941302c..63c0f129 100644 --- a/Assets/GameScripts/DotNet/Core/Helper/Proto/ProtoBufHelper.cs +++ b/Assets/GameScripts/DotNet/Core/Helper/Proto/ProtoBufHelper.cs @@ -1,38 +1,89 @@ using System; +using System.Buffers; using System.IO; +using System.Reflection; using ProtoBuf; -#pragma warning disable CS8604 +using ProtoBuf.Meta; namespace TEngine.Core { public static class ProtoBufHelper { + public static object FromSpan(Type type, Span span) + { +#if TENGINE_UNITY + using var recyclableMemoryStream = MemoryStreamHelper.GetRecyclableMemoryStream(); + recyclableMemoryStream.Write(span); + recyclableMemoryStream.Seek(0, SeekOrigin.Begin); + return Serializer.Deserialize(type, recyclableMemoryStream); +#else + return RuntimeTypeModel.Default.Deserialize(type, span); +#endif + } + + public static object FromMemory(Type type, Memory memory) + { +#if TENGINE_UNITY + using var recyclableMemoryStream = MemoryStreamHelper.GetRecyclableMemoryStream(); + recyclableMemoryStream.Write(memory.Span); + recyclableMemoryStream.Seek(0, SeekOrigin.Begin); + return Serializer.Deserialize(type, recyclableMemoryStream); +#else + return RuntimeTypeModel.Default.Deserialize(type, memory); +#endif + } + public static object FromBytes(Type type, byte[] bytes, int index, int count) { - using var stream = new MemoryStream(bytes, index, count); +#if TENGINE_UNITY + using var stream = MemoryStreamHelper.GetRecyclableMemoryStream(); + stream.Write(bytes, index, count); + stream.Seek(0, SeekOrigin.Begin); return Serializer.Deserialize(type, stream); +#else + var memory = new Memory(bytes, index, count); + return RuntimeTypeModel.Default.Deserialize(type, memory); +#endif } public static T FromBytes(byte[] bytes) { - using var stream = new MemoryStream(bytes, 0, bytes.Length); +#if TENGINE_UNITY + using var stream = MemoryStreamHelper.GetRecyclableMemoryStream(); + stream.Write(bytes, 0, bytes.Length); + stream.Seek(0, SeekOrigin.Begin); return Serializer.Deserialize(stream); - // return FromBytes(bytes, 0, bytes.Length); +#else + return Serializer.Deserialize(new Span(bytes)); +#endif } public static T FromBytes(byte[] bytes, int index, int count) { - using var stream = new MemoryStream(bytes, index, count); +#if TENGINE_UNITY + using var stream = MemoryStreamHelper.GetRecyclableMemoryStream(); + stream.Write(bytes, 0, bytes.Length); + stream.Seek(0, SeekOrigin.Begin); return Serializer.Deserialize(stream); +#else + return Serializer.Deserialize(new Span(bytes, index, count)); +#endif } public static byte[] ToBytes(object message) { - using var stream = new MemoryStream(); + using var stream = MemoryStreamHelper.GetRecyclableMemoryStream(); Serializer.Serialize(stream, message); return stream.ToArray(); } + public static void ToMemory(object message, Memory memory) + { + using var stream = MemoryStreamHelper.GetRecyclableMemoryStream(); + Serializer.Serialize(stream, message); + stream.GetBuffer().AsMemory().CopyTo(memory); + } + public static void ToStream(object message, MemoryStream stream) { Serializer.Serialize(stream, message); @@ -50,8 +101,8 @@ namespace TEngine.Core public static T Clone(T t) { - var bytes = ToBytes(t); - using var stream = new MemoryStream(bytes, 0, bytes.Length); + using var stream = MemoryStreamHelper.GetRecyclableMemoryStream(); + Serializer.Serialize(stream, t); return Serializer.Deserialize(stream); } } diff --git a/Assets/GameScripts/DotNet/Core/Network/Base/Interface/ANetwork.cs b/Assets/GameScripts/DotNet/Core/Network/Base/Interface/ANetwork.cs index 8247cdb9..6dde100d 100644 --- a/Assets/GameScripts/DotNet/Core/Network/Base/Interface/ANetwork.cs +++ b/Assets/GameScripts/DotNet/Core/Network/Base/Interface/ANetwork.cs @@ -1,6 +1,7 @@ using System; using System.IO; using TEngine.Core; +using System.Buffers; // ReSharper disable ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract #pragma warning disable CS8625 #pragma warning disable CS8618 diff --git a/Assets/GameScripts/DotNet/Core/Network/Entity/Session/ServerInnerSession.cs b/Assets/GameScripts/DotNet/Core/Network/Entity/Session/ServerInnerSession.cs index 7d3d52f4..781d9ad4 100644 --- a/Assets/GameScripts/DotNet/Core/Network/Entity/Session/ServerInnerSession.cs +++ b/Assets/GameScripts/DotNet/Core/Network/Entity/Session/ServerInnerSession.cs @@ -1,5 +1,3 @@ -using TEngine.Core; - #if TENGINE_NET namespace TEngine.Core.Network; @@ -11,14 +9,8 @@ public sealed class ServerInnerSession : Session { return; } - - // 序列化消息到流中 - var memoryStream = MemoryStreamHelper.GetRecyclableMemoryStream(); - InnerPacketParser.Serialize(message, memoryStream); - memoryStream.Seek(0, SeekOrigin.Begin); - // 分发消息 - var packInfo = InnerPackInfo.Create(rpcId, routeId, ((IMessage)message).OpCode(), 0, memoryStream); - NetworkMessageScheduler.Scheduler(this, packInfo).Coroutine(); + + NetworkMessageScheduler.InnerScheduler(this, rpcId, routeId, ((IMessage)message).OpCode(), 0, message).Coroutine(); } public override void Send(IRouteMessage routeMessage, uint rpcId = 0, long routeId = 0) @@ -27,13 +19,8 @@ public sealed class ServerInnerSession : Session { return; } - // 序列化消息到流中 - var memoryStream = MemoryStreamHelper.GetRecyclableMemoryStream(); - InnerPacketParser.Serialize(routeMessage, memoryStream); - memoryStream.Seek(0, SeekOrigin.Begin); - // 分发消息 - var packInfo = InnerPackInfo.Create(rpcId, routeId, routeMessage.OpCode(), routeMessage.RouteTypeOpCode(), memoryStream); - NetworkMessageScheduler.Scheduler(this, packInfo).Coroutine(); + + NetworkMessageScheduler.InnerScheduler(this, rpcId, routeId, routeMessage.OpCode(), routeMessage.RouteTypeOpCode(), routeMessage).Coroutine(); } public override void Send(MemoryStream memoryStream, uint rpcId = 0, long routeTypeOpCode = 0, long routeId = 0) diff --git a/Assets/GameScripts/DotNet/Core/Network/Message/Interface/Scheduler/ANetworkMessageScheduler.cs b/Assets/GameScripts/DotNet/Core/Network/Message/Interface/Scheduler/ANetworkMessageScheduler.cs index 46bccd54..8cd2a056 100644 --- a/Assets/GameScripts/DotNet/Core/Network/Message/Interface/Scheduler/ANetworkMessageScheduler.cs +++ b/Assets/GameScripts/DotNet/Core/Network/Message/Interface/Scheduler/ANetworkMessageScheduler.cs @@ -1,18 +1,17 @@ using System; using System.IO; -using TEngine.Core; -#pragma warning disable CS8600 namespace TEngine.Core.Network { public abstract class ANetworkMessageScheduler { + protected bool DisposePackInfo; private readonly PingResponse _pingResponse = new PingResponse(); public async FTask Scheduler(Session session, APackInfo packInfo) { Type messageType = null; - var packInfoMemoryStream = packInfo.MemoryStream; + DisposePackInfo = true; try { @@ -56,17 +55,6 @@ namespace TEngine.Core.Network // 服务器之间发送消息因为走的是MessageHelper、所以接收消息的回调也应该放到MessageHelper里处理 MessageHelper.ResponseHandler(packInfo.RpcId, aResponse); #else - -#if TENGINE_UNITY - if (MessageDispatcherSystem.Instance.MsgHandles.TryGetValue(packInfo.ProtocolCode,out var msgDelegates)) - { - foreach (var msgDelegate in msgDelegates) - { - msgDelegate.Invoke(aResponse); - } - return; - } -#endif // 这个一般是客户端Session.Call发送时使用的、目前这个逻辑只有Unity客户端时使用 if (!session.RequestCallback.TryGetValue(packInfo.RpcId, out var action)) @@ -84,20 +72,57 @@ namespace TEngine.Core.Network } catch (Exception e) { - if (packInfoMemoryStream.CanRead) - { - // ReSharper disable once MethodHasAsyncOverload - packInfoMemoryStream.Dispose(); - } - Log.Error($"NetworkMessageScheduler error messageProtocolCode:{packInfo.ProtocolCode} messageType:{messageType} SessionId {session.Id} IsDispose {session.IsDisposed} {e}"); } finally { - packInfo.Dispose(); + if (DisposePackInfo) + { + packInfo.Dispose(); + } } } - + + public async FTask InnerScheduler(Session session, uint rpcId, long routeId, uint protocolCode, long routeTypeCode, object message) + { + var messageType = message.GetType(); + + try + { + if (session.IsDisposed) + { + return; + } + + switch (protocolCode) + { + case >= Opcode.OuterRouteMessage: + { + await InnerHandler(session, rpcId, routeId, protocolCode, routeTypeCode, messageType, message); + return; + } + case < Opcode.OuterResponse: + { + MessageDispatcherSystem.Instance.MessageHandler(session, messageType, message, rpcId, protocolCode); + return; + } + default: + { +#if TENGINE_NET + // 服务器之间发送消息因为走的是MessageHelper、所以接收消息的回调也应该放到MessageHelper里处理 + MessageHelper.ResponseHandler(rpcId, (IResponse)message); +#endif + return; + } + } + } + catch (Exception e) + { + Log.Error($"NetworkMessageScheduler error messageProtocolCode:{protocolCode} messageType:{messageType} SessionId {session.Id} IsDispose {session.IsDisposed} {e}"); + } + } + protected abstract FTask Handler(Session session, Type messageType, APackInfo packInfo); + protected abstract FTask InnerHandler(Session session, uint rpcId, long routeId, uint protocolCode, long routeTypeCode, Type messageType, object message); } } \ No newline at end of file diff --git a/Assets/GameScripts/DotNet/Core/Network/Message/Scheduler/ClientMessageScheduler.cs b/Assets/GameScripts/DotNet/Core/Network/Message/Scheduler/ClientMessageScheduler.cs index 0c04cf7d..78d022b7 100644 --- a/Assets/GameScripts/DotNet/Core/Network/Message/Scheduler/ClientMessageScheduler.cs +++ b/Assets/GameScripts/DotNet/Core/Network/Message/Scheduler/ClientMessageScheduler.cs @@ -7,8 +7,6 @@ namespace TEngine.Core.Network { protected override async FTask Handler(Session session, Type messageType, APackInfo packInfo) { - var packInfoMemoryStream = packInfo.MemoryStream; - try { switch (packInfo.ProtocolCode) @@ -28,7 +26,7 @@ namespace TEngine.Core.Network Log.Error($"not found rpc {packInfo.RpcId}, response message: {aResponse.GetType().Name}"); return; } - + session.RequestCallback.Remove(packInfo.RpcId); action.SetResult(aResponse); return; @@ -43,19 +41,22 @@ namespace TEngine.Core.Network } catch (Exception e) { - if (packInfoMemoryStream.CanRead) - { - // ReSharper disable once MethodHasAsyncOverload - packInfoMemoryStream.Dispose(); - } - Log.Error(e); return; } + finally + { + packInfo.Dispose(); + } await FTask.CompletedTask; throw new NotSupportedException($"Received unsupported message protocolCode:{packInfo.ProtocolCode} messageType:{messageType}"); } + + protected override FTask InnerHandler(Session session, uint rpcId, long routeId, uint protocolCode, long routeTypeCode, Type messageType, object message) + { + throw new NotImplementedException(); + } } #endif #if TENGINE_NET @@ -65,6 +66,11 @@ namespace TEngine.Core.Network { throw new NotSupportedException($"Received unsupported message protocolCode:{packInfo.ProtocolCode} messageType:{messageType}"); } + + protected override FTask InnerHandler(Session session, uint rpcId, long routeId, uint protocolCode, long routeTypeCode, Type messageType, object message) + { + throw new NotSupportedException($"Received unsupported message protocolCode:{protocolCode} messageType:{messageType}"); + } } #endif } \ No newline at end of file diff --git a/Assets/GameScripts/DotNet/Core/Network/Message/Scheduler/InnerMessageScheduler.cs b/Assets/GameScripts/DotNet/Core/Network/Message/Scheduler/InnerMessageScheduler.cs index 2e9b2695..1cde77dd 100644 --- a/Assets/GameScripts/DotNet/Core/Network/Message/Scheduler/InnerMessageScheduler.cs +++ b/Assets/GameScripts/DotNet/Core/Network/Message/Scheduler/InnerMessageScheduler.cs @@ -7,11 +7,10 @@ namespace TEngine.Core.Network { protected override async FTask Handler(Session session, Type messageType, APackInfo packInfo) { - var disposeMemoryStream = true; - var packInfoMemoryStream = packInfo.MemoryStream; - try { + DisposePackInfo = false; + switch (packInfo.ProtocolCode) { case >= Opcode.InnerBsonRouteResponse: @@ -80,8 +79,7 @@ namespace TEngine.Core.Network case Session gateSession: { // 这里如果是Session只可能是Gate的Session、如果是的话、肯定是转发Address消息 - disposeMemoryStream = false; - gateSession.Send(packInfoMemoryStream, packInfo.RpcId); + gateSession.Send(packInfo.CreateMemoryStream(), packInfo.RpcId); return; } default: @@ -94,20 +92,108 @@ namespace TEngine.Core.Network } default: { - throw new NotSupportedException($"Received unsupported message protocolCode:{packInfo.ProtocolCode} messageType:{messageType}"); + throw new NotSupportedException( + $"Received unsupported message protocolCode:{packInfo.ProtocolCode} messageType:{messageType}"); } } } catch (Exception e) { - if (disposeMemoryStream && packInfoMemoryStream.CanRead) - { - // ReSharper disable once MethodHasAsyncOverload - packInfoMemoryStream.Dispose(); - } - Log.Error($"InnerMessageSchedulerHandler error messageProtocolCode:{packInfo.ProtocolCode} messageType:{messageType} {e}"); } + finally + { + packInfo.Dispose(); + } + } + + protected override async FTask InnerHandler(Session session, uint rpcId, long routeId, uint protocolCode, long routeTypeCode, Type messageType, object message) + { + try + { + switch (protocolCode) + { + case >= Opcode.InnerBsonRouteResponse: + case >= Opcode.InnerRouteResponse: + { + MessageHelper.ResponseHandler(rpcId, (IRouteResponse)message); + return; + } + case >= Opcode.OuterRouteResponse: + { + // 如果Gate服务器、需要转发Addressable协议、所以这里有可能会接收到该类型协议 + MessageHelper.ResponseHandler(rpcId, (IResponse)message); + return; + } + case > Opcode.InnerBsonRouteMessage: + { + var entity = Entity.GetEntity(routeId); + + if (entity == null) + { + if (protocolCode > Opcode.InnerBsonRouteRequest) + { + MessageDispatcherSystem.Instance.FailResponse(session, (IRouteRequest)message, CoreErrorCode.ErrNotFoundRoute, rpcId); + } + + return; + } + + await MessageDispatcherSystem.Instance.RouteMessageHandler(session, messageType, entity, message, rpcId); + return; + } + case > Opcode.InnerRouteMessage: + { + var entity = Entity.GetEntity(routeId); + + if (entity == null) + { + if (protocolCode > Opcode.InnerRouteRequest) + { + MessageDispatcherSystem.Instance.FailResponse(session, (IRouteRequest)message, CoreErrorCode.ErrNotFoundRoute, rpcId); + } + + return; + } + + await MessageDispatcherSystem.Instance.RouteMessageHandler(session, messageType, entity, message, rpcId); + return; + } + case > Opcode.OuterRouteMessage: + { + var entity = Entity.GetEntity(routeId); + + switch (entity) + { + case null: + { + var response = MessageDispatcherSystem.Instance.CreateResponse((IRouteMessage)message, CoreErrorCode.ErrNotFoundRoute); + session.Send(response, rpcId, routeId); + return; + } + case Session gateSession: + { + // 这里如果是Session只可能是Gate的Session、如果是的话、肯定是转发Address消息 + gateSession.Send(message, rpcId); + return; + } + default: + { + await MessageDispatcherSystem.Instance.RouteMessageHandler(session, messageType, entity, message, rpcId); + return; + } + } + } + default: + { + throw new NotSupportedException($"Received unsupported message protocolCode:{protocolCode} messageType:{messageType}"); + } + } + } + catch (Exception e) + { + Log.Error($"InnerMessageSchedulerHandler error messageProtocolCode:{protocolCode} messageType:{messageType} {e}"); + } } } } diff --git a/Assets/GameScripts/DotNet/Core/Network/Message/Scheduler/OuterMessageScheduler.cs b/Assets/GameScripts/DotNet/Core/Network/Message/Scheduler/OuterMessageScheduler.cs index 78b9d120..76ea1c39 100644 --- a/Assets/GameScripts/DotNet/Core/Network/Message/Scheduler/OuterMessageScheduler.cs +++ b/Assets/GameScripts/DotNet/Core/Network/Message/Scheduler/OuterMessageScheduler.cs @@ -11,6 +11,11 @@ namespace TEngine.Core.Network { throw new NotSupportedException($"Received unsupported message protocolCode:{packInfo.ProtocolCode} messageType:{messageType}"); } + + protected override FTask InnerHandler(Session session, uint rpcId, long routeId, uint protocolCode, long routeTypeCode, Type messageType, object message) + { + throw new NotImplementedException(); + } } #endif #if TENGINE_NET @@ -23,10 +28,10 @@ namespace TEngine.Core.Network throw new NotSupportedException($"Received unsupported message protocolCode:{packInfo.ProtocolCode} messageType:{messageType}"); } - var packInfoMemoryStream = packInfo.MemoryStream; - try { + DisposePackInfo = false; + switch (packInfo.RouteTypeCode) { case CoreRouteType.Route: @@ -49,10 +54,8 @@ namespace TEngine.Core.Network case > Opcode.OuterRouteRequest: { var runtimeId = session.RuntimeId; - var response = await addressableRouteComponent.Call(packInfo.RouteTypeCode, messageType, packInfoMemoryStream); - + var response = await addressableRouteComponent.Call(packInfo.RouteTypeCode, messageType, packInfo.CreateMemoryStream()); // session可能已经断开了,所以这里需要判断 - if (session.RuntimeId == runtimeId) { session.Send(response, packInfo.RpcId); @@ -62,7 +65,7 @@ namespace TEngine.Core.Network } case > Opcode.OuterRouteMessage: { - addressableRouteComponent.Send(packInfo.RouteTypeCode, messageType, packInfoMemoryStream); + addressableRouteComponent.Send(packInfo.RouteTypeCode, messageType, packInfo.CreateMemoryStream()); return; } } @@ -90,7 +93,7 @@ namespace TEngine.Core.Network case > Opcode.OuterRouteRequest: { var runtimeId = session.RuntimeId; - var response = await MessageHelper.CallInnerRoute(session.Scene, routeId, packInfo.RouteTypeCode, messageType, packInfoMemoryStream); + var response = await MessageHelper.CallInnerRoute(session.Scene, routeId, packInfo.RouteTypeCode, messageType, packInfo.CreateMemoryStream()); // session可能已经断开了,所以这里需要判断 if (session.RuntimeId == runtimeId) { @@ -101,7 +104,7 @@ namespace TEngine.Core.Network } case > Opcode.OuterRouteMessage: { - MessageHelper.SendInnerRoute(session.Scene, routeId, packInfo.RouteTypeCode, packInfoMemoryStream); + MessageHelper.SendInnerRoute(session.Scene, routeId, packInfo.RouteTypeCode, packInfo.CreateMemoryStream()); return; } } @@ -112,18 +115,21 @@ namespace TEngine.Core.Network } catch (Exception e) { - if (packInfoMemoryStream.CanRead) - { - // ReSharper disable once MethodHasAsyncOverload - packInfoMemoryStream.Dispose(); - } - Log.Error(e); return; } + finally + { + packInfo.Dispose(); + } throw new NotSupportedException($"Received unsupported message protocolCode:{packInfo.ProtocolCode} messageType:{messageType}"); } + + protected override FTask InnerHandler(Session session, uint rpcId, long routeId, uint protocolCode, long routeTypeCode, Type messageType, object message) + { + throw new NotSupportedException($"OuterMessageScheduler NotSupported InnerHandler"); + } } #endif } \ No newline at end of file diff --git a/Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/KCP/Base/KCP.cs b/Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/KCP/Base/KCP.cs deleted file mode 100644 index 10837be4..00000000 --- a/Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/KCP/Base/KCP.cs +++ /dev/null @@ -1,218 +0,0 @@ -using System; -using System.Runtime.InteropServices; - -namespace TEngine.Core.Network -{ - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate int KcpOutput(IntPtr buf, int len, IntPtr kcp, IntPtr user); - - public static class KCP - { -#if UNITY_IPHONE && !UNITY_EDITOR - const string KcpDll = "__Internal"; -#else - const string KcpDll = "kcp"; -#endif - [DllImport(KcpDll, CallingConvention = CallingConvention.Cdecl)] - private static extern uint ikcp_check(IntPtr kcp, uint current); - - [DllImport(KcpDll, CallingConvention = CallingConvention.Cdecl)] - private static extern IntPtr ikcp_create(uint conv, IntPtr user); - - [DllImport(KcpDll, CallingConvention = CallingConvention.Cdecl)] - private static extern void ikcp_flush(IntPtr kcp); - - [DllImport(KcpDll, CallingConvention = CallingConvention.Cdecl)] - private static extern uint ikcp_getconv(IntPtr ptr); - - [DllImport(KcpDll, CallingConvention = CallingConvention.Cdecl)] - private static extern int ikcp_input(IntPtr kcp, byte[] data, int offset, int size); - - [DllImport(KcpDll, CallingConvention = CallingConvention.Cdecl)] - private static extern int ikcp_nodelay(IntPtr kcp, int nodelay, int interval, int resend, int nc); - - [DllImport(KcpDll, CallingConvention = CallingConvention.Cdecl)] - private static extern int ikcp_peeksize(IntPtr kcp); - - [DllImport(KcpDll, CallingConvention = CallingConvention.Cdecl)] - private static extern int ikcp_recv(IntPtr kcp, byte[] buffer, int len); - - [DllImport(KcpDll, CallingConvention = CallingConvention.Cdecl)] - private static extern void ikcp_release(IntPtr kcp); - - [DllImport(KcpDll, CallingConvention = CallingConvention.Cdecl)] - private static extern int ikcp_send(IntPtr kcp, byte[] buffer, int len); - - [DllImport(KcpDll, CallingConvention=CallingConvention.Cdecl)] - private static extern void ikcp_setminrto(IntPtr ptr, int minrto); - - [DllImport(KcpDll, CallingConvention = CallingConvention.Cdecl)] - private static extern int ikcp_setmtu(IntPtr kcp, int mtu); - - [DllImport(KcpDll, CallingConvention = CallingConvention.Cdecl)] - private static extern void ikcp_setoutput(IntPtr kcp, KcpOutput output); - - [DllImport(KcpDll, CallingConvention = CallingConvention.Cdecl)] - private static extern void ikcp_update(IntPtr kcp, uint current); - - [DllImport(KcpDll, CallingConvention = CallingConvention.Cdecl)] - private static extern int ikcp_waitsnd(IntPtr kcp); - - [DllImport(KcpDll, CallingConvention = CallingConvention.Cdecl)] - private static extern int ikcp_wndsize(IntPtr kcp, int sndwnd, int rcvwnd); - - public static uint KcpCheck(IntPtr kcp, uint current) - { - if (kcp == IntPtr.Zero) - { - throw new Exception($"kcp error, kcp point is zero"); - } - - return ikcp_check(kcp, current); - } - - public static IntPtr KcpCreate(uint conv, IntPtr user) - { - return ikcp_create(conv, user); - } - - public static void KcpFlush(IntPtr kcp) - { - if (kcp == IntPtr.Zero) - { - throw new Exception($"kcp error, kcp point is zero"); - } - - ikcp_flush(kcp); - } - - public static uint KcpGetconv(IntPtr ptr) - { - if (ptr == IntPtr.Zero) - { - throw new Exception($"kcp error, kcp point is zero"); - } - - return ikcp_getconv(ptr); - } - - public static int KcpInput(IntPtr kcp, byte[] data, int offset, int size) - { - if (kcp == IntPtr.Zero) - { - throw new Exception($"kcp error, kcp point is zero"); - } - - return ikcp_input(kcp, data, offset, size); - } - - public static int KcpNodelay(IntPtr kcp, int nodelay, int interval, int resend, int nc) - { - if (kcp == IntPtr.Zero) - { - throw new Exception($"kcp error, kcp point is zero"); - } - - return ikcp_nodelay(kcp, nodelay, interval, resend, nc); - } - - public static int KcpPeeksize(IntPtr kcp) - { - if (kcp == IntPtr.Zero) - { - throw new Exception($"kcp error, kcp point is zero"); - } - - return ikcp_peeksize(kcp); - } - - public static int KcpRecv(IntPtr kcp, byte[] buffer, int len) - { - if (kcp == IntPtr.Zero) - { - throw new Exception($"kcp error, kcp point is zero"); - } - - return ikcp_recv(kcp, buffer, len); - } - - public static void KcpRelease(IntPtr kcp) - { - if (kcp == IntPtr.Zero) - { - throw new Exception($"kcp error, kcp point is zero"); - } - - ikcp_release(kcp); - } - - public static int KcpSend(IntPtr kcp, byte[] buffer, int len) - { - if (kcp == IntPtr.Zero) - { - throw new Exception($"kcp error, kcp point is zero"); - } - - return ikcp_send(kcp, buffer, len); - } - - public static void KcpSetminrto(IntPtr kcp, int minrto) - { - if (kcp == IntPtr.Zero) - { - throw new Exception($"kcp error, kcp point is zero"); - } - ikcp_setminrto(kcp, minrto); - } - - public static int KcpSetmtu(IntPtr kcp, int mtu) - { - if (kcp == IntPtr.Zero) - { - throw new Exception($"kcp error, kcp point is zero"); - } - - return ikcp_setmtu(kcp, mtu); - } - - public static void KcpSetoutput(IntPtr kcp, KcpOutput output) - { - if (kcp == IntPtr.Zero) - { - throw new Exception($"kcp error, kcp point is zero"); - } - - ikcp_setoutput(kcp, output); - } - - public static void KcpUpdate(IntPtr kcp, uint current) - { - if (kcp == IntPtr.Zero) - { - throw new Exception($"kcp error, kcp point is zero"); - } - - ikcp_update(kcp, current); - } - - public static int KcpWaitsnd(IntPtr kcp) - { - if (kcp == IntPtr.Zero) - { - throw new Exception($"kcp error, kcp point is zero"); - } - - return ikcp_waitsnd(kcp); - } - - public static int KcpWndsize(IntPtr kcp, int sndwnd, int rcvwnd) - { - if (kcp == IntPtr.Zero) - { - throw new Exception($"kcp error, kcp point is zero"); - } - - return ikcp_wndsize(kcp, sndwnd, rcvwnd); - } - } -} \ No newline at end of file diff --git a/Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/KCP/Base/KCPSettings.cs b/Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/KCP/Base/KCPSettings.cs index 72bd966b..bc1c70ff 100644 --- a/Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/KCP/Base/KCPSettings.cs +++ b/Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/KCP/Base/KCPSettings.cs @@ -4,9 +4,9 @@ namespace TEngine.Core.Network { public class KCPSettings { - public int Mtu { get; private set; } - public int SendWindowSize { get; private set; } - public int ReceiveWindowSize { get; private set; } + public uint Mtu { get; private set; } + public uint SendWindowSize { get; private set; } + public uint ReceiveWindowSize { get; private set; } public int MaxSendWindowSize { get; private set; } public static KCPSettings Create(NetworkTarget networkTarget) diff --git a/Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/KCP/Base/KcpProtocalType.cs b/Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/KCP/Base/KcpProtocalType.cs deleted file mode 100644 index e58e56f8..00000000 --- a/Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/KCP/Base/KcpProtocalType.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace TEngine -{ - public static class KcpProtocalType - { - public const byte SYN = 1; - public const byte ACK = 2; - public const byte FIN = 3; - public const byte MSG = 4; - public const byte RouterReconnectSYN = 5; - public const byte RouterReconnectACK = 6; - public const byte RouterSYN = 7; - public const byte RouterACK = 8; - } -} \ No newline at end of file diff --git a/Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/KCP/Base/KcpProtocalType.cs.meta b/Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/KCP/Base/KcpProtocalType.cs.meta deleted file mode 100644 index f01e12a0..00000000 --- a/Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/KCP/Base/KcpProtocalType.cs.meta +++ /dev/null @@ -1,3 +0,0 @@ -fileFormatVersion: 2 -guid: 9bd29f721d3e458d88d43160fd7cca55 -timeCreated: 1689230822 \ No newline at end of file diff --git a/Assets/GameScripts/ThirdParty/KCP/Plugins.meta b/Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/KCP/Base/kcp2k.meta similarity index 77% rename from Assets/GameScripts/ThirdParty/KCP/Plugins.meta rename to Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/KCP/Base/kcp2k.meta index c9ecbff7..fcca0b6e 100644 --- a/Assets/GameScripts/ThirdParty/KCP/Plugins.meta +++ b/Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/KCP/Base/kcp2k.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 6ec9cf984eeccbb4ba7470e5beeac5a1 +guid: b6517b73296f4564a8411993d86fcafb folderAsset: yes DefaultImporter: externalObjects: {} diff --git a/Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/KCP/Base/kcp2k/LICENSE b/Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/KCP/Base/kcp2k/LICENSE new file mode 100644 index 00000000..c77582e8 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/KCP/Base/kcp2k/LICENSE @@ -0,0 +1,24 @@ +MIT License + +Copyright (c) 2016 limpo1989 +Copyright (c) 2020 Paul Pacheco +Copyright (c) 2020 Lymdun +Copyright (c) 2020 vis2k + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/Assets/GameScripts/ThirdParty/KCP/Plugins/MacOS/kcp.bundle/Contents/MacOS/kcp.meta b/Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/KCP/Base/kcp2k/LICENSE.meta similarity index 74% rename from Assets/GameScripts/ThirdParty/KCP/Plugins/MacOS/kcp.bundle/Contents/MacOS/kcp.meta rename to Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/KCP/Base/kcp2k/LICENSE.meta index a8c588e4..16bff191 100644 --- a/Assets/GameScripts/ThirdParty/KCP/Plugins/MacOS/kcp.bundle/Contents/MacOS/kcp.meta +++ b/Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/KCP/Base/kcp2k/LICENSE.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 72dcdf060ecf34d279acdee20e77b200 +guid: 60e959aa2b0fc774fb97f3a52197e41b DefaultImporter: externalObjects: {} userData: diff --git a/Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/KCP/Base/kcp2k/LICENSE.txt b/Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/KCP/Base/kcp2k/LICENSE.txt new file mode 100644 index 00000000..c77582e8 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/KCP/Base/kcp2k/LICENSE.txt @@ -0,0 +1,24 @@ +MIT License + +Copyright (c) 2016 limpo1989 +Copyright (c) 2020 Paul Pacheco +Copyright (c) 2020 Lymdun +Copyright (c) 2020 vis2k + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/Assets/GameScripts/ThirdParty/KCP/Plugins/Android.meta b/Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/KCP/Base/kcp2k/LICENSE.txt.meta similarity index 57% rename from Assets/GameScripts/ThirdParty/KCP/Plugins/Android.meta rename to Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/KCP/Base/kcp2k/LICENSE.txt.meta index e8af9edb..90dee819 100644 --- a/Assets/GameScripts/ThirdParty/KCP/Plugins/Android.meta +++ b/Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/KCP/Base/kcp2k/LICENSE.txt.meta @@ -1,7 +1,6 @@ fileFormatVersion: 2 -guid: 479fdc912cd3148078b6596aa438b98d -folderAsset: yes -DefaultImporter: +guid: e29e0db9c79ad6348aa2997ac1326446 +TextScriptImporter: externalObjects: {} userData: assetBundleName: diff --git a/Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/KCP/Base/kcp2k/README.md b/Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/KCP/Base/kcp2k/README.md new file mode 100644 index 00000000..d8d6ba56 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/KCP/Base/kcp2k/README.md @@ -0,0 +1,32 @@ +# kcp2k +C# KCP based on the original C [kcp](https://github.com/skywind3000/kcp). + +Works with **netcore** and **Unity**. + +Developed for [Mirror Networking](https://github.com/MirrorNetworking/Mirror). + +# Features +* Kcp.cs based on kcp.c v1.7, line-by-line translation to C# +* Heavy test coverage +* Fixed [WND_RCV bug](https://github.com/skywind3000/kcp/pull/291) from original kcp +* Optional high level C# code for client/server connection handling +* Optional high level Unreliable channel added + +Pull requests for bug fixes & tests welcome. + +# Unity +kcp2k works perfectly with Unity, see the Mirror repository's KcpTransport. + +# Allocations +The client is allocation free. +The server's SendTo/ReceiveFrom still allocate. + +Previously, [where-allocation](https://github.com/vis2k/where-allocation) for a 25x reduction in server allocations. However: +- It only worked with Unity's old Mono version. +- It didn't work in Unity's IL2CPP builds, which are still faster than Mono + NonAlloc +- It didn't work in regular C# projects. +- Overall, the extra complexity is not worth it. Use IL2CPP instead. +- Microsoft is considering to [remove the allocation](https://github.com/dotnet/runtime/issues/30797#issuecomment-1308599410). + +# Remarks +- **Congestion Control** should be left disabled. It seems to be broken in KCP. diff --git a/Assets/GameScripts/ThirdParty/KCP/Plugins/Android/arm64_v8a.meta b/Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/KCP/Base/kcp2k/README.md.meta similarity index 57% rename from Assets/GameScripts/ThirdParty/KCP/Plugins/Android/arm64_v8a.meta rename to Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/KCP/Base/kcp2k/README.md.meta index 100e24cf..bcff4a42 100644 --- a/Assets/GameScripts/ThirdParty/KCP/Plugins/Android/arm64_v8a.meta +++ b/Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/KCP/Base/kcp2k/README.md.meta @@ -1,7 +1,6 @@ fileFormatVersion: 2 -guid: 8626627ffb7484407ad050b63b7a6676 -folderAsset: yes -DefaultImporter: +guid: 2e2e6b3195633174f85b149e87c9312b +TextScriptImporter: externalObjects: {} userData: assetBundleName: diff --git a/Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/KCP/Base/kcp2k/VERSION.txt b/Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/KCP/Base/kcp2k/VERSION.txt new file mode 100644 index 00000000..b92a8aed --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/KCP/Base/kcp2k/VERSION.txt @@ -0,0 +1,234 @@ +V1.36 [2023-06-08] +- fix: #49 KcpPeer.RawInput message size check now considers cookie as well +- kcp.cs cleanups + +V1.35 [2023-04-05] +- fix: KcpClients now need to validate with a secure cookie in order to protect against + UDP spoofing. fixes: + https://github.com/MirrorNetworking/Mirror/issues/3286 + [disclosed by IncludeSec] +- KcpClient/Server: change callbacks to protected so inheriting classes can use them too +- KcpClient/Server: change config visibility to protected + +V1.34 [2023-03-15] +- Send/SendTo/Receive/ReceiveFrom NonBlocking extensions. + to encapsulate WouldBlock allocations, exceptions, etc. + allows for reuse when overwriting KcpServer/Client (i.e. for relays). + +V1.33 [2023-03-14] +- perf: KcpServer/Client RawReceive now call socket.Poll to avoid non-blocking + socket's allocating a new SocketException in case they WouldBlock. + fixes https://github.com/MirrorNetworking/Mirror/issues/3413 +- perf: KcpServer/Client RawSend now call socket.Poll to avoid non-blocking + socket's allocating a new SocketException in case they WouldBlock. + fixes https://github.com/MirrorNetworking/Mirror/issues/3413 + +V1.32 [2023-03-12] +- fix: KcpPeer RawInput now doesn't disconnect in case of random internet noise + +V1.31 [2023-03-05] +- KcpClient: Tick/Incoming/Outgoing can now be overwritten (virtual) +- breaking: KcpClient now takes KcpConfig in constructor instead of in Connect. + cleaner, and prepares for KcpConfig.MTU setting. +- KcpConfig now includes MTU; KcpPeer now works with KcpConfig's MTU, KcpServer/Client + buffers are now created with config's MTU. + +V1.30 [2023-02-20] +- fix: set send/recv buffer sizes directly instead of iterating to find the limit. + fixes: https://github.com/MirrorNetworking/Mirror/issues/3390 +- fix: server & client sockets are now always non-blocking to ensure main thread never + blocks on socket.recv/send. Send() now also handles WouldBlock. +- fix: socket.Receive/From directly with non-blocking sockets and handle WouldBlock, + instead of socket.Poll. faster, more obvious, and fixes Poll() looping forever while + socket is in error state. fixes: https://github.com/MirrorNetworking/Mirror/issues/2733 + +V1.29 [2023-01-28] +- fix: KcpServer.CreateServerSocket now handles NotSupportedException when setting DualMode + https://github.com/MirrorNetworking/Mirror/issues/3358 + +V1.28 [2023-01-28] +- fix: KcpClient.Connect now resolves hostname before creating peer + https://github.com/MirrorNetworking/Mirror/issues/3361 + +V1.27 [2023-01-08] +- KcpClient.Connect: invoke own events directly instead of going through peer, + which calls our own events anyway +- fix: KcpPeer/Client/Server callbacks are readonly and assigned in constructor + to ensure they are safe to use at all times. + fixes https://github.com/MirrorNetworking/Mirror/issues/3337 + +V1.26 [2022-12-22] +- KcpPeer.RawInput: fix compile error in old Unity Mono versions +- fix: KcpServer sets up a new connection's OnError immediately. + fixes KcpPeer throwing NullReferenceException when attempting to call OnError + after authentication errors. +- improved log messages + +V1.25 [2022-12-14] +- breaking: removed where-allocation. use IL2CPP on servers instead. +- breaking: KcpConfig to simplify configuration +- high level cleanups + +V1.24 [2022-12-14] +- KcpClient: fixed NullReferenceException when connection without a server. + added test coverage to ensure this never happens again. + +V1.23 [2022-12-07] +- KcpClient: rawReceiveBuffer exposed +- fix: KcpServer RawSend uses connection.remoteEndPoint instead of the helper + 'newClientEP'. fixes clients receiving the wrong messages meant for others. + https://github.com/MirrorNetworking/Mirror/issues/3296 + +V1.22 [2022-11-30] +- high level refactor, part two. + +V1.21 [2022-11-24] +- high level refactor, part one. + - KcpPeer instead of KcpConnection, KcpClientConnection, KcpServerConnection + - RawSend/Receive can now easily be overwritten in KcpClient/Server. + for non-alloc, relays, etc. + +V1.20 [2022-11-22] +- perf: KcpClient receive allocation was removed entirely. + reduces Mirror benchmark client sided allocations from 4.9 KB / 1.7 KB (non-alloc) to 0B. +- fix: KcpConnection.Disconnect does not check socket.Connected anymore. + UDP sockets don't have a connection. + fixes Disconnects not being sent to clients in netcore. +- KcpConnection.SendReliable: added OnError instead of logs + +V1.19 [2022-05-12] +- feature: OnError ErrorCodes + +V1.18 [2022-05-08] +- feature: OnError to allow higher level to show popups etc. +- feature: KcpServer.GetClientAddress is now GetClientEndPoint in order to + expose more details +- ResolveHostname: include exception in log for easier debugging +- fix: KcpClientConnection.RawReceive now logs the SocketException even if + it was expected. makes debugging easier. +- fix: KcpServer.TickIncoming now logs the SocketException even if it was + expected. makes debugging easier. +- fix: KcpClientConnection.RawReceive now calls Disconnect() if the other end + has closed the connection. better than just remaining in a state with unusable + sockets. + +V1.17 [2022-01-09] +- perf: server/client MaximizeSendReceiveBuffersToOSLimit option to set send/recv + buffer sizes to OS limit. avoids drops due to small buffers under heavy load. + +V1.16 [2022-01-06] +- fix: SendUnreliable respects ArraySegment.Offset +- fix: potential bug with negative length (see PR #2) +- breaking: removed pause handling because it's not necessary for Mirror anymore + +V1.15 [2021-12-11] +- feature: feature: MaxRetransmits aka dead_link now configurable +- dead_link disconnect message improved to show exact retransmit count + +V1.14 [2021-11-30] +- fix: Send() now throws an exception for messages which require > 255 fragments +- fix: ReliableMaxMessageSize is now limited to messages which require <= 255 fragments + +V1.13 [2021-11-28] +- fix: perf: uncork max message size from 144 KB to as much as we want based on + receive window size. + fixes https://github.com/vis2k/kcp2k/issues/22 + fixes https://github.com/skywind3000/kcp/pull/291 +- feature: OnData now includes channel it was received on + +V1.12 [2021-07-16] +- Tests: don't depend on Unity anymore +- fix: #26 - Kcp now catches exception if host couldn't be resolved, and calls + OnDisconnected to let the user now. +- fix: KcpServer.DualMode is now configurable in the constructor instead of + using #if UNITY_SWITCH. makes it run on all other non dual mode platforms too. +- fix: where-allocation made optional via virtuals and inheriting + KcpServer/Client/Connection NonAlloc classes. fixes a bug where some platforms + might not support where-allocation. + +V1.11 rollback [2021-06-01] +- perf: Segment MemoryStream initial capacity set to MTU to avoid early runtime + resizing/allocations + +V1.10 [2021-05-28] +- feature: configurable Timeout +- allocations explained with comments (C# ReceiveFrom / IPEndPoint.GetHashCode) +- fix: #17 KcpConnection.ReceiveNextReliable now assigns message default so it + works in .net too +- fix: Segment pool is not static anymore. Each kcp instance now has it's own + Pool. fixes #18 concurrency issues + +V1.9 [2021-03-02] +- Tick() split into TickIncoming()/TickOutgoing() to use in Mirror's new update + functions. allows to minimize latency. + => original Tick() is still supported for convenience. simply processes both! + +V1.8 [2021-02-14] +- fix: Unity IPv6 errors on Nintendo Switch +- fix: KcpConnection now disconnects if data message was received without content. + previously it would call OnData with an empty ArraySegment, causing all kinds of + weird behaviour in Mirror/DOTSNET. Added tests too. +- fix: KcpConnection.SendData: don't allow sending empty messages anymore. disconnect + and log a warning to make it completely obvious. + +V1.7 [2021-01-13] +- fix: unreliable messages reset timeout now too +- perf: KcpConnection OnCheckEnabled callback changed to a simple 'paused' boolean. + This is faster than invoking a Func every time and allows us to fix #8 more + easily later by calling .Pause/.Unpause from OnEnable/OnDisable in MirrorTransport. +- fix #8: Unpause now resets timeout to fix a bug where Mirror would pause kcp, + change the scene which took >10s, then unpause and kcp would detect the lack of + any messages for >10s as timeout. Added test to make sure it never happens again. +- MirrorTransport: statistics logging for headless servers +- Mirror Transport: Send/Receive window size increased once more from 2048 to 4096. + +V1.6 [2021-01-10] +- Unreliable channel added! +- perf: KcpHeader byte added to every kcp message to indicate + Handshake/Data/Ping/Disconnect instead of scanning each message for Hello/Byte/Ping + content via SegmentEquals. It's a lot cleaner, should be faster and should avoid + edge cases where a message content would equal Hello/Ping/Bye sequence accidentally. +- Kcp.Input: offset moved to parameters for cases where it's needed +- Kcp.SetMtu from original Kcp.c + +V1.5 [2021-01-07] +- KcpConnection.MaxSend/ReceiveRate calculation based on the article +- MirrorTransport: large send/recv window size defaults to avoid high latencies caused + by packets not being processed fast enough +- MirrorTransport: show MaxSend/ReceiveRate in debug gui +- MirrorTransport: don't Log.Info to console in headless mode if debug log is disabled + +V1.4 [2020-11-27] +- fix: OnCheckEnabled added. KcpConnection message processing while loop can now + be interrupted immediately. fixes Mirror Transport scene changes which need to stop + processing any messages immediately after a scene message) +- perf: Mirror KcpTransport: FastResend enabled by default. turbo mode according to: + https://github.com/skywind3000/kcp/blob/master/README.en.md#protocol-configuration +- perf: Mirror KcpTransport: CongestionControl disabled by default (turbo mode) + +V1.3 [2020-11-17] +- Log.Info/Warning/Error so logging doesn't depend on UnityEngine anymore +- fix: Server.Tick catches SocketException which happens if Android client is killed +- MirrorTransport: debugLog option added that can be checked in Unity Inspector +- Utils.Clamp so Kcp.cs doesn't depend on UnityEngine +- Utils.SegmentsEqual: use Linq SequenceEqual so it doesn't depend on UnityEngine +=> kcp2k can now be used in any C# project even without Unity + +V1.2 [2020-11-10] +- more tests added +- fix: raw receive buffers are now all of MTU size +- fix: raw receive detects error where buffer was too small for msgLength and + result in excess data being dropped silently +- KcpConnection.MaxMessageSize added for use in high level +- KcpConnection.MaxMessageSize increased from 1200 bytes to to maximum allowed + message size of 145KB for kcp (based on mtu, overhead, wnd_rcv) + +V1.1 [2020-10-30] +- high level cleanup, fixes, improvements + +V1.0 [2020-10-22] +- Kcp.cs now mirrors original Kcp.c behaviour + (this fixes dozens of bugs) + +V0.1 +- initial kcp-csharp based version \ No newline at end of file diff --git a/Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/KCP/Base/kcp2k/VERSION.txt.meta b/Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/KCP/Base/kcp2k/VERSION.txt.meta new file mode 100644 index 00000000..e736fdac --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/KCP/Base/kcp2k/VERSION.txt.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 0d1fd9853ebb3d047acd92fae8441350 +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/ThirdParty/KCP/Plugins/Android/armeabi-v7a.meta b/Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/KCP/Base/kcp2k/kcp.meta similarity index 77% rename from Assets/GameScripts/ThirdParty/KCP/Plugins/Android/armeabi-v7a.meta rename to Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/KCP/Base/kcp2k/kcp.meta index aee16a23..a8cad8ed 100644 --- a/Assets/GameScripts/ThirdParty/KCP/Plugins/Android/armeabi-v7a.meta +++ b/Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/KCP/Base/kcp2k/kcp.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 1e40c3d1ddee94950b16c1495dfcd31b +guid: d84ce75a45b625749bb2ee11dadf14e9 folderAsset: yes DefaultImporter: externalObjects: {} diff --git a/Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/KCP/Base/kcp2k/kcp/AckItem.cs b/Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/KCP/Base/kcp2k/kcp/AckItem.cs new file mode 100644 index 00000000..820f4511 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/KCP/Base/kcp2k/kcp/AckItem.cs @@ -0,0 +1,8 @@ +namespace kcp2k +{ + internal struct AckItem + { + internal uint serialNumber; + internal uint timestamp; + } +} diff --git a/Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/KCP/Base/KCP.cs.meta b/Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/KCP/Base/kcp2k/kcp/AckItem.cs.meta similarity index 83% rename from Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/KCP/Base/KCP.cs.meta rename to Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/KCP/Base/kcp2k/kcp/AckItem.cs.meta index 593d8d6e..7df7cf99 100644 --- a/Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/KCP/Base/KCP.cs.meta +++ b/Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/KCP/Base/kcp2k/kcp/AckItem.cs.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: eb202d4e9e36ea846861fa8378dd1c23 +guid: cbc45820dcb825444a1e4b3a1bcba94a MonoImporter: externalObjects: {} serializedVersion: 2 diff --git a/Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/KCP/Base/kcp2k/kcp/AssemblyInfo.cs b/Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/KCP/Base/kcp2k/kcp/AssemblyInfo.cs new file mode 100644 index 00000000..5fe5547e --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/KCP/Base/kcp2k/kcp/AssemblyInfo.cs @@ -0,0 +1,3 @@ +using System.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("kcp2k.Tests")] \ No newline at end of file diff --git a/Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/KCP/Base/kcp2k/kcp/AssemblyInfo.cs.meta b/Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/KCP/Base/kcp2k/kcp/AssemblyInfo.cs.meta new file mode 100644 index 00000000..6aa2576f --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/KCP/Base/kcp2k/kcp/AssemblyInfo.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 1731c38e547afe045bd600f9082a0f33 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/KCP/Base/kcp2k/kcp/Kcp.cs b/Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/KCP/Base/kcp2k/kcp/Kcp.cs new file mode 100644 index 00000000..bddfa980 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/KCP/Base/kcp2k/kcp/Kcp.cs @@ -0,0 +1,1267 @@ +// Kcp based on https://github.com/skywind3000/kcp +// Kept as close to original as possible. +using System; +using System.Collections.Generic; + +namespace kcp2k +{ + public class Kcp + { + // original Kcp has a define option, which is not defined by default: + // #define FASTACK_CONSERVE + + public const int RTO_NDL = 30; // no delay min rto + public const int RTO_MIN = 100; // normal min rto + public const int RTO_DEF = 200; // default RTO + public const int RTO_MAX = 60000; // maximum RTO + public const int CMD_PUSH = 81; // cmd: push data + public const int CMD_ACK = 82; // cmd: ack + public const int CMD_WASK = 83; // cmd: window probe (ask) + public const int CMD_WINS = 84; // cmd: window size (tell/insert) + public const int ASK_SEND = 1; // need to send CMD_WASK + public const int ASK_TELL = 2; // need to send CMD_WINS + public const int WND_SND = 32; // default send window + public const int WND_RCV = 128; // default receive window. must be >= max fragment size + public const int MTU_DEF = 1200; // default MTU (reduced to 1200 to fit all cases: https://en.wikipedia.org/wiki/Maximum_transmission_unit ; steam uses 1200 too!) + public const int ACK_FAST = 3; + public const int INTERVAL = 100; + public const int OVERHEAD = 24; + public const int FRG_MAX = byte.MaxValue; // kcp encodes 'frg' as byte. so we can only ever send up to 255 fragments. + public const int DEADLINK = 20; // default maximum amount of 'xmit' retransmissions until a segment is considered lost + public const int THRESH_INIT = 2; + public const int THRESH_MIN = 2; + public const int PROBE_INIT = 7000; // 7 secs to probe window size + public const int PROBE_LIMIT = 120000; // up to 120 secs to probe window + public const int FASTACK_LIMIT = 5; // max times to trigger fastack + + // kcp members. + internal int state; + readonly uint conv; // conversation + internal uint mtu; + internal uint mss; // maximum segment size := MTU - OVERHEAD + internal uint snd_una; // unacknowledged. e.g. snd_una is 9 it means 8 has been confirmed, 9 and 10 have been sent + internal uint snd_nxt; // forever growing send counter for sequence numbers + internal uint rcv_nxt; // forever growing receive counter for sequence numbers + internal uint ssthresh; // slow start threshold + internal int rx_rttval; // average deviation of rtt, used to measure the jitter of rtt + internal int rx_srtt; // smoothed round trip time (a weighted average of rtt) + internal int rx_rto; + internal int rx_minrto; + internal uint snd_wnd; // send window + internal uint rcv_wnd; // receive window + internal uint rmt_wnd; // remote window + internal uint cwnd; // congestion window + internal uint probe; + internal uint interval; + internal uint ts_flush; // last flush timestamp in milliseconds + internal uint xmit; + internal uint nodelay; // not a bool. original Kcp has '<2 else' check. + internal bool updated; + internal uint ts_probe; // probe timestamp + internal uint probe_wait; + internal uint dead_link; // maximum amount of 'xmit' retransmissions until a segment is considered lost + internal uint incr; + internal uint current; // current time (milliseconds). set by Update. + + internal int fastresend; + internal int fastlimit; + internal bool nocwnd; // congestion control, negated. heavily restricts send/recv window sizes. + internal readonly Queue snd_queue = new Queue(16); // send queue + internal readonly Queue rcv_queue = new Queue(16); // receive queue + // snd_buffer needs index removals. + // C# LinkedList allocates for each entry, so let's keep List for now. + internal readonly List snd_buf = new List(16); // send buffer + // rcv_buffer needs index insertions and backwards iteration. + // C# LinkedList allocates for each entry, so let's keep List for now. + internal readonly List rcv_buf = new List(16); // receive buffer + internal readonly List acklist = new List(16); + + // memory buffer + // size depends on MTU. + // MTU can be changed at runtime, which resizes the buffer. + internal byte[] buffer; + + // output function of type + readonly Action output; + + // segment pool to avoid allocations in C#. + // this is not part of the original C code. + readonly Pool SegmentPool = new Pool( + // create new segment + () => new Segment(), + // reset segment before reuse + (segment) => segment.Reset(), + // initial capacity + 32 + ); + + // ikcp_create + // create a new kcp control object, 'conv' must equal in two endpoint + // from the same connection. + public Kcp(uint conv, Action output) + { + this.conv = conv; + this.output = output; + snd_wnd = WND_SND; + rcv_wnd = WND_RCV; + rmt_wnd = WND_RCV; + mtu = MTU_DEF; + mss = mtu - OVERHEAD; + rx_rto = RTO_DEF; + rx_minrto = RTO_MIN; + interval = INTERVAL; + ts_flush = INTERVAL; + ssthresh = THRESH_INIT; + fastlimit = FASTACK_LIMIT; + dead_link = DEADLINK; + buffer = new byte[(mtu + OVERHEAD) * 3]; + } + + // ikcp_segment_new + // we keep the original function and add our pooling to it. + // this way we'll never miss it anywhere. + Segment SegmentNew() => SegmentPool.Take(); + + // ikcp_segment_delete + // we keep the original function and add our pooling to it. + // this way we'll never miss it anywhere. + void SegmentDelete(Segment seg) => SegmentPool.Return(seg); + + // calculate how many packets are waiting to be sent + public int WaitSnd => snd_buf.Count + snd_queue.Count; + + // ikcp_wnd_unused + // returns the remaining space in receive window (rcv_wnd - rcv_queue) + internal uint WndUnused() + { + if (rcv_queue.Count < rcv_wnd) + return rcv_wnd - (uint)rcv_queue.Count; + return 0; + } + + public int Receive(Memory memory, int len) + { + // kcp's ispeek feature is not supported. + // this makes 'merge fragment' code significantly easier because + // we can iterate while queue.Count > 0 and dequeue each time. + // if we had to consider ispeek then count would always be > 0 and + // we would have to remove only after the loop. + // + //bool ispeek = len < 0; + if (len < 0) + throw new NotSupportedException("Receive ispeek for negative len is not supported!"); + + if (rcv_queue.Count == 0) + return -1; + + if (len < 0) len = -len; + + int peeksize = PeekSize(); + + if (peeksize < 0) + return -2; + + if (peeksize > len) + return -3; + + bool recover = rcv_queue.Count >= rcv_wnd; + + // merge fragment. + int offset = 0; + len = 0; + // original KCP iterates rcv_queue and deletes if !ispeek. + // removing from a c# queue while iterating is not possible, but + // we can change to 'while Count > 0' and remove every time. + // (we can remove every time because we removed ispeek support!) + while (rcv_queue.Count > 0) + { + // unlike original kcp, we dequeue instead of just getting the + // entry. this is fine because we remove it in ANY case. + Segment seg = rcv_queue.Dequeue(); + // copy segment data into our buffer + var asMemory = seg.data.GetBuffer().AsMemory(); + var slice = asMemory.Slice(0, (int)seg.data.Position); + var offsetMemory = memory.Slice(offset); + slice.CopyTo(offsetMemory); + offset += (int)seg.data.Position; + len += (int)seg.data.Position; + uint fragment = seg.frg; + + // note: ispeek is not supported in order to simplify this loop + + // unlike original kcp, we don't need to remove seg from queue + // because we already dequeued it. + // simply delete it + SegmentDelete(seg); + + if (fragment == 0) + break; + } + + // move available data from rcv_buf -> rcv_queue + int removed = 0; + foreach (Segment seg in rcv_buf) + { + if (seg.sn == rcv_nxt && rcv_queue.Count < rcv_wnd) + { + // can't remove while iterating. remember how many to remove + // and do it after the loop. + // note: don't return segment. we only add it to rcv_queue + ++removed; + // add + rcv_queue.Enqueue(seg); + // increase sequence number for next segment + rcv_nxt++; + } + else + { + break; + } + } + + rcv_buf.RemoveRange(0, removed); + + // fast recover + if (rcv_queue.Count < rcv_wnd && recover) + { + // ready to send back CMD_WINS in flush + // tell remote my window size + probe |= ASK_TELL; + } + + return len; + } + + // ikcp_recv + // receive data from kcp state machine + // returns number of bytes read. + // returns negative on error. + // note: pass negative length to peek. + public int Receive(byte[] buffer, int len) + { + // kcp's ispeek feature is not supported. + // this makes 'merge fragment' code significantly easier because + // we can iterate while queue.Count > 0 and dequeue each time. + // if we had to consider ispeek then count would always be > 0 and + // we would have to remove only after the loop. + // + //bool ispeek = len < 0; + if (len < 0) + throw new NotSupportedException("Receive ispeek for negative len is not supported!"); + + if (rcv_queue.Count == 0) + return -1; + + if (len < 0) len = -len; + + int peeksize = PeekSize(); + + if (peeksize < 0) + return -2; + + if (peeksize > len) + return -3; + + bool recover = rcv_queue.Count >= rcv_wnd; + + // merge fragment. + int offset = 0; + len = 0; + // original KCP iterates rcv_queue and deletes if !ispeek. + // removing from a c# queue while iterating is not possible, but + // we can change to 'while Count > 0' and remove every time. + // (we can remove every time because we removed ispeek support!) + while (rcv_queue.Count > 0) + { + // unlike original kcp, we dequeue instead of just getting the + // entry. this is fine because we remove it in ANY case. + Segment seg = rcv_queue.Dequeue(); + + // copy segment data into our buffer + Buffer.BlockCopy(seg.data.GetBuffer(), 0, buffer, offset, (int)seg.data.Position); + offset += (int)seg.data.Position; + + len += (int)seg.data.Position; + uint fragment = seg.frg; + + // note: ispeek is not supported in order to simplify this loop + + // unlike original kcp, we don't need to remove seg from queue + // because we already dequeued it. + // simply delete it + SegmentDelete(seg); + + if (fragment == 0) + break; + } + + // move available data from rcv_buf -> rcv_queue + int removed = 0; + foreach (Segment seg in rcv_buf) + { + if (seg.sn == rcv_nxt && rcv_queue.Count < rcv_wnd) + { + // can't remove while iterating. remember how many to remove + // and do it after the loop. + // note: don't return segment. we only add it to rcv_queue + ++removed; + // add + rcv_queue.Enqueue(seg); + // increase sequence number for next segment + rcv_nxt++; + } + else + { + break; + } + } + rcv_buf.RemoveRange(0, removed); + + // fast recover + if (rcv_queue.Count < rcv_wnd && recover) + { + // ready to send back CMD_WINS in flush + // tell remote my window size + probe |= ASK_TELL; + } + + return len; + } + + // ikcp_peeksize + // check the size of next message in the recv queue. + // returns -1 if there is no message, or if the message is still incomplete. + public int PeekSize() + { + int length = 0; + + // empty queue? + if (rcv_queue.Count == 0) return -1; + + // peek the first segment + Segment seq = rcv_queue.Peek(); + + // seg.frg is 0 if the message requires no fragmentation. + // in that case, the segment's size is the final message size. + if (seq.frg == 0) return (int)seq.data.Position; + + // check if all fragment parts were received yet. + // seg.frg is the n-th fragment, but in reverse. + // this way the first received segment tells us how many fragments there are for the message. + // for example, if a message contains 3 segments: + // first segment: .frg is 2 (index in reverse) + // second segment: .frg is 1 (index in reverse) + // third segment: .frg is 0 (index in reverse) + if (rcv_queue.Count < seq.frg + 1) return -1; + + // recv_queue contains all the fragments necessary to reconstruct the message. + // sum all fragment's sizes to get the full message size. + foreach (Segment seg in rcv_queue) + { + length += (int)seg.data.Position; + if (seg.frg == 0) break; + } + + return length; + } + + public int Send(Memory memory) + { + // fragment count + int count; + var len = memory.Length; + if (len < 0) return -1; + + // streaming mode: removed. we never want to send 'hello' and + // receive 'he' 'll' 'o'. we want to always receive 'hello'. + + // calculate amount of fragments necessary for 'len' + if (len <= mss) count = 1; + else count = (int)((len + mss - 1) / mss); + + // IMPORTANT kcp encodes 'frg' as 1 byte. + // so we can only support up to 255 fragments. + // (which limits max message size to around 288 KB) + // this is difficult to debug. let's make this 100% obvious. + if (count > FRG_MAX) + throw new Exception($"Send len={len} requires {count} fragments, but kcp can only handle up to {FRG_MAX} fragments."); + + // original kcp uses WND_RCV const instead of rcv_wnd runtime: + // https://github.com/skywind3000/kcp/pull/291/files + // which always limits max message size to 144 KB: + //if (count >= WND_RCV) return -2; + // using configured rcv_wnd uncorks max message size to 'any': + if (count >= rcv_wnd) return -2; + + if (count == 0) count = 1; + + // fragment + var offset = 0; + for (int i = 0; i < count; i++) + { + int size = len > (int)mss ? (int)mss : len; + Segment seg = SegmentNew(); + + if (len > 0) + { + var slice = memory.Span.Slice(offset, size); + seg.data.Write(slice); + } + // seg.len = size: WriteBytes sets segment.Position! + + // set fragment number. + // if the message requires no fragmentation, then + // seg.frg becomes 1-0-1 = 0 + seg.frg = (uint)(count - i - 1); + snd_queue.Enqueue(seg); + offset += size; + len -= size; + } + + return 0; + } + + // ikcp_send + // splits message into MTU sized fragments, adds them to snd_queue. + public int Send(byte[] buffer, int offset, int len) + { + // fragment count + int count; + + if (len < 0) return -1; + + // streaming mode: removed. we never want to send 'hello' and + // receive 'he' 'll' 'o'. we want to always receive 'hello'. + + // calculate amount of fragments necessary for 'len' + if (len <= mss) count = 1; + else count = (int)((len + mss - 1) / mss); + + // IMPORTANT kcp encodes 'frg' as 1 byte. + // so we can only support up to 255 fragments. + // (which limits max message size to around 288 KB) + // this is difficult to debug. let's make this 100% obvious. + if (count > FRG_MAX) + throw new Exception($"Send len={len} requires {count} fragments, but kcp can only handle up to {FRG_MAX} fragments."); + + // original kcp uses WND_RCV const instead of rcv_wnd runtime: + // https://github.com/skywind3000/kcp/pull/291/files + // which always limits max message size to 144 KB: + //if (count >= WND_RCV) return -2; + // using configured rcv_wnd uncorks max message size to 'any': + if (count >= rcv_wnd) return -2; + + if (count == 0) count = 1; + + // fragment + for (int i = 0; i < count; i++) + { + int size = len > (int)mss ? (int)mss : len; + Segment seg = SegmentNew(); + + if (len > 0) + { + seg.data.Write(buffer, offset, size); + } + // seg.len = size: WriteBytes sets segment.Position! + + // set fragment number. + // if the message requires no fragmentation, then + // seg.frg becomes 1-0-1 = 0 + seg.frg = (uint)(count - i - 1); + snd_queue.Enqueue(seg); + offset += size; + len -= size; + } + + return 0; + } + + // ikcp_update_ack + void UpdateAck(int rtt) // round trip time + { + // https://tools.ietf.org/html/rfc6298 + if (rx_srtt == 0) + { + rx_srtt = rtt; + rx_rttval = rtt / 2; + } + else + { + int delta = rtt - rx_srtt; + if (delta < 0) delta = -delta; + rx_rttval = (3 * rx_rttval + delta) / 4; + rx_srtt = (7 * rx_srtt + rtt) / 8; + if (rx_srtt < 1) rx_srtt = 1; + } + int rto = rx_srtt + Math.Max((int)interval, 4 * rx_rttval); + rx_rto = Utils.Clamp(rto, rx_minrto, RTO_MAX); + } + + // ikcp_shrink_buf + internal void ShrinkBuf() + { + if (snd_buf.Count > 0) + { + Segment seg = snd_buf[0]; + snd_una = seg.sn; + } + else + { + snd_una = snd_nxt; + } + } + + // ikcp_parse_ack + // removes the segment with 'sn' from send buffer + internal void ParseAck(uint sn) + { + if (Utils.TimeDiff(sn, snd_una) < 0 || Utils.TimeDiff(sn, snd_nxt) >= 0) + return; + + // for-int so we can erase while iterating + for (int i = 0; i < snd_buf.Count; ++i) + { + // is this the segment? + Segment seg = snd_buf[i]; + if (sn == seg.sn) + { + // remove and return + snd_buf.RemoveAt(i); + SegmentDelete(seg); + break; + } + if (Utils.TimeDiff(sn, seg.sn) < 0) + { + break; + } + } + } + + // ikcp_parse_una + // removes all unacknowledged segments with sequence numbers < una from send buffer + internal void ParseUna(uint una) + { + int removed = 0; + foreach (Segment seg in snd_buf) + { + // if (Utils.TimeDiff(una, seg.sn) > 0) + if (seg.sn < una) + { + // can't remove while iterating. remember how many to remove + // and do it after the loop. + ++removed; + SegmentDelete(seg); + } + else + { + break; + } + } + snd_buf.RemoveRange(0, removed); + } + + // ikcp_parse_fastack + internal void ParseFastack(uint sn, uint ts) // serial number, timestamp + { + // sn needs to be between snd_una and snd_nxt + // if !(snd_una <= sn && sn < snd_nxt) return; + + // if (Utils.TimeDiff(sn, snd_una) < 0) + if (sn < snd_una) + return; + + // if (Utils.TimeDiff(sn, snd_nxt) >= 0) + if (sn >= snd_nxt) + return; + + foreach (Segment seg in snd_buf) + { + // if (Utils.TimeDiff(sn, seg.sn) < 0) + if (sn < seg.sn) + { + break; + } + else if (sn != seg.sn) + { +#if !FASTACK_CONSERVE + seg.fastack++; +#else + if (Utils.TimeDiff(ts, seg.ts) >= 0) + seg.fastack++; +#endif + } + } + } + + // ikcp_ack_push + // appends an ack. + void AckPush(uint sn, uint ts) // serial number, timestamp + { + acklist.Add(new AckItem{ serialNumber = sn, timestamp = ts }); + } + + // ikcp_parse_data + void ParseData(Segment newseg) + { + uint sn = newseg.sn; + + if (Utils.TimeDiff(sn, rcv_nxt + rcv_wnd) >= 0 || + Utils.TimeDiff(sn, rcv_nxt) < 0) + { + SegmentDelete(newseg); + return; + } + + InsertSegmentInReceiveBuffer(newseg); + MoveReceiveBufferReadySegmentsToQueue(); + } + + // inserts the segment into rcv_buf, ordered by seg.sn. + // drops the segment if one with the same seg.sn already exists. + // goes through receive buffer in reverse order for performance. + // + // note: see KcpTests.InsertSegmentInReceiveBuffer test! + // note: 'insert or delete' can be done in different ways, but let's + // keep consistency with original C kcp. + internal void InsertSegmentInReceiveBuffer(Segment newseg) + { + bool repeat = false; // 'duplicate' + + // original C iterates backwards, so we need to do that as well. + // note if rcv_buf.Count == 0, i becomes -1 and no looping happens. + int i; + for (i = rcv_buf.Count - 1; i >= 0; i--) + { + Segment seg = rcv_buf[i]; + if (seg.sn == newseg.sn) + { + // duplicate segment found. nothing will be added. + repeat = true; + break; + } + if (Utils.TimeDiff(newseg.sn, seg.sn) > 0) + { + // this entry's sn is < newseg.sn, so let's stop + break; + } + } + + // no duplicate? then insert. + if (!repeat) + { + rcv_buf.Insert(i + 1, newseg); + } + // duplicate. just delete it. + else + { + SegmentDelete(newseg); + } + } + + // move ready segments from rcv_buf -> rcv_queue. + // moves only the ready segments which are in rcv_nxt sequence order. + // some may still be missing an inserted later. + void MoveReceiveBufferReadySegmentsToQueue() + { + int removed = 0; + foreach (Segment seg in rcv_buf) + { + // move segments while they are in 'rcv_nxt' sequence order. + // some may still be missing and inserted later, in this case it stops immediately + // because segments always need to be received in the exact sequence order. + if (seg.sn == rcv_nxt && rcv_queue.Count < rcv_wnd) + { + // can't remove while iterating. remember how many to remove + // and do it after the loop. + ++removed; + rcv_queue.Enqueue(seg); + // increase sequence number for next segment + rcv_nxt++; + } + else + { + break; + } + } + rcv_buf.RemoveRange(0, removed); + } + + // ikcp_input + // used when you receive a low level packet (e.g. UDP packet) + // => original kcp uses offset=0, we made it a parameter so that high + // level can skip the channel byte more easily + public int Input(byte[] data, int offset, int size) + { + uint prev_una = snd_una; + uint maxack = 0; + uint latest_ts = 0; + int flag = 0; + + if (data == null || size < OVERHEAD) return -1; + + while (true) + { + // enough data left to decode segment (aka OVERHEAD bytes)? + if (size < OVERHEAD) break; + + // decode segment + offset += Utils.Decode32U(data, offset, out uint conv_); + if (conv_ != conv) return -1; + + offset += Utils.Decode8u(data, offset, out byte cmd); + // IMPORTANT kcp encodes 'frg' as 1 byte. + // so we can only support up to 255 fragments. + // (which limits max message size to around 288 KB) + offset += Utils.Decode8u(data, offset, out byte frg); + offset += Utils.Decode16U(data, offset, out ushort wnd); + offset += Utils.Decode32U(data, offset, out uint ts); + offset += Utils.Decode32U(data, offset, out uint sn); + offset += Utils.Decode32U(data, offset, out uint una); + offset += Utils.Decode32U(data, offset, out uint len); + + // reduce remaining size by what was read + size -= OVERHEAD; + + // enough remaining to read 'len' bytes of the actual payload? + // note: original kcp casts uint len to int for <0 check. + if (size < len || (int)len < 0) return -2; + + // validate command type + if (cmd != CMD_PUSH && cmd != CMD_ACK && + cmd != CMD_WASK && cmd != CMD_WINS) + return -3; + + rmt_wnd = wnd; + ParseUna(una); + ShrinkBuf(); + + if (cmd == CMD_ACK) + { + if (Utils.TimeDiff(current, ts) >= 0) + { + UpdateAck(Utils.TimeDiff(current, ts)); + } + ParseAck(sn); + ShrinkBuf(); + if (flag == 0) + { + flag = 1; + maxack = sn; + latest_ts = ts; + } + else + { + if (Utils.TimeDiff(sn, maxack) > 0) + { +#if !FASTACK_CONSERVE + maxack = sn; + latest_ts = ts; +#else + if (Utils.TimeDiff(ts, latest_ts) > 0) + { + maxack = sn; + latest_ts = ts; + } +#endif + } + } + } + else if (cmd == CMD_PUSH) + { + if (Utils.TimeDiff(sn, rcv_nxt + rcv_wnd) < 0) + { + AckPush(sn, ts); + if (Utils.TimeDiff(sn, rcv_nxt) >= 0) + { + Segment seg = SegmentNew(); + seg.conv = conv_; + seg.cmd = cmd; + seg.frg = frg; + seg.wnd = wnd; + seg.ts = ts; + seg.sn = sn; + seg.una = una; + if (len > 0) + { + seg.data.Write(data, offset, (int)len); + } + ParseData(seg); + } + } + } + else if (cmd == CMD_WASK) + { + // ready to send back CMD_WINS in flush + // tell remote my window size + probe |= ASK_TELL; + } + else if (cmd == CMD_WINS) + { + // do nothing + } + else + { + return -3; + } + + offset += (int)len; + size -= (int)len; + } + + if (flag != 0) + { + ParseFastack(maxack, latest_ts); + } + + // cwnd update when packet arrived + if (Utils.TimeDiff(snd_una, prev_una) > 0) + { + if (cwnd < rmt_wnd) + { + if (cwnd < ssthresh) + { + cwnd++; + incr += mss; + } + else + { + if (incr < mss) incr = mss; + incr += (mss * mss) / incr + (mss / 16); + if ((cwnd + 1) * mss <= incr) + { + cwnd = (incr + mss - 1) / ((mss > 0) ? mss : 1); + } + } + if (cwnd > rmt_wnd) + { + cwnd = rmt_wnd; + incr = rmt_wnd * mss; + } + } + } + + return 0; + } + + // flush helper function + void MakeSpace(ref int size, int space) + { + if (size + space > mtu) + { + output(buffer, size); + size = 0; + } + } + + // flush helper function + void FlushBuffer(int size) + { + // flush buffer up to 'offset' (<= MTU) + if (size > 0) + { + output(buffer, size); + } + } + + // ikcp_flush + // flush remain ack segments. + // flush may output multiple <= MTU messages from MakeSpace / FlushBuffer. + // the amount of messages depends on the sliding window. + // configured by send/receive window sizes + congestion control. + // with congestion control, the window will be extremely small(!). + public void Flush() + { + int size = 0; // amount of bytes to flush. 'buffer ptr' in C. + bool lost = false; // lost segments + + // update needs to be called before flushing + if (!updated) return; + + // kcp only stack allocates a segment here for performance, leaving + // its data buffer null because this segment's data buffer is never + // used. that's fine in C, but in C# our segment is a class so we + // need to allocate and most importantly, not forget to deallocate + // it before returning. + Segment seg = SegmentNew(); + seg.conv = conv; + seg.cmd = CMD_ACK; + seg.wnd = WndUnused(); + seg.una = rcv_nxt; + + // flush acknowledges + foreach (AckItem ack in acklist) + { + MakeSpace(ref size, OVERHEAD); + // ikcp_ack_get assigns ack[i] to seg.sn, seg.ts + seg.sn = ack.serialNumber; + seg.ts = ack.timestamp; + size += seg.Encode(buffer, size); + } + acklist.Clear(); + + // probe window size (if remote window size equals zero) + if (rmt_wnd == 0) + { + if (probe_wait == 0) + { + probe_wait = PROBE_INIT; + ts_probe = current + probe_wait; + } + else + { + if (Utils.TimeDiff(current, ts_probe) >= 0) + { + if (probe_wait < PROBE_INIT) + probe_wait = PROBE_INIT; + probe_wait += probe_wait / 2; + if (probe_wait > PROBE_LIMIT) + probe_wait = PROBE_LIMIT; + ts_probe = current + probe_wait; + probe |= ASK_SEND; + } + } + } + else + { + ts_probe = 0; + probe_wait = 0; + } + + // flush window probing commands + if ((probe & ASK_SEND) != 0) + { + seg.cmd = CMD_WASK; + MakeSpace(ref size, OVERHEAD); + size += seg.Encode(buffer, size); + } + + // flush window probing commands + if ((probe & ASK_TELL) != 0) + { + seg.cmd = CMD_WINS; + MakeSpace(ref size, OVERHEAD); + size += seg.Encode(buffer, size); + } + + probe = 0; + + // calculate the window size which is currently safe to send. + // it's send window, or remote window, whatever is smaller. + // for our max + uint cwnd_ = Math.Min(snd_wnd, rmt_wnd); + + // double negative: if congestion window is enabled: + // limit window size to cwnd. + // + // note this may heavily limit window sizes. + // for our max message size test with super large windows of 32k, + // 'congestion window' limits it down from 32.000 to 2. + if (!nocwnd) cwnd_ = Math.Min(cwnd, cwnd_); + + // move cwnd_ 'window size' messages from snd_queue to snd_buf + // 'snd_nxt' is what we want to send. + // 'snd_una' is what hasn't been acked yet. + // copy up to 'cwnd_' difference between them (sliding window) + while (Utils.TimeDiff(snd_nxt, snd_una + cwnd_) < 0) + { + if (snd_queue.Count == 0) break; + + Segment newseg = snd_queue.Dequeue(); + + newseg.conv = conv; + newseg.cmd = CMD_PUSH; + newseg.wnd = seg.wnd; + newseg.ts = current; + newseg.sn = snd_nxt; + snd_nxt += 1; // increase sequence number for next segment + newseg.una = rcv_nxt; + newseg.resendts = current; + newseg.rto = rx_rto; + newseg.fastack = 0; + newseg.xmit = 0; + snd_buf.Add(newseg); + } + + // calculate resent + uint resent = fastresend > 0 ? (uint)fastresend : 0xffffffff; + uint rtomin = nodelay == 0 ? (uint)rx_rto >> 3 : 0; + + // flush data segments + int change = 0; + foreach (Segment segment in snd_buf) + { + bool needsend = false; + + // initial transmit + if (segment.xmit == 0) + { + needsend = true; + segment.xmit++; + segment.rto = rx_rto; + segment.resendts = current + (uint)segment.rto + rtomin; + } + // RTO + else if (Utils.TimeDiff(current, segment.resendts) >= 0) + { + needsend = true; + segment.xmit++; + xmit++; + if (nodelay == 0) + { + segment.rto += Math.Max(segment.rto, rx_rto); + } + else + { + int step = (nodelay < 2) ? segment.rto : rx_rto; + segment.rto += step / 2; + } + segment.resendts = current + (uint)segment.rto; + lost = true; + } + // fast retransmit + else if (segment.fastack >= resent) + { + if (segment.xmit <= fastlimit || fastlimit <= 0) + { + needsend = true; + segment.xmit++; + segment.fastack = 0; + segment.resendts = current + (uint)segment.rto; + change++; + } + } + + if (needsend) + { + segment.ts = current; + segment.wnd = seg.wnd; + segment.una = rcv_nxt; + + int need = OVERHEAD + (int)segment.data.Position; + MakeSpace(ref size, need); + + size += segment.Encode(buffer, size); + + if (segment.data.Position > 0) + { + Buffer.BlockCopy(segment.data.GetBuffer(), 0, buffer, size, (int)segment.data.Position); + size += (int)segment.data.Position; + } + + // dead link happens if a message was resent N times, but an + // ack was still not received. + if (segment.xmit >= dead_link) + { + state = -1; + } + } + } + + // kcp stackallocs 'seg'. our C# segment is a class though, so we + // need to properly delete and return it to the pool now that we are + // done with it. + SegmentDelete(seg); + + // flush remaining segments + FlushBuffer(size); + + // update ssthresh + // rate halving, https://tools.ietf.org/html/rfc6937 + if (change > 0) + { + uint inflight = snd_nxt - snd_una; + ssthresh = inflight / 2; + if (ssthresh < THRESH_MIN) + ssthresh = THRESH_MIN; + cwnd = ssthresh + resent; + incr = cwnd * mss; + } + + // congestion control, https://tools.ietf.org/html/rfc5681 + if (lost) + { + // original C uses 'cwnd', not kcp->cwnd! + ssthresh = cwnd_ / 2; + if (ssthresh < THRESH_MIN) + ssthresh = THRESH_MIN; + cwnd = 1; + incr = mss; + } + + if (cwnd < 1) + { + cwnd = 1; + incr = mss; + } + } + + // ikcp_update + // update state (call it repeatedly, every 10ms-100ms), or you can ask + // Check() when to call it again (without Input/Send calling). + // + // 'current' - current timestamp in millisec. pass it to Kcp so that + // Kcp doesn't have to do any stopwatch/deltaTime/etc. code + // + // time as uint, likely to minimize bandwidth. + // uint.max = 4294967295 ms = 1193 hours = 49 days + public void Update(uint currentTimeMilliSeconds) + { + current = currentTimeMilliSeconds; + + // not updated yet? then set updated and last flush time. + if (!updated) + { + updated = true; + ts_flush = current; + } + + // slap is time since last flush in milliseconds + int slap = Utils.TimeDiff(current, ts_flush); + + // hard limit: if 10s elapsed, always flush no matter what + if (slap >= 10000 || slap < -10000) + { + ts_flush = current; + slap = 0; + } + + // last flush is increased by 'interval' each time. + // so slap >= is a strange way to check if interval has elapsed yet. + if (slap >= 0) + { + // increase last flush time by one interval + ts_flush += interval; + + // if last flush is still behind, increase it to current + interval + // if (Utils.TimeDiff(current, ts_flush) >= 0) // original kcp.c + if (current >= ts_flush) // less confusing + { + ts_flush = current + interval; + } + Flush(); + } + } + + // ikcp_check + // Determine when should you invoke update + // Returns when you should invoke update in millisec, if there is no + // input/send calling. you can call update in that time, instead of + // call update repeatly. + // + // Important to reduce unnecessary update invoking. use it to schedule + // update (e.g. implementing an epoll-like mechanism, or optimize update + // when handling massive kcp connections). + public uint Check(uint current_) + { + uint ts_flush_ = ts_flush; + // int tm_flush = 0x7fffffff; original kcp: useless assignment + int tm_packet = 0x7fffffff; + + if (!updated) + { + return current_; + } + + if (Utils.TimeDiff(current_, ts_flush_) >= 10000 || + Utils.TimeDiff(current_, ts_flush_) < -10000) + { + ts_flush_ = current_; + } + + if (Utils.TimeDiff(current_, ts_flush_) >= 0) + { + return current_; + } + + int tm_flush = Utils.TimeDiff(ts_flush_, current_); + + foreach (Segment seg in snd_buf) + { + int diff = Utils.TimeDiff(seg.resendts, current_); + if (diff <= 0) + { + return current_; + } + if (diff < tm_packet) tm_packet = diff; + } + + uint minimal = (uint)(tm_packet < tm_flush ? tm_packet : tm_flush); + if (minimal >= interval) minimal = interval; + + return current_ + minimal; + } + + // ikcp_setmtu + // Change MTU (Maximum Transmission Unit) size. + public void SetMtu(uint mtu) + { + if (mtu < 50 || mtu < OVERHEAD) + throw new ArgumentException("MTU must be higher than 50 and higher than OVERHEAD"); + + buffer = new byte[(mtu + OVERHEAD) * 3]; + this.mtu = mtu; + mss = mtu - OVERHEAD; + } + + // ikcp_interval + public void SetInterval(uint interval) + { + // clamp interval between 10 and 5000 + if (interval > 5000) interval = 5000; + else if (interval < 10) interval = 10; + this.interval = interval; + } + + // ikcp_nodelay + // configuration: https://github.com/skywind3000/kcp/blob/master/README.en.md#protocol-configuration + // nodelay : Whether nodelay mode is enabled, 0 is not enabled; 1 enabled. + // interval :Protocol internal work interval, in milliseconds, such as 10 ms or 20 ms. + // resend :Fast retransmission mode, 0 represents off by default, 2 can be set (2 ACK spans will result in direct retransmission) + // nc :Whether to turn off flow control, 0 represents “Do not turn off” by default, 1 represents “Turn off”. + // Normal Mode: ikcp_nodelay(kcp, 0, 40, 0, 0); + // Turbo Mode: ikcp_nodelay(kcp, 1, 10, 2, 1); + public void SetNoDelay(uint nodelay, uint interval = INTERVAL, int resend = 0, bool nocwnd = false) + { + this.nodelay = nodelay; + if (nodelay != 0) + { + rx_minrto = RTO_NDL; + } + else + { + rx_minrto = RTO_MIN; + } + + if (interval >= 0) + { + // clamp interval between 10 and 5000 + if (interval > 5000) interval = 5000; + else if (interval < 10) interval = 10; + this.interval = interval; + } + + if (resend >= 0) + { + fastresend = resend; + } + + this.nocwnd = nocwnd; + } + + // ikcp_wndsize + public void SetWindowSize(uint sendWindow, uint receiveWindow) + { + if (sendWindow > 0) + { + snd_wnd = sendWindow; + } + + if (receiveWindow > 0) + { + // must >= max fragment size + rcv_wnd = Math.Max(receiveWindow, WND_RCV); + } + } + } +} diff --git a/Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/KCP/Base/kcp2k/kcp/Kcp.cs.meta b/Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/KCP/Base/kcp2k/kcp/Kcp.cs.meta new file mode 100644 index 00000000..94ac97e2 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/KCP/Base/kcp2k/kcp/Kcp.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: c43eaf92abd8f764bb6f94c9b5a701c4 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/KCP/Base/kcp2k/kcp/Pool.cs b/Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/KCP/Base/kcp2k/kcp/Pool.cs new file mode 100644 index 00000000..81b52895 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/KCP/Base/kcp2k/kcp/Pool.cs @@ -0,0 +1,46 @@ +// Pool to avoid allocations (from libuv2k & Mirror) +using System; +using System.Collections.Generic; + +namespace kcp2k +{ + public class Pool + { + // Mirror is single threaded, no need for concurrent collections + readonly Stack objects = new Stack(); + + // some types might need additional parameters in their constructor, so + // we use a Func generator + readonly Func objectGenerator; + + // some types might need additional cleanup for returned objects + readonly Action objectResetter; + + public Pool(Func objectGenerator, Action objectResetter, int initialCapacity) + { + this.objectGenerator = objectGenerator; + this.objectResetter = objectResetter; + + // allocate an initial pool so we have fewer (if any) + // allocations in the first few frames (or seconds). + for (int i = 0; i < initialCapacity; ++i) + objects.Push(objectGenerator()); + } + + // take an element from the pool, or create a new one if empty + public T Take() => objects.Count > 0 ? objects.Pop() : objectGenerator(); + + // return an element to the pool + public void Return(T item) + { + objectResetter(item); + objects.Push(item); + } + + // clear the pool + public void Clear() => objects.Clear(); + + // count to see how many objects are in the pool. useful for tests. + public int Count => objects.Count; + } +} diff --git a/Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/KCP/Base/kcp2k/kcp/Pool.cs.meta b/Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/KCP/Base/kcp2k/kcp/Pool.cs.meta new file mode 100644 index 00000000..8bb774ee --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/KCP/Base/kcp2k/kcp/Pool.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d78a539c8e8e73a46bd6ffd83d5e9d8c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/KCP/Base/kcp2k/kcp/Segment.cs b/Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/KCP/Base/kcp2k/kcp/Segment.cs new file mode 100644 index 00000000..d7e41317 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/KCP/Base/kcp2k/kcp/Segment.cs @@ -0,0 +1,78 @@ +using System.IO; + +namespace kcp2k +{ + // KCP Segment Definition + internal class Segment + { + internal uint conv; // conversation + internal uint cmd; // command, e.g. Kcp.CMD_ACK etc. + // fragment (sent as 1 byte). + // 0 if unfragmented, otherwise fragment numbers in reverse: N,..,32,1,0 + // this way the first received segment tells us how many fragments there are. + internal uint frg; + internal uint wnd; // window size that the receive can currently receive + internal uint ts; // timestamp + internal uint sn; // sequence number + internal uint una; + internal uint resendts; // resend timestamp + internal int rto; + internal uint fastack; + internal uint xmit; // retransmit count + + // we need an auto scaling byte[] with a WriteBytes function. + // MemoryStream does that perfectly, no need to reinvent the wheel. + // note: no need to pool it, because Segment is already pooled. + // -> default MTU as initial capacity to avoid most runtime resizing/allocations + // + // .data is only used for Encode(), which always fits it into a buffer. + // the buffer is always Kcp.buffer. Kcp ctor creates the buffer of size: + // (mtu + OVERHEAD) * 3 bytes. + // in other words, Encode only ever writes up to the above amount of bytes. + internal MemoryStream data = new MemoryStream(Kcp.MTU_DEF); + + // ikcp_encode_seg + // encode a segment into buffer. + // buffer is always Kcp.buffer. Kcp ctor creates the buffer of size: + // (mtu + OVERHEAD) * 3 bytes. + // in other words, Encode only ever writes up to the above amount of bytes. + internal int Encode(byte[] ptr, int offset) + { + int previousPosition = offset; + + offset += Utils.Encode32U(ptr, offset, conv); + offset += Utils.Encode8u(ptr, offset, (byte)cmd); + // IMPORTANT kcp encodes 'frg' as 1 byte. + // so we can only support up to 255 fragments. + // (which limits max message size to around 288 KB) + offset += Utils.Encode8u(ptr, offset, (byte)frg); + offset += Utils.Encode16U(ptr, offset, (ushort)wnd); + offset += Utils.Encode32U(ptr, offset, ts); + offset += Utils.Encode32U(ptr, offset, sn); + offset += Utils.Encode32U(ptr, offset, una); + offset += Utils.Encode32U(ptr, offset, (uint)data.Position); + + int written = offset - previousPosition; + return written; + } + + // reset to return a fresh segment to the pool + internal void Reset() + { + conv = 0; + cmd = 0; + frg = 0; + wnd = 0; + ts = 0; + sn = 0; + una = 0; + rto = 0; + xmit = 0; + resendts = 0; + fastack = 0; + + // keep buffer for next pool usage, but reset length (= bytes written) + data.SetLength(0); + } + } +} diff --git a/Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/KCP/Base/kcp2k/kcp/Segment.cs.meta b/Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/KCP/Base/kcp2k/kcp/Segment.cs.meta new file mode 100644 index 00000000..3e8f7b42 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/KCP/Base/kcp2k/kcp/Segment.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 8749c094620ccde478c15165010ffbf1 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/KCP/Base/kcp2k/kcp/Utils.cs b/Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/KCP/Base/kcp2k/kcp/Utils.cs new file mode 100644 index 00000000..2cb74628 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/KCP/Base/kcp2k/kcp/Utils.cs @@ -0,0 +1,76 @@ +using System.Runtime.CompilerServices; + +namespace kcp2k +{ + public static partial class Utils + { + // Clamp so we don't have to depend on UnityEngine + public static int Clamp(int value, int min, int max) + { + if (value < min) return min; + if (value > max) return max; + return value; + } + + // encode 8 bits unsigned int + public static int Encode8u(byte[] p, int offset, byte value) + { + p[0 + offset] = value; + return 1; + } + + // decode 8 bits unsigned int + public static int Decode8u(byte[] p, int offset, out byte value) + { + value = p[0 + offset]; + return 1; + } + + // encode 16 bits unsigned int (lsb) + public static int Encode16U(byte[] p, int offset, ushort value) + { + p[0 + offset] = (byte)(value >> 0); + p[1 + offset] = (byte)(value >> 8); + return 2; + } + + // decode 16 bits unsigned int (lsb) + public static int Decode16U(byte[] p, int offset, out ushort value) + { + ushort result = 0; + result |= p[0 + offset]; + result |= (ushort)(p[1 + offset] << 8); + value = result; + return 2; + } + + // encode 32 bits unsigned int (lsb) + public static int Encode32U(byte[] p, int offset, uint value) + { + p[0 + offset] = (byte)(value >> 0); + p[1 + offset] = (byte)(value >> 8); + p[2 + offset] = (byte)(value >> 16); + p[3 + offset] = (byte)(value >> 24); + return 4; + } + + // decode 32 bits unsigned int (lsb) + public static int Decode32U(byte[] p, int offset, out uint value) + { + uint result = 0; + result |= p[0 + offset]; + result |= (uint)(p[1 + offset] << 8); + result |= (uint)(p[2 + offset] << 16); + result |= (uint)(p[3 + offset] << 24); + value = result; + return 4; + } + + // timediff was a macro in original Kcp. let's inline it if possible. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int TimeDiff(uint later, uint earlier) + { + return (int)(later - earlier); + } + } +} diff --git a/Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/KCP/Base/kcp2k/kcp/Utils.cs.meta b/Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/KCP/Base/kcp2k/kcp/Utils.cs.meta new file mode 100644 index 00000000..0835b931 --- /dev/null +++ b/Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/KCP/Base/kcp2k/kcp/Utils.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 927985771e12e4c45b199b20c8477369 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/KCP/Client/KCPClientNetwork.cs b/Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/KCP/Client/KCPClientNetwork.cs index 3f6498f7..dbef82d3 100644 --- a/Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/KCP/Client/KCPClientNetwork.cs +++ b/Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/KCP/Client/KCPClientNetwork.cs @@ -1,16 +1,15 @@ using System; +using System.Buffers; using System.Collections.Generic; using System.IO; using System.Net; using System.Net.Sockets; using System.Runtime.InteropServices; using System.Threading; -using TEngine.Core; +using kcp2k; +// ReSharper disable ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract // ReSharper disable PossibleNullReferenceException // ReSharper disable InconsistentNaming -#pragma warning disable CS8602 -#pragma warning disable CS8625 -#pragma warning disable CS8618 namespace TEngine.Core.Network { @@ -55,21 +54,22 @@ namespace TEngine.Core.Network { ThreadSynchronizationContext.Main.Post(OnConnectDisconnect); } - + _socket.Disconnect(false); _socket.Close(); } - + + _kcp = null; _maxSndWnd = 0; _updateMinTime = 0; - + _memoryPool.Dispose(); + _memoryPool = null; _sendAction = null; _rawSendBuffer = null; _rawReceiveBuffer = null; _packetParser?.Dispose(); - _receiveMemoryStream?.Dispose(); - + ClearConnectTimeout(ref _connectTimeoutId); if (_messageCache != null) @@ -77,13 +77,6 @@ namespace TEngine.Core.Network _messageCache.Clear(); _messageCache = null; } - - if (_kcpIntPtr != IntPtr.Zero) - { - KCP.KcpRelease(_kcpIntPtr); - ConnectionPtrChannel.Remove(_kcpIntPtr); - _kcpIntPtr = IntPtr.Zero; - } #if NETDEBUG Log.Debug($"KCPClientNetwork ConnectionPtrChannel:{ConnectionPtrChannel.Count}"); #endif @@ -110,6 +103,7 @@ namespace TEngine.Core.Network _maxSndWnd = _kcpSettings.MaxSendWindowSize; _messageCache = new Queue(); _rawReceiveBuffer = new byte[_kcpSettings.Mtu + 5]; + _memoryPool = MemoryPool.Shared; _sendAction = (rpcId, routeTypeOpCode, routeId, memoryStream, message) => { @@ -151,7 +145,7 @@ namespace TEngine.Core.Network private Socket _socket; private int _maxSndWnd; - private IntPtr _kcpIntPtr; + private Kcp _kcp; private bool _isDisconnect; private long _updateMinTime; private byte[] _rawSendBuffer; @@ -159,15 +153,12 @@ namespace TEngine.Core.Network private byte[] _rawReceiveBuffer; private KCPSettings _kcpSettings; private APacketParser _packetParser; - private MemoryStream _receiveMemoryStream; + private MemoryPool _memoryPool; private Queue _messageCache; private Action _sendAction; - private readonly Queue _updateTimeOutTime = new Queue(); private EndPoint _clientEndPoint = new IPEndPoint(IPAddress.Any, 0); - private readonly SortedDictionary _updateTimer = new SortedDictionary(); - private static readonly Dictionary ConnectionPtrChannel = new Dictionary(); - + private readonly SortedSet _updateTimer = new SortedSet(); private uint TimeNow => (uint) (TimeHelper.Now - _startTime); private void Receive() @@ -213,14 +204,11 @@ namespace TEngine.Core.Network SendHeader(KcpHeader.ConfirmConnection); ClearConnectTimeout(ref _connectTimeoutId); // 创建KCP和相关的初始化 - _kcpIntPtr = KCP.KcpCreate(channelId, new IntPtr(channelId)); - KCP.KcpNodelay(_kcpIntPtr, 1, 5, 2, 1); - KCP.KcpWndsize(_kcpIntPtr, _kcpSettings.SendWindowSize, _kcpSettings.ReceiveWindowSize); - KCP.KcpSetmtu(_kcpIntPtr, _kcpSettings.Mtu); - KCP.KcpSetminrto(_kcpIntPtr, 30); - KCP.KcpSetoutput(_kcpIntPtr, KcpOutput); + _kcp = new Kcp(channelId, Output); + _kcp.SetNoDelay(1, 5, 2, true); + _kcp.SetWindowSize(_kcpSettings.SendWindowSize, _kcpSettings.ReceiveWindowSize); + _kcp.SetMtu(_kcpSettings.Mtu); _rawSendBuffer = new byte[ushort.MaxValue]; - _receiveMemoryStream = MemoryStreamHelper.GetRecyclableMemoryStream(); _packetParser = APacketParser.CreatePacketParser(NetworkTarget); // 把缓存的消息全部发送给服务器 @@ -248,7 +236,6 @@ namespace TEngine.Core.Network _messageCache.Clear(); _messageCache = null; - ConnectionPtrChannel.Add(_kcpIntPtr, this); // 调用ChannelId改变事件、就算没有改变也要发下、接收事件的地方会判定下 ThreadSynchronizationContext.Main.Post(() => { @@ -272,8 +259,8 @@ namespace TEngine.Core.Network { break; } - - KCP.KcpInput(_kcpIntPtr, _rawReceiveBuffer, 5, messageLength); + + _kcp.Input(_rawReceiveBuffer, 5, messageLength); AddToUpdate(0); KcpReceive(); break; @@ -284,7 +271,7 @@ namespace TEngine.Core.Network { break; } - + _isDisconnect = true; Dispose(); break; @@ -310,8 +297,8 @@ namespace TEngine.Core.Network } #endif // 检查等待发送的消息,如果超出两倍窗口大小,KCP作者给的建议是要断开连接 - - var waitSendSize = KCP.KcpWaitsnd(_kcpIntPtr); + + var waitSendSize = _kcp.WaitSnd; if (waitSendSize > _maxSndWnd) { @@ -319,15 +306,9 @@ namespace TEngine.Core.Network Dispose(); return; } - - // 发送消息 - - KCP.KcpSend(_kcpIntPtr, memoryStream.GetBuffer(), (int) memoryStream.Length); - - // 因为memoryStream对象池出来的、所以需要手动回收下 + _kcp.Send(memoryStream.GetBuffer(), 0, (int)memoryStream.Length); memoryStream.Dispose(); - AddToUpdate(0); } @@ -365,7 +346,7 @@ namespace TEngine.Core.Network _sendAction(rpcId, routeTypeOpCode, entityId, null, message); } - private void Output(IntPtr bytes, int count) + private void Output(byte[] bytes, int count) { #if TENGINE_DEVELOP if (NetworkThread.Instance.ManagedThreadId != Thread.CurrentThread.ManagedThreadId) @@ -374,7 +355,7 @@ namespace TEngine.Core.Network return; } #endif - if (IsDisposed || _kcpIntPtr == IntPtr.Zero) + if (IsDisposed) { return; } @@ -388,7 +369,7 @@ namespace TEngine.Core.Network _rawSendBuffer.WriteTo(0, (byte) KcpHeader.ReceiveData); _rawSendBuffer.WriteTo(1, ChannelId); - Marshal.Copy(bytes, _rawSendBuffer, 5, count); + Buffer.BlockCopy(bytes, 0, _rawSendBuffer, 5, count); _socket.Send(_rawSendBuffer, 0, count + 5, SocketFlags.None); } catch (Exception e) @@ -406,7 +387,7 @@ namespace TEngine.Core.Network return; } #endif - if (IsDisposed || _kcpIntPtr == IntPtr.Zero) + if (IsDisposed) { return; } @@ -416,8 +397,8 @@ namespace TEngine.Core.Network try { // 获得一个完整消息的长度 - - var peekSize = KCP.KcpPeeksize(_kcpIntPtr); + + var peekSize = _kcp.PeekSize(); // 如果没有接收的消息那就跳出当前循环。 @@ -433,9 +414,8 @@ namespace TEngine.Core.Network throw new Exception("SocketError.NetworkReset"); } - _receiveMemoryStream.SetLength(peekSize); - _receiveMemoryStream.Seek(0, SeekOrigin.Begin); - var receiveCount = KCP.KcpRecv(_kcpIntPtr, _receiveMemoryStream.GetBuffer(), peekSize); + var receiveMemoryOwner = _memoryPool.Rent(Packet.OuterPacketMaxLength); + var receiveCount = _kcp.Receive(receiveMemoryOwner.Memory, peekSize); // 如果接收的长度跟peekSize不一样,不需要处理,因为消息肯定有问题的(虽然不可能出现)。 @@ -445,13 +425,10 @@ namespace TEngine.Core.Network break; } - var packInfo = _packetParser.UnPack(_receiveMemoryStream); - - if (packInfo == null) + if (!_packetParser.UnPack(receiveMemoryOwner, out var packInfo)) { break; } - ThreadSynchronizationContext.Main.Post(() => { @@ -483,15 +460,13 @@ namespace TEngine.Core.Network foreach (var timeId in _updateTimer) { - var key = timeId.Key; - - if (key > nowTime) + if (timeId > nowTime) { - _updateMinTime = key; + _updateMinTime = timeId; break; } - _updateTimeOutTime.Enqueue(key); + _updateTimeOutTime.Enqueue(timeId); } while (_updateTimeOutTime.TryDequeue(out var time)) @@ -521,7 +496,7 @@ namespace TEngine.Core.Network _updateMinTime = tillTime; } - _updateTimer[tillTime] = KcpUpdate; + _updateTimer.Add(tillTime); } private void KcpUpdate() @@ -537,17 +512,14 @@ namespace TEngine.Core.Network try { - KCP.KcpUpdate(_kcpIntPtr, nowTime); + _kcp.Update(nowTime); } catch (Exception e) { Log.Error(e); } - if (_kcpIntPtr != IntPtr.Zero) - { - AddToUpdate(KCP.KcpCheck(_kcpIntPtr, nowTime)); - } + AddToUpdate(_kcp.Check(nowTime)); } public override void RemoveChannel(uint channelId) @@ -590,38 +562,6 @@ namespace TEngine.Core.Network TimerScheduler.Instance.Core.RemoveByRef(ref connectTimeoutId); } - -#if ENABLE_IL2CPP - [AOT.MonoPInvokeCallback(typeof(KcpOutput))] -#endif - private static int KcpOutput(IntPtr bytes, int count, IntPtr kcp, IntPtr user) - { -#if TENGINE_DEVELOP - if (NetworkThread.Instance.ManagedThreadId != Thread.CurrentThread.ManagedThreadId) - { - Log.Error("not in NetworkThread!"); - return 0; - } -#endif - try - { - if (kcp == IntPtr.Zero || !ConnectionPtrChannel.TryGetValue(kcp, out var channel)) - { - return 0; - } - - if (!channel.IsDisposed) - { - channel.Output(bytes, count); - } - } - catch (Exception e) - { - Log.Error(e); - } - - return count; - } #endregion } diff --git a/Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/KCP/Server/KCPServerNetwork.cs b/Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/KCP/Server/KCPServerNetwork.cs index 0439e4d0..57184341 100644 --- a/Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/KCP/Server/KCPServerNetwork.cs +++ b/Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/KCP/Server/KCPServerNetwork.cs @@ -5,11 +5,10 @@ using System.Linq; using System.Net; using System.Net.Sockets; using TEngine.DataStructure; -using TEngine.Core; +using kcp2k; + // ReSharper disable InconsistentNaming -#pragma warning disable CS8601 -#pragma warning disable CS8625 -#pragma warning disable CS8618 +// ReSharper disable ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract namespace TEngine.Core.Network { @@ -102,7 +101,6 @@ namespace TEngine.Core.Network private readonly SortedOneToManyList _pendingConnectionTimer = new SortedOneToManyList(); private readonly Dictionary _pendingConnection = new Dictionary(); private readonly Dictionary _connectionChannel = new Dictionary(); - public static readonly Dictionary ConnectionPtrChannel = new Dictionary(); private KCPSettings KcpSettings { get; set; } private uint TimeNow => (uint) (TimeHelper.Now - _startTime); @@ -170,6 +168,7 @@ namespace TEngine.Core.Network try { var receiveLength = _socket.ReceiveFrom(_rawReceiveBuffer, ref _clientEndPoint); + if (receiveLength < 1) { continue; @@ -182,7 +181,6 @@ namespace TEngine.Core.Network { case KcpHeader.RequestConnection: { - // Log.Debug("KcpHeader.RequestConnection"); if (receiveLength != 5) { break; @@ -232,17 +230,12 @@ namespace TEngine.Core.Network break; } - var kcpIntPtr = KCP.KcpCreate(channelId, new IntPtr(channelId)); - - KCP.KcpNodelay(kcpIntPtr, 1, 5, 2, 1); - KCP.KcpWndsize(kcpIntPtr, KcpSettings.SendWindowSize, KcpSettings.ReceiveWindowSize); - KCP.KcpSetmtu(kcpIntPtr, KcpSettings.Mtu); - KCP.KcpSetminrto(kcpIntPtr, 30); - KCP.KcpSetoutput(kcpIntPtr, KcpOutput); - + var kcp = new Kcp(channelId, channel.Output); + kcp.SetNoDelay(1, 5, 2, true); + kcp.SetWindowSize(KcpSettings.SendWindowSize, KcpSettings.ReceiveWindowSize); + kcp.SetMtu(KcpSettings.Mtu); _connectionChannel.Add(channel.Id, channel); - ConnectionPtrChannel.Add(kcpIntPtr, channel); - channel.Connect(kcpIntPtr, AddToUpdate, KcpSettings.MaxSendWindowSize, NetworkTarget, NetworkMessageScheduler); + channel.Connect(kcp, AddToUpdate, KcpSettings.MaxSendWindowSize, NetworkTarget, NetworkMessageScheduler); break; } case KcpHeader.ReceiveData: @@ -260,7 +253,7 @@ namespace TEngine.Core.Network break; } - KCP.KcpInput(channel.KcpIntPtr, _rawReceiveBuffer, 5, messageLength); + channel.Kcp.Input(_rawReceiveBuffer, 5, messageLength); AddToUpdate(0, channel.Id); channel.Receive(); break; @@ -279,6 +272,7 @@ namespace TEngine.Core.Network } } } + private bool RemovePendingConnection(uint channelId, EndPoint remoteEndPoint, out KCPServerNetworkChannel channel) { @@ -391,18 +385,19 @@ namespace TEngine.Core.Network continue; } + var channelKcp = channel.Kcp; try { - KCP.KcpUpdate(channel.KcpIntPtr, nowTime); + channelKcp.Update(nowTime); } catch (Exception e) { Log.Error(e); } - - if (channel.KcpIntPtr != IntPtr.Zero) + + if (channelKcp != null) { - AddToUpdate(KCP.KcpCheck(channel.KcpIntPtr, nowTime), channelId); + AddToUpdate(channelKcp.Check(nowTime), channelId); } } @@ -461,39 +456,6 @@ namespace TEngine.Core.Network _updateTimer.Add(tillTime, channelId); } - - -#if ENABLE_IL2CPP - [AOT.MonoPInvokeCallback(typeof(KcpOutput))] -#endif - private static int KcpOutput(IntPtr bytes, int count, IntPtr kcp, IntPtr user) - { -#if TENGINE_DEVELOP - if (NetworkThread.Instance.ManagedThreadId != Thread.CurrentThread.ManagedThreadId) - { - Log.Error("not in NetworkThread!"); - return 0; - } -#endif - try - { - if (kcp == IntPtr.Zero || !ConnectionPtrChannel.TryGetValue(kcp, out var channel)) - { - return 0; - } - - if (!channel.IsDisposed) - { - channel.Output(bytes, count); - } - } - catch (Exception e) - { - Log.Error(e); - } - - return count; - } #endregion } diff --git a/Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/KCP/Server/KCPServerNetworkChannel.cs b/Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/KCP/Server/KCPServerNetworkChannel.cs index 1e411dbd..ae6c65db 100644 --- a/Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/KCP/Server/KCPServerNetworkChannel.cs +++ b/Assets/GameScripts/DotNet/Core/Network/NetworkProtocol/KCP/Server/KCPServerNetworkChannel.cs @@ -1,12 +1,14 @@ using System; +using System.Buffers; using System.IO; using System.Net; using System.Net.Sockets; using System.Runtime.InteropServices; -using TEngine.Core; +using TEngine.DataStructure; +using kcp2k; +// ReSharper disable ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract + // ReSharper disable InconsistentNaming -#pragma warning disable CS8625 -#pragma warning disable CS8618 namespace TEngine.Core.Network { @@ -19,8 +21,8 @@ namespace TEngine.Core.Network public readonly uint CreateTime; private readonly Socket _socket; private Action _addToUpdate; - private MemoryStream _receiveMemoryStream; - public IntPtr KcpIntPtr { get; private set; } + private MemoryPool _memoryPool; + public Kcp Kcp { get; private set; } public override event Action OnDispose; public override event Action OnReceiveMemoryStream; @@ -37,6 +39,7 @@ namespace TEngine.Core.Network _socket = socket; CreateTime = createTime; RemoteEndPoint = remoteEndPoint; + _memoryPool = MemoryPool.Shared; } public override void Dispose() @@ -53,28 +56,23 @@ namespace TEngine.Core.Network return; } + Kcp = null; var buff = new byte[5]; buff.WriteTo(0, (byte) KcpHeader.Disconnect); buff.WriteTo(1, Id); _socket.SendTo(buff, 5, SocketFlags.None, RemoteEndPoint); - - if (KcpIntPtr != IntPtr.Zero) - { - KCPServerNetwork.ConnectionPtrChannel.Remove(KcpIntPtr); - KCP.KcpRelease(KcpIntPtr); - KcpIntPtr = IntPtr.Zero; - } #if NETDEBUG Log.Debug($"KCPServerNetworkChannel ConnectionPtrChannel:{KCPServerNetwork.ConnectionPtrChannel.Count}"); #endif _maxSndWnd = 0; _addToUpdate = null; - _receiveMemoryStream?.Dispose(); + _memoryPool.Dispose(); + _memoryPool = null; ThreadSynchronizationContext.Main.Post(OnDispose); base.Dispose(); } - public void Connect(IntPtr kcpIntPtr, Action addToUpdate, int maxSndWnd, NetworkTarget networkTarget, ANetworkMessageScheduler networkMessageScheduler) + public void Connect(Kcp kcp, Action addToUpdate, int maxSndWnd, NetworkTarget networkTarget, ANetworkMessageScheduler networkMessageScheduler) { #if TENGINE_DEVELOP if (NetworkThread.Instance.ManagedThreadId != Thread.CurrentThread.ManagedThreadId) @@ -83,11 +81,10 @@ namespace TEngine.Core.Network return; } #endif - KcpIntPtr = kcpIntPtr; + Kcp = kcp; _maxSndWnd = maxSndWnd; _addToUpdate = addToUpdate; _rawSendBuffer = new byte[ushort.MaxValue]; - _receiveMemoryStream = MemoryStreamHelper.GetRecyclableMemoryStream(); PacketParser = APacketParser.CreatePacketParser(networkTarget); ThreadSynchronizationContext.Main.Post(() => @@ -110,14 +107,14 @@ namespace TEngine.Core.Network return; } #endif - if (IsDisposed || KcpIntPtr == IntPtr.Zero) + if (IsDisposed) { return; } // 检查等待发送的消息,如果超出两倍窗口大小,KCP作者给的建议是要断开连接 - - var waitSendSize = KCP.KcpWaitsnd(KcpIntPtr); + + var waitSendSize = Kcp.WaitSnd; if (waitSendSize > _maxSndWnd) { @@ -125,15 +122,10 @@ namespace TEngine.Core.Network Dispose(); return; } - // 发送消息 - - KCP.KcpSend(KcpIntPtr, memoryStream.GetBuffer(), (int) memoryStream.Length); - + Kcp.Send(memoryStream.GetBuffer(), 0, (int)memoryStream.Length); // 因为memoryStream对象池出来的、所以需要手动回收下 - memoryStream.Dispose(); - _addToUpdate(0, Id); } @@ -146,7 +138,7 @@ namespace TEngine.Core.Network return; } #endif - if (IsDisposed || KcpIntPtr == IntPtr.Zero) + if (IsDisposed) { return; } @@ -155,32 +147,25 @@ namespace TEngine.Core.Network { try { - if (KcpIntPtr == IntPtr.Zero) - { - return; - } - // 获得一个完整消息的长度 - - var peekSize = KCP.KcpPeeksize(KcpIntPtr); - + + var peekSize = Kcp.PeekSize(); + // 如果没有接收的消息那就跳出当前循环。 - + if (peekSize < 0) { return; } - + // 如果为0,表示当前消息发生错误。提示下、一般情况不会发生的 - if (peekSize == 0) { throw new Exception("SocketError.NetworkReset"); } - - _receiveMemoryStream.SetLength(peekSize); - _receiveMemoryStream.Seek(0, SeekOrigin.Begin); - var receiveCount = KCP.KcpRecv(KcpIntPtr, _receiveMemoryStream.GetBuffer(), peekSize); + + var receiveMemoryOwner = _memoryPool.Rent(Packet.OuterPacketMaxLength); + var receiveCount = Kcp.Receive(receiveMemoryOwner.Memory, peekSize); // 如果接收的长度跟peekSize不一样,不需要处理,因为消息肯定有问题的(虽然不可能出现)。 @@ -190,9 +175,7 @@ namespace TEngine.Core.Network break; } - var packInfo = PacketParser.UnPack(_receiveMemoryStream); - - if (packInfo == null) + if (!PacketParser.UnPack(receiveMemoryOwner,out var packInfo)) { break; } @@ -215,7 +198,7 @@ namespace TEngine.Core.Network } } - public void Output(IntPtr bytes, int count) + public void Output(byte[] bytes, int count) { #if TENGINE_DEVELOP if (NetworkThread.Instance.ManagedThreadId != Thread.CurrentThread.ManagedThreadId) @@ -224,7 +207,7 @@ namespace TEngine.Core.Network return; } #endif - if (IsDisposed || KcpIntPtr == IntPtr.Zero) + if (IsDisposed) { return; } @@ -238,7 +221,7 @@ namespace TEngine.Core.Network _rawSendBuffer.WriteTo(0, (byte) KcpHeader.ReceiveData); _rawSendBuffer.WriteTo(1, Id); - Marshal.Copy(bytes, _rawSendBuffer, 5, count); + Buffer.BlockCopy(bytes, 0, _rawSendBuffer, 5, count); _socket.SendTo(_rawSendBuffer, 0, count + 5, SocketFlags.None, RemoteEndPoint); } catch (Exception e) diff --git a/Assets/GameScripts/DotNet/Core/Network/PacketParser/InnerPacketParser.cs b/Assets/GameScripts/DotNet/Core/Network/PacketParser/InnerPacketParser.cs index ea00b223..63a243d3 100644 --- a/Assets/GameScripts/DotNet/Core/Network/PacketParser/InnerPacketParser.cs +++ b/Assets/GameScripts/DotNet/Core/Network/PacketParser/InnerPacketParser.cs @@ -1,37 +1,25 @@ #if TENGINE_NET +using System.Buffers; using TEngine.DataStructure; -using TEngine.Core; -#pragma warning disable CS8600 -#pragma warning disable CS8625 -#pragma warning disable CS8603 +// ReSharper disable ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract namespace TEngine.Core.Network; public sealed class InnerPackInfo : APackInfo { - public static InnerPackInfo Create() + public static InnerPackInfo Create(IMemoryOwner memoryOwner) { - return Pool.Rent(); - } - - public static InnerPackInfo Create(uint rpcId, long routeId, uint protocolCode) - { - var innerPackInfo = Pool.Rent(); - innerPackInfo.RpcId = rpcId; - innerPackInfo.RouteId = routeId; - innerPackInfo.ProtocolCode = protocolCode; + var innerPackInfo = Rent(); + innerPackInfo.MemoryOwner = memoryOwner; return innerPackInfo; } - public static InnerPackInfo Create(uint rpcId, long routeId, uint protocolCode, long routeTypeCode, MemoryStream memoryStream) + public override MemoryStream CreateMemoryStream() { - var innerPackInfo = Pool.Rent(); - innerPackInfo.RpcId = rpcId; - innerPackInfo.RouteId = routeId; - innerPackInfo.ProtocolCode = protocolCode; - innerPackInfo.RouteTypeCode = routeTypeCode; - innerPackInfo.MemoryStream = memoryStream; - return innerPackInfo; + var recyclableMemoryStream = MemoryStreamHelper.GetRecyclableMemoryStream(); + recyclableMemoryStream.Write(MemoryOwner.Memory.Span.Slice(0, Packet.InnerPacketHeadLength + MessagePacketLength)); + recyclableMemoryStream.Seek(0, SeekOrigin.Begin); + return recyclableMemoryStream; } public override void Dispose() @@ -42,38 +30,36 @@ public sealed class InnerPackInfo : APackInfo public override object Deserialize(Type messageType) { - using (MemoryStream) + var memoryOwnerMemory = MemoryOwner.Memory; + memoryOwnerMemory = memoryOwnerMemory.Slice(Packet.InnerPacketHeadLength, MessagePacketLength); + + switch (ProtocolCode) { - MemoryStream.Seek(Packet.InnerPacketHeadLength, SeekOrigin.Begin); - - switch (ProtocolCode) + case >= Opcode.InnerBsonRouteResponse: { - case >= Opcode.InnerBsonRouteResponse: - { - return MongoHelper.Instance.DeserializeFrom(messageType, MemoryStream); - } - case >= Opcode.InnerRouteResponse: - { - return ProtoBufHelper.FromStream(messageType, MemoryStream); - } - case >= Opcode.OuterRouteResponse: - { - return ProtoBufHelper.FromStream(messageType, MemoryStream); - } - case >= Opcode.InnerBsonRouteMessage: - { - return MongoHelper.Instance.DeserializeFrom(messageType, MemoryStream); - } - case >= Opcode.InnerRouteMessage: - case >= Opcode.OuterRouteMessage: - { - return ProtoBufHelper.FromStream(messageType, MemoryStream); - } - default: - { - Log.Error($"protocolCode:{ProtocolCode} Does not support processing protocol"); - return null; - } + return MongoHelper.Instance.Deserialize(memoryOwnerMemory, messageType); + } + case >= Opcode.InnerRouteResponse: + { + return ProtoBufHelper.FromMemory(messageType, memoryOwnerMemory); + } + case >= Opcode.OuterRouteResponse: + { + return ProtoBufHelper.FromMemory(messageType, memoryOwnerMemory); + } + case >= Opcode.InnerBsonRouteMessage: + { + return MongoHelper.Instance.Deserialize(memoryOwnerMemory, messageType); + } + case >= Opcode.InnerRouteMessage: + case >= Opcode.OuterRouteMessage: + { + return ProtoBufHelper.FromMemory(messageType, memoryOwnerMemory); + } + default: + { + Log.Error($"protocolCode:{ProtocolCode} Does not support processing protocol"); + return null; } } } @@ -88,10 +74,15 @@ public sealed class InnerPacketParser : APacketParser private bool _isUnPackHead = true; private readonly byte[] _messageHead = new byte[Packet.InnerPacketHeadLength]; + public InnerPacketParser() + { + MemoryPool = MemoryPool.Shared; + } + public override bool UnPack(CircularBuffer buffer, out APackInfo packInfo) { packInfo = null; - + while (!IsDisposed) { if (_isUnPackHead) @@ -100,48 +91,40 @@ public sealed class InnerPacketParser : APacketParser { return false; } - + _ = buffer.Read(_messageHead, 0, Packet.InnerPacketHeadLength); _messagePacketLength = BitConverter.ToInt32(_messageHead, 0); - + if (_messagePacketLength > Packet.PacketBodyMaxLength) { throw new ScanException($"The received information exceeds the maximum limit = {_messagePacketLength}"); } _protocolCode = BitConverter.ToUInt32(_messageHead, Packet.PacketLength); - _rpcId = BitConverter.ToUInt32(_messageHead, Packet.OuterPacketRpcIdLocation); + _rpcId = BitConverter.ToUInt32(_messageHead, Packet.InnerPacketRpcIdLocation); _routeId = BitConverter.ToInt64(_messageHead, Packet.InnerPacketRouteRouteIdLocation); _isUnPackHead = false; } - + try { if (buffer.Length < _messagePacketLength) { return false; } - + _isUnPackHead = true; - packInfo = InnerPackInfo.Create(_rpcId, _routeId, _protocolCode); - - if (_messagePacketLength > 0) - { - return true; - } - - var memoryStream = MemoryStreamHelper.GetRecyclableMemoryStream(); + // 创建消息包 + var memoryOwner = MemoryPool.Rent(Packet.InnerPacketMaxLength); + packInfo = InnerPackInfo.Create(memoryOwner); + packInfo.RpcId = _rpcId; + packInfo.RouteId = _routeId; + packInfo.ProtocolCode = _protocolCode; + packInfo.MessagePacketLength = _messagePacketLength; // 写入消息体的信息到内存中 - memoryStream.Seek(Packet.InnerPacketHeadLength, SeekOrigin.Begin); - buffer.Read(memoryStream, _messagePacketLength); + buffer.Read(memoryOwner.Memory.Slice(Packet.InnerPacketHeadLength), _messagePacketLength); // 写入消息头的信息到内存中 - memoryStream.Seek(0, SeekOrigin.Begin); - memoryStream.Write(BitConverter.GetBytes(_messagePacketLength)); - memoryStream.Write(BitConverter.GetBytes(packInfo.ProtocolCode)); - memoryStream.Write(BitConverter.GetBytes(packInfo.RpcId)); - memoryStream.Write(BitConverter.GetBytes(packInfo.RouteId)); - memoryStream.Seek(0, SeekOrigin.Begin); - packInfo.MemoryStream = memoryStream; + _messageHead.AsMemory().CopyTo( memoryOwner.Memory.Slice(0, Packet.InnerPacketHeadLength)); return true; } catch (Exception e) @@ -151,69 +134,50 @@ public sealed class InnerPacketParser : APacketParser return false; } } - + return false; } - - public override APackInfo UnPack(MemoryStream memoryStream) + + public override bool UnPack(IMemoryOwner memoryOwner, out APackInfo packInfo) { - InnerPackInfo packInfo = null; - + packInfo = null; + try { - if (memoryStream == null || memoryStream.Length < Packet.InnerPacketHeadLength) - { - return null; - } - - _ = memoryStream.Read(_messageHead, 0, Packet.InnerPacketHeadLength); - _messagePacketLength = BitConverter.ToInt32(_messageHead, 0); + var memorySpan = memoryOwner.Memory.Span; + if (memorySpan.Length < Packet.InnerPacketHeadLength) + { + return false; + } + + _messagePacketLength = BitConverter.ToInt32(memorySpan); + if (_messagePacketLength > Packet.PacketBodyMaxLength) { throw new ScanException($"The received information exceeds the maximum limit = {_messagePacketLength}"); } - - packInfo = InnerPackInfo.Create(); - packInfo.ProtocolCode = BitConverter.ToUInt32(_messageHead, Packet.PacketLength); - packInfo.RpcId = BitConverter.ToUInt32(_messageHead, Packet.OuterPacketRpcIdLocation); - packInfo.RouteId = BitConverter.ToInt64(_messageHead, Packet.InnerPacketRouteRouteIdLocation); - - if (memoryStream.Length < _messagePacketLength) - { - return null; - } - - if (_messagePacketLength <= 0) - { - return packInfo; - } - var outMemoryStream = MemoryStreamHelper.GetRecyclableMemoryStream(); - memoryStream.WriteTo(outMemoryStream); - outMemoryStream.Seek(0, SeekOrigin.Begin); - packInfo.MemoryStream = outMemoryStream; - return packInfo; + packInfo = InnerPackInfo.Create(memoryOwner); + packInfo.MessagePacketLength = _messagePacketLength; + packInfo.ProtocolCode = BitConverter.ToUInt32(memorySpan[Packet.PacketLength..]); + packInfo.RpcId = BitConverter.ToUInt32(memorySpan[Packet.OuterPacketRpcIdLocation..]); + packInfo.RouteId = BitConverter.ToInt64(memorySpan[Packet.InnerPacketRouteRouteIdLocation..]); + + if (memorySpan.Length < _messagePacketLength) + { + return false; + } + + return _messagePacketLength >= 0; } catch (Exception e) { - packInfo?.Dispose(); - Log.Error(e); - return null; + Console.WriteLine(e); + throw; } } - - public static void Serialize(object message, MemoryStream stream) - { - if (message is IBsonMessage) - { - MongoHelper.Instance.SerializeTo(message, stream); - return; - } - ProtoBufHelper.ToStream(message, stream); - } - public static MemoryStream Pack(uint rpcId, long routeId, MemoryStream memoryStream) { memoryStream.Seek(Packet.InnerPacketRpcIdLocation, SeekOrigin.Begin); @@ -233,7 +197,15 @@ public sealed class InnerPacketParser : APacketParser if (message != null) { - Serialize(message, memoryStream); + if (message is IBsonMessage) + { + MongoHelper.Instance.SerializeTo(message, memoryStream); + } + else + { + ProtoBufHelper.ToStream(message, memoryStream); + } + opCode = MessageDispatcherSystem.Instance.GetOpCode(message.GetType()); packetBodyCount = (int)(memoryStream.Position - Packet.InnerPacketHeadLength); } @@ -255,7 +227,6 @@ public sealed class InnerPacketParser : APacketParser public override void Dispose() { _messagePacketLength = 0; - Array.Clear(_messageHead, 0, _messageHead.Length); base.Dispose(); } } diff --git a/Assets/GameScripts/DotNet/Core/Network/PacketParser/Interface/APackInfo.cs b/Assets/GameScripts/DotNet/Core/Network/PacketParser/Interface/APackInfo.cs index dd95008e..3f2bee72 100644 --- a/Assets/GameScripts/DotNet/Core/Network/PacketParser/Interface/APackInfo.cs +++ b/Assets/GameScripts/DotNet/Core/Network/PacketParser/Interface/APackInfo.cs @@ -1,7 +1,6 @@ using System; +using System.Buffers; using System.IO; -#pragma warning disable CS8625 -#pragma warning disable CS8618 namespace TEngine.Core.Network { @@ -11,17 +10,34 @@ namespace TEngine.Core.Network public long RouteId; public uint ProtocolCode; public long RouteTypeCode; - public MemoryStream MemoryStream; + public int MessagePacketLength; + public IMemoryOwner MemoryOwner; + public bool IsDisposed; + public static T Rent() where T : APackInfo + { + var aPackInfo = Pool.Rent(); + aPackInfo.IsDisposed = false; + return aPackInfo; + } + public abstract object Deserialize(Type messageType); - + public abstract MemoryStream CreateMemoryStream(); public virtual void Dispose() { + if (IsDisposed) + { + return; + } + RpcId = 0; RouteId = 0; ProtocolCode = 0; RouteTypeCode = 0; - MemoryStream = null; + MessagePacketLength = 0; + MemoryOwner.Dispose(); + MemoryOwner = null; + IsDisposed = true; } } } \ No newline at end of file diff --git a/Assets/GameScripts/DotNet/Core/Network/PacketParser/Interface/APacketParser.cs b/Assets/GameScripts/DotNet/Core/Network/PacketParser/Interface/APacketParser.cs index 6202565b..ba8973ff 100644 --- a/Assets/GameScripts/DotNet/Core/Network/PacketParser/Interface/APacketParser.cs +++ b/Assets/GameScripts/DotNet/Core/Network/PacketParser/Interface/APacketParser.cs @@ -1,12 +1,13 @@ using System; +using System.Buffers; using System.IO; using TEngine.DataStructure; -using TEngine.Core; namespace TEngine.Core.Network { public abstract class APacketParser : IDisposable { + protected MemoryPool MemoryPool; protected bool IsDisposed { get; private set; } public static APacketParser CreatePacketParser(NetworkTarget networkTarget) @@ -17,8 +18,8 @@ namespace TEngine.Core.Network { #if TENGINE_NET return new InnerPacketParser(); - #else - return null; +#else + throw new NotSupportedException($"PacketParserHelper Create NotSupport {networkTarget}"); #endif } case NetworkTarget.Outer: @@ -33,11 +34,11 @@ namespace TEngine.Core.Network } public abstract bool UnPack(CircularBuffer buffer, out APackInfo packInfo); - public abstract APackInfo UnPack(MemoryStream memoryStream); - + public abstract bool UnPack(IMemoryOwner memoryOwner, out APackInfo packInfo); public virtual void Dispose() { IsDisposed = true; + MemoryPool.Dispose(); } } } \ No newline at end of file diff --git a/Assets/GameScripts/DotNet/Core/Network/PacketParser/OuterPacketParser.cs b/Assets/GameScripts/DotNet/Core/Network/PacketParser/OuterPacketParser.cs index 31030c0c..1e5a9203 100644 --- a/Assets/GameScripts/DotNet/Core/Network/PacketParser/OuterPacketParser.cs +++ b/Assets/GameScripts/DotNet/Core/Network/PacketParser/OuterPacketParser.cs @@ -1,42 +1,39 @@ using System; +using System.Buffers; using System.IO; using TEngine.DataStructure; -using TEngine.Core; -#pragma warning disable CS8603 -#pragma warning disable CS8600 -#pragma warning disable CS8625 +// ReSharper disable ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract namespace TEngine.Core.Network { public sealed class OuterPackInfo : APackInfo { - public static OuterPackInfo Create() + public static OuterPackInfo Create(IMemoryOwner memoryOwner) { - return Pool.Rent(); - } - - public static OuterPackInfo Create(uint rpcId, uint protocolCode, long routeTypeCode) - { - var outerPackInfo = Pool.Rent(); - outerPackInfo.RpcId = rpcId; - outerPackInfo.ProtocolCode = protocolCode; - outerPackInfo.RouteTypeCode = routeTypeCode; + var outerPackInfo = Rent();; + outerPackInfo.MemoryOwner = memoryOwner; return outerPackInfo; } + public override MemoryStream CreateMemoryStream() + { + var recyclableMemoryStream = MemoryStreamHelper.GetRecyclableMemoryStream(); + recyclableMemoryStream.Write(MemoryOwner.Memory.Span.Slice(0, Packet.InnerPacketHeadLength + MessagePacketLength)); + recyclableMemoryStream.Seek(0, SeekOrigin.Begin); + return recyclableMemoryStream; + } + public override void Dispose() { base.Dispose(); Pool.Return(this); } - + public override object Deserialize(Type messageType) { - using (MemoryStream) - { - MemoryStream.Seek(Packet.OuterPacketHeadLength, SeekOrigin.Begin); - return ProtoBufHelper.FromStream(messageType, MemoryStream); - } + var memoryOwnerMemory = MemoryOwner.Memory; + var memory = memoryOwnerMemory.Slice(Packet.OuterPacketHeadLength, MessagePacketLength); + return ProtoBufHelper.FromMemory(messageType, memory); } } @@ -49,6 +46,11 @@ namespace TEngine.Core.Network private bool _isUnPackHead = true; private readonly byte[] _messageHead = new byte[Packet.OuterPacketHeadLength]; + public OuterPacketParser() + { + MemoryPool = MemoryPool.Shared; + } + public override bool UnPack(CircularBuffer buffer, out APackInfo packInfo) { packInfo = null; @@ -84,26 +86,18 @@ namespace TEngine.Core.Network } _isUnPackHead = true; - packInfo = OuterPackInfo.Create(_rpcId, _protocolCode, _routeTypeCode); - - if (_messagePacketLength <= 0) - { - return true; - } - - var memoryStream = MemoryStreamHelper.GetRecyclableMemoryStream(); + // 创建消息包 + var memoryOwner = MemoryPool.Rent(Packet.OuterPacketMaxLength); + packInfo = OuterPackInfo.Create(memoryOwner); + packInfo.RpcId = _rpcId; + packInfo.ProtocolCode = _protocolCode; + packInfo.RouteTypeCode = _routeTypeCode; + packInfo.MessagePacketLength = _messagePacketLength; // 写入消息体的信息到内存中 - memoryStream.Seek(Packet.OuterPacketHeadLength, SeekOrigin.Begin); - buffer.Read(memoryStream, _messagePacketLength); + buffer.Read(memoryOwner.Memory.Slice(Packet.OuterPacketHeadLength), _messagePacketLength); // 写入消息头的信息到内存中 - memoryStream.Seek(0, SeekOrigin.Begin); - memoryStream.Write(BitConverter.GetBytes(_messagePacketLength)); - memoryStream.Write(BitConverter.GetBytes(_protocolCode)); - memoryStream.Write(BitConverter.GetBytes(_rpcId)); - memoryStream.Write(BitConverter.GetBytes(_routeTypeCode)); - memoryStream.Seek(0, SeekOrigin.Begin); - packInfo.MemoryStream = memoryStream; - return true; + _messageHead.AsMemory().CopyTo(memoryOwner.Memory.Slice(0, Packet.OuterPacketHeadLength)); + return _messagePacketLength > 0; } catch (Exception e) { @@ -116,60 +110,44 @@ namespace TEngine.Core.Network return false; } - public override APackInfo UnPack(MemoryStream memoryStream) + public override bool UnPack(IMemoryOwner memoryOwner, out APackInfo packInfo) { - OuterPackInfo packInfo = null; - + packInfo = null; + var memory = memoryOwner.Memory; + try { - if (memoryStream == null) + if (memory.Length < Packet.OuterPacketHeadLength) { - return null; + return false; } - if (memoryStream.Length < Packet.OuterPacketHeadLength) - { - return null; - } - - _ = memoryStream.Read(_messageHead, 0, Packet.OuterPacketHeadLength); - _messagePacketLength = BitConverter.ToInt32(_messageHead, 0); + var memorySpan = memory.Span; + _messagePacketLength = BitConverter.ToInt32(memorySpan); #if TENGINE_NET if (_messagePacketLength > Packet.PacketBodyMaxLength) { throw new ScanException($"The received information exceeds the maximum limit = {_messagePacketLength}"); } #endif - packInfo = OuterPackInfo.Create(); - if (packInfo == null) - { - return null; - } - packInfo.ProtocolCode = BitConverter.ToUInt32(_messageHead, Packet.PacketLength); - packInfo.RpcId = BitConverter.ToUInt32(_messageHead, Packet.OuterPacketRpcIdLocation); - packInfo.RouteTypeCode = BitConverter.ToUInt16(_messageHead, Packet.OuterPacketRouteTypeOpCodeLocation); + packInfo = OuterPackInfo.Create(memoryOwner); + packInfo.MessagePacketLength = _messagePacketLength; + packInfo.ProtocolCode = BitConverter.ToUInt32(memorySpan.Slice(Packet.PacketLength)); + packInfo.RpcId = BitConverter.ToUInt32(memorySpan.Slice(Packet.OuterPacketRpcIdLocation)); + packInfo.RouteTypeCode = BitConverter.ToUInt16(memorySpan.Slice(Packet.OuterPacketRouteTypeOpCodeLocation)); - if (memoryStream.Length < _messagePacketLength) + if (memory.Length < _messagePacketLength) { - return null; + return false; } - if (_messagePacketLength <= 0) - { - return packInfo; - } - - var outMemoryStream = MemoryStreamHelper.GetRecyclableMemoryStream(); - memoryStream.WriteTo(outMemoryStream); - outMemoryStream.Seek(0, SeekOrigin.Begin); - packInfo.MemoryStream = outMemoryStream; - return packInfo; + return _messagePacketLength >= 0; } catch (Exception e) { packInfo?.Dispose(); Log.Error(e); - return null; + return false; } } diff --git a/Assets/GameScripts/DotNet/Core/Network/PacketParser/Packet.cs b/Assets/GameScripts/DotNet/Core/Network/PacketParser/Packet.cs index 044d50b5..5809c4cc 100644 --- a/Assets/GameScripts/DotNet/Core/Network/PacketParser/Packet.cs +++ b/Assets/GameScripts/DotNet/Core/Network/PacketParser/Packet.cs @@ -45,7 +45,11 @@ namespace TEngine.Core.Network /// /// 外网消息总长度(消息体最大长度 + 外网消息头长度) /// - public const int PacketMaxLength = OuterPacketHeadLength + PacketBodyMaxLength; + public const int OuterPacketMaxLength = OuterPacketHeadLength + PacketBodyMaxLength; + /// + /// 内网消息总长度(消息体最大长度 + 外网消息头长度) + /// + public const int InnerPacketMaxLength = InnerPacketHeadLength + PacketBodyMaxLength; /// /// 外网消息头长度(消息体长度在消息头占用的长度 + 协议编号在消息头占用的长度 + RPCId长度 + RouteTypeOpCode长度) /// diff --git a/Assets/GameScripts/ThirdParty/KCP/Plugins/Android/arm64_v8a/libkcp.so b/Assets/GameScripts/ThirdParty/KCP/Plugins/Android/arm64_v8a/libkcp.so deleted file mode 100644 index 966dd3c0..00000000 Binary files a/Assets/GameScripts/ThirdParty/KCP/Plugins/Android/arm64_v8a/libkcp.so and /dev/null differ diff --git a/Assets/GameScripts/ThirdParty/KCP/Plugins/Android/arm64_v8a/libkcp.so.meta b/Assets/GameScripts/ThirdParty/KCP/Plugins/Android/arm64_v8a/libkcp.so.meta deleted file mode 100644 index e9cbfd0e..00000000 --- a/Assets/GameScripts/ThirdParty/KCP/Plugins/Android/arm64_v8a/libkcp.so.meta +++ /dev/null @@ -1,33 +0,0 @@ -fileFormatVersion: 2 -guid: 95cef76c7e8fc924594a2c535022c037 -PluginImporter: - externalObjects: {} - serializedVersion: 2 - iconMap: {} - executionOrder: {} - defineConstraints: [] - isPreloaded: 0 - isOverridable: 0 - isExplicitlyReferenced: 0 - validateReferences: 1 - platformData: - - first: - Android: Android - second: - enabled: 1 - settings: - CPU: ARM64 - - first: - Any: - second: - enabled: 0 - settings: {} - - first: - Editor: Editor - second: - enabled: 0 - settings: - DefaultValueInitialized: true - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/GameScripts/ThirdParty/KCP/Plugins/Android/armeabi-v7a/libkcp.so b/Assets/GameScripts/ThirdParty/KCP/Plugins/Android/armeabi-v7a/libkcp.so deleted file mode 100644 index b12f7e3a..00000000 Binary files a/Assets/GameScripts/ThirdParty/KCP/Plugins/Android/armeabi-v7a/libkcp.so and /dev/null differ diff --git a/Assets/GameScripts/ThirdParty/KCP/Plugins/Android/armeabi-v7a/libkcp.so.meta b/Assets/GameScripts/ThirdParty/KCP/Plugins/Android/armeabi-v7a/libkcp.so.meta deleted file mode 100644 index d4f2b32b..00000000 --- a/Assets/GameScripts/ThirdParty/KCP/Plugins/Android/armeabi-v7a/libkcp.so.meta +++ /dev/null @@ -1,33 +0,0 @@ -fileFormatVersion: 2 -guid: 1f628c9d6f9d08f40ace37fb91786108 -PluginImporter: - externalObjects: {} - serializedVersion: 2 - iconMap: {} - executionOrder: {} - defineConstraints: [] - isPreloaded: 0 - isOverridable: 0 - isExplicitlyReferenced: 0 - validateReferences: 1 - platformData: - - first: - Android: Android - second: - enabled: 1 - settings: - CPU: ARMv7 - - first: - Any: - second: - enabled: 0 - settings: {} - - first: - Editor: Editor - second: - enabled: 0 - settings: - DefaultValueInitialized: true - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/GameScripts/ThirdParty/KCP/Plugins/Android/x86.meta b/Assets/GameScripts/ThirdParty/KCP/Plugins/Android/x86.meta deleted file mode 100644 index f2a46b78..00000000 --- a/Assets/GameScripts/ThirdParty/KCP/Plugins/Android/x86.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: 45f26a7bb227645d482d410f20c4e20b -folderAsset: yes -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/GameScripts/ThirdParty/KCP/Plugins/Android/x86/libkcp.so b/Assets/GameScripts/ThirdParty/KCP/Plugins/Android/x86/libkcp.so deleted file mode 100644 index 937fd086..00000000 Binary files a/Assets/GameScripts/ThirdParty/KCP/Plugins/Android/x86/libkcp.so and /dev/null differ diff --git a/Assets/GameScripts/ThirdParty/KCP/Plugins/Android/x86/libkcp.so.meta b/Assets/GameScripts/ThirdParty/KCP/Plugins/Android/x86/libkcp.so.meta deleted file mode 100644 index 5d585f6e..00000000 --- a/Assets/GameScripts/ThirdParty/KCP/Plugins/Android/x86/libkcp.so.meta +++ /dev/null @@ -1,33 +0,0 @@ -fileFormatVersion: 2 -guid: 618bbca818d51974ea5aaf2c99cbeb25 -PluginImporter: - externalObjects: {} - serializedVersion: 2 - iconMap: {} - executionOrder: {} - defineConstraints: [] - isPreloaded: 0 - isOverridable: 0 - isExplicitlyReferenced: 0 - validateReferences: 1 - platformData: - - first: - Android: Android - second: - enabled: 1 - settings: - CPU: x86 - - first: - Any: - second: - enabled: 0 - settings: {} - - first: - Editor: Editor - second: - enabled: 0 - settings: - DefaultValueInitialized: true - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/GameScripts/ThirdParty/KCP/Plugins/IOS.meta b/Assets/GameScripts/ThirdParty/KCP/Plugins/IOS.meta deleted file mode 100644 index 4ae2d49c..00000000 --- a/Assets/GameScripts/ThirdParty/KCP/Plugins/IOS.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: b5ee1b6d2f25b4a889ae7286f23a7f93 -folderAsset: yes -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/GameScripts/ThirdParty/KCP/Plugins/IOS/libkcp.a b/Assets/GameScripts/ThirdParty/KCP/Plugins/IOS/libkcp.a deleted file mode 100644 index abd42fd1..00000000 Binary files a/Assets/GameScripts/ThirdParty/KCP/Plugins/IOS/libkcp.a and /dev/null differ diff --git a/Assets/GameScripts/ThirdParty/KCP/Plugins/IOS/libkcp.a.meta b/Assets/GameScripts/ThirdParty/KCP/Plugins/IOS/libkcp.a.meta deleted file mode 100644 index bb444204..00000000 --- a/Assets/GameScripts/ThirdParty/KCP/Plugins/IOS/libkcp.a.meta +++ /dev/null @@ -1,33 +0,0 @@ -fileFormatVersion: 2 -guid: 0c70323b22bc343ff88a9509c7355d2e -PluginImporter: - externalObjects: {} - serializedVersion: 2 - iconMap: {} - executionOrder: {} - defineConstraints: [] - isPreloaded: 0 - isOverridable: 0 - isExplicitlyReferenced: 0 - validateReferences: 1 - platformData: - - first: - Any: - second: - enabled: 0 - settings: {} - - first: - Editor: Editor - second: - enabled: 0 - settings: - DefaultValueInitialized: true - - first: - iPhone: iOS - second: - enabled: 1 - settings: - AddToEmbeddedBinaries: false - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/GameScripts/ThirdParty/KCP/Plugins/MacOS.meta b/Assets/GameScripts/ThirdParty/KCP/Plugins/MacOS.meta deleted file mode 100644 index ed064cc3..00000000 --- a/Assets/GameScripts/ThirdParty/KCP/Plugins/MacOS.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: b4d8faeac22dc4f3d82bd2a806c47e84 -folderAsset: yes -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/GameScripts/ThirdParty/KCP/Plugins/MacOS/kcp.bundle.meta b/Assets/GameScripts/ThirdParty/KCP/Plugins/MacOS/kcp.bundle.meta deleted file mode 100644 index f51d8d16..00000000 --- a/Assets/GameScripts/ThirdParty/KCP/Plugins/MacOS/kcp.bundle.meta +++ /dev/null @@ -1,33 +0,0 @@ -fileFormatVersion: 2 -guid: 06130465dd5c3436fa423fd696d85760 -PluginImporter: - externalObjects: {} - serializedVersion: 2 - iconMap: {} - executionOrder: {} - defineConstraints: [] - isPreloaded: 0 - isOverridable: 0 - isExplicitlyReferenced: 0 - validateReferences: 1 - platformData: - - first: - Any: - second: - enabled: 0 - settings: {} - - first: - Editor: Editor - second: - enabled: 1 - settings: - DefaultValueInitialized: true - - first: - Standalone: OSXUniversal - second: - enabled: 1 - settings: - CPU: AnyCPU - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/GameScripts/ThirdParty/KCP/Plugins/MacOS/kcp.bundle/Contents.meta b/Assets/GameScripts/ThirdParty/KCP/Plugins/MacOS/kcp.bundle/Contents.meta deleted file mode 100644 index fae59c0c..00000000 --- a/Assets/GameScripts/ThirdParty/KCP/Plugins/MacOS/kcp.bundle/Contents.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: 37b47a7cb1f2446318439587a188627b -folderAsset: yes -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/GameScripts/ThirdParty/KCP/Plugins/MacOS/kcp.bundle/Contents/MacOS.meta b/Assets/GameScripts/ThirdParty/KCP/Plugins/MacOS/kcp.bundle/Contents/MacOS.meta deleted file mode 100644 index 0d60269f..00000000 --- a/Assets/GameScripts/ThirdParty/KCP/Plugins/MacOS/kcp.bundle/Contents/MacOS.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: f263dcd07777d46948e95f4dcff8991e -folderAsset: yes -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/GameScripts/ThirdParty/KCP/Plugins/MacOS/kcp.bundle/Contents/MacOS/kcp b/Assets/GameScripts/ThirdParty/KCP/Plugins/MacOS/kcp.bundle/Contents/MacOS/kcp deleted file mode 100644 index 729b398b..00000000 Binary files a/Assets/GameScripts/ThirdParty/KCP/Plugins/MacOS/kcp.bundle/Contents/MacOS/kcp and /dev/null differ diff --git a/Assets/GameScripts/ThirdParty/KCP/Plugins/x86_64.meta b/Assets/GameScripts/ThirdParty/KCP/Plugins/x86_64.meta deleted file mode 100644 index 2504b328..00000000 --- a/Assets/GameScripts/ThirdParty/KCP/Plugins/x86_64.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: bd47571478aff4eb6a9fa70ca39031cf -folderAsset: yes -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/GameScripts/ThirdParty/KCP/Plugins/x86_64/kcp.dll b/Assets/GameScripts/ThirdParty/KCP/Plugins/x86_64/kcp.dll deleted file mode 100644 index ce18e81f..00000000 Binary files a/Assets/GameScripts/ThirdParty/KCP/Plugins/x86_64/kcp.dll and /dev/null differ diff --git a/Assets/GameScripts/ThirdParty/KCP/Plugins/x86_64/kcp.dll.meta b/Assets/GameScripts/ThirdParty/KCP/Plugins/x86_64/kcp.dll.meta deleted file mode 100644 index cb81e95f..00000000 --- a/Assets/GameScripts/ThirdParty/KCP/Plugins/x86_64/kcp.dll.meta +++ /dev/null @@ -1,52 +0,0 @@ -fileFormatVersion: 2 -guid: 07d3660260d3a4e46944eeb95bb82783 -PluginImporter: - externalObjects: {} - serializedVersion: 2 - iconMap: {} - executionOrder: {} - defineConstraints: [] - isPreloaded: 0 - isOverridable: 0 - isExplicitlyReferenced: 0 - validateReferences: 1 - platformData: - - first: - Any: - second: - enabled: 1 - settings: {} - - first: - Editor: Editor - second: - enabled: 0 - settings: - CPU: x86_64 - DefaultValueInitialized: true - - first: - Standalone: Linux64 - second: - enabled: 1 - settings: - CPU: x86_64 - - first: - Standalone: OSXUniversal - second: - enabled: 1 - settings: - CPU: x86_64 - - first: - Standalone: Win - second: - enabled: 0 - settings: - CPU: None - - first: - Standalone: Win64 - second: - enabled: 1 - settings: - CPU: x86_64 - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/GameScripts/ThirdParty/Kcp.meta b/Assets/GameScripts/ThirdParty/Kcp.meta deleted file mode 100644 index d92dcfe3..00000000 --- a/Assets/GameScripts/ThirdParty/Kcp.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: 96533933eb80840fea1fde1774562a3d -folderAsset: yes -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/DotNet/ThirdParty/KCPLibrary/kcp.dll b/DotNet/ThirdParty/KCPLibrary/kcp.dll deleted file mode 100644 index cf4dbf63..00000000 Binary files a/DotNet/ThirdParty/KCPLibrary/kcp.dll and /dev/null differ diff --git a/DotNet/ThirdParty/KCPLibrary/kcp.dylib b/DotNet/ThirdParty/KCPLibrary/kcp.dylib deleted file mode 100644 index 729b398b..00000000 Binary files a/DotNet/ThirdParty/KCPLibrary/kcp.dylib and /dev/null differ diff --git a/DotNet/ThirdParty/KCPLibrary/kcp.so b/DotNet/ThirdParty/KCPLibrary/kcp.so deleted file mode 100644 index c5b7cc9d..00000000 Binary files a/DotNet/ThirdParty/KCPLibrary/kcp.so and /dev/null differ diff --git a/DotNet/ThirdParty/ThirdParty.csproj b/DotNet/ThirdParty/ThirdParty.csproj index 06a401e2..18859bb1 100644 --- a/DotNet/ThirdParty/ThirdParty.csproj +++ b/DotNet/ThirdParty/ThirdParty.csproj @@ -62,21 +62,6 @@ RecastDll.dll PreserveNewest - - - kcp.dylib - PreserveNewest - - - kcp.dll - PreserveNewest - - - - kcp.so - PreserveNewest - -