Repository: passiony/kcp-unity Branch: master Commit: 4350fcc2fd77 Files: 143 Total size: 271.8 KB Directory structure: gitextract_xqrk26b3/ ├── .gitignore ├── Assets/ │ ├── Plugins/ │ │ ├── Kcp/ │ │ │ ├── Android/ │ │ │ │ ├── libs/ │ │ │ │ │ ├── armeabi-v7a/ │ │ │ │ │ │ └── libkcp.so.meta │ │ │ │ │ ├── armeabi-v7a.meta │ │ │ │ │ ├── x86/ │ │ │ │ │ │ └── libkcp.so.meta │ │ │ │ │ └── x86.meta │ │ │ │ └── libs.meta │ │ │ ├── Android.meta │ │ │ ├── MacOS/ │ │ │ │ ├── kcp.bundle/ │ │ │ │ │ ├── Contents/ │ │ │ │ │ │ ├── MacOS/ │ │ │ │ │ │ │ ├── kcp │ │ │ │ │ │ │ └── kcp.meta │ │ │ │ │ │ └── MacOS.meta │ │ │ │ │ └── Contents.meta │ │ │ │ └── kcp.bundle.meta │ │ │ ├── MacOS.meta │ │ │ ├── iOS/ │ │ │ │ ├── kcp.a │ │ │ │ └── kcp.a.meta │ │ │ ├── iOS.meta │ │ │ ├── x86/ │ │ │ │ └── kcp.dll.meta │ │ │ ├── x86.meta │ │ │ ├── x86_64/ │ │ │ │ └── kcp.dll.meta │ │ │ └── x86_64.meta │ │ └── Kcp.meta │ ├── Plugins.meta │ ├── Scenes/ │ │ ├── Client.unity │ │ ├── Client.unity.meta │ │ ├── Server.unity │ │ └── Server.unity.meta │ ├── Scenes.meta │ ├── Scripts/ │ │ ├── Helper/ │ │ │ ├── ByteHelper.cs │ │ │ ├── ByteHelper.cs.meta │ │ │ ├── MonoSingleton.cs │ │ │ ├── MonoSingleton.cs.meta │ │ │ ├── NetworkHelper.cs │ │ │ ├── NetworkHelper.cs.meta │ │ │ ├── RandomHelper.cs │ │ │ ├── RandomHelper.cs.meta │ │ │ ├── TimeHelper.cs │ │ │ └── TimeHelper.cs.meta │ │ ├── Helper.meta │ │ ├── Network/ │ │ │ ├── AChannel.cs │ │ │ ├── AChannel.cs.meta │ │ │ ├── AService.cs │ │ │ ├── AService.cs.meta │ │ │ ├── ClientManager.cs │ │ │ ├── ClientManager.cs.meta │ │ │ ├── Core/ │ │ │ │ ├── KCP/ │ │ │ │ │ ├── KCP.cs │ │ │ │ │ ├── KCP.cs.meta │ │ │ │ │ ├── KChannel.cs │ │ │ │ │ ├── KChannel.cs.meta │ │ │ │ │ ├── KService.cs │ │ │ │ │ ├── KService.cs.meta │ │ │ │ │ ├── KcpDll.cs │ │ │ │ │ └── KcpDll.cs.meta │ │ │ │ ├── KCP.meta │ │ │ │ ├── TCP/ │ │ │ │ │ ├── PacketParser.cs │ │ │ │ │ ├── PacketParser.cs.meta │ │ │ │ │ ├── TChannel.cs │ │ │ │ │ ├── TChannel.cs.meta │ │ │ │ │ ├── TChannelConnector.cs │ │ │ │ │ ├── TChannelConnector.cs.meta │ │ │ │ │ ├── TChannelServer.cs │ │ │ │ │ ├── TChannelServer.cs.meta │ │ │ │ │ ├── TService.cs │ │ │ │ │ ├── TService.cs.meta │ │ │ │ │ ├── TServiceServer.cs │ │ │ │ │ └── TServiceServer.cs.meta │ │ │ │ ├── TCP.meta │ │ │ │ ├── WebSocket/ │ │ │ │ │ ├── WChannel.cs │ │ │ │ │ ├── WChannel.cs.meta │ │ │ │ │ ├── WService.cs │ │ │ │ │ └── WService.cs.meta │ │ │ │ └── WebSocket.meta │ │ │ ├── Core.meta │ │ │ ├── INetworkManager.cs │ │ │ ├── INetworkManager.cs.meta │ │ │ ├── Message/ │ │ │ │ ├── Circularbuffer.cs │ │ │ │ ├── Circularbuffer.cs.meta │ │ │ │ ├── ErrorCode.cs │ │ │ │ ├── ErrorCode.cs.meta │ │ │ │ ├── IMessage.cs │ │ │ │ ├── IMessage.cs.meta │ │ │ │ ├── IMessageDispatcher.cs │ │ │ │ ├── IMessageDispatcher.cs.meta │ │ │ │ ├── IMessagePacker.cs │ │ │ │ ├── IMessagePacker.cs.meta │ │ │ │ ├── MessageInfo.cs │ │ │ │ ├── MessageInfo.cs.meta │ │ │ │ ├── MessagePool.cs │ │ │ │ ├── MessagePool.cs.meta │ │ │ │ ├── Opcode.cs │ │ │ │ ├── Opcode.cs.meta │ │ │ │ ├── OuterMessageDispatcher.cs │ │ │ │ ├── OuterMessageDispatcher.cs.meta │ │ │ │ ├── ProtobufPacker.cs │ │ │ │ ├── ProtobufPacker.cs.meta │ │ │ │ ├── RecyclableMemoryStream/ │ │ │ │ │ ├── Events.cs │ │ │ │ │ ├── Events.cs.meta │ │ │ │ │ ├── OneThreadSynchronizationContext.cs │ │ │ │ │ ├── OneThreadSynchronizationContext.cs.meta │ │ │ │ │ ├── RecyclableMemoryStream.cs │ │ │ │ │ ├── RecyclableMemoryStream.cs.meta │ │ │ │ │ ├── RecyclableMemoryStreamManager.cs │ │ │ │ │ └── RecyclableMemoryStreamManager.cs.meta │ │ │ │ ├── RecyclableMemoryStream.meta │ │ │ │ ├── Session.cs │ │ │ │ ├── Session.cs.meta │ │ │ │ ├── SessionConnector.cs │ │ │ │ ├── SessionConnector.cs.meta │ │ │ │ ├── SessionServer.cs │ │ │ │ └── SessionServer.cs.meta │ │ │ ├── Message.meta │ │ │ ├── ServerManager.cs │ │ │ └── ServerManager.cs.meta │ │ ├── Network.meta │ │ ├── TestClient.cs │ │ ├── TestClient.cs.meta │ │ ├── TestServer.cs │ │ └── TestServer.cs.meta │ └── Scripts.meta ├── ProjectSettings/ │ ├── AudioManager.asset │ ├── AutoStreamingSettings.asset │ ├── ClusterInputManager.asset │ ├── DynamicsManager.asset │ ├── EditorBuildSettings.asset │ ├── EditorSettings.asset │ ├── GraphicsSettings.asset │ ├── InputManager.asset │ ├── MemorySettings.asset │ ├── NavMeshAreas.asset │ ├── NetworkManager.asset │ ├── PackageManagerSettings.asset │ ├── Physics2DSettings.asset │ ├── PresetManager.asset │ ├── ProjectSettings.asset │ ├── ProjectVersion.txt │ ├── QualitySettings.asset │ ├── SceneTemplateSettings.json │ ├── TagManager.asset │ ├── TimeManager.asset │ ├── UnityConnectSettings.asset │ ├── VFXManager.asset │ ├── VersionControlSettings.asset │ └── boot.config └── README.md ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ # =============== # # Unity generated # # =============== # [Tt]emp/ [Oo]bj/ [Bb]uild [Ll]ibrary/ [Gg]oogle/ [Ll]ogs/ [Pp]ackages/ sysinfo.txt *.stackdump StreamingAssets/ # ===================================== # # Visual Studio / MonoDevelop generated # # ===================================== # [Ee]xported[Oo]bj/ .vs/ .idea/ .vscode/ /*.userprefs /*.csproj /*.pidb /*.suo /*.sln* /*.user /*.unityproj /*.booproj /*.pdb # ============ # # OS generated # # ============ # .DS_Store* ._* .Spotlight-V100 .Trashes Icon? ehthumbs.db [Tt]humbs.db *.apk ================================================ FILE: Assets/Plugins/Kcp/Android/libs/armeabi-v7a/libkcp.so.meta ================================================ fileFormatVersion: 2 guid: 8e03e31ef66b1d340bfd9de539878633 timeCreated: 1530266293 licenseType: Pro PluginImporter: serializedVersion: 2 iconMap: {} executionOrder: {} isPreloaded: 0 isOverridable: 0 platformData: data: first: Android: Android second: enabled: 1 settings: CPU: ARMv7 data: first: Any: second: enabled: 0 settings: {} data: first: Editor: Editor second: enabled: 0 settings: DefaultValueInitialized: true userData: assetBundleName: assetBundleVariant: ================================================ FILE: Assets/Plugins/Kcp/Android/libs/armeabi-v7a.meta ================================================ fileFormatVersion: 2 guid: 4b9de99f882e1334f88d3fffa436fe26 folderAsset: yes timeCreated: 1529980199 licenseType: Pro DefaultImporter: userData: assetBundleName: assetBundleVariant: ================================================ FILE: Assets/Plugins/Kcp/Android/libs/x86/libkcp.so.meta ================================================ fileFormatVersion: 2 guid: 3869fe202d9b7494cbf94cbd4c9ae4c5 timeCreated: 1530266298 licenseType: Pro PluginImporter: serializedVersion: 2 iconMap: {} executionOrder: {} isPreloaded: 0 isOverridable: 0 platformData: data: first: Android: Android second: enabled: 1 settings: CPU: x86 data: first: Any: second: enabled: 0 settings: {} data: first: Editor: Editor second: enabled: 0 settings: DefaultValueInitialized: true userData: assetBundleName: assetBundleVariant: ================================================ FILE: Assets/Plugins/Kcp/Android/libs/x86.meta ================================================ fileFormatVersion: 2 guid: af440bcca3a80614185db5d493411e3e folderAsset: yes timeCreated: 1529980199 licenseType: Pro DefaultImporter: userData: assetBundleName: assetBundleVariant: ================================================ FILE: Assets/Plugins/Kcp/Android/libs.meta ================================================ fileFormatVersion: 2 guid: e683bde7d2130ef4e9cd605a119c319e folderAsset: yes timeCreated: 1529980199 licenseType: Pro DefaultImporter: userData: assetBundleName: assetBundleVariant: ================================================ FILE: Assets/Plugins/Kcp/Android.meta ================================================ fileFormatVersion: 2 guid: 89f91d9886713c242b08fc47677c6cd6 folderAsset: yes DefaultImporter: externalObjects: {} userData: assetBundleName: assetBundleVariant: ================================================ FILE: Assets/Plugins/Kcp/MacOS/kcp.bundle/Contents/MacOS/kcp.meta ================================================ fileFormatVersion: 2 guid: ca685b77470a94b838156da4c499b38a DefaultImporter: externalObjects: {} userData: assetBundleName: assetBundleVariant: ================================================ FILE: Assets/Plugins/Kcp/MacOS/kcp.bundle/Contents/MacOS.meta ================================================ fileFormatVersion: 2 guid: 957c90d60724445898ad398879db8b83 folderAsset: yes DefaultImporter: externalObjects: {} userData: assetBundleName: assetBundleVariant: ================================================ FILE: Assets/Plugins/Kcp/MacOS/kcp.bundle/Contents.meta ================================================ fileFormatVersion: 2 guid: 19bc16a76b8ac49e6bafebcacef13384 folderAsset: yes DefaultImporter: externalObjects: {} userData: assetBundleName: assetBundleVariant: ================================================ FILE: Assets/Plugins/Kcp/MacOS/kcp.bundle.meta ================================================ fileFormatVersion: 2 guid: eb0cef4b32a23b24688b70b0d9b607e6 folderAsset: yes PluginImporter: externalObjects: {} serializedVersion: 2 iconMap: {} executionOrder: {} isPreloaded: 0 isOverridable: 0 platformData: - first: '': OSXIntel second: enabled: 1 settings: {} - first: '': OSXIntel64 second: enabled: 1 settings: {} - first: Any: second: enabled: 0 settings: {} - first: Editor: Editor second: enabled: 1 settings: DefaultValueInitialized: true - first: Standalone: OSXUniversal second: enabled: 1 settings: {} userData: assetBundleName: assetBundleVariant: ================================================ FILE: Assets/Plugins/Kcp/MacOS.meta ================================================ fileFormatVersion: 2 guid: 747c6b8c8af2e794da1e88402d7cc394 folderAsset: yes DefaultImporter: externalObjects: {} userData: assetBundleName: assetBundleVariant: ================================================ FILE: Assets/Plugins/Kcp/iOS/kcp.a.meta ================================================ fileFormatVersion: 2 guid: 81c5e3645249f404986889388c75e6de PluginImporter: externalObjects: {} serializedVersion: 2 iconMap: {} executionOrder: {} isPreloaded: 0 isOverridable: 0 platformData: - first: Any: second: enabled: 0 settings: {} - first: Editor: Editor second: enabled: 0 settings: DefaultValueInitialized: true - first: iPhone: iOS second: enabled: 1 settings: {} userData: assetBundleName: assetBundleVariant: ================================================ FILE: Assets/Plugins/Kcp/iOS.meta ================================================ fileFormatVersion: 2 guid: fd0f4e125884b09478cd65e157e73225 folderAsset: yes DefaultImporter: externalObjects: {} userData: assetBundleName: assetBundleVariant: ================================================ FILE: Assets/Plugins/Kcp/x86/kcp.dll.meta ================================================ fileFormatVersion: 2 guid: 5163e0119c1604a43adbc28ec6fb277e timeCreated: 1530266315 licenseType: Pro PluginImporter: serializedVersion: 2 iconMap: {} executionOrder: {} isPreloaded: 0 isOverridable: 0 platformData: data: first: '': Any second: enabled: 0 settings: Exclude Android: 1 Exclude Editor: 1 Exclude Linux: 0 Exclude Linux64: 0 Exclude LinuxUniversal: 0 Exclude OSXIntel: 0 Exclude OSXIntel64: 0 Exclude OSXUniversal: 0 Exclude Win: 0 Exclude Win64: 1 data: first: Android: Android second: enabled: 0 settings: CPU: ARMv7 data: first: Any: second: enabled: 0 settings: {} data: first: Editor: Editor second: enabled: 0 settings: CPU: x86 DefaultValueInitialized: true OS: AnyOS data: first: Facebook: Win second: enabled: 1 settings: CPU: AnyCPU data: first: Facebook: Win64 second: enabled: 0 settings: CPU: None data: first: Standalone: Linux second: enabled: 1 settings: CPU: x86 data: first: Standalone: Linux64 second: enabled: 1 settings: CPU: None data: first: Standalone: LinuxUniversal second: enabled: 1 settings: CPU: AnyCPU data: first: Standalone: OSXIntel second: enabled: 1 settings: CPU: AnyCPU data: first: Standalone: OSXIntel64 second: enabled: 1 settings: CPU: None data: first: Standalone: OSXUniversal second: enabled: 1 settings: CPU: AnyCPU data: first: Standalone: Win second: enabled: 1 settings: CPU: AnyCPU data: first: Standalone: Win64 second: enabled: 0 settings: CPU: None userData: assetBundleName: assetBundleVariant: ================================================ FILE: Assets/Plugins/Kcp/x86.meta ================================================ fileFormatVersion: 2 guid: 93e339b4ca47c8f4b81c2d9742800ec5 folderAsset: yes DefaultImporter: externalObjects: {} userData: assetBundleName: assetBundleVariant: ================================================ FILE: Assets/Plugins/Kcp/x86_64/kcp.dll.meta ================================================ fileFormatVersion: 2 guid: 803f7857fdb47b34fbe04eda49b8aa85 PluginImporter: externalObjects: {} serializedVersion: 2 iconMap: {} executionOrder: {} isPreloaded: 0 isOverridable: 0 platformData: - first: '': Any second: enabled: 0 settings: Exclude Android: 1 Exclude Editor: 0 Exclude Linux: 0 Exclude Linux64: 0 Exclude LinuxUniversal: 0 Exclude OSXIntel: 1 Exclude OSXIntel64: 1 Exclude OSXUniversal: 0 Exclude Win: 1 Exclude Win64: 0 - first: Android: Android second: enabled: 0 settings: CPU: ARMv7 - first: Any: second: enabled: 0 settings: {} - first: Editor: Editor second: enabled: 1 settings: CPU: x86_64 DefaultValueInitialized: true OS: Windows - first: Facebook: Win second: enabled: 0 settings: CPU: None - first: Facebook: Win64 second: enabled: 1 settings: CPU: AnyCPU - first: Standalone: Linux second: enabled: 1 settings: CPU: None - first: Standalone: Linux64 second: enabled: 1 settings: CPU: x86_64 - first: Standalone: LinuxUniversal second: enabled: 1 settings: CPU: AnyCPU - first: Standalone: OSXIntel second: enabled: 0 settings: CPU: None - first: Standalone: OSXIntel64 second: enabled: 0 settings: CPU: AnyCPU - first: Standalone: OSXUniversal second: enabled: 1 settings: CPU: None - first: Standalone: Win second: enabled: 0 settings: CPU: None - first: Standalone: Win64 second: enabled: 1 settings: CPU: AnyCPU userData: assetBundleName: assetBundleVariant: ================================================ FILE: Assets/Plugins/Kcp/x86_64.meta ================================================ fileFormatVersion: 2 guid: 06020764b763c6647b21f6933162724f folderAsset: yes DefaultImporter: externalObjects: {} userData: assetBundleName: assetBundleVariant: ================================================ FILE: Assets/Plugins/Kcp.meta ================================================ fileFormatVersion: 2 guid: 3c6e92a7004dbcd45bed3397705d35bd folderAsset: yes DefaultImporter: externalObjects: {} userData: assetBundleName: assetBundleVariant: ================================================ FILE: Assets/Plugins.meta ================================================ fileFormatVersion: 2 guid: a20cd3483a2a77b4bb8d9638ea3dde5f folderAsset: yes DefaultImporter: externalObjects: {} userData: assetBundleName: assetBundleVariant: ================================================ FILE: Assets/Scenes/Client.unity ================================================ %YAML 1.1 %TAG !u! tag:unity3d.com,2011: --- !u!29 &1 OcclusionCullingSettings: m_ObjectHideFlags: 0 serializedVersion: 2 m_OcclusionBakeSettings: smallestOccluder: 5 smallestHole: 0.25 backfaceThreshold: 100 m_SceneGUID: 00000000000000000000000000000000 m_OcclusionCullingData: {fileID: 0} --- !u!104 &2 RenderSettings: m_ObjectHideFlags: 0 serializedVersion: 9 m_Fog: 0 m_FogColor: {r: 0.5, g: 0.5, b: 0.5, a: 1} m_FogMode: 3 m_FogDensity: 0.01 m_LinearFogStart: 0 m_LinearFogEnd: 300 m_AmbientSkyColor: {r: 0.212, g: 0.227, b: 0.259, a: 1} m_AmbientEquatorColor: {r: 0.114, g: 0.125, b: 0.133, a: 1} m_AmbientGroundColor: {r: 0.047, g: 0.043, b: 0.035, a: 1} m_AmbientIntensity: 1 m_AmbientMode: 3 m_SubtractiveShadowColor: {r: 0.42, g: 0.478, b: 0.627, a: 1} m_SkyboxMaterial: {fileID: 0} m_HaloStrength: 0.5 m_FlareStrength: 1 m_FlareFadeSpeed: 3 m_HaloTexture: {fileID: 0} m_SpotCookie: {fileID: 10001, guid: 0000000000000000e000000000000000, type: 0} m_DefaultReflectionMode: 0 m_DefaultReflectionResolution: 128 m_ReflectionBounces: 1 m_ReflectionIntensity: 1 m_CustomReflection: {fileID: 0} m_Sun: {fileID: 0} m_IndirectSpecularColor: {r: 0, g: 0, b: 0, a: 1} m_UseRadianceAmbientProbe: 0 --- !u!157 &3 LightmapSettings: m_ObjectHideFlags: 0 serializedVersion: 11 m_GIWorkflowMode: 1 m_GISettings: serializedVersion: 2 m_BounceScale: 1 m_IndirectOutputScale: 1 m_AlbedoBoost: 1 m_EnvironmentLightingMode: 0 m_EnableBakedLightmaps: 0 m_EnableRealtimeLightmaps: 0 m_LightmapEditorSettings: serializedVersion: 10 m_Resolution: 2 m_BakeResolution: 40 m_AtlasSize: 1024 m_AO: 0 m_AOMaxDistance: 1 m_CompAOExponent: 1 m_CompAOExponentDirect: 0 m_Padding: 2 m_LightmapParameters: {fileID: 0} m_LightmapsBakeMode: 1 m_TextureCompression: 1 m_FinalGather: 0 m_FinalGatherFiltering: 1 m_FinalGatherRayCount: 256 m_ReflectionCompression: 2 m_MixedBakeMode: 2 m_BakeBackend: 0 m_PVRSampling: 1 m_PVRDirectSampleCount: 32 m_PVRSampleCount: 500 m_PVRBounces: 2 m_PVRFilterTypeDirect: 0 m_PVRFilterTypeIndirect: 0 m_PVRFilterTypeAO: 0 m_PVRFilteringMode: 1 m_PVRCulling: 1 m_PVRFilteringGaussRadiusDirect: 1 m_PVRFilteringGaussRadiusIndirect: 5 m_PVRFilteringGaussRadiusAO: 2 m_PVRFilteringAtrousPositionSigmaDirect: 0.5 m_PVRFilteringAtrousPositionSigmaIndirect: 2 m_PVRFilteringAtrousPositionSigmaAO: 1 m_ShowResolutionOverlay: 1 m_LightingDataAsset: {fileID: 0} m_UseShadowmask: 1 --- !u!196 &4 NavMeshSettings: serializedVersion: 2 m_ObjectHideFlags: 0 m_BuildSettings: serializedVersion: 2 agentTypeID: 0 agentRadius: 0.5 agentHeight: 2 agentSlope: 45 agentClimb: 0.4 ledgeDropHeight: 0 maxJumpAcrossDistance: 0 minRegionArea: 2 manualCellSize: 0 cellSize: 0.16666667 manualTileSize: 0 tileSize: 256 accuratePlacement: 0 debug: m_Flags: 0 m_NavMeshData: {fileID: 0} --- !u!1 &519420028 GameObject: m_ObjectHideFlags: 0 m_CorrespondingSourceObject: {fileID: 0} m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} serializedVersion: 6 m_Component: - component: {fileID: 519420032} - component: {fileID: 519420031} - component: {fileID: 519420029} - component: {fileID: 519420033} - component: {fileID: 519420030} m_Layer: 0 m_Name: Main Camera m_TagString: MainCamera m_Icon: {fileID: 0} m_NavMeshLayer: 0 m_StaticEditorFlags: 0 m_IsActive: 1 --- !u!81 &519420029 AudioListener: m_ObjectHideFlags: 0 m_CorrespondingSourceObject: {fileID: 0} m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 519420028} m_Enabled: 1 --- !u!114 &519420030 MonoBehaviour: m_ObjectHideFlags: 0 m_CorrespondingSourceObject: {fileID: 0} m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 519420028} m_Enabled: 1 m_EditorHideFlags: 0 m_Script: {fileID: 11500000, guid: c47d1a2e288e53f4285b397665f9a1dc, type: 3} m_Name: m_EditorClassIdentifier: --- !u!20 &519420031 Camera: m_ObjectHideFlags: 0 m_CorrespondingSourceObject: {fileID: 0} m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 519420028} m_Enabled: 1 serializedVersion: 2 m_ClearFlags: 2 m_BackGroundColor: {r: 0.19215687, g: 0.3019608, b: 0.4745098, a: 0} m_projectionMatrixMode: 1 m_SensorSize: {x: 36, y: 24} m_LensShift: {x: 0, y: 0} m_GateFitMode: 2 m_FocalLength: 50 m_NormalizedViewPortRect: serializedVersion: 2 x: 0 y: 0 width: 1 height: 1 near clip plane: 0.3 far clip plane: 1000 field of view: 60 orthographic: 1 orthographic size: 5 m_Depth: -1 m_CullingMask: serializedVersion: 2 m_Bits: 4294967295 m_RenderingPath: -1 m_TargetTexture: {fileID: 0} m_TargetDisplay: 0 m_TargetEye: 0 m_HDR: 1 m_AllowMSAA: 0 m_AllowDynamicResolution: 0 m_ForceIntoRT: 0 m_OcclusionCulling: 0 m_StereoConvergence: 10 m_StereoSeparation: 0.022 --- !u!4 &519420032 Transform: m_ObjectHideFlags: 0 m_CorrespondingSourceObject: {fileID: 0} m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 519420028} m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} m_LocalPosition: {x: 0, y: 0, z: -10} m_LocalScale: {x: 1, y: 1, z: 1} m_Children: [] m_Father: {fileID: 0} m_RootOrder: 0 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!114 &519420033 MonoBehaviour: m_ObjectHideFlags: 0 m_CorrespondingSourceObject: {fileID: 0} m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 519420028} m_Enabled: 1 m_EditorHideFlags: 0 m_Script: {fileID: 11500000, guid: 151d2db7aab5bc8438bc039443ba0e3b, type: 3} m_Name: m_EditorClassIdentifier: Protocol: 1 ================================================ FILE: Assets/Scenes/Client.unity.meta ================================================ fileFormatVersion: 2 guid: 53e18807e282965479d061608139b64b DefaultImporter: externalObjects: {} userData: assetBundleName: assetBundleVariant: ================================================ FILE: Assets/Scenes/Server.unity ================================================ %YAML 1.1 %TAG !u! tag:unity3d.com,2011: --- !u!29 &1 OcclusionCullingSettings: m_ObjectHideFlags: 0 serializedVersion: 2 m_OcclusionBakeSettings: smallestOccluder: 5 smallestHole: 0.25 backfaceThreshold: 100 m_SceneGUID: 00000000000000000000000000000000 m_OcclusionCullingData: {fileID: 0} --- !u!104 &2 RenderSettings: m_ObjectHideFlags: 0 serializedVersion: 9 m_Fog: 0 m_FogColor: {r: 0.5, g: 0.5, b: 0.5, a: 1} m_FogMode: 3 m_FogDensity: 0.01 m_LinearFogStart: 0 m_LinearFogEnd: 300 m_AmbientSkyColor: {r: 0.212, g: 0.227, b: 0.259, a: 1} m_AmbientEquatorColor: {r: 0.114, g: 0.125, b: 0.133, a: 1} m_AmbientGroundColor: {r: 0.047, g: 0.043, b: 0.035, a: 1} m_AmbientIntensity: 1 m_AmbientMode: 3 m_SubtractiveShadowColor: {r: 0.42, g: 0.478, b: 0.627, a: 1} m_SkyboxMaterial: {fileID: 0} m_HaloStrength: 0.5 m_FlareStrength: 1 m_FlareFadeSpeed: 3 m_HaloTexture: {fileID: 0} m_SpotCookie: {fileID: 10001, guid: 0000000000000000e000000000000000, type: 0} m_DefaultReflectionMode: 0 m_DefaultReflectionResolution: 128 m_ReflectionBounces: 1 m_ReflectionIntensity: 1 m_CustomReflection: {fileID: 0} m_Sun: {fileID: 0} m_IndirectSpecularColor: {r: 0, g: 0, b: 0, a: 1} m_UseRadianceAmbientProbe: 0 --- !u!157 &3 LightmapSettings: m_ObjectHideFlags: 0 serializedVersion: 12 m_GIWorkflowMode: 1 m_GISettings: serializedVersion: 2 m_BounceScale: 1 m_IndirectOutputScale: 1 m_AlbedoBoost: 1 m_EnvironmentLightingMode: 0 m_EnableBakedLightmaps: 0 m_EnableRealtimeLightmaps: 0 m_LightmapEditorSettings: serializedVersion: 12 m_Resolution: 2 m_BakeResolution: 40 m_AtlasSize: 1024 m_AO: 0 m_AOMaxDistance: 1 m_CompAOExponent: 1 m_CompAOExponentDirect: 0 m_ExtractAmbientOcclusion: 0 m_Padding: 2 m_LightmapParameters: {fileID: 0} m_LightmapsBakeMode: 1 m_TextureCompression: 1 m_FinalGather: 0 m_FinalGatherFiltering: 1 m_FinalGatherRayCount: 256 m_ReflectionCompression: 2 m_MixedBakeMode: 2 m_BakeBackend: 1 m_PVRSampling: 1 m_PVRDirectSampleCount: 32 m_PVRSampleCount: 512 m_PVRBounces: 2 m_PVREnvironmentSampleCount: 256 m_PVREnvironmentReferencePointCount: 2048 m_PVRFilteringMode: 1 m_PVRDenoiserTypeDirect: 1 m_PVRDenoiserTypeIndirect: 1 m_PVRDenoiserTypeAO: 1 m_PVRFilterTypeDirect: 0 m_PVRFilterTypeIndirect: 0 m_PVRFilterTypeAO: 0 m_PVREnvironmentMIS: 1 m_PVRCulling: 1 m_PVRFilteringGaussRadiusDirect: 1 m_PVRFilteringGaussRadiusIndirect: 5 m_PVRFilteringGaussRadiusAO: 2 m_PVRFilteringAtrousPositionSigmaDirect: 0.5 m_PVRFilteringAtrousPositionSigmaIndirect: 2 m_PVRFilteringAtrousPositionSigmaAO: 1 m_ExportTrainingData: 0 m_TrainingDataDestination: TrainingData m_LightProbeSampleCountMultiplier: 4 m_LightingDataAsset: {fileID: 0} m_LightingSettings: {fileID: 0} --- !u!196 &4 NavMeshSettings: serializedVersion: 2 m_ObjectHideFlags: 0 m_BuildSettings: serializedVersion: 2 agentTypeID: 0 agentRadius: 0.5 agentHeight: 2 agentSlope: 45 agentClimb: 0.4 ledgeDropHeight: 0 maxJumpAcrossDistance: 0 minRegionArea: 2 manualCellSize: 0 cellSize: 0.16666667 manualTileSize: 0 tileSize: 256 accuratePlacement: 0 maxJobWorkers: 0 preserveTilesOutsideBounds: 0 debug: m_Flags: 0 m_NavMeshData: {fileID: 0} --- !u!1 &2029809421 GameObject: m_ObjectHideFlags: 0 m_CorrespondingSourceObject: {fileID: 0} m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} serializedVersion: 6 m_Component: - component: {fileID: 2029809422} - component: {fileID: 2029809424} - component: {fileID: 2029809423} m_Layer: 0 m_Name: ServerManager m_TagString: Untagged m_Icon: {fileID: 0} m_NavMeshLayer: 0 m_StaticEditorFlags: 0 m_IsActive: 1 --- !u!4 &2029809422 Transform: m_ObjectHideFlags: 0 m_CorrespondingSourceObject: {fileID: 0} m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 2029809421} m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 0} m_RootOrder: 0 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!114 &2029809423 MonoBehaviour: m_ObjectHideFlags: 0 m_CorrespondingSourceObject: {fileID: 0} m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 2029809421} m_Enabled: 1 m_EditorHideFlags: 0 m_Script: {fileID: 11500000, guid: 8518a6a6d5ca49ecb048613cc3a03e07, type: 3} m_Name: m_EditorClassIdentifier: --- !u!114 &2029809424 MonoBehaviour: m_ObjectHideFlags: 0 m_CorrespondingSourceObject: {fileID: 0} m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 2029809421} m_Enabled: 1 m_EditorHideFlags: 0 m_Script: {fileID: 11500000, guid: 84fd225f324f42999874057579220d14, type: 3} m_Name: m_EditorClassIdentifier: ================================================ FILE: Assets/Scenes/Server.unity.meta ================================================ fileFormatVersion: 2 guid: 7a58935d2e567f043835f59be74400f6 DefaultImporter: externalObjects: {} userData: assetBundleName: assetBundleVariant: ================================================ FILE: Assets/Scenes.meta ================================================ fileFormatVersion: 2 guid: e865c368c8aa84b4783f03af294abb01 folderAsset: yes DefaultImporter: externalObjects: {} userData: assetBundleName: assetBundleVariant: ================================================ FILE: Assets/Scripts/Helper/ByteHelper.cs ================================================ using System.Text; namespace UNetwork { public static class ByteHelper { public static string ToHex(this byte b) { return b.ToString("X2"); } public static string ToHex(this byte[] bytes) { StringBuilder stringBuilder = new StringBuilder(); foreach (byte b in bytes) { stringBuilder.Append(b.ToString("X2")); } return stringBuilder.ToString(); } public static string ToHex(this byte[] bytes, string format) { StringBuilder stringBuilder = new StringBuilder(); foreach (byte b in bytes) { stringBuilder.Append(b.ToString(format)); } return stringBuilder.ToString(); } public static string ToHex(this byte[] bytes, int offset, int count) { StringBuilder stringBuilder = new StringBuilder(); for (int i = offset; i < offset + count; ++i) { stringBuilder.Append(bytes[i].ToString("X2")); } return stringBuilder.ToString(); } public static string ToStr(this byte[] bytes) { return Encoding.Default.GetString(bytes); } public static string ToStr(this byte[] bytes, int index, int count) { return Encoding.Default.GetString(bytes, index, count); } public static string Utf8ToStr(this byte[] bytes) { return Encoding.UTF8.GetString(bytes); } public static string Utf8ToStr(this byte[] bytes, int index, int count) { return Encoding.UTF8.GetString(bytes, index, count); } public static void WriteTo(this byte[] bytes, int offset, uint num) { bytes[offset] = (byte)(num & 0xff); bytes[offset + 1] = (byte)((num & 0xff00) >> 8); bytes[offset + 2] = (byte)((num & 0xff0000) >> 16); bytes[offset + 3] = (byte)((num & 0xff000000) >> 24); } public static void WriteTo(this byte[] bytes, int offset, int num) { bytes[offset] = (byte)(num & 0xff); bytes[offset + 1] = (byte)((num & 0xff00) >> 8); bytes[offset + 2] = (byte)((num & 0xff0000) >> 16); bytes[offset + 3] = (byte)((num & 0xff000000) >> 24); } public static void WriteTo(this byte[] bytes, int offset, byte num) { bytes[offset] = num; } public static void WriteTo(this byte[] bytes, int offset, short num) { bytes[offset] = (byte)(num & 0xff); bytes[offset + 1] = (byte)((num & 0xff00) >> 8); } public static void WriteTo(this byte[] bytes, int offset, ushort num) { bytes[offset] = (byte)(num & 0xff); bytes[offset + 1] = (byte)((num & 0xff00) >> 8); } } } ================================================ FILE: Assets/Scripts/Helper/ByteHelper.cs.meta ================================================ fileFormatVersion: 2 guid: c31f9c17b9d7c0e45b3b3f77811cb795 MonoImporter: externalObjects: {} serializedVersion: 2 defaultReferences: [] executionOrder: 0 icon: {instanceID: 0} userData: assetBundleName: assetBundleVariant: ================================================ FILE: Assets/Scripts/Helper/MonoSingleton.cs ================================================ using UnityEngine; public abstract class MonoSingleton : MonoBehaviour where T : MonoSingleton { private static T mInstance = null; public static T Instance { get { if (mInstance == null) { mInstance = GameObject.FindObjectOfType(typeof(T)) as T; if (mInstance == null) { GameObject go = new GameObject(typeof(T).Name); mInstance = go.AddComponent(); GameObject parent = GameObject.Find("Boot"); if (parent != null) { go.transform.parent = parent.transform; } } } return mInstance; } } /* * 没有任何实现的函数,用于保证MonoSingleton在使用前已创建 */ public void Startup() { } private void Awake() { if (mInstance == null) { mInstance = this as T; } DontDestroyOnLoad(gameObject); Init(); } public virtual void Init() { } public void DestroySelf() { Dispose(); mInstance = null; Destroy(gameObject); } public virtual void Dispose() { } } ================================================ FILE: Assets/Scripts/Helper/MonoSingleton.cs.meta ================================================ fileFormatVersion: 2 guid: bbde26f5f28b63743b17d086fd52d70a MonoImporter: externalObjects: {} serializedVersion: 2 defaultReferences: [] executionOrder: 0 icon: {instanceID: 0} userData: assetBundleName: assetBundleVariant: ================================================ FILE: Assets/Scripts/Helper/NetworkHelper.cs ================================================ using System.Net; namespace UNetwork { public static class NetworkHelper { public static IPEndPoint ToIPEndPoint(string host, int port) { return new IPEndPoint(IPAddress.Parse(host), port); } public static IPEndPoint ToIPEndPoint(string address) { int index = address.LastIndexOf(':'); string host = address.Substring(0, index); string p = address.Substring(index + 1); int port = int.Parse(p); return ToIPEndPoint(host, port); } } } ================================================ FILE: Assets/Scripts/Helper/NetworkHelper.cs.meta ================================================ fileFormatVersion: 2 guid: 5f24e42ac059909498f35805b76f9102 timeCreated: 1511941046 licenseType: Free MonoImporter: serializedVersion: 2 defaultReferences: [] executionOrder: 0 icon: {instanceID: 0} userData: assetBundleName: assetBundleVariant: ================================================ FILE: Assets/Scripts/Helper/RandomHelper.cs ================================================ using System; namespace UNetwork { public static class RandomHelper { private static readonly Random random = new Random(); public static UInt64 RandUInt64() { var bytes = new byte[8]; random.NextBytes(bytes); return BitConverter.ToUInt64(bytes, 0); } public static Int64 RandInt64() { var bytes = new byte[8]; random.NextBytes(bytes); return BitConverter.ToInt64(bytes, 0); } /// /// 获取lower与Upper之间的随机数 /// /// /// /// public static int RandomNumber(int lower, int upper) { int value = random.Next(lower, upper); return value; } } } ================================================ FILE: Assets/Scripts/Helper/RandomHelper.cs.meta ================================================ fileFormatVersion: 2 guid: ec912a7512f681b43bb4cc56873631af MonoImporter: externalObjects: {} serializedVersion: 2 defaultReferences: [] executionOrder: 0 icon: {instanceID: 0} userData: assetBundleName: assetBundleVariant: ================================================ FILE: Assets/Scripts/Helper/TimeHelper.cs ================================================ using System; namespace UNetwork { public static class TimeHelper { private static readonly long epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).Ticks; /// /// 客户端时间 /// /// public static long ClientNow() { return (DateTime.UtcNow.Ticks - epoch) / 10000; } public static long ClientNowSeconds() { return (DateTime.UtcNow.Ticks - epoch) / 10000000; } public static long Now() { return ClientNow(); } } } ================================================ FILE: Assets/Scripts/Helper/TimeHelper.cs.meta ================================================ fileFormatVersion: 2 guid: 53e39d24f06688d4d961473c5a977410 MonoImporter: externalObjects: {} serializedVersion: 2 defaultReferences: [] executionOrder: 0 icon: {instanceID: 0} userData: assetBundleName: assetBundleVariant: ================================================ FILE: Assets/Scripts/Helper.meta ================================================ fileFormatVersion: 2 guid: 3a479f7d4b18e0f439a7ba86124bebe4 folderAsset: yes DefaultImporter: externalObjects: {} userData: assetBundleName: assetBundleVariant: ================================================ FILE: Assets/Scripts/Network/AChannel.cs ================================================ using System; using System.IO; using System.Net; using UnityEngine; namespace UNetwork { public enum ChannelType { Connect, Accept, } public class AChannel { public ChannelType ChannelType { get; } public AService Service { get; } protected MemoryStream memoryStream; public MemoryStream Stream => this.memoryStream; public int Error { get; set; } public IPEndPoint RemoteAddress { get; protected set; } private Action connectCallback; public event Action ConnectCallback { add { this.connectCallback += value; } remove { this.connectCallback -= value; } } private Action errorCallback; public event Action ErrorCallback { add { this.errorCallback += value; } remove { this.errorCallback -= value; } } private Action readCallback; public event Action ReadCallback { add { this.readCallback += value; } remove { this.readCallback -= value; } } public void OnConnect(int code) { Debug.Log("connect success"); this.connectCallback.Invoke(this, code); } protected void OnRead(MemoryStream memoryStream) { this.readCallback.Invoke(memoryStream); } protected void OnError(int e) { this.Error = e; this.errorCallback?.Invoke(this, e); } protected AChannel(AService service, ChannelType channelType) { this.ChannelType = channelType; this.Service = service; } public virtual void Start() { } public virtual void Send(MemoryStream stream) { } public virtual void Dispose() { this.Service.Dispose(); } } } ================================================ FILE: Assets/Scripts/Network/AChannel.cs.meta ================================================ fileFormatVersion: 2 guid: 05a074f965a8c214f8f1f1af1ecf4dcc timeCreated: 1503986857 licenseType: Free MonoImporter: serializedVersion: 2 defaultReferences: [] executionOrder: 0 icon: {instanceID: 0} userData: assetBundleName: assetBundleVariant: ================================================ FILE: Assets/Scripts/Network/AService.cs ================================================ using System; using System.Net; namespace UNetwork { public enum NetworkProtocol { KCP, TCP, WebSocket, } public abstract class AService { public abstract AChannel GetChannel(); public abstract AChannel ConnectChannel(IPEndPoint ipEndPoint); public abstract AChannel ConnectChannel(string address); public abstract void Update(); public abstract void Dispose(); } } ================================================ FILE: Assets/Scripts/Network/AService.cs.meta ================================================ fileFormatVersion: 2 guid: b5af0de1c471e6c43989dc0349cc2bba timeCreated: 1503986858 licenseType: Free MonoImporter: serializedVersion: 2 defaultReferences: [] executionOrder: 0 icon: {instanceID: 0} userData: assetBundleName: assetBundleVariant: ================================================ FILE: Assets/Scripts/Network/ClientManager.cs ================================================ using System; using System.Net; using System.Threading; namespace UNetwork { /// /// 客户端的 业务逻辑 管理类 /// public class ClientManager : MonoSingleton, INetworkManager { public AService Service { get; private set; } public Session Session { get; private set; } public IMessagePacker MessagePacker { get; set; } public IMessageDispatcher MessageDispatcher { get; set; } public Action OnConnect { get; set; } public Action OnError { get; set; } public Action OnMessage { get; set; } public override void Init() { SynchronizationContext.SetSynchronizationContext(OneThreadSynchronizationContext.Instance); } //clinet public void InitService(NetworkProtocol protocol, int packetSize = Packet.PacketSizeLength4) { switch (protocol) { case NetworkProtocol.KCP: this.Service = new KService() { }; break; case NetworkProtocol.TCP: this.Service = new TService(packetSize) { }; break; case NetworkProtocol.WebSocket: this.Service = new WService() { }; break; } } /// /// 创建一个新Session /// public void Connect(IPEndPoint ipEndPoint) { AChannel channel = this.Service.ConnectChannel(ipEndPoint); Session = new Session(channel); Session.Start(this); } /// /// 创建一个新Session /// public void Connect(string address) { AChannel channel = this.Service.ConnectChannel(address); Session = new Session(channel); Session.Start(this); } public void Connect(string ip, int port) { AChannel channel = this.Service.ConnectChannel(NetworkHelper.ToIPEndPoint(ip, port)); Session = new Session(channel); Session.Start(this); } public void Update() { OneThreadSynchronizationContext.Instance.Update(); if (this.Service == null) { return; } this.Service.Update(); } public void Send(byte[] data) { Session.Send(data); } public override void Dispose() { Session.Dispose(); } } } ================================================ FILE: Assets/Scripts/Network/ClientManager.cs.meta ================================================ fileFormatVersion: 2 guid: 151d2db7aab5bc8438bc039443ba0e3b timeCreated: 1498117616 licenseType: Free MonoImporter: serializedVersion: 2 defaultReferences: [] executionOrder: 0 icon: {instanceID: 0} userData: assetBundleName: assetBundleVariant: ================================================ FILE: Assets/Scripts/Network/Core/KCP/KCP.cs ================================================ // Copyright (C) 2017 prototyped.cn All rights reserved. // Distributed under the terms and conditions of the MIT License. // See accompanying files LICENSE. using System; using System.Diagnostics; using System.Collections.Generic; namespace UNetwork { public class KCP { public const int IKCP_RTO_NDL = 30; // no delay min rto public const int IKCP_RTO_MIN = 100; // normal min rto public const int IKCP_RTO_DEF = 200; public const int IKCP_RTO_MAX = 60000; public const int IKCP_CMD_PUSH = 81; // cmd: push data public const int IKCP_CMD_ACK = 82; // cmd: ack public const int IKCP_CMD_WASK = 83; // cmd: window probe (ask) public const int IKCP_CMD_WINS = 84; // cmd: window size (tell) public const int IKCP_ASK_SEND = 1; // need to send IKCP_CMD_WASK public const int IKCP_ASK_TELL = 2; // need to send IKCP_CMD_WINS public const int IKCP_WND_SND = 32; public const int IKCP_WND_RCV = 32; public const int IKCP_MTU_DEF = 1400; public const int IKCP_ACK_FAST = 3; public const int IKCP_INTERVAL = 100; public const int IKCP_OVERHEAD = 24; public const int IKCP_DEADLINK = 20; public const int IKCP_THRESH_INIT = 2; public const int IKCP_THRESH_MIN = 2; public const int IKCP_PROBE_INIT = 7000; // 7 secs to probe window size public const int IKCP_PROBE_LIMIT = 120000; // up to 120 secs to probe window public const int IKCP_LOG_OUTPUT = 0x1; public const int IKCP_LOG_INPUT = 0x2; public const int IKCP_LOG_SEND = 0x4; public const int IKCP_LOG_RECV = 0x8; public const int IKCP_LOG_IN_DATA = 0x10; public const int IKCP_LOG_IN_ACK = 0x20; public const int IKCP_LOG_IN_PROBE = 0x40; public const int IKCP_LOG_IN_WINS = 0x80; public const int IKCP_LOG_OUT_DATA = 0x100; public const int IKCP_LOG_OUT_ACK = 0x200; public const int IKCP_LOG_OUT_PROBE = 0x400; public const int IKCP_LOG_OUT_WINS = 0x800; // encode 8 bits unsigned int public static void ikcp_encode8u(byte[] p, int offset, byte c) { p[offset] = c; } // decode 8 bits unsigned int public static byte ikcp_decode8u(byte[] p, ref int offset) { return p[offset++]; } // encode 16 bits unsigned int (lsb) public static void ikcp_encode16u(byte[] p, int offset, UInt16 v) { p[offset] = (byte)(v & 0xFF); p[offset + 1] = (byte)(v >> 8); } // decode 16 bits unsigned int (lsb) public static UInt16 ikcp_decode16u(byte[] p, ref int offset) { int pos = offset; offset += 2; return (UInt16)((UInt16)p[pos] | (UInt16)(p[pos + 1] << 8)); } // encode 32 bits unsigned int (lsb) public static void ikcp_encode32u(byte[] p, int offset, UInt32 l) { p[offset] = (byte)(l & 0xFF); p[offset + 1] = (byte)(l >> 8); p[offset + 2] = (byte)(l >> 16); p[offset + 3] = (byte)(l >> 24); } // decode 32 bits unsigned int (lsb) public static UInt32 ikcp_decode32u(byte[] p, ref int offset) { int pos = offset; offset += 4; return ((UInt32)p[pos] | (UInt32)(p[pos + 1] << 8) | (UInt32)(p[pos + 2] << 16) | (UInt32)(p[pos + 3] << 24)); } public static UInt32 _imin_(UInt32 a, UInt32 b) { return a <= b ? a : b; } public static UInt32 _imax_(UInt32 a, UInt32 b) { return a >= b ? a : b; } public static UInt32 _ibound_(UInt32 lower, UInt32 middle, UInt32 upper) { return _imin_(_imax_(lower, middle), upper); } public static Int32 _itimediff(UInt32 later, UInt32 earlier) { return (Int32)(later - earlier); } internal class Segment { internal UInt32 conv = 0; internal UInt32 cmd = 0; internal UInt32 frg = 0; internal UInt32 wnd = 0; internal UInt32 ts = 0; internal UInt32 sn = 0; internal UInt32 una = 0; internal UInt32 resendts = 0; internal UInt32 rto = 0; internal UInt32 faskack = 0; internal UInt32 xmit = 0; internal byte[] data; internal Segment(int size = 0) { data = new byte[size]; } internal void Encode(byte[] ptr, ref int offset) { UInt32 len = (UInt32)data.Length; ikcp_encode32u(ptr, offset, conv); ikcp_encode8u(ptr, offset + 4, (byte)cmd); ikcp_encode8u(ptr, offset + 5, (byte)frg); ikcp_encode16u(ptr, offset + 6, (UInt16)wnd); ikcp_encode32u(ptr, offset + 8, ts); ikcp_encode32u(ptr, offset + 12, sn); ikcp_encode32u(ptr, offset + 16, una); ikcp_encode32u(ptr, offset + 20, len); offset += IKCP_OVERHEAD; } } UInt32 conv_ = 0; UInt32 mtu_ = 0; UInt32 mss_ = 0; UInt32 state_ = 0; UInt32 snd_una_ = 0; UInt32 snd_nxt_ = 0; UInt32 rcv_nxt_ = 0; UInt32 ts_recent_ = 0; UInt32 ts_lastack_ = 0; UInt32 ssthresh_ = 0; Int32 rx_rttval_ = 0; Int32 rx_srtt_ = 0; Int32 rx_rto_ = 0; Int32 rx_minrto_ = 0; UInt32 snd_wnd_ = 0; UInt32 rcv_wnd_ = 0; UInt32 rmt_wnd_ = 0; UInt32 cwnd_ = 0; UInt32 probe_ = 0; UInt32 current_ = 0; UInt32 interval_ = 0; UInt32 ts_flush_ = 0; UInt32 xmit_ = 0; UInt32 nrcv_buf_ = 0; UInt32 nsnd_buf_ = 0; UInt32 nrcv_que_ = 0; UInt32 nsnd_que_ = 0; UInt32 nodelay_ = 0; UInt32 updated_ = 0; UInt32 ts_probe_ = 0; UInt32 probe_wait_ = 0; UInt32 dead_link_ = 0; UInt32 incr_ = 0; LinkedList snd_queue_; LinkedList rcv_queue_; LinkedList snd_buf_; LinkedList rcv_buf_; UInt32[] acklist_; UInt32 ackcount_ = 0; UInt32 ackblock_ = 0; byte[] buffer_; object user_; Int32 fastresend_ = 0; Int32 nocwnd_ = 0; public delegate void OutputDelegate(byte[] data, int size, object user); OutputDelegate output_; // create a new kcp control object, 'conv' must equal in two endpoint // from the same connection. 'user' will be passed to the output callback // output callback can be setup like this: 'kcp->output = my_udp_output' public KCP(UInt32 conv, object user) { Debug.Assert(BitConverter.IsLittleEndian); // we only support little endian device user_ = user; conv_ = conv; snd_wnd_ = IKCP_WND_SND; rcv_wnd_ = IKCP_WND_RCV; rmt_wnd_ = IKCP_WND_RCV; mtu_ = IKCP_MTU_DEF; mss_ = mtu_ - IKCP_OVERHEAD; rx_rto_ = IKCP_RTO_DEF; rx_minrto_ = IKCP_RTO_MIN; interval_ = IKCP_INTERVAL; ts_flush_ = IKCP_INTERVAL; ssthresh_ = IKCP_THRESH_INIT; dead_link_ = IKCP_DEADLINK; buffer_ = new byte[(mtu_ + IKCP_OVERHEAD) * 3]; snd_queue_ = new LinkedList(); rcv_queue_ = new LinkedList(); snd_buf_ = new LinkedList(); rcv_buf_ = new LinkedList(); } // release kcp control object public void Release() { snd_buf_.Clear(); rcv_buf_.Clear(); snd_queue_.Clear(); rcv_queue_.Clear(); nrcv_buf_ = 0; nsnd_buf_ = 0; nrcv_que_ = 0; nsnd_que_ = 0; ackblock_ = 0; ackcount_ = 0; buffer_ = null; acklist_ = null; } // set output callback, which will be invoked by kcp public void SetOutput(OutputDelegate output) { output_ = output; } // user/upper level recv: returns size, returns below zero for EAGAIN public int Recv(byte[] buffer, int offset, int len) { int ispeek = (len < 0 ? 1 : 0); int recover = 0; 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; if (nrcv_que_ >= rcv_wnd_) recover = 1; // merge fragment len = 0; LinkedListNode next = null; for (var node = rcv_queue_.First; node != null; node = next) { int fragment = 0; var seg = node.Value; next = node.Next; if (buffer != null) { Buffer.BlockCopy(seg.data, 0, buffer, offset, seg.data.Length); offset += seg.data.Length; } len += seg.data.Length; fragment = (int)seg.frg; Log(IKCP_LOG_RECV, "recv sn={0}", seg.sn); if (ispeek == 0) { rcv_queue_.Remove(node); nrcv_que_--; } if (fragment == 0) break; } Debug.Assert(len == peeksize); // move available data from rcv_buf -> rcv_queue while (rcv_buf_.Count > 0) { var node = rcv_buf_.First; var seg = node.Value; if (seg.sn == rcv_nxt_ && nrcv_que_ < rcv_wnd_) { rcv_buf_.Remove(node); nrcv_buf_--; rcv_queue_.AddLast(node); nrcv_que_++; rcv_nxt_++; } else { break; } } // fast recover if (nrcv_que_ < rcv_wnd_ && recover != 0) { // ready to send back IKCP_CMD_WINS in ikcp_flush // tell remote my window size probe_ |= IKCP_ASK_TELL; } return len; } // check the size of next message in the recv queue public int PeekSize() { if (rcv_queue_.Count == 0) return -1; var node = rcv_queue_.First; var seg = node.Value; if (seg.frg == 0) return seg.data.Length; if (nrcv_que_ < seg.frg + 1) return -1; int length = 0; for (node = rcv_queue_.First; node != null; node = node.Next) { seg = node.Value; length += seg.data.Length; if (seg.frg == 0) break; } return length; } // user/upper level send, returns below zero for error public int Send(byte[] buffer, int offset, int len) { Debug.Assert(mss_ > 0); if (len < 0) return -1; // // not implement streaming mode here as ikcp.c // int count = 0; if (len <= (int)mss_) count = 1; else count = (len + (int)mss_ - 1) / (int)mss_; if (count > 255) // maximum value `frg` can present return -2; if (count == 0) count = 1; // fragment for (int i = 0; i < count; i++) { int size = len > (int)mss_ ? (int)mss_ : len; var seg = new Segment(size); if (buffer != null && len > 0) { Buffer.BlockCopy(buffer, offset, seg.data, 0, size); offset += size; } seg.frg = (UInt32)(count - i - 1); snd_queue_.AddLast(seg); nsnd_que_++; len -= size; } return 0; } // parse ack void UpdateACK(Int32 rtt) { if (rx_srtt_ == 0) { rx_srtt_ = rtt; rx_rttval_ = rtt / 2; } else { Int32 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; } var rto = rx_srtt_ + _imax_(interval_, (UInt32)(4 * rx_rttval_)); rx_rto_ = (Int32)_ibound_((UInt32)rx_minrto_, (UInt32)rto, IKCP_RTO_MAX); } void ShrinkBuf() { var node = snd_buf_.First; if (node != null) { var seg = node.Value; snd_una_ = seg.sn; } else { snd_una_ = snd_nxt_; } } void ParseACK(UInt32 sn) { if (_itimediff(sn, snd_una_) < 0 || _itimediff(sn, snd_nxt_) >= 0) return; LinkedListNode next = null; for (var node = snd_buf_.First; node != null; node = next) { var seg = node.Value; next = node.Next; if (sn == seg.sn) { snd_buf_.Remove(node); nsnd_buf_--; break; } if (_itimediff(sn, seg.sn) < 0) break; } } void ParseUNA(UInt32 una) { LinkedListNode next = null; for (var node = snd_buf_.First; node != null; node = next) { var seg = node.Value; next = node.Next; if (_itimediff(una, seg.sn) > 0) { snd_buf_.Remove(node); nsnd_buf_--; } else { break; } } } void ParseFastACK(UInt32 sn) { if (_itimediff(sn, snd_una_) < 0 || _itimediff(sn, snd_nxt_) >= 0) return; LinkedListNode next = null; for (var node = snd_buf_.First; node != null; node = next) { var seg = node.Value; next = node.Next; if (_itimediff(sn, seg.sn) < 0) { break; } else if (sn != seg.sn) { seg.faskack++; } } } // ack append void ACKPush(UInt32 sn, UInt32 ts) { var newsize = ackcount_ + 1; if (newsize > ackblock_) { UInt32 newblock = 8; for (; newblock < newsize; newblock <<= 1) ; var acklist = new UInt32[newblock * 2]; if (acklist_ != null) { for (var i = 0; i < ackcount_; i++) { acklist[i * 2] = acklist_[i * 2]; acklist[i * 2 + 1] = acklist_[i * 2 + 1]; } } acklist_ = acklist; ackblock_ = newblock; } acklist_[ackcount_ * 2] = sn; acklist_[ackcount_ * 2 + 1] = ts; ackcount_++; } void ACKGet(int pos, ref UInt32 sn, ref UInt32 ts) { sn = acklist_[pos * 2]; ts = acklist_[pos * 2 + 1]; } // parse data void ParseData(Segment newseg) { UInt32 sn = newseg.sn; int repeat = 0; if (_itimediff(sn, rcv_nxt_ + rcv_wnd_) >= 0 || _itimediff(sn, rcv_nxt_) < 0) { return; } LinkedListNode node = null; LinkedListNode prev = null; for (node = rcv_buf_.Last; node != null; node = prev) { var seg = node.Value; prev = node.Previous; if (seg.sn == sn) { repeat = 1; break; } if (_itimediff(sn, seg.sn) > 0) { break; } } if (repeat == 0) { if (node != null) { rcv_buf_.AddAfter(node, newseg); } else { rcv_buf_.AddFirst(newseg); } nrcv_buf_++; } // move available data from rcv_buf -> rcv_queue while (rcv_buf_.Count > 0) { node = rcv_buf_.First; var seg = node.Value; if (seg.sn == rcv_nxt_ && nrcv_que_ < rcv_wnd_) { rcv_buf_.Remove(node); nrcv_buf_--; rcv_queue_.AddLast(node); nrcv_que_++; rcv_nxt_++; } else { break; } } } // when you received a low level packet (eg. UDP packet), call it public int Input(byte[] data, int offset, int size) { UInt32 maxack = 0; int flag = 0; Log(IKCP_LOG_INPUT, "[RI] {0} bytes", size); if (data == null || size < IKCP_OVERHEAD) return -1; while (true) { if (size < IKCP_OVERHEAD) break; UInt32 conv = ikcp_decode32u(data, ref offset); if (conv_ != conv) return -1; UInt32 cmd = ikcp_decode8u(data, ref offset); UInt32 frg = ikcp_decode8u(data, ref offset); UInt32 wnd = ikcp_decode16u(data, ref offset); UInt32 ts = ikcp_decode32u(data, ref offset); UInt32 sn = ikcp_decode32u(data, ref offset); UInt32 una = ikcp_decode32u(data, ref offset); UInt32 len = ikcp_decode32u(data, ref offset); size -= IKCP_OVERHEAD; if (size < len) return -2; if (cmd != IKCP_CMD_PUSH && cmd != IKCP_CMD_ACK && cmd != IKCP_CMD_WASK && cmd != IKCP_CMD_WINS) return -3; rmt_wnd_ = wnd; ParseUNA(una); ShrinkBuf(); if (cmd == IKCP_CMD_ACK) { if (_itimediff(current_, ts) >= 0) { UpdateACK(_itimediff(current_, ts)); } ParseACK(sn); ShrinkBuf(); if (flag == 0) { flag = 1; maxack = sn; } else { if (_itimediff(sn, maxack) > 0) { maxack = sn; } } Log(IKCP_LOG_IN_DATA, "input ack: sn={0} rtt={1} rto={2}", sn, _itimediff(current_, ts), rx_rto_); } else if (cmd == IKCP_CMD_PUSH) { Log(IKCP_LOG_IN_DATA, "input psh: sn={0} ts={1}", sn, ts); if (_itimediff(sn, rcv_nxt_ + rcv_wnd_) < 0) { ACKPush(sn, ts); if (_itimediff(sn, rcv_nxt_) >= 0) { var seg = new Segment((int)len); seg.conv = conv; seg.cmd = cmd; seg.frg = frg; seg.wnd = wnd; seg.ts = ts; seg.sn = sn; seg.una = una; if (len > 0) { Buffer.BlockCopy(data, offset, seg.data, 0, (int)len); } ParseData(seg); } } } else if (cmd == IKCP_CMD_WASK) { // ready to send back IKCP_CMD_WINS in ikcp_flush // tell remote my window size probe_ |= IKCP_ASK_TELL; Log(IKCP_LOG_IN_PROBE, "input probe"); } else if (cmd == IKCP_CMD_WINS) { // do nothing Log(IKCP_LOG_IN_WINS, "input wins: {0}", wnd); } else { return -3; } offset += (int)len; size -= (int)len; } if (flag != 0) { ParseFastACK(maxack); } UInt32 unack = snd_una_; if (_itimediff(snd_una_, unack) > 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_++; } if (cwnd_ > rmt_wnd_) { cwnd_ = rmt_wnd_; incr_ = rmt_wnd_ * mss_; } } } return 0; } int WndUnused() { if (nrcv_que_ < rcv_wnd_) return (int)(rcv_wnd_ - nrcv_que_); return 0; } // flush pending data void Flush() { int change = 0; int lost = 0; int offset = 0; // 'ikcp_update' haven't been called. if (updated_ == 0) return; var seg = new Segment { conv = conv_, cmd = IKCP_CMD_ACK, wnd = (UInt32)WndUnused(), una = rcv_nxt_, }; // flush acknowledges int count = (int)ackcount_; for (int i = 0; i < count; i++) { if ((offset + IKCP_OVERHEAD) > mtu_) { output_(buffer_, offset, user_); offset = 0; } ACKGet(i, ref seg.sn, ref seg.ts); seg.Encode(buffer_, ref offset); } ackcount_ = 0; // probe window size (if remote window size equals zero) if (rmt_wnd_ == 0) { if (probe_wait_ == 0) { probe_wait_ = IKCP_PROBE_INIT; ts_probe_ = current_ + probe_wait_; } else { if (_itimediff(current_, ts_probe_) >= 0) { if (probe_wait_ < IKCP_PROBE_INIT) probe_wait_ = IKCP_PROBE_INIT; probe_wait_ += probe_wait_ / 2; if (probe_wait_ > IKCP_PROBE_LIMIT) probe_wait_ = IKCP_PROBE_LIMIT; ts_probe_ = current_ + probe_wait_; probe_ |= IKCP_ASK_SEND; } } } else { ts_probe_ = 0; probe_wait_ = 0; } // flush window probing commands if ((probe_ & IKCP_ASK_SEND) > 0) { seg.cmd = IKCP_CMD_WASK; if ((offset + IKCP_OVERHEAD) > mtu_) { output_(buffer_, offset, user_); offset = 0; } seg.Encode(buffer_, ref offset); } // flush window probing commands if ((probe_ & IKCP_ASK_TELL) > 0) { seg.cmd = IKCP_CMD_WINS; if ((offset + IKCP_OVERHEAD) > mtu_) { output_(buffer_, offset, user_); offset = 0; } seg.Encode(buffer_, ref offset); } probe_ = 0; // calculate window size UInt32 cwnd = _imin_(snd_wnd_, rmt_wnd_); if (nocwnd_ == 0) cwnd = _imin_(cwnd_, cwnd); // move data from snd_queue to snd_buf while (_itimediff(snd_nxt_, snd_una_ + cwnd) < 0) { if (snd_queue_.Count == 0) break; var node = snd_queue_.First; var newseg = node.Value; snd_queue_.Remove(node); snd_buf_.AddLast(node); nsnd_que_--; nsnd_buf_++; newseg.conv = conv_; newseg.cmd = IKCP_CMD_PUSH; newseg.wnd = seg.wnd; newseg.ts = current_; newseg.sn = snd_nxt_++; newseg.una = rcv_nxt_; newseg.resendts = current_; newseg.rto = (UInt32)rx_rto_; newseg.faskack = 0; newseg.xmit = 0; } // calculate resent UInt32 resent = (fastresend_ > 0 ? (UInt32)fastresend_ : 0xffffffff); UInt32 rtomin = (nodelay_ == 0 ? (UInt32)(rx_rto_ >> 3) : 0); // flush data segments for (var node = snd_buf_.First; node != null; node = node.Next) { var segment = node.Value; int needsend = 0; if (segment.xmit == 0) { needsend = 1; segment.xmit++; segment.rto = (UInt32)rx_rto_; segment.resendts = current_ + segment.rto + rtomin; } else if (_itimediff(current_, segment.resendts) >= 0) { needsend = 1; segment.xmit++; xmit_++; if (nodelay_ == 0) segment.rto += (UInt32)rx_rto_; else segment.rto += (UInt32)rx_rto_ / 2; segment.resendts = current_ + segment.rto; lost = 1; } else if (segment.faskack >= resent) { needsend = 1; segment.xmit++; segment.faskack = 0; segment.resendts = current_ + segment.rto; change++; } if (needsend > 0) { segment.ts = current_; segment.wnd = seg.wnd; segment.una = rcv_nxt_; int need = IKCP_OVERHEAD; if (segment.data != null) need += segment.data.Length; if (offset + need > mtu_) { output_(buffer_, offset, user_); offset = 0; } segment.Encode(buffer_, ref offset); if (segment.data.Length > 0) { Buffer.BlockCopy(segment.data, 0, buffer_, offset, segment.data.Length); offset += segment.data.Length; } if (segment.xmit >= dead_link_) state_ = 0xffffffff; } } // flush remain segments if (offset > 0) { output_(buffer_, offset, user_); offset = 0; } // update ssthresh if (change > 0) { UInt32 inflight = snd_nxt_ - snd_una_; ssthresh_ = inflight / 2; if (ssthresh_ < IKCP_THRESH_MIN) ssthresh_ = IKCP_THRESH_MIN; cwnd_ = ssthresh_ + resent; incr_ = cwnd_ * mss_; } if (lost > 0) { ssthresh_ = cwnd / 2; if (ssthresh_ < IKCP_THRESH_MIN) ssthresh_ = IKCP_THRESH_MIN; cwnd_ = 1; incr_ = mss_; } if (cwnd_ < 1) { cwnd_ = 1; incr_ = mss_; } } // update state (call it repeatedly, every 10ms-100ms), or you can ask // ikcp_check when to call it again (without ikcp_input/_send calling). // 'current' - current timestamp in millisec. public void Update(UInt32 current) { current_ = current; if (updated_ == 0) { updated_ = 1; ts_flush_ = current; } Int32 slap = _itimediff(current_, ts_flush_); if (slap >= 10000 || slap < -10000) { ts_flush_ = current; slap = 0; } if (slap >= 0) { ts_flush_ += interval_; if (_itimediff(current_, ts_flush_) >= 0) ts_flush_ = current_ + interval_; Flush(); } } // Determine when should you invoke ikcp_update: // returns when you should invoke ikcp_update in millisec, if there // is no ikcp_input/_send calling. you can call ikcp_update in that // time, instead of call update repeatly. // Important to reduce unnacessary ikcp_update invoking. use it to // schedule ikcp_update (eg. implementing an epoll-like mechanism, // or optimize ikcp_update when handling massive kcp connections) public UInt32 Check(UInt32 current) { UInt32 ts_flush = ts_flush_; Int32 tm_flush = 0x7fffffff; Int32 tm_packet = 0x7fffffff; if (updated_ == 0) return current; if (_itimediff(current, ts_flush) >= 10000 || _itimediff(current, ts_flush) < -10000) { ts_flush = current; } if (_itimediff(current, ts_flush) >= 0) return current; tm_flush = _itimediff(ts_flush, current); for (var node = snd_buf_.First; node != null; node = node.Next) { var seg = node.Value; Int32 diff = _itimediff(seg.resendts, current); if (diff <= 0) return current; if (diff < tm_packet) tm_packet = diff; } UInt32 minimal = (UInt32)(tm_packet < tm_flush ? tm_packet : tm_flush); if (minimal >= interval_) minimal = interval_; return current + minimal; } // change MTU size, default is 1400 public int SetMTU(int mtu) { if (mtu < 50 || mtu < IKCP_OVERHEAD) return -1; var buffer = new byte[(mtu + IKCP_OVERHEAD) * 3]; mtu_ = (UInt32)mtu; mss_ = mtu_ - IKCP_OVERHEAD; buffer_ = buffer; return 0; } public int Interval(int interval) { if (interval > 5000) interval = 5000; else if (interval < 10) interval = 10; interval_ = (UInt32)interval; return 0; } // fastest: ikcp_nodelay(kcp, 1, 20, 2, 1) // nodelay: 0:disable(default), 1:enable // interval: internal update timer interval in millisec, default is 100ms // resend: 0:disable fast resend(default), 1:enable fast resend // nc: 0:normal congestion control(default), 1:disable congestion control public int NoDelay(int nodelay, int interval, int resend, int nc) { if (nodelay >= 0) { nodelay_ = (UInt32)nodelay; if (nodelay > 0) { rx_minrto_ = IKCP_RTO_NDL; } else { rx_minrto_ = IKCP_RTO_MIN; } } if (interval >= 0) { if (interval > 5000) interval = 5000; else if (interval < 10) interval = 10; interval_ = (UInt32)interval; } if (resend >= 0) fastresend_ = resend; if (nc >= 0) nocwnd_ = nc; return 0; } // set maximum window size: sndwnd=32, rcvwnd=32 by default public int WndSize(int sndwnd, int rcvwnd) { if (sndwnd > 0) snd_wnd_ = (UInt32)sndwnd; if (rcvwnd > 0) rcv_wnd_ = (UInt32)rcvwnd; return 0; } // get how many packet is waiting to be sent public int WaitSnd() { return (int)(nsnd_buf_ + nsnd_que_); } // read conv public UInt32 GetConv() { return conv_; } public UInt32 GetState() { return state_; } public void SetMinRTO(int minrto) { rx_minrto_ = minrto; } public void SetFastResend(int resend) { fastresend_ = resend; } void Log(int mask, string format, params object[] args) { // Console.WriteLine(mask + String.Format(format, args)); } } } ================================================ FILE: Assets/Scripts/Network/Core/KCP/KCP.cs.meta ================================================ fileFormatVersion: 2 guid: 20f9afa1793f2d54f811abe0972ada7c MonoImporter: externalObjects: {} serializedVersion: 2 defaultReferences: [] executionOrder: 0 icon: {instanceID: 0} userData: assetBundleName: assetBundleVariant: ================================================ FILE: Assets/Scripts/Network/Core/KCP/KChannel.cs ================================================ using System; using System.Collections.Generic; using System.IO; using System.Net; using System.Net.Sockets; using UnityEngine; #if dynamic_kcp using System.Runtime.InteropServices; #endif namespace UNetwork { public struct WaitSendBuffer { public byte[] Bytes; public int Length; public WaitSendBuffer(byte[] bytes, int length) { this.Bytes = bytes; this.Length = length; } } #if dynamic_kcp public class KChannel : AChannel { private Socket socket; private IntPtr kcp; private readonly Queue sendBuffer = new Queue(); private bool isConnected; private readonly IPEndPoint remoteEndPoint; private uint lastRecvTime; private readonly uint createTime; public uint LocalConn { get; private set; } public uint RemoteConn { get; private set; } private readonly MemoryStream memoryStream; // connect public KChannel(uint localConn, Socket socket, IPEndPoint remoteEndPoint, KService kService) : base(kService, ChannelType.Connect) { this.memoryStream = this.GetService().MemoryStreamManager.GetStream("message", ushort.MaxValue); this.LocalConn = localConn; this.socket = socket; this.remoteEndPoint = remoteEndPoint; this.lastRecvTime = kService.TimeNow; this.createTime = kService.TimeNow; this.HandleConnnect(localConn); } public void Dispose() { try { if (this.Error == ErrorCode.ERR_Success) { for (int i = 0; i < 4; i++) { this.Disconnect(); } } } catch (Exception) { // ignored } if (this.kcp != IntPtr.Zero) { Kcp.KcpRelease(this.kcp); this.kcp = IntPtr.Zero; } this.socket = null; this.memoryStream.Dispose(); } public override MemoryStream Stream { get { return this.memoryStream; } } public void Disconnect(int error) { this.OnError(error); } private KService GetService() { return (KService)this.Service; } public void HandleConnnect(uint remoteConn) { if (this.isConnected) { return; } this.RemoteConn = remoteConn; this.kcp = Kcp.KcpCreate(this.RemoteConn, new IntPtr(this.LocalConn)); SetOutput(); Kcp.KcpNodelay(this.kcp, 1, 10, 1, 1); Kcp.KcpWndsize(this.kcp, 32, 32); Kcp.KcpSetmtu(this.kcp, 470); this.isConnected = true; this.lastRecvTime = this.GetService().TimeNow; Connect(); } public void Accept() { if (this.socket == null) { return; } uint timeNow = this.GetService().TimeNow; try { byte[] buffer = this.memoryStream.GetBuffer(); buffer.WriteTo(0, KcpProtocalType.ACK); buffer.WriteTo(1, LocalConn); buffer.WriteTo(5, RemoteConn); this.socket.SendTo(buffer, 0, 9, SocketFlags.None, remoteEndPoint); } catch (Exception e) { Debug.LogError(e); this.OnError(ErrorCode.ERR_SocketCantSend); } } /// /// 发送请求连接消息 /// private void Connect() { try { uint timeNow = this.GetService().TimeNow; this.lastRecvTime = timeNow; byte[] buffer = this.memoryStream.GetBuffer(); buffer.WriteTo(0, KcpProtocalType.SYN); buffer.WriteTo(1, this.LocalConn); this.socket.SendTo(buffer, 0, 5, SocketFlags.None, remoteEndPoint); } catch (Exception e) { Debug.LogError(e); this.OnError(ErrorCode.ERR_SocketCantSend); } } /// /// 发送请求断开消息 /// private void Disconnect() { if (this.socket == null) { return; } try { byte[] buffer = this.memoryStream.GetBuffer(); buffer.WriteTo(0, KcpProtocalType.FIN); buffer.WriteTo(1, this.LocalConn); buffer.WriteTo(5, this.RemoteConn); buffer.WriteTo(9, (uint)this.Error); this.socket.SendTo(buffer, 0, 13, SocketFlags.None, remoteEndPoint); } catch (Exception e) { Debug.LogError(e); this.OnError(ErrorCode.ERR_SocketCantSend); } } public void Update() { uint timeNow = this.GetService().TimeNow; // 如果还没连接上,发送连接请求 if (!this.isConnected) { // // 10秒没连接上则报错 // if (timeNow - this.createTime > 10 * 1000) // { // this.OnError(ErrorCode.ERR_KcpCantConnect); // return; // } // // if (timeNow - this.lastRecvTime < 500) // { // return; // } } try { Kcp.KcpUpdate(this.kcp, timeNow); } catch (Exception e) { Debug.LogError(e); this.OnError(ErrorCode.ERR_SocketError); return; } if (this.kcp != IntPtr.Zero) { uint nextUpdateTime = Kcp.KcpCheck(this.kcp, timeNow); } } private void HandleSend() { while (true) { if (this.sendBuffer.Count <= 0) { break; } WaitSendBuffer buffer = this.sendBuffer.Dequeue(); this.KcpSend(buffer.Bytes, buffer.Length); } } public void HandleRecv(byte[] date, int offset, int length) { this.isConnected = true; Kcp.KcpInput(this.kcp, date, offset, length); while (true) { int n = Kcp.KcpPeeksize(this.kcp); if (n < 0) { return; } if (n == 0) { this.OnError((int)SocketError.NetworkReset); return; } byte[] buffer = this.memoryStream.GetBuffer(); this.memoryStream.SetLength(n); this.memoryStream.Seek(0, SeekOrigin.Begin); int count = Kcp.KcpRecv(this.kcp, buffer, ushort.MaxValue); if (n != count) { return; } if (count <= 0) { return; } this.lastRecvTime = this.GetService().TimeNow; this.OnRead(this.memoryStream); } } public override void Start() { } public void Output(IntPtr bytes, int count) { try { if (count == 0) { Debug.LogError($"output 0"); return; } byte[] buffer = this.memoryStream.GetBuffer(); Marshal.Copy(bytes, buffer, 0, count); this.socket.SendTo(buffer, 0, count, SocketFlags.None, this.remoteEndPoint); } catch (Exception e) { Debug.LogError(e); this.OnError(ErrorCode.ERR_SocketCantSend); } } #if !ENABLE_IL2CPP private KcpOutput kcpOutput; #endif public void SetOutput() { #if ENABLE_IL2CPP Kcp.KcpSetoutput(this.kcp, KcpOutput); #else // 跟上一行一样写法,pc跟linux会出错, 保存防止被GC kcpOutput = KcpOutput; Kcp.KcpSetoutput(this.kcp, kcpOutput); #endif } #if ENABLE_IL2CPP [AOT.MonoPInvokeCallback(typeof(KcpOutput))] #endif public static int KcpOutput(IntPtr bytes, int len, IntPtr kcp, IntPtr user) { KService.Output(bytes, len, user); return len; } private void KcpSend(byte[] buffers, int length) { Kcp.KcpSend(this.kcp, buffers, length); } private void Send(byte[] buffer, int index, int length) { if (isConnected) { this.KcpSend(buffer, length); return; } this.sendBuffer.Enqueue(new WaitSendBuffer(buffer, length)); } public override void Send(MemoryStream stream) { if (this.kcp != IntPtr.Zero) { // 检查等待发送的消息,如果超出两倍窗口大小,应该断开连接 if (Kcp.KcpWaitsnd(this.kcp) > 256 * 2) { this.OnError(ErrorCode.ERR_KcpWaitSendSizeTooLarge); return; } } ushort size = (ushort)(stream.Length - stream.Position); byte[] bytes = stream.GetBuffer(); Send(bytes, 0, size); } } #else public class KChannel : AChannel { private Socket socket; private KCP kcp; private readonly Queue sendBuffer = new Queue(); private bool isConnected; private readonly IPEndPoint remoteEndPoint; private uint lastRecvTime; private readonly uint createTime; public uint LocalConn { get; private set; } public uint RemoteConn { get; private set; } // connect public KChannel(uint localConn, Socket socket, IPEndPoint remoteEndPoint, KService kService) : base(kService, ChannelType.Connect) { this.memoryStream = this.GetService().MemoryStreamManager.GetStream("message", ushort.MaxValue); this.LocalConn = localConn; this.socket = socket; this.remoteEndPoint = remoteEndPoint; this.lastRecvTime = kService.TimeNow; this.createTime = kService.TimeNow; this.HandleConnnect(localConn); } public override void Dispose() { try { if (this.Error == ErrorCode.ERR_Success) { for (int i = 0; i < 4; i++) { this.Disconnect(); } } } catch (Exception) { // ignored } if (this.kcp != null) { kcp.Release(); this.kcp = null; } this.socket = null; this.memoryStream.Dispose(); } public void Disconnect(int error) { this.OnError(error); } private KService GetService() { return (KService)this.Service; } public void HandleConnnect(uint remoteConn) { if (this.isConnected) { return; } this.RemoteConn = remoteConn; this.kcp = new KCP(this.RemoteConn, new IntPtr(this.LocalConn)); SetOutput(); kcp.NoDelay(1, 10, 1, 1); kcp.WndSize(32, 32); kcp.SetMTU(470); this.lastRecvTime = this.GetService().TimeNow; Connect(); } /// /// 发送请求连接消息 /// private void Connect() { try { uint timeNow = this.GetService().TimeNow; this.lastRecvTime = timeNow; byte[] buffer = this.memoryStream.GetBuffer(); buffer.WriteTo(0, 4); buffer.WriteTo(4, KcpProtocalType.SYN); Send(buffer,0,6); HandleSend(); } catch (Exception e) { Debug.LogError(e); this.OnError(ErrorCode.ERR_SocketCantSend); } } /// /// 发送请求断开消息 /// private void Disconnect() { if (this.socket == null) { return; } try { byte[] buffer = this.memoryStream.GetBuffer(); buffer.WriteTo(0, 4); buffer.WriteTo(4, KcpProtocalType.FIN); Send(buffer,0,6); HandleSend(); } catch (Exception e) { Debug.LogError(e); this.OnError(ErrorCode.ERR_SocketCantSend); } } public void Update() { uint timeNow = this.GetService().TimeNow; // 如果还没连接上,发送连接请求 if (!this.isConnected) { // 10秒没连接上则报错 if (timeNow - this.createTime > 10 * 1000) { this.OnError(ErrorCode.ERR_KcpCantConnect); return; } // // if (timeNow - this.lastRecvTime < 500) // { // return; // } } try { kcp.Update(timeNow); } catch (Exception e) { Debug.LogError(e); this.OnError(ErrorCode.ERR_SocketError); return; } if (this.kcp != null) { uint nextUpdateTime = kcp.Check(timeNow); } } private void HandleSend() { while (true) { if (this.sendBuffer.Count <= 0) { break; } WaitSendBuffer buffer = this.sendBuffer.Dequeue(); this.KcpSend(buffer.Bytes, buffer.Length); } } public void HandleRecv(byte[] date, int offset, int length) { kcp.Input(date, offset, length); while (true) { int n = kcp.PeekSize(); if (n < 0) { return; } if (n == 0) { this.OnError((int)SocketError.NetworkReset); return; } byte[] buffer = this.memoryStream.GetBuffer(); this.memoryStream.SetLength(n); this.memoryStream.Seek(0, SeekOrigin.Begin); int count = kcp.Recv(buffer,0, ushort.MaxValue); if (n != count) { return; } if (count <= 0) { return; } this.lastRecvTime = this.GetService().TimeNow; if (!isConnected) { this.isConnected = true; OnConnect((int)SocketError.Success); } else { this.OnRead(this.memoryStream); } } } public override void Start() { } public void Output(byte[] bytes, int count) { try { if (count == 0) { Debug.LogError($"output 0"); return; } byte[] buffer = this.memoryStream.GetBuffer(); Buffer.BlockCopy(bytes, 0,buffer, 0, count); this.socket.SendTo(buffer, 0, count, SocketFlags.None, this.remoteEndPoint); } catch (Exception e) { Debug.LogError(e); this.OnError(ErrorCode.ERR_SocketCantSend); } } private KCP.OutputDelegate kcpOutput; public void SetOutput() { kcpOutput = KcpOutput; kcp.SetOutput(kcpOutput); } public static void KcpOutput(byte[] bytes, int len, object user) { KService.Output(bytes, len, user); } private void KcpSend(byte[] buffers, int length) { kcp.Send(buffers,0, length); } private void Send(byte[] buffer, int index, int length) { if (isConnected) { this.KcpSend(buffer, length); return; } this.sendBuffer.Enqueue(new WaitSendBuffer(buffer, length)); } public override void Send(MemoryStream stream) { if (this.kcp != null) { // 检查等待发送的消息,如果超出两倍窗口大小,应该断开连接 if (kcp.WaitSnd() > 256 * 2) { this.OnError(ErrorCode.ERR_KcpWaitSendSizeTooLarge); return; } } ushort size = (ushort)(stream.Length - stream.Position); byte[] bytes = stream.GetBuffer(); Send(bytes, 0, size); } } #endif } ================================================ FILE: Assets/Scripts/Network/Core/KCP/KChannel.cs.meta ================================================ fileFormatVersion: 2 guid: 5479891425d5a284fb4c8246e1a52d34 timeCreated: 1511491627 licenseType: Free MonoImporter: serializedVersion: 2 defaultReferences: [] executionOrder: 0 icon: {instanceID: 0} userData: assetBundleName: assetBundleVariant: ================================================ FILE: Assets/Scripts/Network/Core/KCP/KService.cs ================================================ using System; using System.Collections.Generic; using System.Net; using System.Net.Sockets; using Microsoft.IO; using UnityEngine; namespace UNetwork { public static class KcpProtocalType { public const short SYN = 101; public const short ACK = 102; public const short FIN = 103; public const short MSG = 104; } public sealed class KService : AService { public static KService Instance { get; private set; } // KService创建的时间 public long StartTime{ get; private set; } // 当前时间 - KService创建的时间 public uint TimeNow { get; private set; } private Socket socket; private KChannel channel; private readonly byte[] cache = new byte[8192]; public readonly RecyclableMemoryStreamManager MemoryStreamManager = new RecyclableMemoryStreamManager(); private EndPoint ipEndPoint = new IPEndPoint(IPAddress.Any, 0); public KService() { this.StartTime = TimeHelper.ClientNow(); this.TimeNow = (uint)(TimeHelper.ClientNow() - this.StartTime); this.socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); //this.socket.Blocking = false; this.socket.Bind(new IPEndPoint(IPAddress.Any, 0)); Instance = this; } public override void Dispose() { this.channel.Dispose(); this.socket.Close(); this.socket = null; Instance = null; } public void Recv() { if (this.socket == null) { return; } while (socket != null && this.socket.Available > 0) { int messageLength = 0; try { // switch (flag) // { // case KcpProtocalType.ACK: // 连接 // this.channel.OnConnect((int)SocketError.Success); // break; // case KcpProtocalType.FIN: // 断开 // break; // } messageLength = this.socket.ReceiveFrom(this.cache, ref this.ipEndPoint); if (messageLength < 1) { continue; } this.channel.HandleRecv(this.cache, 0, messageLength); } catch (Exception e) { Debug.LogError(e); } } } public KChannel GetKChannel() { return (KChannel)channel; } public override AChannel GetChannel() { return this.channel; } #if dynamic_kcp public static void Output(IntPtr bytes, int count, IntPtr user) #else public static void Output(byte[] bytes, int count, object user) #endif { if (Instance == null) { return; } KChannel kChannel = Instance.GetKChannel(); if (kChannel == null) { Debug.LogError($"not found kchannel, {(uint)user}"); return; } kChannel.Output(bytes, count); } public override AChannel ConnectChannel(IPEndPoint remoteEndPoint) { uint localConn = (uint)RandomHelper.RandomNumber(1000, int.MaxValue); this.channel = new KChannel(localConn, this.socket, remoteEndPoint, this); return channel; } public override AChannel ConnectChannel(string address) { IPEndPoint ipEndPoint2 = NetworkHelper.ToIPEndPoint(address); return this.ConnectChannel(ipEndPoint2); } public override void Update() { if (Instance == null) { return; } this.TimeNow = (uint) (TimeHelper.ClientNow() - this.StartTime); this.Recv(); this.channel.Update(); } } } ================================================ FILE: Assets/Scripts/Network/Core/KCP/KService.cs.meta ================================================ fileFormatVersion: 2 guid: 38037f5390cd34e41849290ca1737863 timeCreated: 1511491627 licenseType: Free MonoImporter: serializedVersion: 2 defaultReferences: [] executionOrder: 0 icon: {instanceID: 0} userData: assetBundleName: assetBundleVariant: ================================================ FILE: Assets/Scripts/Network/Core/KCP/KcpDll.cs ================================================ using System; using System.Runtime.InteropServices; namespace UNetwork { public delegate int KcpOutput(IntPtr buf, int len, IntPtr kcp, IntPtr user); public class KcpDll { #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); } } } ================================================ FILE: Assets/Scripts/Network/Core/KCP/KcpDll.cs.meta ================================================ fileFormatVersion: 2 guid: 0832a74dcd3e44341bcffd3b1f182419 timeCreated: 1511491627 licenseType: Free MonoImporter: serializedVersion: 2 defaultReferences: [] executionOrder: 0 icon: {instanceID: 0} userData: assetBundleName: assetBundleVariant: ================================================ FILE: Assets/Scripts/Network/Core/KCP.meta ================================================ fileFormatVersion: 2 guid: 80771ba2b140e0f47b8df6dfe75d5c68 folderAsset: yes timeCreated: 1511491627 licenseType: Free DefaultImporter: userData: assetBundleName: assetBundleVariant: ================================================ FILE: Assets/Scripts/Network/Core/TCP/PacketParser.cs ================================================ using System; using System.IO; namespace UNetwork { public enum ParserState { PacketSize, PacketBody } public static class Packet { public const int PacketSizeLength2 = 2; public const int PacketSizeLength4 = 4; public const int MinPacketSize = 2; public const int OpcodeIndex = 0; public const int MessageIndex = 2; } public class PacketParser { private readonly CircularBuffer buffer; private int packetSize; private ParserState state; public MemoryStream memoryStream; private bool isOK; private readonly int packetSizeLength; public PacketParser(int packetSizeLength, CircularBuffer buffer, MemoryStream memoryStream) { this.packetSizeLength = packetSizeLength; this.buffer = buffer; this.memoryStream = memoryStream; } public bool Parse() { if (this.isOK) { return true; } bool finish = false; while (!finish) { switch (this.state) { case ParserState.PacketSize: if (this.buffer.Length < this.packetSizeLength) { finish = true; } else { this.buffer.Read(this.memoryStream.GetBuffer(), 0, this.packetSizeLength); switch (this.packetSizeLength) { case Packet.PacketSizeLength4: this.packetSize = BitConverter.ToInt32(this.memoryStream.GetBuffer(), 0); if (this.packetSize > ushort.MaxValue * 16 || this.packetSize < Packet.MinPacketSize) { throw new Exception($"recv packet size error, 可能是外网探测端口: {this.packetSize}"); } break; case Packet.PacketSizeLength2: this.packetSize = BitConverter.ToUInt16(this.memoryStream.GetBuffer(), 0); if (this.packetSize > ushort.MaxValue || this.packetSize < Packet.MinPacketSize) { throw new Exception($"recv packet size error:, 可能是外网探测端口: {this.packetSize}"); } break; default: throw new Exception("packet size byte count must be 2 or 4!"); } this.state = ParserState.PacketBody; } break; case ParserState.PacketBody: if (this.buffer.Length < this.packetSize) { finish = true; } else { this.memoryStream.Seek(0, SeekOrigin.Begin); this.memoryStream.SetLength(this.packetSize); byte[] bytes = this.memoryStream.GetBuffer(); this.buffer.Read(bytes, 0, this.packetSize); this.isOK = true; this.state = ParserState.PacketSize; finish = true; } break; } } return this.isOK; } public MemoryStream GetPacket() { this.isOK = false; return this.memoryStream; } } } ================================================ FILE: Assets/Scripts/Network/Core/TCP/PacketParser.cs.meta ================================================ fileFormatVersion: 2 guid: 8b71d12882faac44992e531533491a5f timeCreated: 1503986857 licenseType: Free MonoImporter: serializedVersion: 2 defaultReferences: [] executionOrder: 0 icon: {instanceID: 0} userData: assetBundleName: assetBundleVariant: ================================================ FILE: Assets/Scripts/Network/Core/TCP/TChannel.cs ================================================ using System; using System.IO; using System.Net; using System.Net.Sockets; using UnityEngine; namespace UNetwork { /// /// 封装Socket,将回调push到主线程处理 /// public sealed class TChannel: AChannel { private Socket socket; private SocketAsyncEventArgs innArgs = new SocketAsyncEventArgs(); private SocketAsyncEventArgs outArgs = new SocketAsyncEventArgs(); private readonly CircularBuffer recvBuffer = new CircularBuffer(); private readonly CircularBuffer sendBuffer = new CircularBuffer(); private bool isSending; private bool isRecving; private bool isConnected; private readonly PacketParser parser; private readonly byte[] packetSizeCache; public TChannel(IPEndPoint ipEndPoint, TService service): base(service, ChannelType.Connect) { int packetSize = service.PacketSizeLength; this.packetSizeCache = new byte[packetSize]; this.memoryStream = service.MemoryStreamManager.GetStream("message", ushort.MaxValue); this.socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); this.socket.NoDelay = true; this.parser = new PacketParser(packetSize, this.recvBuffer, this.memoryStream); this.innArgs.Completed += this.OnComplete; this.outArgs.Completed += this.OnComplete; this.RemoteAddress = ipEndPoint; this.isConnected = false; this.isSending = false; } public override void Dispose() { this.socket.Close(); this.innArgs.Dispose(); this.outArgs.Dispose(); this.innArgs = null; this.outArgs = null; this.socket = null; this.memoryStream.Dispose(); } private TService GetService() { return (TService)this.Service; } public override void Start() { if (!this.isConnected) { this.ConnectAsync(this.RemoteAddress); return; } if (!this.isRecving) { this.isRecving = true; this.StartRecv(); } } public override void Send(MemoryStream stream) { switch (this.GetService().PacketSizeLength) { case Packet.PacketSizeLength4: if (stream.Length > ushort.MaxValue * 16) { throw new Exception($"send packet too large: {stream.Length}"); } this.packetSizeCache.WriteTo(0, (int) stream.Length); break; case Packet.PacketSizeLength2: if (stream.Length > ushort.MaxValue) { throw new Exception($"send packet too large: {stream.Length}"); } this.packetSizeCache.WriteTo(0, (ushort) stream.Length); break; default: throw new Exception("packet size must be 2 or 4!"); } this.sendBuffer.Write(this.packetSizeCache, 0, this.packetSizeCache.Length); this.sendBuffer.Write(stream); } private void OnComplete(object sender, SocketAsyncEventArgs e) { switch (e.LastOperation) { case SocketAsyncOperation.Connect: OneThreadSynchronizationContext.Instance.Post(this.OnConnectComplete, e); break; case SocketAsyncOperation.Receive: OneThreadSynchronizationContext.Instance.Post(this.OnRecvComplete, e); break; case SocketAsyncOperation.Send: OneThreadSynchronizationContext.Instance.Post(this.OnSendComplete, e); break; case SocketAsyncOperation.Disconnect: OneThreadSynchronizationContext.Instance.Post(this.OnDisconnectComplete, e); break; default: throw new Exception($"socket error: {e.LastOperation}"); } } public void ConnectAsync(IPEndPoint ipEndPoint) { this.outArgs.RemoteEndPoint = ipEndPoint; if (this.socket.ConnectAsync(this.outArgs)) { return; } OnConnectComplete(this.outArgs); } private void OnConnectComplete(object o) { if (this.socket == null) { return; } SocketAsyncEventArgs e = (SocketAsyncEventArgs) o; if (e.SocketError != SocketError.Success) { this.OnError((int)e.SocketError); return; } e.RemoteEndPoint = null; this.isConnected = true; this.OnConnect((int)SocketError.Success); this.Start(); } private void OnDisconnectComplete(object o) { SocketAsyncEventArgs e = (SocketAsyncEventArgs)o; this.OnError((int)e.SocketError); } private void StartRecv() { int size = this.recvBuffer.ChunkSize - this.recvBuffer.LastIndex; this.RecvAsync(this.recvBuffer.Last, this.recvBuffer.LastIndex, size); } public void RecvAsync(byte[] buffer, int offset, int count) { try { this.innArgs.SetBuffer(buffer, offset, count); } catch (Exception e) { throw new Exception($"socket set buffer error: {buffer.Length}, {offset}, {count}", e); } if (this.socket.ReceiveAsync(this.innArgs)) { return; } OnRecvComplete(this.innArgs); } private void OnRecvComplete(object o) { if (this.socket == null) { return; } SocketAsyncEventArgs e = (SocketAsyncEventArgs) o; if (e.SocketError != SocketError.Success) { this.OnError((int)e.SocketError); return; } if (e.BytesTransferred == 0) { this.OnError(ErrorCode.ERR_PeerDisconnect); return; } this.recvBuffer.LastIndex += e.BytesTransferred; if (this.recvBuffer.LastIndex == this.recvBuffer.ChunkSize) { this.recvBuffer.AddLast(); this.recvBuffer.LastIndex = 0; } // 收到消息回调 while (true) { try { if (!this.parser.Parse()) { break; } } catch (Exception ee) { Debug.LogError($"ip: {this.RemoteAddress} {ee}"); this.OnError(ErrorCode.ERR_SocketError); return; } try { this.OnRead(this.parser.GetPacket()); } catch (Exception ee) { Debug.LogError(ee); } } if (this.socket == null) { return; } this.StartRecv(); } public bool IsSending => this.isSending; public void StartSend() { if(!this.isConnected) { return; } // 没有数据需要发送 if (this.sendBuffer.Length == 0) { this.isSending = false; return; } this.isSending = true; int sendSize = this.sendBuffer.ChunkSize - this.sendBuffer.FirstIndex; if (sendSize > this.sendBuffer.Length) { sendSize = (int)this.sendBuffer.Length; } this.SendAsync(this.sendBuffer.First, this.sendBuffer.FirstIndex, sendSize); } public void SendAsync(byte[] buffer, int offset, int count) { try { this.outArgs.SetBuffer(buffer, offset, count); } catch (Exception e) { throw new Exception($"socket set buffer error: {buffer.Length}, {offset}, {count}", e); } if (this.socket.SendAsync(this.outArgs)) { return; } OnSendComplete(this.outArgs); } private void OnSendComplete(object o) { if (this.socket == null) { return; } SocketAsyncEventArgs e = (SocketAsyncEventArgs) o; if (e.SocketError != SocketError.Success) { this.OnError((int)e.SocketError); return; } if (e.BytesTransferred == 0) { this.OnError(ErrorCode.ERR_PeerDisconnect); return; } this.sendBuffer.FirstIndex += e.BytesTransferred; if (this.sendBuffer.FirstIndex == this.sendBuffer.ChunkSize) { this.sendBuffer.FirstIndex = 0; this.sendBuffer.RemoveFirst(); } this.StartSend(); } } } ================================================ FILE: Assets/Scripts/Network/Core/TCP/TChannel.cs.meta ================================================ fileFormatVersion: 2 guid: 4cacc90d7e561c64a8ea0e9079163bb9 timeCreated: 1503986857 licenseType: Free MonoImporter: serializedVersion: 2 defaultReferences: [] executionOrder: 0 icon: {instanceID: 0} userData: assetBundleName: assetBundleVariant: ================================================ FILE: Assets/Scripts/Network/Core/TCP/TChannelConnector.cs ================================================ using System; using System.IO; using System.Net.Sockets; using System.Text; using UnityEngine; using UnityEngine.Events; namespace UNetwork { /// /// 管理客户端连接的类 /// public sealed class TClientConnection : AChannel { private int PacketSizeLength; private readonly Socket clientSocket; private readonly SocketAsyncEventArgs innArgs = new SocketAsyncEventArgs(); private readonly SocketAsyncEventArgs outArgs = new SocketAsyncEventArgs(); private readonly CircularBuffer recvBuffer = new CircularBuffer(); private readonly CircularBuffer sendBuffer = new CircularBuffer(); private readonly PacketParser parser; private readonly byte[] packetSizeCache; private bool isConnected; private bool isSending; private bool isRecving; private readonly SessionConnector session; public UnityAction OnDisposeCallback; public TClientConnection(Socket clientSocket, TServiceServer service) : base(service, ChannelType.Connect) { this.session = new SessionConnector(this); this.clientSocket = clientSocket; this.memoryStream = service.MemoryStreamManager.GetStream("client", ushort.MaxValue); int packetSize = service.PacketSizeLength; this.PacketSizeLength = packetSize; this.packetSizeCache = new byte[packetSize]; this.parser = new PacketParser(packetSize, this.recvBuffer, this.memoryStream); this.innArgs.Completed += this.OnComplete; this.outArgs.Completed += this.OnComplete; isConnected = false; } public override void Dispose() { OnDisposeCallback?.Invoke(this); this.clientSocket.Close(); this.innArgs.Dispose(); this.outArgs.Dispose(); this.memoryStream.Dispose(); } public override void Start() { if (!this.isRecving) { this.isRecving = true; this.StartRecv(); this.isConnected = true; } } public void Update() { if (isSending) { return; } try { StartSend(); } catch (Exception e) { Debug.LogError(e); } } private void OnComplete(object sender, SocketAsyncEventArgs e) { switch (e.LastOperation) { case SocketAsyncOperation.Receive: OneThreadSynchronizationContext.Instance.Post(this.OnRecvComplete, e); break; case SocketAsyncOperation.Send: OneThreadSynchronizationContext.Instance.Post(this.OnSendComplete, e); break; case SocketAsyncOperation.Disconnect: OneThreadSynchronizationContext.Instance.Post(this.OnDisconnectComplete, e); break; default: throw new Exception($"socket error: {e.LastOperation}"); } } private void OnDisconnectComplete(object o) { SocketAsyncEventArgs e = (SocketAsyncEventArgs)o; this.OnError((int)e.SocketError); } private void StartRecv() { int size = this.recvBuffer.ChunkSize - this.recvBuffer.LastIndex; this.RecvAsync(this.recvBuffer.Last, this.recvBuffer.LastIndex, size); } public void RecvAsync(byte[] buffer, int offset, int count) { try { this.innArgs.SetBuffer(buffer, offset, count); } catch (Exception e) { throw new Exception($"socket set buffer error: {buffer.Length}, {offset}, {count}", e); } if (this.clientSocket.ReceiveAsync(this.innArgs)) { return; } OnRecvComplete(this.innArgs); } private void OnRecvComplete(object o) { if (this.clientSocket == null) { return; } SocketAsyncEventArgs e = (SocketAsyncEventArgs)o; if (e.SocketError != SocketError.Success) { this.OnError((int)e.SocketError); return; } if (e.BytesTransferred == 0) { this.OnError(ErrorCode.ERR_PeerDisconnect); return; } this.recvBuffer.LastIndex += e.BytesTransferred; if (this.recvBuffer.LastIndex == this.recvBuffer.ChunkSize) { this.recvBuffer.AddLast(); this.recvBuffer.LastIndex = 0; } // 收到消息回调 while (true) { try { if (!this.parser.Parse()) { break; } } catch (Exception ee) { Debug.LogError($"Client {clientSocket.RemoteEndPoint} {ee}"); this.OnError(ErrorCode.ERR_SocketError); return; } try { this.OnRead(this.parser.GetPacket()); } catch (Exception ee) { Debug.LogError(ee); } } this.StartRecv(); } public override void Send(MemoryStream stream) { switch (PacketSizeLength) { case Packet.PacketSizeLength4: if (stream.Length > ushort.MaxValue * 16) { throw new Exception($"send packet too large: {stream.Length}"); } this.packetSizeCache.WriteTo(0, (int)stream.Length); break; case Packet.PacketSizeLength2: if (stream.Length > ushort.MaxValue) { throw new Exception($"send packet too large: {stream.Length}"); } this.packetSizeCache.WriteTo(0, (ushort)stream.Length); break; default: throw new Exception("packet size must be 2 or 4!"); } this.sendBuffer.Write(this.packetSizeCache, 0, this.packetSizeCache.Length); this.sendBuffer.Write(stream); } public void StartSend() { if (!this.isConnected) { return; } // 没有数据需要发送 if (this.sendBuffer.Length == 0) { this.isSending = false; return; } if (!this.isSending) { this.isSending = true; } int sendSize = this.sendBuffer.ChunkSize - this.sendBuffer.FirstIndex; if (sendSize > this.sendBuffer.Length) { sendSize = (int)this.sendBuffer.Length; } this.SendAsync(this.sendBuffer.First, this.sendBuffer.FirstIndex, sendSize); } public void SendAsync(byte[] buffer, int offset, int count) { try { this.outArgs.SetBuffer(buffer, offset, count); } catch (Exception e) { throw new Exception($"socket set buffer error: {buffer.Length}, {offset}, {count}", e); } if (this.clientSocket.SendAsync(this.outArgs)) { return; } OnSendComplete(this.outArgs); } private void OnSendComplete(object o) { SocketAsyncEventArgs e = (SocketAsyncEventArgs)o; if (e.SocketError != SocketError.Success) { this.OnError((int)e.SocketError); return; } if (e.BytesTransferred == 0) { this.OnError(ErrorCode.ERR_PeerDisconnect); return; } this.sendBuffer.FirstIndex += e.BytesTransferred; if (this.sendBuffer.FirstIndex == this.sendBuffer.ChunkSize) { this.sendBuffer.FirstIndex = 0; this.sendBuffer.RemoveFirst(); } this.StartSend(); } private new void OnError(int error) { Debug.LogError($"Socket error: {error}"); this.Dispose(); } private void OnMessage(byte[] obj) { var msg = Encoding.UTF8.GetString(obj); Debug.Log($"Receive{obj.Length}" + msg); } } } ================================================ FILE: Assets/Scripts/Network/Core/TCP/TChannelConnector.cs.meta ================================================ fileFormatVersion: 2 guid: 1740169c35694ff9aa464756e50c4646 timeCreated: 1719314153 ================================================ FILE: Assets/Scripts/Network/Core/TCP/TChannelServer.cs ================================================ using System; using System.Collections.Generic; using System.IO; using System.Net; using System.Net.Sockets; using UnityEngine; namespace UNetwork { /// /// 封装 Server 端Socket监听 /// public sealed class TChannelServer : AChannel { private Socket listener; private List clientConnections = new List(); private SocketAsyncEventArgs acceptArgs = new SocketAsyncEventArgs(); private readonly PacketParser parser; private readonly TServiceServer service; private bool isSending; private bool isRecving; public TChannelServer(IPEndPoint ipEndPoint, TServiceServer service) : base(service, ChannelType.Accept) { this.service = service; this.memoryStream = service.MemoryStreamManager.GetStream("server", ushort.MaxValue); this.listener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); listener.Bind(ipEndPoint); listener.Listen(10); // 最大挂起连接队列的长度 this.listener.NoDelay = true; Debug.Log("开启监听:" + ipEndPoint); this.acceptArgs.Completed += this.OnAcceptComplete; } public void Update() { foreach (var connection in clientConnections) { connection.Update(); } } public override void Send(MemoryStream stream) { foreach (var connection in clientConnections) { connection.Send(stream); } } public override void Dispose() { this.listener.Close(); foreach (var clientConnection in this.clientConnections) { clientConnection.Dispose(); } this.acceptArgs.Dispose(); this.listener = null; } public override void Start() { this.AcceptAsync(); } private void AcceptAsync() { acceptArgs.AcceptSocket = null; // Reset AcceptSocket listener.AcceptAsync(acceptArgs); } private void OnAcceptComplete(object sender, SocketAsyncEventArgs e) { if (this.listener == null) { return; } if (e.SocketError != SocketError.Success) { this.OnError((int)e.SocketError); return; } Debug.Log("客户端连接成功:" + e.RemoteEndPoint); Socket clientSocket = e.AcceptSocket; TClientConnection clientConnection = new TClientConnection(clientSocket, this.service); clientConnections.Add(clientConnection); clientConnection.Start(); clientConnection.OnDisposeCallback=OnDisconnect; // Start accepting next client this.AcceptAsync(); } public void OnDisconnect(TClientConnection connection) { clientConnections.Remove(connection); } } } ================================================ FILE: Assets/Scripts/Network/Core/TCP/TChannelServer.cs.meta ================================================ fileFormatVersion: 2 guid: e1134ac288dc42f2892e29f9f7c754e0 timeCreated: 1719304015 ================================================ FILE: Assets/Scripts/Network/Core/TCP/TService.cs ================================================ using System; using System.Net; using System.Net.Sockets; using Microsoft.IO; using UnityEngine; namespace UNetwork { /// /// TChannle的一个封装管理 /// public sealed class TService : AService { private TChannel channel; public RecyclableMemoryStreamManager MemoryStreamManager = new RecyclableMemoryStreamManager(); public int PacketSizeLength { get; } public TService(int packetSizeLength) { this.PacketSizeLength = packetSizeLength; } public override void Dispose() { this.channel.Dispose(); } public override AChannel GetChannel() { return channel; } public override AChannel ConnectChannel(IPEndPoint ipEndPoint) { channel = new TChannel(ipEndPoint, this); return channel; } public override AChannel ConnectChannel(string address) { IPEndPoint ipEndPoint = NetworkHelper.ToIPEndPoint(address); return this.ConnectChannel(ipEndPoint); } public override void Update() { if (channel.IsSending) { return; } try { channel.StartSend(); } catch (Exception e) { Debug.LogError(e); } } } } ================================================ FILE: Assets/Scripts/Network/Core/TCP/TService.cs.meta ================================================ fileFormatVersion: 2 guid: 9c6cb526a0e4374438cbcbaa70239e4a timeCreated: 1503986857 licenseType: Free MonoImporter: serializedVersion: 2 defaultReferences: [] executionOrder: 0 icon: {instanceID: 0} userData: assetBundleName: assetBundleVariant: ================================================ FILE: Assets/Scripts/Network/Core/TCP/TServiceServer.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Net; using System.Net.Sockets; using Microsoft.IO; using UnityEngine; namespace UNetwork { public sealed class TServiceServer : AService { private TChannelServer channel; public RecyclableMemoryStreamManager MemoryStreamManager = new RecyclableMemoryStreamManager(); public int PacketSizeLength { get; } public TServiceServer(int packetSizeLength) { this.PacketSizeLength = packetSizeLength; } public override void Dispose() { this.channel.Dispose(); } public override AChannel GetChannel() { return channel; } public override AChannel ConnectChannel(IPEndPoint ipEndPoint) { channel = new TChannelServer(ipEndPoint, this); return channel; } public override AChannel ConnectChannel(string address) { IPEndPoint ipEndPoint = NetworkHelper.ToIPEndPoint(address); return this.ConnectChannel(ipEndPoint); } public override void Update() { try { channel.Update(); } catch (Exception e) { Debug.LogError(e); } } } } ================================================ FILE: Assets/Scripts/Network/Core/TCP/TServiceServer.cs.meta ================================================ fileFormatVersion: 2 guid: a284183e90dc44f4a26c8434514d7da0 timeCreated: 1719304034 ================================================ FILE: Assets/Scripts/Network/Core/TCP.meta ================================================ fileFormatVersion: 2 guid: 2b578de151e7f46489a1326f6b38658c folderAsset: yes timeCreated: 1440989101 licenseType: Pro DefaultImporter: userData: assetBundleName: assetBundleVariant: ================================================ FILE: Assets/Scripts/Network/Core/WebSocket/WChannel.cs ================================================ using System; using System.Collections.Generic; using System.IO; using System.Net.WebSockets; using System.Threading; using UnityEngine; namespace UNetwork { public class WChannel: AChannel { public HttpListenerWebSocketContext WebSocketContext { get; } private readonly WebSocket webSocket; private readonly Queue queue = new Queue(); private bool isSending; private bool isConnected; private readonly MemoryStream recvStream; private CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); public WChannel(HttpListenerWebSocketContext webSocketContext, AService service): base(service, ChannelType.Accept) { this.WebSocketContext = webSocketContext; this.webSocket = webSocketContext.WebSocket; this.memoryStream = this.GetService().MemoryStreamManager.GetStream("message", ushort.MaxValue); this.recvStream = this.GetService().MemoryStreamManager.GetStream("message", ushort.MaxValue); isConnected = true; } public WChannel(WebSocket webSocket, AService service): base(service, ChannelType.Connect) { this.webSocket = webSocket; this.memoryStream = this.GetService().MemoryStreamManager.GetStream("message", ushort.MaxValue); this.recvStream = this.GetService().MemoryStreamManager.GetStream("message", ushort.MaxValue); isConnected = false; } public override void Dispose() { this.cancellationTokenSource.Cancel(); this.cancellationTokenSource.Dispose(); this.cancellationTokenSource = null; this.webSocket.Dispose(); this.memoryStream.Dispose(); } public override void Start() { if (!this.isConnected) { return; } this.StartRecv(); this.StartSend(); } private WService GetService() { return (WService) this.Service; } public async void ConnectAsync(string url) { try { await ((ClientWebSocket) this.webSocket).ConnectAsync(new Uri(url), cancellationTokenSource.Token); isConnected = true; this.Start(); } catch (Exception e) { Debug.LogError(e); this.OnError(ErrorCode.ERR_WebsocketConnectError); } } public override void Send(MemoryStream stream) { byte[] bytes = new byte[stream.Length]; Array.Copy(stream.GetBuffer(), bytes, bytes.Length); this.queue.Enqueue(bytes); if (this.isConnected) { this.StartSend(); } } public async void StartSend() { try { if (this.isSending) { return; } this.isSending = true; while (true) { if (this.queue.Count == 0) { this.isSending = false; return; } byte[] bytes = this.queue.Dequeue(); try { await this.webSocket.SendAsync(new ArraySegment(bytes, 0, bytes.Length), WebSocketMessageType.Binary, true, cancellationTokenSource.Token); } catch (Exception e) { Debug.LogError(e); this.OnError(ErrorCode.ERR_WebsocketSendError); return; } } } catch (Exception e) { Debug.LogError(e); } } public async void StartRecv() { try { while (true) { WebSocketReceiveResult receiveResult; int receiveCount = 0; do { receiveResult = await this.webSocket.ReceiveAsync( new ArraySegment(this.recvStream.GetBuffer(), receiveCount, this.recvStream.Capacity - receiveCount), cancellationTokenSource.Token); receiveCount += receiveResult.Count; } while (!receiveResult.EndOfMessage); if (receiveResult.MessageType == WebSocketMessageType.Close) { this.OnError(ErrorCode.ERR_WebsocketPeerReset); return; } if (receiveResult.Count > ushort.MaxValue) { await this.webSocket.CloseAsync(WebSocketCloseStatus.MessageTooBig, $"message too big: {receiveResult.Count}", cancellationTokenSource.Token); this.OnError(ErrorCode.ERR_WebsocketMessageTooBig); return; } this.recvStream.SetLength(receiveResult.Count); this.OnRead(this.recvStream); } } catch (Exception e) { Debug.LogError(e); this.OnError(ErrorCode.ERR_WebsocketRecvError); } } } } ================================================ FILE: Assets/Scripts/Network/Core/WebSocket/WChannel.cs.meta ================================================ fileFormatVersion: 2 guid: 8e9431d46e7e100479fce069d4c08079 MonoImporter: externalObjects: {} serializedVersion: 2 defaultReferences: [] executionOrder: 0 icon: {instanceID: 0} userData: assetBundleName: assetBundleVariant: ================================================ FILE: Assets/Scripts/Network/Core/WebSocket/WService.cs ================================================ using System; using System.Collections.Generic; using System.Net; using System.Net.WebSockets; using Microsoft.IO; using UnityEngine; namespace UNetwork { public class WService: AService { private readonly HttpListener httpListener; private WChannel channel; public RecyclableMemoryStreamManager MemoryStreamManager = new RecyclableMemoryStreamManager(); public WService() { } public override AChannel GetChannel() { return channel; } public override AChannel ConnectChannel(IPEndPoint ipEndPoint) { throw new NotImplementedException(); } public override AChannel ConnectChannel(string address) { ClientWebSocket webSocket = new ClientWebSocket(); channel = new WChannel(webSocket, this); channel.ConnectAsync(address); return channel; } public override void Update() { } public override void Dispose() { } } } ================================================ FILE: Assets/Scripts/Network/Core/WebSocket/WService.cs.meta ================================================ fileFormatVersion: 2 guid: ab08a203cf5da164c86fbc14d09e3a3b MonoImporter: externalObjects: {} serializedVersion: 2 defaultReferences: [] executionOrder: 0 icon: {instanceID: 0} userData: assetBundleName: assetBundleVariant: ================================================ FILE: Assets/Scripts/Network/Core/WebSocket.meta ================================================ fileFormatVersion: 2 guid: 02cb26b1214c31847af71b0796e90476 folderAsset: yes DefaultImporter: externalObjects: {} userData: assetBundleName: assetBundleVariant: ================================================ FILE: Assets/Scripts/Network/Core.meta ================================================ fileFormatVersion: 2 guid: 819d45d33f6b1124e9159ed5ef0bd490 folderAsset: yes DefaultImporter: externalObjects: {} userData: assetBundleName: assetBundleVariant: ================================================ FILE: Assets/Scripts/Network/INetworkManager.cs ================================================ using System; namespace UNetwork { public interface INetworkManager { public IMessagePacker MessagePacker { get; set; } public IMessageDispatcher MessageDispatcher { get; set; } public Action OnConnect{ get; set; } public Action OnError{ get; set; } public Action OnMessage{ get; set; } } } ================================================ FILE: Assets/Scripts/Network/INetworkManager.cs.meta ================================================ fileFormatVersion: 2 guid: ec147b2fba3e42f1a6c41924df308bfb timeCreated: 1719310787 ================================================ FILE: Assets/Scripts/Network/Message/Circularbuffer.cs ================================================ using System; using System.Collections.Generic; using System.IO; using UnityEngine; namespace UNetwork { public class CircularBuffer: Stream { public int ChunkSize = 8192; private readonly Queue bufferQueue = new Queue(); private readonly Queue bufferCache = new Queue(); public int LastIndex { get; set; } public int FirstIndex { get; set; } private byte[] lastBuffer; public CircularBuffer() { this.AddLast(); } public override long Length { get { int c = 0; if (this.bufferQueue.Count == 0) { c = 0; } else { c = (this.bufferQueue.Count - 1) * ChunkSize + this.LastIndex - this.FirstIndex; } if (c < 0) { Debug.LogErrorFormat("CircularBuffer count < 0: {0}, {1}, {2}",this.bufferQueue.Count, this.LastIndex, this.FirstIndex); } return c; } } public void AddLast() { byte[] buffer; if (this.bufferCache.Count > 0) { buffer = this.bufferCache.Dequeue(); } else { buffer = new byte[ChunkSize]; } this.bufferQueue.Enqueue(buffer); this.lastBuffer = buffer; } public void RemoveFirst() { this.bufferCache.Enqueue(bufferQueue.Dequeue()); } public byte[] First { get { if (this.bufferQueue.Count == 0) { this.AddLast(); } return this.bufferQueue.Peek(); } } public byte[] Last { get { if (this.bufferQueue.Count == 0) { this.AddLast(); } return this.lastBuffer; } } /// /// 从CircularBuffer读到stream中 /// /// /// public void ReadAsync(Stream stream) { long buffLength = this.Length; int sendSize = this.ChunkSize - this.FirstIndex; if (sendSize > buffLength) { sendSize = (int)buffLength; } stream.Write(this.First, this.FirstIndex, sendSize); this.FirstIndex += sendSize; if (this.FirstIndex == this.ChunkSize) { this.FirstIndex = 0; this.RemoveFirst(); } } // 从CircularBuffer读到stream public void Read(Stream stream, int count) { if (count > this.Length) { throw new Exception($"bufferList length < count, {Length} {count}"); } int alreadyCopyCount = 0; while (alreadyCopyCount < count) { int n = count - alreadyCopyCount; if (ChunkSize - this.FirstIndex > n) { stream.Write(this.First, this.FirstIndex, n); this.FirstIndex += n; alreadyCopyCount += n; } else { stream.Write(this.First, this.FirstIndex, ChunkSize - this.FirstIndex); alreadyCopyCount += ChunkSize - this.FirstIndex; this.FirstIndex = 0; this.RemoveFirst(); } } } // 从stream写入CircularBuffer public void Write(Stream stream) { int count = (int)(stream.Length - stream.Position); int alreadyCopyCount = 0; while (alreadyCopyCount < count) { if (this.LastIndex == ChunkSize) { this.AddLast(); this.LastIndex = 0; } int n = count - alreadyCopyCount; if (ChunkSize - this.LastIndex > n) { stream.Read(this.lastBuffer, this.LastIndex, n); this.LastIndex += count - alreadyCopyCount; alreadyCopyCount += n; } else { stream.Read(this.lastBuffer, this.LastIndex, ChunkSize - this.LastIndex); alreadyCopyCount += ChunkSize - this.LastIndex; this.LastIndex = ChunkSize; } } } /// /// 从stream写入CircularBuffer /// /// /// public int WriteAsync(Stream stream) { int size = this.ChunkSize - this.LastIndex; int n = stream.Read(this.Last, this.LastIndex, size); // int n = await stream.ReadAsync(this.Last, this.LastIndex, size); if (n == 0) { return 0; } this.LastIndex += n; if (this.LastIndex == this.ChunkSize) { this.AddLast(); this.LastIndex = 0; } return n; } // 把CircularBuffer中数据写入buffer public override int Read(byte[] buffer, int offset, int count) { if (buffer.Length < offset + count) { throw new Exception($"bufferList length < coutn, buffer length: {buffer.Length} {offset} {count}"); } long length = this.Length; if (length < count) { count = (int)length; } int alreadyCopyCount = 0; while (alreadyCopyCount < count) { int n = count - alreadyCopyCount; if (ChunkSize - this.FirstIndex > n) { Array.Copy(this.First, this.FirstIndex, buffer, alreadyCopyCount + offset, n); this.FirstIndex += n; alreadyCopyCount += n; } else { Array.Copy(this.First, this.FirstIndex, buffer, alreadyCopyCount + offset, ChunkSize - this.FirstIndex); alreadyCopyCount += ChunkSize - this.FirstIndex; this.FirstIndex = 0; this.RemoveFirst(); } } return count; } // 把buffer写入CircularBuffer中 public override void Write(byte[] buffer, int offset, int count) { int alreadyCopyCount = 0; while (alreadyCopyCount < count) { if (this.LastIndex == ChunkSize) { this.AddLast(); this.LastIndex = 0; } int n = count - alreadyCopyCount; if (ChunkSize - this.LastIndex > n) { Array.Copy(buffer, alreadyCopyCount + offset, this.lastBuffer, this.LastIndex, n); this.LastIndex += count - alreadyCopyCount; alreadyCopyCount += n; } else { Array.Copy(buffer, alreadyCopyCount + offset, this.lastBuffer, this.LastIndex, ChunkSize - this.LastIndex); alreadyCopyCount += ChunkSize - this.LastIndex; this.LastIndex = ChunkSize; } } } public override void Flush() { throw new NotImplementedException(); } public override long Seek(long offset, SeekOrigin origin) { throw new NotImplementedException(); } public override void SetLength(long value) { throw new NotImplementedException(); } public override bool CanRead { get { return true; } } public override bool CanSeek { get { return false; } } public override bool CanWrite { get { return true; } } public override long Position { get; set; } } } ================================================ FILE: Assets/Scripts/Network/Message/Circularbuffer.cs.meta ================================================ fileFormatVersion: 2 guid: ea037025225a9c74b8ddb5349b0c18a6 timeCreated: 1503986858 licenseType: Free MonoImporter: serializedVersion: 2 defaultReferences: [] executionOrder: 0 icon: {instanceID: 0} userData: assetBundleName: assetBundleVariant: ================================================ FILE: Assets/Scripts/Network/Message/ErrorCode.cs ================================================ namespace UNetwork { public static class ErrorCode { public const int ERR_Success = 0; // 1-11004 是SocketError请看SocketError定义 //----------------------------------- // 100000 以上,避免跟SocketError冲突 public const int ERR_MyErrorCode = 100000; public const int ERR_NotFoundActor = 100002; public const int ERR_ActorNoMailBoxComponent = 100003; public const int ERR_ActorRemove = 100004; public const int ERR_PacketParserError = 100005; public const int ERR_ConnectGateKeyError = 100006; public const int ERR_KcpCantConnect = 102005; public const int ERR_KcpChannelTimeout = 102006; public const int ERR_KcpRemoteDisconnect = 102007; public const int ERR_PeerDisconnect = 102008; public const int ERR_SocketCantSend = 102009; public const int ERR_SocketError = 102010; public const int ERR_KcpWaitSendSizeTooLarge = 102011; public const int ERR_WebsocketPeerReset = 103001; public const int ERR_WebsocketMessageTooBig = 103002; public const int ERR_WebsocketError = 103003; public const int ERR_WebsocketConnectError = 103004; public const int ERR_WebsocketSendError = 103005; public const int ERR_WebsocketRecvError = 103006; public const int ERR_ReloadFail = 102003; //----------------------------------- // 小于这个Rpc会抛异常,大于这个异常的error需要自己判断处理,也就是说需要处理的错误应该要大于该值 public const int ERR_Exception = 200000; public const int ERR_AccountOrPasswordError = 200102; } } ================================================ FILE: Assets/Scripts/Network/Message/ErrorCode.cs.meta ================================================ fileFormatVersion: 2 guid: 303bb884f0f9ad24da9b0fe1acbcedaf timeCreated: 1501467234 licenseType: Free MonoImporter: serializedVersion: 2 defaultReferences: [] executionOrder: 0 icon: {instanceID: 0} userData: assetBundleName: assetBundleVariant: ================================================ FILE: Assets/Scripts/Network/Message/IMessage.cs ================================================ namespace UNetwork { public interface IMessage { } public interface IRequest: IMessage { int RpcId { get; set; } } public interface IResponse : IMessage { int Error { get; set; } string Message { get; set; } int RpcId { get; set; } } public class ErrorResponse : IResponse { public int Error { get; set; } public string Message { get; set; } public int RpcId { get; set; } } } ================================================ FILE: Assets/Scripts/Network/Message/IMessage.cs.meta ================================================ fileFormatVersion: 2 guid: 7f7b01638a2ed1242a9b81b7d6f408eb MonoImporter: externalObjects: {} serializedVersion: 2 defaultReferences: [] executionOrder: 0 icon: {instanceID: 0} userData: assetBundleName: assetBundleVariant: ================================================ FILE: Assets/Scripts/Network/Message/IMessageDispatcher.cs ================================================ namespace UNetwork { public interface IMessageDispatcher { void Dispatch(Session session, byte[] buffer); } } ================================================ FILE: Assets/Scripts/Network/Message/IMessageDispatcher.cs.meta ================================================ fileFormatVersion: 2 guid: 69481855785b4b59834c766e4e2798e1 MonoImporter: externalObjects: {} serializedVersion: 2 defaultReferences: [] executionOrder: 0 icon: {instanceID: 0} userData: assetBundleName: assetBundleVariant: ================================================ FILE: Assets/Scripts/Network/Message/IMessagePacker.cs ================================================ using System; using System.IO; namespace UNetwork { public interface IMessagePacker { byte[] SerializeTo(object obj); void SerializeTo(object obj, MemoryStream stream); object DeserializeFrom(Type type, byte[] bytes, int index, int count); object DeserializeFrom(object instance, byte[] bytes, int index, int count); object DeserializeFrom(Type type, MemoryStream stream); object DeserializeFrom(object instance, MemoryStream stream); } } ================================================ FILE: Assets/Scripts/Network/Message/IMessagePacker.cs.meta ================================================ fileFormatVersion: 2 guid: 09fe3a4882b1b50498860b3f672d1d47 MonoImporter: externalObjects: {} serializedVersion: 2 defaultReferences: [] executionOrder: 0 icon: {instanceID: 0} userData: assetBundleName: assetBundleVariant: ================================================ FILE: Assets/Scripts/Network/Message/MessageInfo.cs ================================================ namespace UNetwork { public struct MessageInfo { public ushort Opcode { get; } public object Message { get; } public MessageInfo(ushort opcode, object message) { this.Opcode = opcode; this.Message = message; } } } ================================================ FILE: Assets/Scripts/Network/Message/MessageInfo.cs.meta ================================================ fileFormatVersion: 2 guid: 0b54864d3eb34ee4b90665941ecf5b87 timeCreated: 1501467234 licenseType: Free MonoImporter: serializedVersion: 2 defaultReferences: [] executionOrder: 0 icon: {instanceID: 0} userData: assetBundleName: assetBundleVariant: ================================================ FILE: Assets/Scripts/Network/Message/MessagePool.cs ================================================ using System; using System.Collections.Generic; namespace UNetwork { // 客户端为了0GC需要消息池,服务端消息需要跨协程不需要消息池 public class MessagePool { public static MessagePool Instance { get; } = new MessagePool(); private readonly Dictionary> dictionary = new Dictionary>(); public object Fetch(Type type) { Queue queue; if (!this.dictionary.TryGetValue(type, out queue)) { queue = new Queue(); this.dictionary.Add(type, queue); } object obj; if (queue.Count > 0) { obj = queue.Dequeue(); } else { obj = Activator.CreateInstance(type); } return obj; } public T Fetch() where T : class { T t = (T) this.Fetch(typeof (T)); return t; } public void Recycle(object obj) { Type type = obj.GetType(); Queue queue; if (!this.dictionary.TryGetValue(type, out queue)) { queue = new Queue(); this.dictionary.Add(type, queue); } queue.Enqueue(obj); } } } ================================================ FILE: Assets/Scripts/Network/Message/MessagePool.cs.meta ================================================ fileFormatVersion: 2 guid: 1d413cb22ac106a439af32933382ce66 MonoImporter: externalObjects: {} serializedVersion: 2 defaultReferences: [] executionOrder: 0 icon: {instanceID: 0} userData: assetBundleName: assetBundleVariant: ================================================ FILE: Assets/Scripts/Network/Message/Opcode.cs ================================================ namespace UNetwork { public static partial class Opcode { public const ushort ActorResponse = 1; public const ushort FrameMessage = 2; public const ushort OneFrameMessage = 3; } } ================================================ FILE: Assets/Scripts/Network/Message/Opcode.cs.meta ================================================ fileFormatVersion: 2 guid: 129811e752b0782479b48ee6a12e1f2b timeCreated: 1517571639 licenseType: Free MonoImporter: serializedVersion: 2 defaultReferences: [] executionOrder: 0 icon: {instanceID: 0} userData: assetBundleName: assetBundleVariant: ================================================ FILE: Assets/Scripts/Network/Message/OuterMessageDispatcher.cs ================================================ using System; using System.Text; using UnityEngine; namespace UNetwork { public class OuterMessageDispatcher: IMessageDispatcher { public void Dispatch(Session session, byte[] buffer) { // ushort opcode = BitConverter.ToUInt16(buffer, Packet.OpcodeIndex); string opcode = Encoding.UTF8.GetString(buffer); // object message = this.Network.MessagePacker.DeserializeFrom(null, memoryStream); TestClient.Receive(); Debug.Log("receive msg:" + opcode); } } } ================================================ FILE: Assets/Scripts/Network/Message/OuterMessageDispatcher.cs.meta ================================================ fileFormatVersion: 2 guid: 1019c6e4151c2374390b6f30c09e0e1f MonoImporter: externalObjects: {} serializedVersion: 2 defaultReferences: [] executionOrder: 0 icon: {instanceID: 0} userData: assetBundleName: assetBundleVariant: ================================================ FILE: Assets/Scripts/Network/Message/ProtobufPacker.cs ================================================ using System; using System.IO; using System.Runtime.Serialization.Formatters.Binary; using System.Text; namespace UNetwork { public class ProtobufPacker : IMessagePacker { public byte[] SerializeTo(object obj) { BinaryFormatter formatter = new BinaryFormatter(); MemoryStream rems = new MemoryStream(); formatter.Serialize(rems, obj); return rems.GetBuffer(); } public void SerializeTo(object obj, MemoryStream stream) { BinaryFormatter formatter = new BinaryFormatter(); formatter.Serialize(stream, obj); } public object DeserializeFrom(Type type, byte[] bytes, int index, int count) { return null; // return ProtobufHelper.FromBytes(type, bytes, index, count); } public object DeserializeFrom(object instance, byte[] bytes, int index, int count) { return null; // return ProtobufHelper.FromBytes(instance, bytes, index, count); } public object DeserializeFrom(Type type, MemoryStream stream) { BinaryFormatter formatter = new BinaryFormatter(); return formatter.Deserialize(stream); } public object DeserializeFrom(object instance, MemoryStream stream) { // return ProtobufHelper.FromStream(instance, stream); return null; } } } ================================================ FILE: Assets/Scripts/Network/Message/ProtobufPacker.cs.meta ================================================ fileFormatVersion: 2 guid: 26802b02461a1454499ebc037e012b5f MonoImporter: externalObjects: {} serializedVersion: 2 defaultReferences: [] executionOrder: 0 icon: {instanceID: 0} userData: assetBundleName: assetBundleVariant: ================================================ FILE: Assets/Scripts/Network/Message/RecyclableMemoryStream/Events.cs ================================================ // --------------------------------------------------------------------- // Copyright (c) 2015 Microsoft // // 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. // --------------------------------------------------------------------- namespace Microsoft.IO { using System; using System.Diagnostics.Tracing; public sealed partial class RecyclableMemoryStreamManager { [EventSource(Name = "Microsoft-IO-RecyclableMemoryStream", Guid = "{B80CD4E4-890E-468D-9CBA-90EB7C82DFC7}")] public sealed class Events : EventSource { public static Events Writer = new Events(); public enum MemoryStreamBufferType { Small, Large } public enum MemoryStreamDiscardReason { TooLarge, EnoughFree } [Event(1, Level = EventLevel.Verbose)] public void MemoryStreamCreated(Guid guid, string tag, int requestedSize) { if (this.IsEnabled(EventLevel.Verbose, EventKeywords.None)) { WriteEvent(1, guid, tag ?? string.Empty, requestedSize); } } [Event(2, Level = EventLevel.Verbose)] public void MemoryStreamDisposed(Guid guid, string tag) { if (this.IsEnabled(EventLevel.Verbose, EventKeywords.None)) { WriteEvent(2, guid, tag ?? string.Empty); } } [Event(3, Level = EventLevel.Critical)] public void MemoryStreamDoubleDispose(Guid guid, string tag, string allocationStack, string disposeStack1, string disposeStack2) { if (this.IsEnabled()) { this.WriteEvent(3, guid, tag ?? string.Empty, allocationStack ?? string.Empty, disposeStack1 ?? string.Empty, disposeStack2 ?? string.Empty); } } [Event(4, Level = EventLevel.Error)] public void MemoryStreamFinalized(Guid guid, string tag, string allocationStack) { if (this.IsEnabled()) { WriteEvent(4, guid, tag ?? string.Empty, allocationStack ?? string.Empty); } } [Event(5, Level = EventLevel.Verbose)] public void MemoryStreamToArray(Guid guid, string tag, string stack, int size) { if (this.IsEnabled(EventLevel.Verbose, EventKeywords.None)) { WriteEvent(5, guid, tag ?? string.Empty, stack ?? string.Empty, size); } } [Event(6, Level = EventLevel.Informational)] public void MemoryStreamManagerInitialized(int blockSize, int largeBufferMultiple, int maximumBufferSize) { if (this.IsEnabled()) { WriteEvent(6, blockSize, largeBufferMultiple, maximumBufferSize); } } [Event(7, Level = EventLevel.Verbose)] public void MemoryStreamNewBlockCreated(long smallPoolInUseBytes) { if (this.IsEnabled(EventLevel.Verbose, EventKeywords.None)) { WriteEvent(7, smallPoolInUseBytes); } } [Event(8, Level = EventLevel.Verbose)] public void MemoryStreamNewLargeBufferCreated(int requiredSize, long largePoolInUseBytes) { if (this.IsEnabled(EventLevel.Verbose, EventKeywords.None)) { WriteEvent(8, requiredSize, largePoolInUseBytes); } } [Event(9, Level = EventLevel.Verbose)] public void MemoryStreamNonPooledLargeBufferCreated(int requiredSize, string tag, string allocationStack) { if (this.IsEnabled(EventLevel.Verbose, EventKeywords.None)) { WriteEvent(9, requiredSize, tag ?? string.Empty, allocationStack ?? string.Empty); } } [Event(10, Level = EventLevel.Warning)] public void MemoryStreamDiscardBuffer(MemoryStreamBufferType bufferType, string tag, MemoryStreamDiscardReason reason) { if (this.IsEnabled()) { WriteEvent(10, bufferType, tag ?? string.Empty, reason); } } [Event(11, Level = EventLevel.Error)] public void MemoryStreamOverCapacity(int requestedCapacity, long maxCapacity, string tag, string allocationStack) { if (this.IsEnabled()) { WriteEvent(11, requestedCapacity, maxCapacity, tag ?? string.Empty, allocationStack ?? string.Empty); } } } } } // This is here for .NET frameworks which don't support EventSource. We basically shim bare functionality used above to #if NET40 namespace System.Diagnostics.Tracing { public enum EventLevel { LogAlways = 0, Critical, Error, Warning, Informational, Verbose, } public enum EventKeywords : long { None = 0x0, } [AttributeUsage(AttributeTargets.Class)] public sealed class EventSourceAttribute : Attribute { public string Name { get; set; } public string Guid { get; set; } } [AttributeUsage(AttributeTargets.Method)] public sealed class EventAttribute : Attribute { public EventAttribute(int id) { } public EventLevel Level { get; set; } } public class EventSource { public void WriteEvent(params object[] unused) { return; } public bool IsEnabled() { return false; } public bool IsEnabled(EventLevel level, EventKeywords keywords) { return false; } } } #endif ================================================ FILE: Assets/Scripts/Network/Message/RecyclableMemoryStream/Events.cs.meta ================================================ fileFormatVersion: 2 guid: 52cd395d4cf275c48b1f8690689d3869 timeCreated: 1527234045 licenseType: Pro MonoImporter: serializedVersion: 2 defaultReferences: [] executionOrder: 0 icon: {instanceID: 0} userData: assetBundleName: assetBundleVariant: ================================================ FILE: Assets/Scripts/Network/Message/RecyclableMemoryStream/OneThreadSynchronizationContext.cs ================================================ using System; using System.Collections.Concurrent; using System.Threading; namespace UNetwork { public class OneThreadSynchronizationContext : SynchronizationContext { public static OneThreadSynchronizationContext Instance { get; } = new OneThreadSynchronizationContext(); private readonly int mainThreadId = Thread.CurrentThread.ManagedThreadId; // 线程同步队列,发送接收socket回调都放到该队列,由poll线程统一执行 private readonly ConcurrentQueue queue = new ConcurrentQueue(); private Action a; public void Update() { while (true) { if (!this.queue.TryDequeue(out a)) { return; } a(); } } public override void Post(SendOrPostCallback callback, object state) { if (Thread.CurrentThread.ManagedThreadId == this.mainThreadId) { callback(state); return; } this.queue.Enqueue(() => { callback(state); }); } } } ================================================ FILE: Assets/Scripts/Network/Message/RecyclableMemoryStream/OneThreadSynchronizationContext.cs.meta ================================================ fileFormatVersion: 2 guid: f9b9ed5f7ab66544eb242d9f98cf1a6b MonoImporter: externalObjects: {} serializedVersion: 2 defaultReferences: [] executionOrder: 0 icon: {instanceID: 0} userData: assetBundleName: assetBundleVariant: ================================================ FILE: Assets/Scripts/Network/Message/RecyclableMemoryStream/RecyclableMemoryStream.cs ================================================ // The MIT License (MIT) // // Copyright (c) 2015-2016 Microsoft // // 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. namespace Microsoft.IO { using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.IO; using System.Threading; /// /// MemoryStream implementation that deals with pooling and managing memory streams which use potentially large /// buffers. /// /// /// This class works in tandem with the RecylableMemoryStreamManager to supply MemoryStream /// objects to callers, while avoiding these specific problems: /// 1. LOH allocations - since all large buffers are pooled, they will never incur a Gen2 GC /// 2. Memory waste - A standard memory stream doubles its size when it runs out of room. This /// leads to continual memory growth as each stream approaches the maximum allowed size. /// 3. Memory copying - Each time a MemoryStream grows, all the bytes are copied into new buffers. /// This implementation only copies the bytes when GetBuffer is called. /// 4. Memory fragmentation - By using homogeneous buffer sizes, it ensures that blocks of memory /// can be easily reused. /// /// The stream is implemented on top of a series of uniformly-sized blocks. As the stream's length grows, /// additional blocks are retrieved from the memory manager. It is these blocks that are pooled, not the stream /// object itself. /// /// The biggest wrinkle in this implementation is when GetBuffer() is called. This requires a single /// contiguous buffer. If only a single block is in use, then that block is returned. If multiple blocks /// are in use, we retrieve a larger buffer from the memory manager. These large buffers are also pooled, /// split by size--they are multiples of a chunk size (1 MB by default). /// /// Once a large buffer is assigned to the stream the blocks are NEVER again used for this stream. All operations take place on the /// large buffer. The large buffer can be replaced by a larger buffer from the pool as needed. All blocks and large buffers /// are maintained in the stream until the stream is disposed (unless AggressiveBufferReturn is enabled in the stream manager). /// /// public sealed class RecyclableMemoryStream : MemoryStream { private const long MaxStreamLength = Int32.MaxValue; private static readonly byte[] emptyArray = new byte[0]; /// /// All of these blocks must be the same size /// private readonly List blocks = new List(1); /// /// This buffer exists so that WriteByte can forward all of its calls to Write /// without creating a new byte[] buffer on every call. /// private readonly byte[] byteBuffer = new byte[1]; private readonly Guid id; private readonly RecyclableMemoryStreamManager memoryManager; private readonly string tag; /// /// This list is used to store buffers once they're replaced by something larger. /// This is for the cases where you have users of this class that may hold onto the buffers longer /// than they should and you want to prevent race conditions which could corrupt the data. /// private List dirtyBuffers; // long to allow Interlocked.Read (for .NET Standard 1.4 compat) private long disposedState; /// /// This is only set by GetBuffer() if the necessary buffer is larger than a single block size, or on /// construction if the caller immediately requests a single large buffer. /// /// If this field is non-null, it contains the concatenation of the bytes found in the individual /// blocks. Once it is created, this (or a larger) largeBuffer will be used for the life of the stream. /// private byte[] largeBuffer; /// /// Unique identifier for this stream across it's entire lifetime /// /// Object has been disposed internal Guid Id { get { this.CheckDisposed(); return this.id; } } /// /// A temporary identifier for the current usage of this stream. /// /// Object has been disposed internal string Tag { get { this.CheckDisposed(); return this.tag; } } /// /// Gets the memory manager being used by this stream. /// /// Object has been disposed internal RecyclableMemoryStreamManager MemoryManager { get { this.CheckDisposed(); return this.memoryManager; } } /// /// Callstack of the constructor. It is only set if MemoryManager.GenerateCallStacks is true, /// which should only be in debugging situations. /// internal string AllocationStack { get; } /// /// Callstack of the Dispose call. It is only set if MemoryManager.GenerateCallStacks is true, /// which should only be in debugging situations. /// internal string DisposeStack { get; private set; } #region Constructors /// /// Allocate a new RecyclableMemoryStream object. /// /// The memory manager public RecyclableMemoryStream(RecyclableMemoryStreamManager memoryManager) : this(memoryManager, null, 0, null) { } /// /// Allocate a new RecyclableMemoryStream object /// /// The memory manager /// A string identifying this stream for logging and debugging purposes public RecyclableMemoryStream(RecyclableMemoryStreamManager memoryManager, string tag) : this(memoryManager, tag, 0, null) { } /// /// Allocate a new RecyclableMemoryStream object /// /// The memory manager /// A string identifying this stream for logging and debugging purposes /// The initial requested size to prevent future allocations public RecyclableMemoryStream(RecyclableMemoryStreamManager memoryManager, string tag, int requestedSize) : this(memoryManager, tag, requestedSize, null) { } /// /// Allocate a new RecyclableMemoryStream object /// /// The memory manager /// A string identifying this stream for logging and debugging purposes /// The initial requested size to prevent future allocations /// An initial buffer to use. This buffer will be owned by the stream and returned to the memory manager upon Dispose. internal RecyclableMemoryStream(RecyclableMemoryStreamManager memoryManager, string tag, int requestedSize, byte[] initialLargeBuffer) : base(emptyArray) { this.memoryManager = memoryManager; this.id = Guid.NewGuid(); this.tag = tag; if (requestedSize < memoryManager.BlockSize) { requestedSize = memoryManager.BlockSize; } if (initialLargeBuffer == null) { this.EnsureCapacity(requestedSize); } else { this.largeBuffer = initialLargeBuffer; } if (this.memoryManager.GenerateCallStacks) { this.AllocationStack = Environment.StackTrace; } RecyclableMemoryStreamManager.Events.Writer.MemoryStreamCreated(this.id, this.tag, requestedSize); this.memoryManager.ReportStreamCreated(); } #endregion #region Dispose and Finalize ~RecyclableMemoryStream() { this.Dispose(false); } /// /// Returns the memory used by this stream back to the pool. /// /// Whether we're disposing (true), or being called by the finalizer (false) [SuppressMessage("Microsoft.Usage", "CA1816:CallGCSuppressFinalizeCorrectly", Justification = "We have different disposal semantics, so SuppressFinalize is in a different spot.")] protected override void Dispose(bool disposing) { if (Interlocked.CompareExchange(ref this.disposedState, 1, 0) != 0) { string doubleDisposeStack = null; if (this.memoryManager.GenerateCallStacks) { doubleDisposeStack = Environment.StackTrace; } RecyclableMemoryStreamManager.Events.Writer.MemoryStreamDoubleDispose(this.id, this.tag, this.AllocationStack, this.DisposeStack, doubleDisposeStack); return; } RecyclableMemoryStreamManager.Events.Writer.MemoryStreamDisposed(this.id, this.tag); if (this.memoryManager.GenerateCallStacks) { this.DisposeStack = Environment.StackTrace; } if (disposing) { this.memoryManager.ReportStreamDisposed(); GC.SuppressFinalize(this); } else { // We're being finalized. RecyclableMemoryStreamManager.Events.Writer.MemoryStreamFinalized(this.id, this.tag, this.AllocationStack); #if !NETSTANDARD1_4 if (AppDomain.CurrentDomain.IsFinalizingForUnload()) { // If we're being finalized because of a shutdown, don't go any further. // We have no idea what's already been cleaned up. Triggering events may cause // a crash. base.Dispose(disposing); return; } #endif this.memoryManager.ReportStreamFinalized(); } this.memoryManager.ReportStreamLength(this.length); if (this.largeBuffer != null) { this.memoryManager.ReturnLargeBuffer(this.largeBuffer, this.tag); } if (this.dirtyBuffers != null) { foreach (var buffer in this.dirtyBuffers) { this.memoryManager.ReturnLargeBuffer(buffer, this.tag); } } this.memoryManager.ReturnBlocks(this.blocks, this.tag); this.blocks.Clear(); base.Dispose(disposing); } /// /// Equivalent to Dispose /// #if NETSTANDARD1_4 public void Close() #else public override void Close() #endif { this.Dispose(true); } #endregion #region MemoryStream overrides /// /// Gets or sets the capacity /// /// Capacity is always in multiples of the memory manager's block size, unless /// the large buffer is in use. Capacity never decreases during a stream's lifetime. /// Explicitly setting the capacity to a lower value than the current value will have no effect. /// This is because the buffers are all pooled by chunks and there's little reason to /// allow stream truncation. /// /// Object has been disposed public override int Capacity { get { this.CheckDisposed(); if (this.largeBuffer != null) { return this.largeBuffer.Length; } long size = (long)this.blocks.Count * this.memoryManager.BlockSize; return (int)Math.Min(int.MaxValue, size); } set { this.CheckDisposed(); this.EnsureCapacity(value); } } private int length; /// /// Gets the number of bytes written to this stream. /// /// Object has been disposed public override long Length { get { this.CheckDisposed(); return this.length; } } private int position; /// /// Gets the current position in the stream /// /// Object has been disposed public override long Position { get { this.CheckDisposed(); return this.position; } set { this.CheckDisposed(); if (value < 0) { throw new ArgumentOutOfRangeException("value", "value must be non-negative"); } if (value > MaxStreamLength) { throw new ArgumentOutOfRangeException("value", "value cannot be more than " + MaxStreamLength); } this.position = (int)value; } } /// /// Whether the stream can currently read /// public override bool CanRead => !this.Disposed; /// /// Whether the stream can currently seek /// public override bool CanSeek => !this.Disposed; /// /// Always false /// public override bool CanTimeout => false; /// /// Whether the stream can currently write /// public override bool CanWrite => !this.Disposed; /// /// Returns a single buffer containing the contents of the stream. /// The buffer may be longer than the stream length. /// /// A byte[] buffer /// IMPORTANT: Doing a Write() after calling GetBuffer() invalidates the buffer. The old buffer is held onto /// until Dispose is called, but the next time GetBuffer() is called, a new buffer from the pool will be required. /// Object has been disposed #if NETSTANDARD1_4 public byte[] GetBuffer() #else public override byte[] GetBuffer() #endif { this.CheckDisposed(); if (this.largeBuffer != null) { return this.largeBuffer; } if (this.blocks.Count == 1) { return this.blocks[0]; } // Buffer needs to reflect the capacity, not the length, because // it's possible that people will manipulate the buffer directly // and set the length afterward. Capacity sets the expectation // for the size of the buffer. var newBuffer = this.memoryManager.GetLargeBuffer(this.Capacity, this.tag); // InternalRead will check for existence of largeBuffer, so make sure we // don't set it until after we've copied the data. this.InternalRead(newBuffer, 0, this.length, 0); this.largeBuffer = newBuffer; if (this.blocks.Count > 0 && this.memoryManager.AggressiveBufferReturn) { this.memoryManager.ReturnBlocks(this.blocks, this.tag); this.blocks.Clear(); } return this.largeBuffer; } /// /// Returns a new array with a copy of the buffer's contents. You should almost certainly be using GetBuffer combined with the Length to /// access the bytes in this stream. Calling ToArray will destroy the benefits of pooled buffers, but it is included /// for the sake of completeness. /// /// Object has been disposed #pragma warning disable CS0809 [Obsolete("This method has degraded performance vs. GetBuffer and should be avoided.")] public override byte[] ToArray() { this.CheckDisposed(); var newBuffer = new byte[this.Length]; this.InternalRead(newBuffer, 0, this.length, 0); string stack = this.memoryManager.GenerateCallStacks ? Environment.StackTrace : null; RecyclableMemoryStreamManager.Events.Writer.MemoryStreamToArray(this.id, this.tag, stack, 0); this.memoryManager.ReportStreamToArray(); return newBuffer; } #pragma warning restore CS0809 /// /// Reads from the current position into the provided buffer /// /// Destination buffer /// Offset into buffer at which to start placing the read bytes. /// Number of bytes to read. /// The number of bytes read /// buffer is null /// offset or count is less than 0 /// offset subtracted from the buffer length is less than count /// Object has been disposed public override int Read(byte[] buffer, int offset, int count) { return this.SafeRead(buffer, offset, count, ref this.position); } /// /// Reads from the specified position into the provided buffer /// /// Destination buffer /// Offset into buffer at which to start placing the read bytes. /// Number of bytes to read. /// Position in the stream to start reading from /// The number of bytes read /// buffer is null /// offset or count is less than 0 /// offset subtracted from the buffer length is less than count /// Object has been disposed public int SafeRead(byte[] buffer, int offset, int count, ref int streamPosition) { this.CheckDisposed(); if (buffer == null) { throw new ArgumentNullException(nameof(buffer)); } if (offset < 0) { throw new ArgumentOutOfRangeException(nameof(offset), "offset cannot be negative"); } if (count < 0) { throw new ArgumentOutOfRangeException(nameof(count), "count cannot be negative"); } if (offset + count > buffer.Length) { throw new ArgumentException("buffer length must be at least offset + count"); } int amountRead = this.InternalRead(buffer, offset, count, streamPosition); streamPosition += amountRead; return amountRead; } /// /// Writes the buffer to the stream /// /// Source buffer /// Start position /// Number of bytes to write /// buffer is null /// offset or count is negative /// buffer.Length - offset is not less than count /// Object has been disposed public override void Write(byte[] buffer, int offset, int count) { this.CheckDisposed(); if (buffer == null) { throw new ArgumentNullException(nameof(buffer)); } if (offset < 0) { throw new ArgumentOutOfRangeException(nameof(offset), offset, "Offset must be in the range of 0 - buffer.Length-1"); } if (count < 0) { throw new ArgumentOutOfRangeException(nameof(count), count, "count must be non-negative"); } if (count + offset > buffer.Length) { throw new ArgumentException("count must be greater than buffer.Length - offset"); } int blockSize = this.memoryManager.BlockSize; long end = (long)this.position + count; // Check for overflow if (end > MaxStreamLength) { throw new IOException("Maximum capacity exceeded"); } long requiredBuffers = (end + blockSize - 1) / blockSize; if (requiredBuffers * blockSize > MaxStreamLength) { throw new IOException("Maximum capacity exceeded"); } this.EnsureCapacity((int)end); if (this.largeBuffer == null) { int bytesRemaining = count; int bytesWritten = 0; var blockAndOffset = this.GetBlockAndRelativeOffset(this.position); while (bytesRemaining > 0) { byte[] currentBlock = this.blocks[blockAndOffset.Block]; int remainingInBlock = blockSize - blockAndOffset.Offset; int amountToWriteInBlock = Math.Min(remainingInBlock, bytesRemaining); Buffer.BlockCopy(buffer, offset + bytesWritten, currentBlock, blockAndOffset.Offset, amountToWriteInBlock); bytesRemaining -= amountToWriteInBlock; bytesWritten += amountToWriteInBlock; ++blockAndOffset.Block; blockAndOffset.Offset = 0; } } else { Buffer.BlockCopy(buffer, offset, this.largeBuffer, this.position, count); } this.position = (int)end; this.length = Math.Max(this.position, this.length); } /// /// Returns a useful string for debugging. This should not normally be called in actual production code. /// public override string ToString() { return $"Id = {this.Id}, Tag = {this.Tag}, Length = {this.Length:N0} bytes"; } /// /// Writes a single byte to the current position in the stream. /// /// byte value to write /// Object has been disposed public override void WriteByte(byte value) { this.CheckDisposed(); this.byteBuffer[0] = value; this.Write(this.byteBuffer, 0, 1); } /// /// Reads a single byte from the current position in the stream. /// /// The byte at the current position, or -1 if the position is at the end of the stream. /// Object has been disposed public override int ReadByte() { return this.SafeReadByte(ref this.position); } /// /// Reads a single byte from the specified position in the stream. /// /// The position in the stream to read from /// The byte at the current position, or -1 if the position is at the end of the stream. /// Object has been disposed public int SafeReadByte(ref int streamPosition) { this.CheckDisposed(); if (streamPosition == this.length) { return -1; } byte value; if (this.largeBuffer == null) { var blockAndOffset = this.GetBlockAndRelativeOffset(streamPosition); value = this.blocks[blockAndOffset.Block][blockAndOffset.Offset]; } else { value = this.largeBuffer[streamPosition]; } streamPosition++; return value; } /// /// Sets the length of the stream /// /// value is negative or larger than MaxStreamLength /// Object has been disposed public override void SetLength(long value) { this.CheckDisposed(); if (value < 0 || value > MaxStreamLength) { throw new ArgumentOutOfRangeException(nameof(value), "value must be non-negative and at most " + MaxStreamLength); } this.EnsureCapacity((int)value); this.length = (int)value; if (this.position > value) { this.position = (int)value; } } /// /// Sets the position to the offset from the seek location /// /// How many bytes to move /// From where /// The new position /// Object has been disposed /// offset is larger than MaxStreamLength /// Invalid seek origin /// Attempt to set negative position public override long Seek(long offset, SeekOrigin loc) { this.CheckDisposed(); if (offset > MaxStreamLength) { throw new ArgumentOutOfRangeException(nameof(offset), "offset cannot be larger than " + MaxStreamLength); } int newPosition; switch (loc) { case SeekOrigin.Begin: newPosition = (int)offset; break; case SeekOrigin.Current: newPosition = (int)offset + this.position; break; case SeekOrigin.End: newPosition = (int)offset + this.length; break; default: throw new ArgumentException("Invalid seek origin", nameof(loc)); } if (newPosition < 0) { throw new IOException("Seek before beginning"); } this.position = newPosition; return this.position; } /// /// Synchronously writes this stream's bytes to the parameter stream. /// /// Destination stream /// Important: This does a synchronous write, which may not be desired in some situations public override void WriteTo(Stream stream) { this.CheckDisposed(); if (stream == null) { throw new ArgumentNullException(nameof(stream)); } if (this.largeBuffer == null) { int currentBlock = 0; int bytesRemaining = this.length; while (bytesRemaining > 0) { int amountToCopy = Math.Min(this.blocks[currentBlock].Length, bytesRemaining); stream.Write(this.blocks[currentBlock], 0, amountToCopy); bytesRemaining -= amountToCopy; ++currentBlock; } } else { stream.Write(this.largeBuffer, 0, this.length); } } #endregion #region Helper Methods private bool Disposed => Interlocked.Read(ref this.disposedState) != 0; private void CheckDisposed() { if (this.Disposed) { throw new ObjectDisposedException($"The stream with Id {this.id} and Tag {this.tag} is disposed."); } } private int InternalRead(byte[] buffer, int offset, int count, int fromPosition) { if (this.length - fromPosition <= 0) { return 0; } int amountToCopy; if (this.largeBuffer == null) { var blockAndOffset = this.GetBlockAndRelativeOffset(fromPosition); int bytesWritten = 0; int bytesRemaining = Math.Min(count, this.length - fromPosition); while (bytesRemaining > 0) { amountToCopy = Math.Min(this.blocks[blockAndOffset.Block].Length - blockAndOffset.Offset, bytesRemaining); Buffer.BlockCopy(this.blocks[blockAndOffset.Block], blockAndOffset.Offset, buffer, bytesWritten + offset, amountToCopy); bytesWritten += amountToCopy; bytesRemaining -= amountToCopy; ++blockAndOffset.Block; blockAndOffset.Offset = 0; } return bytesWritten; } amountToCopy = Math.Min(count, this.length - fromPosition); Buffer.BlockCopy(this.largeBuffer, fromPosition, buffer, offset, amountToCopy); return amountToCopy; } private struct BlockAndOffset { public int Block; public int Offset; public BlockAndOffset(int block, int offset) { this.Block = block; this.Offset = offset; } } private BlockAndOffset GetBlockAndRelativeOffset(int offset) { var blockSize = this.memoryManager.BlockSize; return new BlockAndOffset(offset / blockSize, offset % blockSize); } private void EnsureCapacity(int newCapacity) { if (newCapacity > this.memoryManager.MaximumStreamCapacity && this.memoryManager.MaximumStreamCapacity > 0) { RecyclableMemoryStreamManager.Events.Writer.MemoryStreamOverCapacity(newCapacity, this.memoryManager .MaximumStreamCapacity, this.tag, this.AllocationStack); throw new InvalidOperationException("Requested capacity is too large: " + newCapacity + ". Limit is " + this.memoryManager.MaximumStreamCapacity); } if (this.largeBuffer != null) { if (newCapacity > this.largeBuffer.Length) { var newBuffer = this.memoryManager.GetLargeBuffer(newCapacity, this.tag); this.InternalRead(newBuffer, 0, this.length, 0); this.ReleaseLargeBuffer(); this.largeBuffer = newBuffer; } } else { while (this.Capacity < newCapacity) { blocks.Add((this.memoryManager.GetBlock())); } } } /// /// Release the large buffer (either stores it for eventual release or returns it immediately). /// private void ReleaseLargeBuffer() { if (this.memoryManager.AggressiveBufferReturn) { this.memoryManager.ReturnLargeBuffer(this.largeBuffer, this.tag); } else { if (this.dirtyBuffers == null) { // We most likely will only ever need space for one this.dirtyBuffers = new List(1); } this.dirtyBuffers.Add(this.largeBuffer); } this.largeBuffer = null; } #endregion } } ================================================ FILE: Assets/Scripts/Network/Message/RecyclableMemoryStream/RecyclableMemoryStream.cs.meta ================================================ fileFormatVersion: 2 guid: 04ecc2633820de7459b8ba26a114f62f timeCreated: 1527234045 licenseType: Pro MonoImporter: serializedVersion: 2 defaultReferences: [] executionOrder: 0 icon: {instanceID: 0} userData: assetBundleName: assetBundleVariant: ================================================ FILE: Assets/Scripts/Network/Message/RecyclableMemoryStream/RecyclableMemoryStreamManager.cs ================================================ // --------------------------------------------------------------------- // Copyright (c) 2015-2016 Microsoft // // 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. // --------------------------------------------------------------------- namespace Microsoft.IO { using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; using System.Threading; /// /// Manages pools of RecyclableMemoryStream objects. /// /// /// There are two pools managed in here. The small pool contains same-sized buffers that are handed to streams /// as they write more data. /// /// For scenarios that need to call GetBuffer(), the large pool contains buffers of various sizes, all /// multiples of LargeBufferMultiple (1 MB by default). They are split by size to avoid overly-wasteful buffer /// usage. There should be far fewer 8 MB buffers than 1 MB buffers, for example. /// public partial class RecyclableMemoryStreamManager { /// /// Generic delegate for handling events without any arguments. /// public delegate void EventHandler(); /// /// Delegate for handling large buffer discard reports. /// /// Reason the buffer was discarded. public delegate void LargeBufferDiscardedEventHandler(Events.MemoryStreamDiscardReason reason); /// /// Delegate for handling reports of stream size when streams are allocated /// /// Bytes allocated. public delegate void StreamLengthReportHandler(long bytes); /// /// Delegate for handling periodic reporting of memory use statistics. /// /// Bytes currently in use in the small pool. /// Bytes currently free in the small pool. /// Bytes currently in use in the large pool. /// Bytes currently free in the large pool. public delegate void UsageReportEventHandler( long smallPoolInUseBytes, long smallPoolFreeBytes, long largePoolInUseBytes, long largePoolFreeBytes); public const int DefaultBlockSize = 128 * 1024; public const int DefaultLargeBufferMultiple = 1024 * 1024; public const int DefaultMaximumBufferSize = 128 * 1024 * 1024; private readonly int blockSize; private readonly long[] largeBufferFreeSize; private readonly long[] largeBufferInUseSize; private readonly int largeBufferMultiple; /// /// pools[0] = 1x largeBufferMultiple buffers /// pools[1] = 2x largeBufferMultiple buffers /// etc., up to maximumBufferSize /// private readonly ConcurrentStack[] largePools; private readonly int maximumBufferSize; private readonly ConcurrentStack smallPool; private long smallPoolFreeSize; private long smallPoolInUseSize; /// /// Initializes the memory manager with the default block/buffer specifications. /// public RecyclableMemoryStreamManager() : this(DefaultBlockSize, DefaultLargeBufferMultiple, DefaultMaximumBufferSize) { } /// /// Initializes the memory manager with the given block requiredSize. /// /// Size of each block that is pooled. Must be > 0. /// Each large buffer will be a multiple of this value. /// Buffers larger than this are not pooled /// blockSize is not a positive number, or largeBufferMultiple is not a positive number, or maximumBufferSize is less than blockSize. /// maximumBufferSize is not a multiple of largeBufferMultiple public RecyclableMemoryStreamManager(int blockSize, int largeBufferMultiple, int maximumBufferSize) { if (blockSize <= 0) { throw new ArgumentOutOfRangeException(nameof(blockSize), blockSize, "blockSize must be a positive number"); } if (largeBufferMultiple <= 0) { throw new ArgumentOutOfRangeException(nameof(largeBufferMultiple), "largeBufferMultiple must be a positive number"); } if (maximumBufferSize < blockSize) { throw new ArgumentOutOfRangeException(nameof(maximumBufferSize), "maximumBufferSize must be at least blockSize"); } this.blockSize = blockSize; this.largeBufferMultiple = largeBufferMultiple; this.maximumBufferSize = maximumBufferSize; if (!this.IsLargeBufferMultiple(maximumBufferSize)) { throw new ArgumentException("maximumBufferSize is not a multiple of largeBufferMultiple", nameof(maximumBufferSize)); } this.smallPool = new ConcurrentStack(); var numLargePools = maximumBufferSize / largeBufferMultiple; // +1 to store size of bytes in use that are too large to be pooled this.largeBufferInUseSize = new long[numLargePools + 1]; this.largeBufferFreeSize = new long[numLargePools]; this.largePools = new ConcurrentStack[numLargePools]; for (var i = 0; i < this.largePools.Length; ++i) { this.largePools[i] = new ConcurrentStack(); } Events.Writer.MemoryStreamManagerInitialized(blockSize, largeBufferMultiple, maximumBufferSize); } /// /// The size of each block. It must be set at creation and cannot be changed. /// public int BlockSize => this.blockSize; /// /// All buffers are multiples of this number. It must be set at creation and cannot be changed. /// public int LargeBufferMultiple => this.largeBufferMultiple; /// /// Gets or sets the maximum buffer size. /// /// Any buffer that is returned to the pool that is larger than this will be /// discarded and garbage collected. public int MaximumBufferSize => this.maximumBufferSize; /// /// Number of bytes in small pool not currently in use /// public long SmallPoolFreeSize => this.smallPoolFreeSize; /// /// Number of bytes currently in use by stream from the small pool /// public long SmallPoolInUseSize => this.smallPoolInUseSize; /// /// Number of bytes in large pool not currently in use /// public long LargePoolFreeSize => this.largeBufferFreeSize.Sum(); /// /// Number of bytes currently in use by streams from the large pool /// public long LargePoolInUseSize => this.largeBufferInUseSize.Sum(); /// /// How many blocks are in the small pool /// public long SmallBlocksFree => this.smallPool.Count; /// /// How many buffers are in the large pool /// public long LargeBuffersFree { get { long free = 0; foreach (var pool in this.largePools) { free += pool.Count; } return free; } } /// /// How many bytes of small free blocks to allow before we start dropping /// those returned to us. /// public long MaximumFreeSmallPoolBytes { get; set; } /// /// How many bytes of large free buffers to allow before we start dropping /// those returned to us. /// public long MaximumFreeLargePoolBytes { get; set; } /// /// Maximum stream capacity in bytes. Attempts to set a larger capacity will /// result in an exception. /// /// A value of 0 indicates no limit. public long MaximumStreamCapacity { get; set; } /// /// Whether to save callstacks for stream allocations. This can help in debugging. /// It should NEVER be turned on generally in production. /// public bool GenerateCallStacks { get; set; } /// /// Whether dirty buffers can be immediately returned to the buffer pool. E.g. when GetBuffer() is called on /// a stream and creates a single large buffer, if this setting is enabled, the other blocks will be returned /// to the buffer pool immediately. /// Note when enabling this setting that the user is responsible for ensuring that any buffer previously /// retrieved from a stream which is subsequently modified is not used after modification (as it may no longer /// be valid). /// public bool AggressiveBufferReturn { get; set; } /// /// Removes and returns a single block from the pool. /// /// A byte[] array internal byte[] GetBlock() { byte[] block; if (!this.smallPool.TryPop(out block)) { // We'll add this back to the pool when the stream is disposed // (unless our free pool is too large) block = new byte[this.BlockSize]; Events.Writer.MemoryStreamNewBlockCreated(this.smallPoolInUseSize); ReportBlockCreated(); } else { Interlocked.Add(ref this.smallPoolFreeSize, -this.BlockSize); } Interlocked.Add(ref this.smallPoolInUseSize, this.BlockSize); return block; } /// /// Returns a buffer of arbitrary size from the large buffer pool. This buffer /// will be at least the requiredSize and always be a multiple of largeBufferMultiple. /// /// The minimum length of the buffer /// The tag of the stream returning this buffer, for logging if necessary. /// A buffer of at least the required size. internal byte[] GetLargeBuffer(int requiredSize, string tag) { requiredSize = this.RoundToLargeBufferMultiple(requiredSize); var poolIndex = requiredSize / this.largeBufferMultiple - 1; byte[] buffer; if (poolIndex < this.largePools.Length) { if (!this.largePools[poolIndex].TryPop(out buffer)) { buffer = new byte[requiredSize]; Events.Writer.MemoryStreamNewLargeBufferCreated(requiredSize, this.LargePoolInUseSize); ReportLargeBufferCreated(); } else { Interlocked.Add(ref this.largeBufferFreeSize[poolIndex], -buffer.Length); } } else { // Buffer is too large to pool. They get a new buffer. // We still want to track the size, though, and we've reserved a slot // in the end of the inuse array for nonpooled bytes in use. poolIndex = this.largeBufferInUseSize.Length - 1; // We still want to round up to reduce heap fragmentation. buffer = new byte[requiredSize]; string callStack = null; if (this.GenerateCallStacks) { // Grab the stack -- we want to know who requires such large buffers callStack = Environment.StackTrace; } Events.Writer.MemoryStreamNonPooledLargeBufferCreated(requiredSize, tag, callStack); ReportLargeBufferCreated(); } Interlocked.Add(ref this.largeBufferInUseSize[poolIndex], buffer.Length); return buffer; } private int RoundToLargeBufferMultiple(int requiredSize) { return ((requiredSize + this.LargeBufferMultiple - 1) / this.LargeBufferMultiple) * this.LargeBufferMultiple; } private bool IsLargeBufferMultiple(int value) { return (value != 0) && (value % this.LargeBufferMultiple) == 0; } /// /// Returns the buffer to the large pool /// /// The buffer to return. /// The tag of the stream returning this buffer, for logging if necessary. /// buffer is null /// buffer.Length is not a multiple of LargeBufferMultiple (it did not originate from this pool) internal void ReturnLargeBuffer(byte[] buffer, string tag) { if (buffer == null) { throw new ArgumentNullException(nameof(buffer)); } if (!this.IsLargeBufferMultiple(buffer.Length)) { throw new ArgumentException( "buffer did not originate from this memory manager. The size is not a multiple of " + this.LargeBufferMultiple); } var poolIndex = buffer.Length / this.largeBufferMultiple - 1; if (poolIndex < this.largePools.Length) { if ((this.largePools[poolIndex].Count + 1) * buffer.Length <= this.MaximumFreeLargePoolBytes || this.MaximumFreeLargePoolBytes == 0) { this.largePools[poolIndex].Push(buffer); Interlocked.Add(ref this.largeBufferFreeSize[poolIndex], buffer.Length); } else { Events.Writer.MemoryStreamDiscardBuffer(Events.MemoryStreamBufferType.Large, tag, Events.MemoryStreamDiscardReason.EnoughFree); ReportLargeBufferDiscarded(Events.MemoryStreamDiscardReason.EnoughFree); } } else { // This is a non-poolable buffer, but we still want to track its size for inuse // analysis. We have space in the inuse array for this. poolIndex = this.largeBufferInUseSize.Length - 1; Events.Writer.MemoryStreamDiscardBuffer(Events.MemoryStreamBufferType.Large, tag, Events.MemoryStreamDiscardReason.TooLarge); ReportLargeBufferDiscarded(Events.MemoryStreamDiscardReason.TooLarge); } Interlocked.Add(ref this.largeBufferInUseSize[poolIndex], -buffer.Length); ReportUsageReport(this.smallPoolInUseSize, this.smallPoolFreeSize, this.LargePoolInUseSize, this.LargePoolFreeSize); } /// /// Returns the blocks to the pool /// /// Collection of blocks to return to the pool /// The tag of the stream returning these blocks, for logging if necessary. /// blocks is null /// blocks contains buffers that are the wrong size (or null) for this memory manager internal void ReturnBlocks(ICollection blocks, string tag) { if (blocks == null) { throw new ArgumentNullException(nameof(blocks)); } var bytesToReturn = blocks.Count * this.BlockSize; Interlocked.Add(ref this.smallPoolInUseSize, -bytesToReturn); foreach (var block in blocks) { if (block == null || block.Length != this.BlockSize) { throw new ArgumentException("blocks contains buffers that are not BlockSize in length"); } } foreach (var block in blocks) { if (this.MaximumFreeSmallPoolBytes == 0 || this.SmallPoolFreeSize < this.MaximumFreeSmallPoolBytes) { Interlocked.Add(ref this.smallPoolFreeSize, this.BlockSize); this.smallPool.Push(block); } else { Events.Writer.MemoryStreamDiscardBuffer(Events.MemoryStreamBufferType.Small, tag, Events.MemoryStreamDiscardReason.EnoughFree); ReportBlockDiscarded(); break; } } ReportUsageReport(this.smallPoolInUseSize, this.smallPoolFreeSize, this.LargePoolInUseSize, this.LargePoolFreeSize); } internal void ReportBlockCreated() { this.BlockCreated?.Invoke(); } internal void ReportBlockDiscarded() { this.BlockDiscarded?.Invoke(); } internal void ReportLargeBufferCreated() { this.LargeBufferCreated?.Invoke(); } internal void ReportLargeBufferDiscarded(Events.MemoryStreamDiscardReason reason) { this.LargeBufferDiscarded?.Invoke(reason); } internal void ReportStreamCreated() { this.StreamCreated?.Invoke(); } internal void ReportStreamDisposed() { this.StreamDisposed?.Invoke(); } internal void ReportStreamFinalized() { this.StreamFinalized?.Invoke(); } internal void ReportStreamLength(long bytes) { this.StreamLength?.Invoke(bytes); } internal void ReportStreamToArray() { this.StreamConvertedToArray?.Invoke(); } internal void ReportUsageReport( long smallPoolInUseBytes, long smallPoolFreeBytes, long largePoolInUseBytes, long largePoolFreeBytes) { this.UsageReport?.Invoke(smallPoolInUseBytes, smallPoolFreeBytes, largePoolInUseBytes, largePoolFreeBytes); } /// /// Retrieve a new MemoryStream object with no tag and a default initial capacity. /// /// A MemoryStream. public MemoryStream GetStream() { return new RecyclableMemoryStream(this); } /// /// Retrieve a new MemoryStream object with the given tag and a default initial capacity. /// /// A tag which can be used to track the source of the stream. /// A MemoryStream. public MemoryStream GetStream(string tag) { return new RecyclableMemoryStream(this, tag); } /// /// Retrieve a new MemoryStream object with the given tag and at least the given capacity. /// /// A tag which can be used to track the source of the stream. /// The minimum desired capacity for the stream. /// A MemoryStream. public MemoryStream GetStream(string tag, int requiredSize) { return new RecyclableMemoryStream(this, tag, requiredSize); } /// /// Retrieve a new MemoryStream object with the given tag and at least the given capacity, possibly using /// a single continugous underlying buffer. /// /// Retrieving a MemoryStream which provides a single contiguous buffer can be useful in situations /// where the initial size is known and it is desirable to avoid copying data between the smaller underlying /// buffers to a single large one. This is most helpful when you know that you will always call GetBuffer /// on the underlying stream. /// A tag which can be used to track the source of the stream. /// The minimum desired capacity for the stream. /// Whether to attempt to use a single contiguous buffer. /// A MemoryStream. public MemoryStream GetStream(string tag, int requiredSize, bool asContiguousBuffer) { if (!asContiguousBuffer || requiredSize <= this.BlockSize) { return this.GetStream(tag, requiredSize); } return new RecyclableMemoryStream(this, tag, requiredSize, this.GetLargeBuffer(requiredSize, tag)); } /// /// Retrieve a new MemoryStream object with the given tag and with contents copied from the provided /// buffer. The provided buffer is not wrapped or used after construction. /// /// The new stream's position is set to the beginning of the stream when returned. /// A tag which can be used to track the source of the stream. /// The byte buffer to copy data from. /// The offset from the start of the buffer to copy from. /// The number of bytes to copy from the buffer. /// A MemoryStream. [SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope")] public MemoryStream GetStream(string tag, byte[] buffer, int offset, int count) { var stream = new RecyclableMemoryStream(this, tag, count); stream.Write(buffer, offset, count); stream.Position = 0; return stream; } /// /// Triggered when a new block is created. /// public event EventHandler BlockCreated; /// /// Triggered when a new block is created. /// public event EventHandler BlockDiscarded; /// /// Triggered when a new large buffer is created. /// public event EventHandler LargeBufferCreated; /// /// Triggered when a new stream is created. /// public event EventHandler StreamCreated; /// /// Triggered when a stream is disposed. /// public event EventHandler StreamDisposed; /// /// Triggered when a stream is finalized. /// public event EventHandler StreamFinalized; /// /// Triggered when a stream is finalized. /// public event StreamLengthReportHandler StreamLength; /// /// Triggered when a user converts a stream to array. /// public event EventHandler StreamConvertedToArray; /// /// Triggered when a large buffer is discarded, along with the reason for the discard. /// public event LargeBufferDiscardedEventHandler LargeBufferDiscarded; /// /// Periodically triggered to report usage statistics. /// public event UsageReportEventHandler UsageReport; } } ================================================ FILE: Assets/Scripts/Network/Message/RecyclableMemoryStream/RecyclableMemoryStreamManager.cs.meta ================================================ fileFormatVersion: 2 guid: e7f5123bf8a488045a8e48c49b605cc9 timeCreated: 1527234045 licenseType: Pro MonoImporter: serializedVersion: 2 defaultReferences: [] executionOrder: 0 icon: {instanceID: 0} userData: assetBundleName: assetBundleVariant: ================================================ FILE: Assets/Scripts/Network/Message/RecyclableMemoryStream.meta ================================================ fileFormatVersion: 2 guid: ab0cd2080e97f064a9283d5729684c3e folderAsset: yes DefaultImporter: externalObjects: {} userData: assetBundleName: assetBundleVariant: ================================================ FILE: Assets/Scripts/Network/Message/Session.cs ================================================ using System; using System.IO; using System.Net; using UnityEngine; namespace UNetwork { public sealed class Session { private AChannel channel; private readonly byte[] opcodeBytes = new byte[2]; public INetworkManager Manager { get;private set; } public int Error { get { return this.channel.Error; } set { this.channel.Error = value; } } public Session(AChannel aChannel) { this.channel = aChannel; channel.ConnectCallback += OnConnect; channel.ErrorCallback += OnError; channel.ReadCallback += OnRead; } private void OnConnect(AChannel channel, int code) { if (Manager.OnConnect != null) { Manager.OnConnect.Invoke(0); } Debug.Log("OnConnect" + code); } private void OnError(AChannel channel, int code) { if (Manager.OnError != null) { Manager.OnError.Invoke(code); } Debug.LogError("OnError:" + code); this.Dispose(); } public void Dispose() { int error = this.channel.Error; if (this.channel.Error != 0) { Debug.LogError($"session dispose: ErrorCode: {error}, please see ErrorCode.cs!"); } this.channel.Dispose(); } public void Start(INetworkManager manager) { this.channel.Start(); this.Manager = manager; } public IPEndPoint RemoteAddress { get { return this.channel.RemoteAddress; } } public ChannelType ChannelType { get { return this.channel.ChannelType; } } public MemoryStream Stream { get { return this.channel.Stream; } } private void Run(MemoryStream memoryStream) { memoryStream.Seek(0, SeekOrigin.Begin); var bytes = new byte[memoryStream.Length]; memoryStream.Read(bytes, 0, bytes.Length); // Manager.MessageDispatcher.Dispatch(this, memoryStream.GetBuffer()); Manager.OnMessage?.Invoke(bytes); } public void OnRead(MemoryStream memoryStream) { try { this.Run(memoryStream); } catch (Exception e) { Debug.LogError(e); } } public void Send(ushort opcode) { MemoryStream stream = this.Stream; stream.Seek(0, SeekOrigin.Begin); stream.SetLength(Packet.MessageIndex); opcodeBytes.WriteTo(0, opcode); Array.Copy(opcodeBytes, 0, stream.GetBuffer(), 0, opcodeBytes.Length); this.Send(stream); } public void Send(byte[] buffers) { MemoryStream stream = this.Stream; stream.Seek(0, SeekOrigin.Begin); stream.SetLength(buffers.Length); Array.Copy(buffers, 0, stream.GetBuffer(), 0, buffers.Length); this.Send(stream); } public void Send(MemoryStream stream) { channel.Send(stream); } } } ================================================ FILE: Assets/Scripts/Network/Message/Session.cs.meta ================================================ fileFormatVersion: 2 guid: 73d149d0c325fa24d91a94843bd520b8 timeCreated: 1498117617 licenseType: Free MonoImporter: serializedVersion: 2 defaultReferences: [] executionOrder: 0 icon: {instanceID: 0} userData: assetBundleName: assetBundleVariant: ================================================ FILE: Assets/Scripts/Network/Message/SessionConnector.cs ================================================ using System; using System.IO; using System.Net; using UnityEngine; namespace UNetwork { public sealed class SessionConnector { private AChannel channel; public SessionConnector(AChannel aChannel) { this.channel = aChannel; channel.ConnectCallback += OnConnect; channel.ErrorCallback += OnError; channel.ReadCallback += OnRead; } private void OnConnect(AChannel channel, int code) { Debug.Log("OnConnect" + code); } private void OnError(AChannel channel, int code) { Debug.LogError("OnError:" + code); } private void Run(MemoryStream memoryStream) { memoryStream.Seek(0, SeekOrigin.Begin); var bytes = new byte[memoryStream.Length]; memoryStream.Read(bytes, 0, bytes.Length); ServerManager.Instance.OnMessage?.Invoke(bytes); } public void OnRead(MemoryStream memoryStream) { try { this.Run(memoryStream); } catch (Exception e) { Debug.LogError(e); } } } } ================================================ FILE: Assets/Scripts/Network/Message/SessionConnector.cs.meta ================================================ fileFormatVersion: 2 guid: 79080751d836414388644ba4afc4410b timeCreated: 1720089809 ================================================ FILE: Assets/Scripts/Network/Message/SessionServer.cs ================================================ using System; using System.IO; using System.Net; using UnityEngine; namespace UNetwork { public sealed class SessionServer { private AChannel channel; private readonly byte[] opcodeBytes = new byte[2]; public INetworkManager Manager { get; set; } public int Error { get { return this.channel.Error; } set { this.channel.Error = value; } } public SessionServer(AChannel aChannel) { this.channel = aChannel; channel.ConnectCallback += OnConnect; channel.ErrorCallback += OnError; channel.ReadCallback += OnRead; } private void OnConnect(AChannel channel, int code) { if (Manager.OnConnect != null) { Manager.OnConnect.Invoke(0); } Debug.Log("OnConnect" + code); } private void OnError(AChannel channel, int code) { if (Manager.OnError != null) { Manager.OnError.Invoke(code); } Debug.LogError("OnError:" + code); this.Dispose(); } public void Dispose() { int error = this.channel.Error; if (this.channel.Error != 0) { Debug.LogError($"session dispose: ErrorCode: {error}, please see ErrorCode.cs!"); } this.channel.Dispose(); } public void Start(INetworkManager manager) { this.channel.Start(); Manager = manager; } public IPEndPoint RemoteAddress { get { return this.channel.RemoteAddress; } } public ChannelType ChannelType { get { return this.channel.ChannelType; } } public MemoryStream Stream { get { return this.channel.Stream; } } private void Run(MemoryStream memoryStream) { memoryStream.Seek(0, SeekOrigin.Begin); var bytes = new byte[memoryStream.Length]; memoryStream.Read(bytes, 0, bytes.Length); // Manager.MessageDispatcher.Dispatch(this, memoryStream.GetBuffer()); Manager.OnMessage?.Invoke(memoryStream.GetBuffer()); } public void OnRead(MemoryStream memoryStream) { try { this.Run(memoryStream); } catch (Exception e) { Debug.LogError(e); } } public void Send(ushort opcode) { MemoryStream stream = this.Stream; stream.Seek(0, SeekOrigin.Begin); stream.SetLength(Packet.MessageIndex); opcodeBytes.WriteTo(0, opcode); Array.Copy(opcodeBytes, 0, stream.GetBuffer(), 0, opcodeBytes.Length); this.Send(stream); } public void Send(byte[] buffers) { MemoryStream stream = this.Stream; stream.Seek(0, SeekOrigin.Begin); stream.SetLength(buffers.Length); Array.Copy(buffers, 0, stream.GetBuffer(), 0, buffers.Length); this.Send(stream); } public void Send(MemoryStream stream) { channel.Send(stream); } } } ================================================ FILE: Assets/Scripts/Network/Message/SessionServer.cs.meta ================================================ fileFormatVersion: 2 guid: d72cceac0b794ac596aed9abc9e343b6 timeCreated: 1719307380 ================================================ FILE: Assets/Scripts/Network/Message.meta ================================================ fileFormatVersion: 2 guid: 6e2fe20a3053dc440938cf6c166337b2 folderAsset: yes DefaultImporter: externalObjects: {} userData: assetBundleName: assetBundleVariant: ================================================ FILE: Assets/Scripts/Network/ServerManager.cs ================================================ using System; using System.Net; using System.Threading; namespace UNetwork { /// /// Server 服务器的 业务逻辑 管理器 /// public class ServerManager : MonoSingleton, INetworkManager { public AService Service { get; private set; } public SessionServer Session { get; private set; } public IMessagePacker MessagePacker { get; set; } public IMessageDispatcher MessageDispatcher { get; set; } public Action OnConnect { get; set; } public Action OnError { get; set; } public Action OnMessage { get; set; } public override void Init() { SynchronizationContext.SetSynchronizationContext(OneThreadSynchronizationContext.Instance); } //server public void InitService(NetworkProtocol protocol, int packetSize = Packet.PacketSizeLength4) { switch (protocol) { case NetworkProtocol.TCP: this.Service = new TServiceServer(packetSize); break; } } /// /// 创建一个新Session /// public void Connect(IPEndPoint ipEndPoint) { AChannel channel = this.Service.ConnectChannel(ipEndPoint); Session = new SessionServer(channel); Session.Start(this); } /// /// 创建一个新Session /// public void Connect(string address) { AChannel channel = this.Service.ConnectChannel(address); Session = new SessionServer(channel); Session.Start(this); } public void Connect(string ip, int port) { AChannel channel = this.Service.ConnectChannel(NetworkHelper.ToIPEndPoint(ip, port)); Session = new SessionServer(channel); Session.Start(this); } public void Update() { OneThreadSynchronizationContext.Instance.Update(); if (this.Service == null) { return; } this.Service.Update(); } public void Send(byte[] data) { Session.Send(data); } public override void Dispose() { Session.Dispose(); } } } ================================================ FILE: Assets/Scripts/Network/ServerManager.cs.meta ================================================ fileFormatVersion: 2 guid: 84fd225f324f42999874057579220d14 timeCreated: 1719302713 ================================================ FILE: Assets/Scripts/Network.meta ================================================ fileFormatVersion: 2 guid: 89949efe62b53a044928bcf3075aad2f folderAsset: yes timeCreated: 1474943042 licenseType: Pro DefaultImporter: userData: assetBundleName: assetBundleVariant: ================================================ FILE: Assets/Scripts/TestClient.cs ================================================ using System; using System.Text; using UNetwork; using UnityEngine; public class TestClient : MonoBehaviour { public string ip = "127.0.0.1"; public int port = 12346; // private string address = "10.200.10.192:3655"; public string sendMessage = "client"; public static long starttime = 0; void Start() { ClientManager client = ClientManager.Instance; client.InitService(NetworkProtocol.TCP); client.MessagePacker = new ProtobufPacker(); client.MessageDispatcher = new OuterMessageDispatcher(); client.Connect(ip,port); client.OnConnect += OnConnect; client.OnError += OnError; client.OnMessage += OnMessage; } private void OnMessage(byte[] obj) { var msg = Encoding.UTF8.GetString(obj); Debug.Log($"Receive{obj.Length}" + msg); } private void OnError(int e) { Debug.LogError("网络错误:" + e); } private void OnConnect(int c) { Debug.Log("连接成功"); } private void Update() { if (Input.GetMouseButtonDown(0)) { var data = Encoding.UTF8.GetBytes(sendMessage); Debug.Log($"Send{data.Length}:" + sendMessage); ClientManager.Instance.Send(data); starttime = GetTimeStamp(); } } /// /// 获取时间戳 /// /// public static long GetTimeStamp() { return new DateTimeOffset(DateTime.UtcNow).ToUnixTimeMilliseconds(); } public static void Receive() { var inteval = GetTimeStamp() - starttime; Debug.LogWarning(inteval); } } ================================================ FILE: Assets/Scripts/TestClient.cs.meta ================================================ fileFormatVersion: 2 guid: c47d1a2e288e53f4285b397665f9a1dc MonoImporter: externalObjects: {} serializedVersion: 2 defaultReferences: [] executionOrder: 0 icon: {instanceID: 0} userData: assetBundleName: assetBundleVariant: ================================================ FILE: Assets/Scripts/TestServer.cs ================================================ using System; using System.Text; using UNetwork; using UnityEngine; public class TestServer : MonoBehaviour { public string ip = "127.0.0.1"; public int port = 12346; public string sendMessage = "server"; public static long starttime = 0; void Start() { ServerManager client = ServerManager.Instance; client.InitService(NetworkProtocol.TCP); client.MessagePacker = new ProtobufPacker(); client.MessageDispatcher = new OuterMessageDispatcher(); client.Connect(ip, port); client.OnConnect += OnConnect; client.OnError += OnError; client.OnMessage += OnMessage; } private void OnMessage(byte[] obj) { var msg = Encoding.UTF8.GetString(obj); Debug.Log(msg); } private void OnError(int e) { Debug.LogError("网络错误:" + e); } private void OnConnect(int c) { Debug.Log("连接成功"); } private void Update() { if (Input.GetMouseButtonDown(0)) { var data = Encoding.UTF8.GetBytes(sendMessage); Debug.Log($"Send{data.Length}:" + sendMessage); ServerManager.Instance.Send(data); starttime = GetTimeStamp(); } } /// /// 获取时间戳 /// /// public static long GetTimeStamp() { return new DateTimeOffset(DateTime.UtcNow).ToUnixTimeMilliseconds(); } public static void Receive() { var inteval = GetTimeStamp() - starttime; Debug.LogWarning(inteval); } } ================================================ FILE: Assets/Scripts/TestServer.cs.meta ================================================ fileFormatVersion: 2 guid: 8518a6a6d5ca49ecb048613cc3a03e07 timeCreated: 1719304719 ================================================ FILE: Assets/Scripts.meta ================================================ fileFormatVersion: 2 guid: 68234a28db005584e9d209109316f50f folderAsset: yes DefaultImporter: externalObjects: {} userData: assetBundleName: assetBundleVariant: ================================================ FILE: ProjectSettings/AudioManager.asset ================================================ %YAML 1.1 %TAG !u! tag:unity3d.com,2011: --- !u!11 &1 AudioManager: m_ObjectHideFlags: 0 m_Volume: 1 Rolloff Scale: 1 Doppler Factor: 1 Default Speaker Mode: 2 m_SampleRate: 0 m_DSPBufferSize: 1024 m_VirtualVoiceCount: 512 m_RealVoiceCount: 32 m_SpatializerPlugin: m_AmbisonicDecoderPlugin: m_DisableAudio: 0 m_VirtualizeEffects: 1 ================================================ FILE: ProjectSettings/AutoStreamingSettings.asset ================================================ %YAML 1.1 %TAG !u! tag:unity3d.com,2011: --- !u!1200 &1 AutoStreamingSettings: m_ObjectHideFlags: 0 serializedVersion: 2 mSearchMode: 15 mCustomSearchFile: mTextureSearchString: mMeshSearchString: mTextures: [] mAudios: [] mMeshes: [] mScenes: [] mConfigCCD: useCCD: 0 cosKey: projectGuid: bucketUuid: bucketName: badgeName: ================================================ FILE: ProjectSettings/ClusterInputManager.asset ================================================ %YAML 1.1 %TAG !u! tag:unity3d.com,2011: --- !u!236 &1 ClusterInputManager: m_ObjectHideFlags: 0 m_Inputs: [] ================================================ FILE: ProjectSettings/DynamicsManager.asset ================================================ %YAML 1.1 %TAG !u! tag:unity3d.com,2011: --- !u!55 &1 PhysicsManager: m_ObjectHideFlags: 0 serializedVersion: 8 m_Gravity: {x: 0, y: -9.81, z: 0} m_DefaultMaterial: {fileID: 0} m_BounceThreshold: 2 m_SleepThreshold: 0.005 m_DefaultContactOffset: 0.01 m_DefaultSolverIterations: 6 m_DefaultSolverVelocityIterations: 1 m_QueriesHitBackfaces: 0 m_QueriesHitTriggers: 1 m_EnableAdaptiveForce: 0 m_ClothInterCollisionDistance: 0 m_ClothInterCollisionStiffness: 0 m_ContactsGeneration: 1 m_LayerCollisionMatrix: ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff m_AutoSimulation: 1 m_AutoSyncTransforms: 0 m_ReuseCollisionCallbacks: 1 m_ClothInterCollisionSettingsToggle: 0 m_ContactPairsMode: 0 m_BroadphaseType: 0 m_WorldBounds: m_Center: {x: 0, y: 0, z: 0} m_Extent: {x: 250, y: 250, z: 250} m_WorldSubdivisions: 8 ================================================ FILE: ProjectSettings/EditorBuildSettings.asset ================================================ %YAML 1.1 %TAG !u! tag:unity3d.com,2011: --- !u!1045 &1 EditorBuildSettings: m_ObjectHideFlags: 0 serializedVersion: 2 m_Scenes: [] m_configObjects: {} ================================================ FILE: ProjectSettings/EditorSettings.asset ================================================ %YAML 1.1 %TAG !u! tag:unity3d.com,2011: --- !u!159 &1 EditorSettings: m_ObjectHideFlags: 0 serializedVersion: 7 m_ExternalVersionControlSupport: Visible Meta Files m_SerializationMode: 2 m_LineEndingsForNewScripts: 2 m_DefaultBehaviorMode: 1 m_PrefabRegularEnvironment: {fileID: 0} m_PrefabUIEnvironment: {fileID: 0} m_SpritePackerMode: 4 m_SpritePackerPaddingPower: 1 m_EtcTextureCompressorBehavior: 1 m_EtcTextureFastCompressor: 1 m_EtcTextureNormalCompressor: 2 m_EtcTextureBestCompressor: 4 m_ProjectGenerationIncludedExtensions: txt;xml;fnt;cd;asmdef m_ProjectGenerationRootNamespace: m_CollabEditorSettings: inProgressEnabled: 1 m_EnableTextureStreamingInPlayMode: 1 ================================================ FILE: ProjectSettings/GraphicsSettings.asset ================================================ %YAML 1.1 %TAG !u! tag:unity3d.com,2011: --- !u!30 &1 GraphicsSettings: m_ObjectHideFlags: 0 serializedVersion: 12 m_Deferred: m_Mode: 1 m_Shader: {fileID: 69, guid: 0000000000000000f000000000000000, type: 0} m_DeferredReflections: m_Mode: 1 m_Shader: {fileID: 74, guid: 0000000000000000f000000000000000, type: 0} m_ScreenSpaceShadows: m_Mode: 1 m_Shader: {fileID: 64, guid: 0000000000000000f000000000000000, type: 0} m_LegacyDeferred: m_Mode: 1 m_Shader: {fileID: 63, guid: 0000000000000000f000000000000000, type: 0} m_DepthNormals: m_Mode: 1 m_Shader: {fileID: 62, guid: 0000000000000000f000000000000000, type: 0} m_MotionVectors: m_Mode: 1 m_Shader: {fileID: 75, guid: 0000000000000000f000000000000000, type: 0} m_LightHalo: m_Mode: 1 m_Shader: {fileID: 105, guid: 0000000000000000f000000000000000, type: 0} m_LensFlare: m_Mode: 1 m_Shader: {fileID: 102, guid: 0000000000000000f000000000000000, type: 0} m_AlwaysIncludedShaders: - {fileID: 10753, guid: 0000000000000000f000000000000000, type: 0} - {fileID: 10770, guid: 0000000000000000f000000000000000, type: 0} - {fileID: 10783, guid: 0000000000000000f000000000000000, type: 0} m_PreloadedShaders: [] m_SpritesDefaultMaterial: {fileID: 10754, guid: 0000000000000000f000000000000000, type: 0} m_CustomRenderPipeline: {fileID: 0} m_TransparencySortMode: 0 m_TransparencySortAxis: {x: 0, y: 0, z: 1} m_DefaultRenderingPath: 1 m_DefaultMobileRenderingPath: 1 m_TierSettings: [] m_LightmapStripping: 0 m_FogStripping: 0 m_InstancingStripping: 0 m_LightmapKeepPlain: 1 m_LightmapKeepDirCombined: 1 m_LightmapKeepDynamicPlain: 1 m_LightmapKeepDynamicDirCombined: 1 m_LightmapKeepShadowMask: 1 m_LightmapKeepSubtractive: 1 m_FogKeepLinear: 1 m_FogKeepExp: 1 m_FogKeepExp2: 1 m_AlbedoSwatchInfos: [] m_LightsUseLinearIntensity: 0 m_LightsUseColorTemperature: 0 ================================================ FILE: ProjectSettings/InputManager.asset ================================================ %YAML 1.1 %TAG !u! tag:unity3d.com,2011: --- !u!13 &1 InputManager: m_ObjectHideFlags: 0 serializedVersion: 2 m_Axes: - serializedVersion: 3 m_Name: Horizontal descriptiveName: descriptiveNegativeName: negativeButton: left positiveButton: right altNegativeButton: a altPositiveButton: d gravity: 3 dead: 0.001 sensitivity: 3 snap: 1 invert: 0 type: 0 axis: 0 joyNum: 0 - serializedVersion: 3 m_Name: Vertical descriptiveName: descriptiveNegativeName: negativeButton: down positiveButton: up altNegativeButton: s altPositiveButton: w gravity: 3 dead: 0.001 sensitivity: 3 snap: 1 invert: 0 type: 0 axis: 0 joyNum: 0 - serializedVersion: 3 m_Name: Fire1 descriptiveName: descriptiveNegativeName: negativeButton: positiveButton: left ctrl altNegativeButton: altPositiveButton: mouse 0 gravity: 1000 dead: 0.001 sensitivity: 1000 snap: 0 invert: 0 type: 0 axis: 0 joyNum: 0 - serializedVersion: 3 m_Name: Fire2 descriptiveName: descriptiveNegativeName: negativeButton: positiveButton: left alt altNegativeButton: altPositiveButton: mouse 1 gravity: 1000 dead: 0.001 sensitivity: 1000 snap: 0 invert: 0 type: 0 axis: 0 joyNum: 0 - serializedVersion: 3 m_Name: Fire3 descriptiveName: descriptiveNegativeName: negativeButton: positiveButton: left shift altNegativeButton: altPositiveButton: mouse 2 gravity: 1000 dead: 0.001 sensitivity: 1000 snap: 0 invert: 0 type: 0 axis: 0 joyNum: 0 - serializedVersion: 3 m_Name: Jump descriptiveName: descriptiveNegativeName: negativeButton: positiveButton: space altNegativeButton: altPositiveButton: gravity: 1000 dead: 0.001 sensitivity: 1000 snap: 0 invert: 0 type: 0 axis: 0 joyNum: 0 - serializedVersion: 3 m_Name: Mouse X descriptiveName: descriptiveNegativeName: negativeButton: positiveButton: altNegativeButton: altPositiveButton: gravity: 0 dead: 0 sensitivity: 0.1 snap: 0 invert: 0 type: 1 axis: 0 joyNum: 0 - serializedVersion: 3 m_Name: Mouse Y descriptiveName: descriptiveNegativeName: negativeButton: positiveButton: altNegativeButton: altPositiveButton: gravity: 0 dead: 0 sensitivity: 0.1 snap: 0 invert: 0 type: 1 axis: 1 joyNum: 0 - serializedVersion: 3 m_Name: Mouse ScrollWheel descriptiveName: descriptiveNegativeName: negativeButton: positiveButton: altNegativeButton: altPositiveButton: gravity: 0 dead: 0 sensitivity: 0.1 snap: 0 invert: 0 type: 1 axis: 2 joyNum: 0 - serializedVersion: 3 m_Name: Horizontal descriptiveName: descriptiveNegativeName: negativeButton: positiveButton: altNegativeButton: altPositiveButton: gravity: 0 dead: 0.19 sensitivity: 1 snap: 0 invert: 0 type: 2 axis: 0 joyNum: 0 - serializedVersion: 3 m_Name: Vertical descriptiveName: descriptiveNegativeName: negativeButton: positiveButton: altNegativeButton: altPositiveButton: gravity: 0 dead: 0.19 sensitivity: 1 snap: 0 invert: 1 type: 2 axis: 1 joyNum: 0 - serializedVersion: 3 m_Name: Fire1 descriptiveName: descriptiveNegativeName: negativeButton: positiveButton: joystick button 0 altNegativeButton: altPositiveButton: gravity: 1000 dead: 0.001 sensitivity: 1000 snap: 0 invert: 0 type: 0 axis: 0 joyNum: 0 - serializedVersion: 3 m_Name: Fire2 descriptiveName: descriptiveNegativeName: negativeButton: positiveButton: joystick button 1 altNegativeButton: altPositiveButton: gravity: 1000 dead: 0.001 sensitivity: 1000 snap: 0 invert: 0 type: 0 axis: 0 joyNum: 0 - serializedVersion: 3 m_Name: Fire3 descriptiveName: descriptiveNegativeName: negativeButton: positiveButton: joystick button 2 altNegativeButton: altPositiveButton: gravity: 1000 dead: 0.001 sensitivity: 1000 snap: 0 invert: 0 type: 0 axis: 0 joyNum: 0 - serializedVersion: 3 m_Name: Jump descriptiveName: descriptiveNegativeName: negativeButton: positiveButton: joystick button 3 altNegativeButton: altPositiveButton: gravity: 1000 dead: 0.001 sensitivity: 1000 snap: 0 invert: 0 type: 0 axis: 0 joyNum: 0 - serializedVersion: 3 m_Name: Submit descriptiveName: descriptiveNegativeName: negativeButton: positiveButton: return altNegativeButton: altPositiveButton: joystick button 0 gravity: 1000 dead: 0.001 sensitivity: 1000 snap: 0 invert: 0 type: 0 axis: 0 joyNum: 0 - serializedVersion: 3 m_Name: Submit descriptiveName: descriptiveNegativeName: negativeButton: positiveButton: enter altNegativeButton: altPositiveButton: space gravity: 1000 dead: 0.001 sensitivity: 1000 snap: 0 invert: 0 type: 0 axis: 0 joyNum: 0 - serializedVersion: 3 m_Name: Cancel descriptiveName: descriptiveNegativeName: negativeButton: positiveButton: escape altNegativeButton: altPositiveButton: joystick button 1 gravity: 1000 dead: 0.001 sensitivity: 1000 snap: 0 invert: 0 type: 0 axis: 0 joyNum: 0 ================================================ FILE: ProjectSettings/MemorySettings.asset ================================================ %YAML 1.1 %TAG !u! tag:unity3d.com,2011: --- !u!387306366 &1 MemorySettings: m_ObjectHideFlags: 0 m_EditorMemorySettings: m_MainAllocatorBlockSize: -1 m_ThreadAllocatorBlockSize: -1 m_MainGfxBlockSize: -1 m_ThreadGfxBlockSize: -1 m_CacheBlockSize: -1 m_TypetreeBlockSize: -1 m_ProfilerBlockSize: -1 m_ProfilerEditorBlockSize: -1 m_BucketAllocatorGranularity: -1 m_BucketAllocatorBucketsCount: -1 m_BucketAllocatorBlockSize: -1 m_BucketAllocatorBlockCount: -1 m_ProfilerBucketAllocatorGranularity: -1 m_ProfilerBucketAllocatorBucketsCount: -1 m_ProfilerBucketAllocatorBlockSize: -1 m_ProfilerBucketAllocatorBlockCount: -1 m_TempAllocatorSizeMain: -1 m_JobTempAllocatorBlockSize: -1 m_BackgroundJobTempAllocatorBlockSize: -1 m_JobTempAllocatorReducedBlockSize: -1 m_TempAllocatorSizeGIBakingWorker: -1 m_TempAllocatorSizeNavMeshWorker: -1 m_TempAllocatorSizeAudioWorker: -1 m_TempAllocatorSizeCloudWorker: -1 m_TempAllocatorSizeGfx: -1 m_TempAllocatorSizeJobWorker: -1 m_TempAllocatorSizeBackgroundWorker: -1 m_TempAllocatorSizePreloadManager: -1 m_PlatformMemorySettings: {} ================================================ FILE: ProjectSettings/NavMeshAreas.asset ================================================ %YAML 1.1 %TAG !u! tag:unity3d.com,2011: --- !u!126 &1 NavMeshProjectSettings: m_ObjectHideFlags: 0 serializedVersion: 2 areas: - name: Walkable cost: 1 - name: Not Walkable cost: 1 - name: Jump cost: 2 - name: cost: 1 - name: cost: 1 - name: cost: 1 - name: cost: 1 - name: cost: 1 - name: cost: 1 - name: cost: 1 - name: cost: 1 - name: cost: 1 - name: cost: 1 - name: cost: 1 - name: cost: 1 - name: cost: 1 - name: cost: 1 - name: cost: 1 - name: cost: 1 - name: cost: 1 - name: cost: 1 - name: cost: 1 - name: cost: 1 - name: cost: 1 - name: cost: 1 - name: cost: 1 - name: cost: 1 - name: cost: 1 - name: cost: 1 - name: cost: 1 - name: cost: 1 - name: cost: 1 m_LastAgentTypeID: -887442657 m_Settings: - serializedVersion: 2 agentTypeID: 0 agentRadius: 0.5 agentHeight: 2 agentSlope: 45 agentClimb: 0.75 ledgeDropHeight: 0 maxJumpAcrossDistance: 0 minRegionArea: 2 manualCellSize: 0 cellSize: 0.16666667 manualTileSize: 0 tileSize: 256 accuratePlacement: 0 debug: m_Flags: 0 m_SettingNames: - Humanoid ================================================ FILE: ProjectSettings/NetworkManager.asset ================================================ %YAML 1.1 %TAG !u! tag:unity3d.com,2011: --- !u!149 &1 NetworkManager: m_ObjectHideFlags: 0 m_DebugLevel: 0 m_Sendrate: 15 m_AssetToPrefab: {} ================================================ FILE: ProjectSettings/PackageManagerSettings.asset ================================================ %YAML 1.1 %TAG !u! tag:unity3d.com,2011: --- !u!114 &1 MonoBehaviour: m_ObjectHideFlags: 61 m_CorrespondingSourceObject: {fileID: 0} m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 0} m_Enabled: 1 m_EditorHideFlags: 0 m_Script: {fileID: 13964, guid: 0000000000000000e000000000000000, type: 0} m_Name: m_EditorClassIdentifier: m_EnablePreReleasePackages: 0 m_EnablePackageDependencies: 0 m_AdvancedSettingsExpanded: 1 m_ScopedRegistriesSettingsExpanded: 1 m_SeeAllPackageVersions: 0 oneTimeWarningShown: 0 m_Registries: - m_Id: main m_Name: m_Url: https://packages.unity.cn m_Scopes: [] m_IsDefault: 1 m_Capabilities: 7 m_ConfigSource: 0 m_UserSelectedRegistryName: m_UserAddingNewScopedRegistry: 0 m_RegistryInfoDraft: m_Modified: 0 m_ErrorMessage: m_UserModificationsInstanceId: -854 m_OriginalInstanceId: -856 m_LoadAssets: 0 ================================================ FILE: ProjectSettings/Physics2DSettings.asset ================================================ %YAML 1.1 %TAG !u! tag:unity3d.com,2011: --- !u!19 &1 Physics2DSettings: m_ObjectHideFlags: 0 serializedVersion: 4 m_Gravity: {x: 0, y: -9.81} m_DefaultMaterial: {fileID: 0} m_VelocityIterations: 8 m_PositionIterations: 3 m_VelocityThreshold: 1 m_MaxLinearCorrection: 0.2 m_MaxAngularCorrection: 8 m_MaxTranslationSpeed: 100 m_MaxRotationSpeed: 360 m_BaumgarteScale: 0.2 m_BaumgarteTimeOfImpactScale: 0.75 m_TimeToSleep: 0.5 m_LinearSleepTolerance: 0.01 m_AngularSleepTolerance: 2 m_DefaultContactOffset: 0.01 m_JobOptions: serializedVersion: 2 useMultithreading: 0 useConsistencySorting: 0 m_InterpolationPosesPerJob: 100 m_NewContactsPerJob: 30 m_CollideContactsPerJob: 100 m_ClearFlagsPerJob: 200 m_ClearBodyForcesPerJob: 200 m_SyncDiscreteFixturesPerJob: 50 m_SyncContinuousFixturesPerJob: 50 m_FindNearestContactsPerJob: 100 m_UpdateTriggerContactsPerJob: 100 m_IslandSolverCostThreshold: 100 m_IslandSolverBodyCostScale: 1 m_IslandSolverContactCostScale: 10 m_IslandSolverJointCostScale: 10 m_IslandSolverBodiesPerJob: 50 m_IslandSolverContactsPerJob: 50 m_AutoSimulation: 1 m_QueriesHitTriggers: 1 m_QueriesStartInColliders: 1 m_CallbacksOnDisable: 1 m_ReuseCollisionCallbacks: 1 m_AutoSyncTransforms: 0 m_AlwaysShowColliders: 0 m_ShowColliderSleep: 1 m_ShowColliderContacts: 0 m_ShowColliderAABB: 0 m_ContactArrowScale: 0.2 m_ColliderAwakeColor: {r: 0.5686275, g: 0.95686275, b: 0.54509807, a: 0.7529412} m_ColliderAsleepColor: {r: 0.5686275, g: 0.95686275, b: 0.54509807, a: 0.36078432} m_ColliderContactColor: {r: 1, g: 0, b: 1, a: 0.6862745} m_ColliderAABBColor: {r: 1, g: 1, b: 0, a: 0.2509804} m_LayerCollisionMatrix: ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff ================================================ FILE: ProjectSettings/PresetManager.asset ================================================ %YAML 1.1 %TAG !u! tag:unity3d.com,2011: --- !u!1386491679 &1 PresetManager: m_ObjectHideFlags: 0 m_DefaultList: - type: m_NativeTypeID: 20 m_ManagedTypePPtr: {fileID: 0} m_ManagedTypeFallback: defaultPresets: - m_Preset: {fileID: 2655988077585873504, guid: bfcfc320427f8224bbb7a96f3d3aebad, type: 2} ================================================ FILE: ProjectSettings/ProjectSettings.asset ================================================ %YAML 1.1 %TAG !u! tag:unity3d.com,2011: --- !u!129 &1 PlayerSettings: m_ObjectHideFlags: 0 serializedVersion: 24 productGUID: 773bc0f7adf1dc245bf5c8831827604a AndroidProfiler: 0 AndroidFilterTouchesWhenObscured: 0 AndroidEnableSustainedPerformanceMode: 0 defaultScreenOrientation: 4 targetDevice: 2 useOnDemandResources: 0 accelerometerFrequency: 60 companyName: DefaultCompany productName: TestKCP defaultCursor: {fileID: 0} cursorHotspot: {x: 0, y: 0} m_SplashScreenBackgroundColor: {r: 0.13725491, g: 0.12156863, b: 0.1254902, a: 1} m_ShowUnitySplashScreen: 1 m_ShowUnitySplashLogo: 1 m_SplashScreenOverlayOpacity: 1 m_SplashScreenAnimation: 1 m_SplashScreenLogoStyle: 1 m_SplashScreenDrawMode: 0 m_SplashScreenBackgroundAnimationZoom: 1 m_SplashScreenLogoAnimationZoom: 1 m_SplashScreenBackgroundLandscapeAspect: 1 m_SplashScreenBackgroundPortraitAspect: 1 m_SplashScreenBackgroundLandscapeUvs: serializedVersion: 2 x: 0 y: 0 width: 1 height: 1 m_SplashScreenBackgroundPortraitUvs: serializedVersion: 2 x: 0 y: 0 width: 1 height: 1 m_SplashScreenLogos: [] m_VirtualRealitySplashScreen: {fileID: 0} m_HolographicTrackingLossScreen: {fileID: 0} defaultScreenWidth: 1024 defaultScreenHeight: 768 defaultScreenWidthWeb: 960 defaultScreenHeightWeb: 600 m_StereoRenderingPath: 0 m_ActiveColorSpace: 0 unsupportedMSAAFallback: 0 m_MTRendering: 1 mipStripping: 0 numberOfMipsStripped: 0 m_StackTraceTypes: 010000000100000001000000010000000100000001000000 iosShowActivityIndicatorOnLoading: -1 androidShowActivityIndicatorOnLoading: -1 iosUseCustomAppBackgroundBehavior: 0 iosAllowHTTPDownload: 1 allowedAutorotateToPortrait: 1 allowedAutorotateToPortraitUpsideDown: 1 allowedAutorotateToLandscapeRight: 1 allowedAutorotateToLandscapeLeft: 1 useOSAutorotation: 1 use32BitDisplayBuffer: 1 preserveFramebufferAlpha: 0 disableDepthAndStencilBuffers: 0 androidStartInFullscreen: 1 androidRenderOutsideSafeArea: 0 androidUseSwappy: 0 androidBlitType: 0 androidResizableWindow: 0 androidDefaultWindowWidth: 1920 androidDefaultWindowHeight: 1080 androidMinimumWindowWidth: 400 androidMinimumWindowHeight: 300 androidFullscreenMode: 1 androidAutoRotationBehavior: 1 defaultIsNativeResolution: 1 macRetinaSupport: 1 runInBackground: 1 captureSingleScreen: 0 muteOtherAudioSources: 0 Prepare IOS For Recording: 0 Force IOS Speakers When Recording: 0 deferSystemGesturesMode: 0 hideHomeButton: 0 submitAnalytics: 1 usePlayerLog: 1 autoStreaming: 0 useAnimationStreaming: 0 useFontStreaming: 0 autoStreamingId: instantGameAppId: bakeCollisionMeshes: 0 forceSingleInstance: 0 useFlipModelSwapchain: 1 resizableWindow: 0 useMacAppStoreValidation: 0 macAppStoreCategory: public.app-category.games gpuSkinning: 0 xboxPIXTextureCapture: 0 xboxEnableAvatar: 0 xboxEnableKinect: 0 xboxEnableKinectAutoTracking: 0 xboxEnableFitness: 0 visibleInBackground: 1 allowFullscreenSwitch: 1 fullscreenMode: 1 xboxSpeechDB: 0 xboxEnableHeadOrientation: 0 xboxEnableGuest: 0 xboxEnablePIXSampling: 0 metalFramebufferOnly: 0 xboxOneResolution: 0 xboxOneSResolution: 0 xboxOneXResolution: 3 xboxOneMonoLoggingLevel: 0 xboxOneLoggingLevel: 1 xboxOneDisableEsram: 0 xboxOneEnableTypeOptimization: 0 xboxOnePresentImmediateThreshold: 0 switchQueueCommandMemory: 0 switchQueueControlMemory: 16384 switchQueueComputeMemory: 262144 switchNVNShaderPoolsGranularity: 33554432 switchNVNDefaultPoolsGranularity: 16777216 switchNVNOtherPoolsGranularity: 16777216 switchNVNMaxPublicTextureIDCount: 0 switchNVNMaxPublicSamplerIDCount: 0 switchMaxWorkerMultiple: 8 stadiaPresentMode: 0 stadiaTargetFramerate: 0 vulkanNumSwapchainBuffers: 3 vulkanEnableSetSRGBWrite: 0 vulkanEnablePreTransform: 0 vulkanEnableLateAcquireNextImage: 0 vulkanEnableCommandBufferRecycling: 1 m_SupportedAspectRatios: 4:3: 1 5:4: 1 16:10: 1 16:9: 1 Others: 1 bundleVersion: 0.1 preloadedAssets: [] metroInputSource: 0 wsaTransparentSwapchain: 0 m_HolographicPauseOnTrackingLoss: 1 xboxOneDisableKinectGpuReservation: 1 xboxOneEnable7thCore: 1 vrSettings: enable360StereoCapture: 0 isWsaHolographicRemotingEnabled: 0 enableFrameTimingStats: 0 enableOpenGLProfilerGPURecorders: 1 useHDRDisplay: 0 D3DHDRBitDepth: 0 m_ColorGamuts: 00000000 targetPixelDensity: 30 resolutionScalingMode: 0 resetResolutionOnWindowResize: 0 androidSupportedAspectRatio: 1 androidMaxAspectRatio: 2.1 applicationIdentifier: Standalone: com.Company.ProductName buildNumber: Standalone: 0 iPhone: 0 tvOS: 0 overrideDefaultApplicationIdentifier: 1 AndroidBundleVersionCode: 1 AndroidMinSdkVersion: 22 AndroidTargetSdkVersion: 0 AndroidPreferredInstallLocation: 1 aotOptions: stripEngineCode: 1 iPhoneStrippingLevel: 0 iPhoneScriptCallOptimization: 0 ForceInternetPermission: 0 ForceSDCardPermission: 0 CreateWallpaper: 0 APKExpansionFiles: 0 keepLoadedShadersAlive: 0 StripUnusedMeshComponents: 1 VertexChannelCompressionMask: 4054 iPhoneSdkVersion: 988 iOSTargetOSVersionString: 12.0 tvOSSdkVersion: 0 tvOSRequireExtendedGameController: 0 tvOSTargetOSVersionString: 12.0 uIPrerenderedIcon: 0 uIRequiresPersistentWiFi: 0 uIRequiresFullScreen: 1 uIStatusBarHidden: 1 uIExitOnSuspend: 0 uIStatusBarStyle: 0 appleTVSplashScreen: {fileID: 0} appleTVSplashScreen2x: {fileID: 0} tvOSSmallIconLayers: [] tvOSSmallIconLayers2x: [] tvOSLargeIconLayers: [] tvOSLargeIconLayers2x: [] tvOSTopShelfImageLayers: [] tvOSTopShelfImageLayers2x: [] tvOSTopShelfImageWideLayers: [] tvOSTopShelfImageWideLayers2x: [] iOSLaunchScreenType: 0 iOSLaunchScreenPortrait: {fileID: 0} iOSLaunchScreenLandscape: {fileID: 0} iOSLaunchScreenBackgroundColor: serializedVersion: 2 rgba: 0 iOSLaunchScreenFillPct: 100 iOSLaunchScreenSize: 100 iOSLaunchScreenCustomXibPath: iOSLaunchScreeniPadType: 0 iOSLaunchScreeniPadImage: {fileID: 0} iOSLaunchScreeniPadBackgroundColor: serializedVersion: 2 rgba: 0 iOSLaunchScreeniPadFillPct: 100 iOSLaunchScreeniPadSize: 100 iOSLaunchScreeniPadCustomXibPath: iOSLaunchScreenCustomStoryboardPath: iOSLaunchScreeniPadCustomStoryboardPath: iOSDeviceRequirements: [] iOSURLSchemes: [] macOSURLSchemes: [] iOSBackgroundModes: 0 iOSMetalForceHardShadows: 0 metalEditorSupport: 1 metalAPIValidation: 1 iOSRenderExtraFrameOnPause: 0 iosCopyPluginsCodeInsteadOfSymlink: 0 appleDeveloperTeamID: iOSManualSigningProvisioningProfileID: tvOSManualSigningProvisioningProfileID: iOSManualSigningProvisioningProfileType: 0 tvOSManualSigningProvisioningProfileType: 0 appleEnableAutomaticSigning: 0 iOSRequireARKit: 0 iOSAutomaticallyDetectAndAddCapabilities: 1 appleEnableProMotion: 0 shaderPrecisionModel: 0 clonedFromGUID: 5f34be1353de5cf4398729fda238591b templatePackageId: com.unity.template.2d@1.3.0 templateDefaultScene: Assets/Scenes/SampleScene.unity useCustomMainManifest: 0 useCustomLauncherManifest: 0 useCustomMainGradleTemplate: 0 useCustomLauncherGradleManifest: 0 useCustomBaseGradleTemplate: 0 useCustomGradlePropertiesTemplate: 0 useCustomProguardFile: 0 AndroidTargetArchitectures: 5 AndroidTargetDevices: 0 AndroidSplashScreenScale: 0 androidSplashScreen: {fileID: 0} AndroidKeystoreName: AndroidKeyaliasName: AndroidBuildApkPerCpuArchitecture: 0 AndroidTVCompatibility: 1 AndroidIsGame: 1 AndroidEnableTango: 0 androidEnableBanner: 1 androidUseLowAccuracyLocation: 0 androidUseCustomKeystore: 0 m_AndroidBanners: - width: 320 height: 180 banner: {fileID: 0} androidGamepadSupportLevel: 0 chromeosInputEmulation: 1 AndroidMinifyWithR8: 0 AndroidMinifyRelease: 0 AndroidMinifyDebug: 0 AndroidValidateAppBundleSize: 1 AndroidAppBundleSizeToValidate: 150 m_BuildTargetIcons: [] m_BuildTargetPlatformIcons: - m_BuildTarget: Android m_Icons: - m_Textures: [] m_Width: 432 m_Height: 432 m_Kind: 2 m_SubKind: - m_Textures: [] m_Width: 324 m_Height: 324 m_Kind: 2 m_SubKind: - m_Textures: [] m_Width: 216 m_Height: 216 m_Kind: 2 m_SubKind: - m_Textures: [] m_Width: 162 m_Height: 162 m_Kind: 2 m_SubKind: - m_Textures: [] m_Width: 108 m_Height: 108 m_Kind: 2 m_SubKind: - m_Textures: [] m_Width: 81 m_Height: 81 m_Kind: 2 m_SubKind: - m_Textures: [] m_Width: 192 m_Height: 192 m_Kind: 1 m_SubKind: - m_Textures: [] m_Width: 144 m_Height: 144 m_Kind: 1 m_SubKind: - m_Textures: [] m_Width: 96 m_Height: 96 m_Kind: 1 m_SubKind: - m_Textures: [] m_Width: 72 m_Height: 72 m_Kind: 1 m_SubKind: - m_Textures: [] m_Width: 48 m_Height: 48 m_Kind: 1 m_SubKind: - m_Textures: [] m_Width: 36 m_Height: 36 m_Kind: 1 m_SubKind: - m_Textures: [] m_Width: 192 m_Height: 192 m_Kind: 0 m_SubKind: - m_Textures: [] m_Width: 144 m_Height: 144 m_Kind: 0 m_SubKind: - m_Textures: [] m_Width: 96 m_Height: 96 m_Kind: 0 m_SubKind: - m_Textures: [] m_Width: 72 m_Height: 72 m_Kind: 0 m_SubKind: - m_Textures: [] m_Width: 48 m_Height: 48 m_Kind: 0 m_SubKind: - m_Textures: [] m_Width: 36 m_Height: 36 m_Kind: 0 m_SubKind: m_BuildTargetBatching: [] m_BuildTargetShaderSettings: [] m_BuildTargetGraphicsJobs: - m_BuildTarget: WindowsStandaloneSupport m_GraphicsJobs: 0 - m_BuildTarget: MacStandaloneSupport m_GraphicsJobs: 0 - m_BuildTarget: LinuxStandaloneSupport m_GraphicsJobs: 0 - m_BuildTarget: AndroidPlayer m_GraphicsJobs: 0 - m_BuildTarget: iOSSupport m_GraphicsJobs: 0 - m_BuildTarget: PS4Player m_GraphicsJobs: 0 - m_BuildTarget: PS5Player m_GraphicsJobs: 0 - m_BuildTarget: XboxOnePlayer m_GraphicsJobs: 0 - m_BuildTarget: GameCoreXboxOneSupport m_GraphicsJobs: 0 - m_BuildTarget: GameCoreScarlettSupport m_GraphicsJobs: 0 - m_BuildTarget: Switch m_GraphicsJobs: 0 - m_BuildTarget: WebGLSupport m_GraphicsJobs: 0 - m_BuildTarget: MetroSupport m_GraphicsJobs: 0 - m_BuildTarget: AppleTVSupport m_GraphicsJobs: 0 - m_BuildTarget: BJMSupport m_GraphicsJobs: 0 - m_BuildTarget: LuminSupport m_GraphicsJobs: 0 - m_BuildTarget: CloudRendering m_GraphicsJobs: 0 - m_BuildTarget: EmbeddedLinux m_GraphicsJobs: 0 m_BuildTargetGraphicsJobMode: - m_BuildTarget: PS4Player m_GraphicsJobMode: 0 - m_BuildTarget: XboxOnePlayer m_GraphicsJobMode: 0 m_BuildTargetGraphicsAPIs: - m_BuildTarget: iOSSupport m_APIs: 10000000 m_Automatic: 1 - m_BuildTarget: AndroidPlayer m_APIs: 0b00000008000000 m_Automatic: 0 m_BuildTargetVRSettings: [] m_DefaultShaderChunkSizeInMB: 16 m_DefaultShaderChunkCount: 0 openGLRequireES31: 0 openGLRequireES31AEP: 0 openGLRequireES32: 0 m_TemplateCustomTags: {} mobileMTRendering: Android: 1 iPhone: 1 tvOS: 1 m_BuildTargetGroupLightmapEncodingQuality: [] m_BuildTargetGroupLightmapSettings: [] m_BuildTargetNormalMapEncoding: [] m_BuildTargetDefaultTextureCompressionFormat: [] playModeTestRunnerEnabled: 0 runPlayModeTestAsEditModeTest: 0 actionOnDotNetUnhandledException: 1 enableInternalProfiler: 0 logObjCUncaughtExceptions: 1 enableCrashReportAPI: 0 cameraUsageDescription: locationUsageDescription: microphoneUsageDescription: bluetoothUsageDescription: switchNMETAOverride: switchNetLibKey: switchSocketMemoryPoolSize: 6144 switchSocketAllocatorPoolSize: 128 switchSocketConcurrencyLimit: 14 switchScreenResolutionBehavior: 2 switchUseCPUProfiler: 0 switchEnableFileSystemTrace: 0 switchLTOSetting: 0 switchApplicationID: 0x01004b9000490000 switchNSODependencies: switchTitleNames_0: switchTitleNames_1: switchTitleNames_2: switchTitleNames_3: switchTitleNames_4: switchTitleNames_5: switchTitleNames_6: switchTitleNames_7: switchTitleNames_8: switchTitleNames_9: switchTitleNames_10: switchTitleNames_11: switchTitleNames_12: switchTitleNames_13: switchTitleNames_14: switchTitleNames_15: switchPublisherNames_0: switchPublisherNames_1: switchPublisherNames_2: switchPublisherNames_3: switchPublisherNames_4: switchPublisherNames_5: switchPublisherNames_6: switchPublisherNames_7: switchPublisherNames_8: switchPublisherNames_9: switchPublisherNames_10: switchPublisherNames_11: switchPublisherNames_12: switchPublisherNames_13: switchPublisherNames_14: switchPublisherNames_15: switchIcons_0: {fileID: 0} switchIcons_1: {fileID: 0} switchIcons_2: {fileID: 0} switchIcons_3: {fileID: 0} switchIcons_4: {fileID: 0} switchIcons_5: {fileID: 0} switchIcons_6: {fileID: 0} switchIcons_7: {fileID: 0} switchIcons_8: {fileID: 0} switchIcons_9: {fileID: 0} switchIcons_10: {fileID: 0} switchIcons_11: {fileID: 0} switchIcons_12: {fileID: 0} switchIcons_13: {fileID: 0} switchIcons_14: {fileID: 0} switchIcons_15: {fileID: 0} switchSmallIcons_0: {fileID: 0} switchSmallIcons_1: {fileID: 0} switchSmallIcons_2: {fileID: 0} switchSmallIcons_3: {fileID: 0} switchSmallIcons_4: {fileID: 0} switchSmallIcons_5: {fileID: 0} switchSmallIcons_6: {fileID: 0} switchSmallIcons_7: {fileID: 0} switchSmallIcons_8: {fileID: 0} switchSmallIcons_9: {fileID: 0} switchSmallIcons_10: {fileID: 0} switchSmallIcons_11: {fileID: 0} switchSmallIcons_12: {fileID: 0} switchSmallIcons_13: {fileID: 0} switchSmallIcons_14: {fileID: 0} switchSmallIcons_15: {fileID: 0} switchManualHTML: switchAccessibleURLs: switchLegalInformation: switchMainThreadStackSize: 1048576 switchPresenceGroupId: switchLogoHandling: 0 switchReleaseVersion: 0 switchDisplayVersion: 1.0.0 switchStartupUserAccount: 0 switchSupportedLanguagesMask: 0 switchLogoType: 0 switchApplicationErrorCodeCategory: switchUserAccountSaveDataSize: 0 switchUserAccountSaveDataJournalSize: 0 switchApplicationAttribute: 0 switchCardSpecSize: -1 switchCardSpecClock: -1 switchRatingsMask: 0 switchRatingsInt_0: 0 switchRatingsInt_1: 0 switchRatingsInt_2: 0 switchRatingsInt_3: 0 switchRatingsInt_4: 0 switchRatingsInt_5: 0 switchRatingsInt_6: 0 switchRatingsInt_7: 0 switchRatingsInt_8: 0 switchRatingsInt_9: 0 switchRatingsInt_10: 0 switchRatingsInt_11: 0 switchRatingsInt_12: 0 switchLocalCommunicationIds_0: switchLocalCommunicationIds_1: switchLocalCommunicationIds_2: switchLocalCommunicationIds_3: switchLocalCommunicationIds_4: switchLocalCommunicationIds_5: switchLocalCommunicationIds_6: switchLocalCommunicationIds_7: switchParentalControl: 0 switchAllowsScreenshot: 1 switchAllowsVideoCapturing: 1 switchAllowsRuntimeAddOnContentInstall: 0 switchDataLossConfirmation: 0 switchUserAccountLockEnabled: 0 switchSystemResourceMemory: 16777216 switchSupportedNpadStyles: 3 switchNativeFsCacheSize: 32 switchIsHoldTypeHorizontal: 0 switchSupportedNpadCount: 8 switchEnableTouchScreen: 1 switchSocketConfigEnabled: 0 switchTcpInitialSendBufferSize: 32 switchTcpInitialReceiveBufferSize: 64 switchTcpAutoSendBufferSizeMax: 256 switchTcpAutoReceiveBufferSizeMax: 256 switchUdpSendBufferSize: 9 switchUdpReceiveBufferSize: 42 switchSocketBufferEfficiency: 4 switchSocketInitializeEnabled: 1 switchNetworkInterfaceManagerInitializeEnabled: 1 switchUseNewStyleFilepaths: 0 switchUseLegacyFmodPriorities: 1 switchUseMicroSleepForYield: 1 switchEnableRamDiskSupport: 0 switchMicroSleepForYieldTime: 25 switchRamDiskSpaceSize: 12 ps4NPAgeRating: 12 ps4NPTitleSecret: ps4NPTrophyPackPath: ps4ParentalLevel: 11 ps4ContentID: ED1633-NPXX51362_00-0000000000000000 ps4Category: 0 ps4MasterVersion: 01.00 ps4AppVersion: 01.00 ps4AppType: 0 ps4ParamSfxPath: ps4VideoOutPixelFormat: 0 ps4VideoOutInitialWidth: 1920 ps4VideoOutBaseModeInitialWidth: 1920 ps4VideoOutReprojectionRate: 60 ps4PronunciationXMLPath: ps4PronunciationSIGPath: ps4BackgroundImagePath: ps4StartupImagePath: ps4StartupImagesFolder: ps4IconImagesFolder: ps4SaveDataImagePath: ps4SdkOverride: ps4BGMPath: ps4ShareFilePath: ps4ShareOverlayImagePath: ps4PrivacyGuardImagePath: ps4ExtraSceSysFile: ps4NPtitleDatPath: ps4RemotePlayKeyAssignment: -1 ps4RemotePlayKeyMappingDir: ps4PlayTogetherPlayerCount: 0 ps4EnterButtonAssignment: 1 ps4ApplicationParam1: 0 ps4ApplicationParam2: 0 ps4ApplicationParam3: 0 ps4ApplicationParam4: 0 ps4DownloadDataSize: 0 ps4GarlicHeapSize: 2048 ps4ProGarlicHeapSize: 2560 playerPrefsMaxSize: 32768 ps4Passcode: frAQBc8Wsa1xVPfvJcrgRYwTiizs2trQ ps4pnSessions: 1 ps4pnPresence: 1 ps4pnFriends: 1 ps4pnGameCustomData: 1 playerPrefsSupport: 0 enableApplicationExit: 0 resetTempFolder: 1 restrictedAudioUsageRights: 0 ps4UseResolutionFallback: 0 ps4ReprojectionSupport: 0 ps4UseAudio3dBackend: 0 ps4UseLowGarlicFragmentationMode: 1 ps4SocialScreenEnabled: 0 ps4ScriptOptimizationLevel: 0 ps4Audio3dVirtualSpeakerCount: 14 ps4attribCpuUsage: 0 ps4PatchPkgPath: ps4PatchLatestPkgPath: ps4PatchChangeinfoPath: ps4PatchDayOne: 0 ps4attribUserManagement: 0 ps4attribMoveSupport: 0 ps4attrib3DSupport: 0 ps4attribShareSupport: 0 ps4attribExclusiveVR: 0 ps4disableAutoHideSplash: 0 ps4videoRecordingFeaturesUsed: 0 ps4contentSearchFeaturesUsed: 0 ps4CompatibilityPS5: 0 ps4AllowPS5Detection: 0 ps4GPU800MHz: 1 ps4attribEyeToEyeDistanceSettingVR: 0 ps4IncludedModules: [] ps4attribVROutputEnabled: 0 monoEnv: splashScreenBackgroundSourceLandscape: {fileID: 0} splashScreenBackgroundSourcePortrait: {fileID: 0} blurSplashScreenBackground: 1 spritePackerPolicy: webGLMemorySize: 256 webGLExceptionSupport: 1 webGLNameFilesAsHashes: 0 webGLDataCaching: 1 webGLDebugSymbols: 0 webGLEmscriptenArgs: webGLModulesDirectory: webGLTemplate: APPLICATION:Default webGLAnalyzeBuildSize: 0 webGLUseEmbeddedResources: 0 webGLCompressionFormat: 1 webGLWasmArithmeticExceptions: 0 webGLLinkerTarget: 1 webGLThreadsSupport: 0 webGLDecompressionFallback: 0 webGLPowerPreference: 2 scriptingDefineSymbols: {} additionalCompilerArguments: {} platformArchitecture: {} scriptingBackend: {} il2cppCompilerConfiguration: {} managedStrippingLevel: EmbeddedLinux: 1 GameCoreScarlett: 1 GameCoreXboxOne: 1 Lumin: 1 Nintendo Switch: 1 PS4: 1 PS5: 1 Stadia: 1 WebGL: 1 Windows Store Apps: 1 XboxOne: 1 iPhone: 1 tvOS: 1 incrementalIl2cppBuild: {} suppressCommonWarnings: 1 allowUnsafeCode: 0 useDeterministicCompilation: 1 enableRoslynAnalyzers: 1 additionalIl2CppArgs: scriptingRuntimeVersion: 1 gcIncremental: 1 assemblyVersionValidation: 1 gcWBarrierValidation: 0 apiCompatibilityLevelPerPlatform: {} m_RenderingPath: 1 m_MobileRenderingPath: 1 metroPackageName: Template_2D metroPackageVersion: metroCertificatePath: metroCertificatePassword: metroCertificateSubject: metroCertificateIssuer: metroCertificateNotAfter: 0000000000000000 metroApplicationDescription: Template_2D wsaImages: {} metroTileShortName: metroTileShowName: 0 metroMediumTileShowName: 0 metroLargeTileShowName: 0 metroWideTileShowName: 0 metroSupportStreamingInstall: 0 metroLastRequiredScene: 0 metroDefaultTileSize: 1 metroTileForegroundText: 2 metroTileBackgroundColor: {r: 0.13333334, g: 0.17254902, b: 0.21568628, a: 0} metroSplashScreenBackgroundColor: {r: 0.12941177, g: 0.17254902, b: 0.21568628, a: 1} metroSplashScreenUseBackgroundColor: 0 platformCapabilities: {} metroTargetDeviceFamilies: {} metroFTAName: metroFTAFileTypes: [] metroProtocolName: vcxProjDefaultLanguage: XboxOneProductId: XboxOneUpdateKey: XboxOneSandboxId: XboxOneContentId: XboxOneTitleId: XboxOneSCId: XboxOneGameOsOverridePath: XboxOnePackagingOverridePath: XboxOneAppManifestOverridePath: XboxOneVersion: 1.0.0.0 XboxOnePackageEncryption: 0 XboxOnePackageUpdateGranularity: 2 XboxOneDescription: XboxOneLanguage: - enus XboxOneCapability: [] XboxOneGameRating: {} XboxOneIsContentPackage: 0 XboxOneEnhancedXboxCompatibilityMode: 0 XboxOneEnableGPUVariability: 1 XboxOneSockets: {} XboxOneSplashScreen: {fileID: 0} XboxOneAllowedProductIds: [] XboxOnePersistentLocalStorageSize: 0 XboxOneXTitleMemory: 8 XboxOneOverrideIdentityName: XboxOneOverrideIdentityPublisher: vrEditorSettings: {} cloudServicesEnabled: UNet: 1 luminIcon: m_Name: m_ModelFolderPath: m_PortalFolderPath: luminCert: m_CertPath: m_SignPackage: 1 luminIsChannelApp: 0 luminVersion: m_VersionCode: 1 m_VersionName: apiCompatibilityLevel: 6 activeInputHandler: 0 windowsGamepadBackendHint: 0 cloudProjectId: framebufferDepthMemorylessMode: 0 qualitySettingsNames: [] projectName: organizationId: cloudEnabled: 0 legacyClampBlendShapeWeights: 0 playerDataPath: forceSRGBBlit: 1 virtualTexturingSupportEnabled: 0 ================================================ FILE: ProjectSettings/ProjectVersion.txt ================================================ m_EditorVersion: 2021.3.36f1c1 m_EditorVersionWithRevision: 2021.3.36f1c1 (0ec3cfe9a403) ================================================ FILE: ProjectSettings/QualitySettings.asset ================================================ %YAML 1.1 %TAG !u! tag:unity3d.com,2011: --- !u!47 &1 QualitySettings: m_ObjectHideFlags: 0 serializedVersion: 5 m_CurrentQuality: 3 m_QualitySettings: - serializedVersion: 2 name: Very Low pixelLightCount: 0 shadows: 0 shadowResolution: 0 shadowProjection: 1 shadowCascades: 1 shadowDistance: 15 shadowNearPlaneOffset: 3 shadowCascade2Split: 0.33333334 shadowCascade4Split: {x: 0.06666667, y: 0.2, z: 0.46666667} shadowmaskMode: 0 blendWeights: 1 textureQuality: 1 anisotropicTextures: 0 antiAliasing: 0 softParticles: 0 softVegetation: 0 realtimeReflectionProbes: 0 billboardsFaceCameraPosition: 0 vSyncCount: 0 lodBias: 0.3 maximumLODLevel: 0 particleRaycastBudget: 4 asyncUploadTimeSlice: 2 asyncUploadBufferSize: 16 resolutionScalingFixedDPIFactor: 1 excludedTargetPlatforms: [] - serializedVersion: 2 name: Low pixelLightCount: 0 shadows: 0 shadowResolution: 0 shadowProjection: 1 shadowCascades: 1 shadowDistance: 20 shadowNearPlaneOffset: 3 shadowCascade2Split: 0.33333334 shadowCascade4Split: {x: 0.06666667, y: 0.2, z: 0.46666667} shadowmaskMode: 0 blendWeights: 2 textureQuality: 0 anisotropicTextures: 0 antiAliasing: 0 softParticles: 0 softVegetation: 0 realtimeReflectionProbes: 0 billboardsFaceCameraPosition: 0 vSyncCount: 0 lodBias: 0.4 maximumLODLevel: 0 particleRaycastBudget: 16 asyncUploadTimeSlice: 2 asyncUploadBufferSize: 16 resolutionScalingFixedDPIFactor: 1 excludedTargetPlatforms: [] - serializedVersion: 2 name: Medium pixelLightCount: 1 shadows: 0 shadowResolution: 0 shadowProjection: 1 shadowCascades: 1 shadowDistance: 20 shadowNearPlaneOffset: 3 shadowCascade2Split: 0.33333334 shadowCascade4Split: {x: 0.06666667, y: 0.2, z: 0.46666667} shadowmaskMode: 0 blendWeights: 2 textureQuality: 0 anisotropicTextures: 0 antiAliasing: 0 softParticles: 0 softVegetation: 0 realtimeReflectionProbes: 0 billboardsFaceCameraPosition: 0 vSyncCount: 1 lodBias: 0.7 maximumLODLevel: 0 particleRaycastBudget: 64 asyncUploadTimeSlice: 2 asyncUploadBufferSize: 16 resolutionScalingFixedDPIFactor: 1 excludedTargetPlatforms: [] - serializedVersion: 2 name: High pixelLightCount: 2 shadows: 0 shadowResolution: 1 shadowProjection: 1 shadowCascades: 2 shadowDistance: 40 shadowNearPlaneOffset: 3 shadowCascade2Split: 0.33333334 shadowCascade4Split: {x: 0.06666667, y: 0.2, z: 0.46666667} shadowmaskMode: 1 blendWeights: 2 textureQuality: 0 anisotropicTextures: 0 antiAliasing: 0 softParticles: 0 softVegetation: 1 realtimeReflectionProbes: 0 billboardsFaceCameraPosition: 0 vSyncCount: 1 lodBias: 1 maximumLODLevel: 0 particleRaycastBudget: 256 asyncUploadTimeSlice: 2 asyncUploadBufferSize: 16 resolutionScalingFixedDPIFactor: 1 excludedTargetPlatforms: [] - serializedVersion: 2 name: Very High pixelLightCount: 3 shadows: 0 shadowResolution: 2 shadowProjection: 1 shadowCascades: 2 shadowDistance: 70 shadowNearPlaneOffset: 3 shadowCascade2Split: 0.33333334 shadowCascade4Split: {x: 0.06666667, y: 0.2, z: 0.46666667} shadowmaskMode: 1 blendWeights: 4 textureQuality: 0 anisotropicTextures: 0 antiAliasing: 0 softParticles: 0 softVegetation: 1 realtimeReflectionProbes: 0 billboardsFaceCameraPosition: 0 vSyncCount: 1 lodBias: 1.5 maximumLODLevel: 0 particleRaycastBudget: 1024 asyncUploadTimeSlice: 2 asyncUploadBufferSize: 16 resolutionScalingFixedDPIFactor: 1 excludedTargetPlatforms: [] - serializedVersion: 2 name: Ultra pixelLightCount: 4 shadows: 0 shadowResolution: 0 shadowProjection: 1 shadowCascades: 4 shadowDistance: 150 shadowNearPlaneOffset: 3 shadowCascade2Split: 0.33333334 shadowCascade4Split: {x: 0.06666667, y: 0.2, z: 0.46666667} shadowmaskMode: 1 blendWeights: 4 textureQuality: 0 anisotropicTextures: 0 antiAliasing: 0 softParticles: 0 softVegetation: 1 realtimeReflectionProbes: 0 billboardsFaceCameraPosition: 0 vSyncCount: 1 lodBias: 2 maximumLODLevel: 0 particleRaycastBudget: 4096 asyncUploadTimeSlice: 2 asyncUploadBufferSize: 16 resolutionScalingFixedDPIFactor: 1 excludedTargetPlatforms: [] m_PerPlatformDefaultQuality: Android: 2 Nintendo 3DS: 5 Nintendo Switch: 5 PS4: 5 PSM: 5 PSP2: 2 Standalone: 5 Tizen: 2 WebGL: 3 WiiU: 5 Windows Store Apps: 5 XboxOne: 5 iPhone: 2 tvOS: 2 ================================================ FILE: ProjectSettings/SceneTemplateSettings.json ================================================ { "templatePinStates": [], "dependencyTypeInfos": [ { "userAdded": false, "type": "UnityEngine.AnimationClip", "defaultInstantiationMode": 0 }, { "userAdded": false, "type": "UnityEditor.Animations.AnimatorController", "defaultInstantiationMode": 0 }, { "userAdded": false, "type": "UnityEngine.AnimatorOverrideController", "defaultInstantiationMode": 0 }, { "userAdded": false, "type": "UnityEditor.Audio.AudioMixerController", "defaultInstantiationMode": 0 }, { "userAdded": false, "type": "UnityEngine.ComputeShader", "defaultInstantiationMode": 1 }, { "userAdded": false, "type": "UnityEngine.Cubemap", "defaultInstantiationMode": 0 }, { "userAdded": false, "type": "UnityEngine.GameObject", "defaultInstantiationMode": 0 }, { "userAdded": false, "type": "UnityEditor.LightingDataAsset", "defaultInstantiationMode": 0 }, { "userAdded": false, "type": "UnityEngine.LightingSettings", "defaultInstantiationMode": 0 }, { "userAdded": false, "type": "UnityEngine.Material", "defaultInstantiationMode": 0 }, { "userAdded": false, "type": "UnityEditor.MonoScript", "defaultInstantiationMode": 1 }, { "userAdded": false, "type": "UnityEngine.PhysicMaterial", "defaultInstantiationMode": 0 }, { "userAdded": false, "type": "UnityEngine.PhysicsMaterial2D", "defaultInstantiationMode": 0 }, { "userAdded": false, "type": "UnityEngine.Rendering.PostProcessing.PostProcessProfile", "defaultInstantiationMode": 0 }, { "userAdded": false, "type": "UnityEngine.Rendering.PostProcessing.PostProcessResources", "defaultInstantiationMode": 0 }, { "userAdded": false, "type": "UnityEngine.Rendering.VolumeProfile", "defaultInstantiationMode": 0 }, { "userAdded": false, "type": "UnityEditor.SceneAsset", "defaultInstantiationMode": 1 }, { "userAdded": false, "type": "UnityEngine.Shader", "defaultInstantiationMode": 1 }, { "userAdded": false, "type": "UnityEngine.ShaderVariantCollection", "defaultInstantiationMode": 1 }, { "userAdded": false, "type": "UnityEngine.Texture", "defaultInstantiationMode": 0 }, { "userAdded": false, "type": "UnityEngine.Texture2D", "defaultInstantiationMode": 0 }, { "userAdded": false, "type": "UnityEngine.Timeline.TimelineAsset", "defaultInstantiationMode": 0 } ], "defaultDependencyTypeInfo": { "userAdded": false, "type": "", "defaultInstantiationMode": 1 }, "newSceneOverride": 0 } ================================================ FILE: ProjectSettings/TagManager.asset ================================================ %YAML 1.1 %TAG !u! tag:unity3d.com,2011: --- !u!78 &1 TagManager: serializedVersion: 2 tags: [] layers: - Default - TransparentFX - Ignore Raycast - - Water - UI - - - - - - - - - - - - - - - - - - - - - - - - - - m_SortingLayers: - name: Default uniqueID: 0 locked: 0 ================================================ FILE: ProjectSettings/TimeManager.asset ================================================ %YAML 1.1 %TAG !u! tag:unity3d.com,2011: --- !u!5 &1 TimeManager: m_ObjectHideFlags: 0 Fixed Timestep: 0.02 Maximum Allowed Timestep: 0.1 m_TimeScale: 1 Maximum Particle Timestep: 0.03 ================================================ FILE: ProjectSettings/UnityConnectSettings.asset ================================================ %YAML 1.1 %TAG !u! tag:unity3d.com,2011: --- !u!310 &1 UnityConnectSettings: m_ObjectHideFlags: 0 serializedVersion: 1 m_Enabled: 0 m_TestMode: 0 m_EventOldUrl: https://api.uca.cloud.unity3d.com/v1/events m_EventUrl: https://cdp.cloud.unity3d.com/v1/events m_ConfigUrl: https://config.uca.cloud.unity3d.com m_TestInitMode: 0 CrashReportingSettings: m_EventUrl: https://perf-events.cloud.unity3d.com m_Enabled: 0 m_LogBufferSize: 10 m_CaptureEditorExceptions: 1 UnityPurchasingSettings: m_Enabled: 0 m_TestMode: 0 UnityAnalyticsSettings: m_Enabled: 0 m_TestMode: 0 m_InitializeOnStartup: 1 UnityAdsSettings: m_Enabled: 0 m_InitializeOnStartup: 1 m_TestMode: 0 m_IosGameId: m_AndroidGameId: m_GameIds: {} m_GameId: PerformanceReportingSettings: m_Enabled: 0 ================================================ FILE: ProjectSettings/VFXManager.asset ================================================ %YAML 1.1 %TAG !u! tag:unity3d.com,2011: --- !u!937362698 &1 VFXManager: m_ObjectHideFlags: 0 m_IndirectShader: {fileID: 0} m_CopyBufferShader: {fileID: 0} m_SortShader: {fileID: 0} m_RenderPipeSettingsPath: m_FixedTimeStep: 0.016666668 m_MaxDeltaTime: 0.05 ================================================ FILE: ProjectSettings/VersionControlSettings.asset ================================================ %YAML 1.1 %TAG !u! tag:unity3d.com,2011: --- !u!890905787 &1 VersionControlSettings: m_ObjectHideFlags: 0 m_Mode: Visible Meta Files m_CollabEditorSettings: inProgressEnabled: 1 ================================================ FILE: ProjectSettings/boot.config ================================================ ================================================ FILE: README.md ================================================ # kcp-unity KCP是一种基于UDP的上层协议,项目地址:[KCP – A Fast and Reliable ARQ Protocol](https://github.com/skywind3000/kcp) kcp就是在udp上再做了一层封装,来实现TCP的效果并且弥补TCP的一些不足。使用kcp的方式就是和服务器连接一个udp连接,然后再udp的通信过程中,使用kcp库对字节进行处理。然后通过kcp拿到数据包。kcp可以保证你拿到的包,一,不会丢包,二,保证数据包的发送顺序和接收顺序一致。这其实就是tcp的功能。但是kcp的速度比tcp要快的多。 ## KCP CSharp unity接入kcp的话,肯定要c#版本的,但是同样是c#版本kcp,也是有区别的, ### 第一种方式:c#移植版 移植版,就是使用c#语言翻译原版c的kcp库。一般这样实现的库有很多,但是质量参差不齐,有的是完美移植,有的可能会遗留一些问题,比如gc太多等 目前用的还比较多的有: - [kcp-csharp](https://github.com/limpo1989/kcp-csharp): kcp的 csharp移植,同时包含一份回话管理,可以连接上面kcp-go的服务端,且和kcp-go的语法相似。 - [kcp-csharp](https://github.com/KumoKyaku/KCP): 新版本 Kcp的 csharp移植。线程安全,运行时无alloc,对gc无压力。 ### 第二种方式:动态库版 动态库版,就是把原版c的kcp库,进行动态库编译,编译为ios,android,pc,linux等平台的库,然后我们使用C#去封装一个udp,然后调用kcp的动态链接库。 这种方式,好处是可以保留原版库的所有特性,不用考虑优化语法的问题了,而且性能比较好。但是坏处就是编译动态库较麻烦,需要针对所有平台...可想而知 [unity3d构建kcp跨平台动态链接库](https://github.com/smilehao/kcp_bulild) 因此本仓库就是基于以上动态链接库实现的,基于[ET框架](https://github.com/egametang/ET)修改而成。 ET框架中网络有自己的封装,包括了server和client。理解起来稍微有点麻烦,所以我把部分逻辑简化,封装了 一个纯client的。其中tcp和kcp均可直接和服务器(go/java)对接 详情链接:https://passion.blog.csdn.net/article/details/112722271 # 友情链接 [skywind3000/kcp](https://github.com/skywind3000/kcp) [xtaci/kcp-go](https://github.com/xtaci/kcp-go) 注意事项: go版本的kcp的demo:echo.go中默认使用了加密模式 kcp.ListenWithOptions("127.0.0.1:12345", block, 10, 3) 这种情况下回合C#端的kcp对接不上。造成死活连接不上的问题 解决方法: 1.改成kcp.ListenWithOptions("127.0.0.1:3656", nil, 0, 0) 2.改成kcp.Listen("127.0.0.1:3656")