mirror of
https://github.com/Alex-Rachel/TEngine.git
synced 2025-08-14 16:51:28 +00:00
Compare commits
237 Commits
TEngine3.0
...
TEngine4.0
Author | SHA1 | Date | |
---|---|---|---|
![]() |
01f8eb9d57 | ||
![]() |
f5021a9688 | ||
![]() |
a632f7a5ad | ||
![]() |
5f968f4154 | ||
![]() |
c9fe83c2bd | ||
![]() |
2c00d103cb | ||
![]() |
1d56437d9f | ||
![]() |
69db1ff977 | ||
![]() |
48887b1aee | ||
![]() |
381ea8bb8d | ||
![]() |
7401edac15 | ||
![]() |
b1c7f30be9 | ||
![]() |
cfaf82a623 | ||
![]() |
6992d12c6c | ||
![]() |
119d9683ad | ||
![]() |
9478868513 | ||
![]() |
6ed32082e1 | ||
![]() |
6ee515e8c5 | ||
![]() |
b839afa76a | ||
![]() |
d9605b348a | ||
![]() |
dfef83919c | ||
![]() |
f5f983f220 | ||
![]() |
d61b1206ee | ||
![]() |
3650ba1a8b | ||
![]() |
8f14a4d2cb | ||
![]() |
dc22e595c9 | ||
![]() |
0e70f7d446 | ||
![]() |
039569b2d4 | ||
![]() |
ea38004ba2 | ||
![]() |
cc97c0583a | ||
![]() |
887094a4b1 | ||
![]() |
0d09a7e73b | ||
![]() |
d8f8514f9d | ||
![]() |
f2f6b2422f | ||
![]() |
3a9cad9397 | ||
![]() |
5e70e7972e | ||
![]() |
8d2b4200d6 | ||
![]() |
b983e85416 | ||
![]() |
99d2afdbd7 | ||
![]() |
57ce836b3c | ||
![]() |
8dce78d6fb | ||
![]() |
6d41adffd9 | ||
![]() |
cb9129261b | ||
![]() |
89dd6214d4 | ||
![]() |
1aec76d64c | ||
![]() |
859f654f6d | ||
![]() |
391d690f9d | ||
![]() |
bb0b4104f9 | ||
![]() |
20d0ecd8da | ||
![]() |
8c3d6308b9 | ||
![]() |
4c8c37ffd8 | ||
![]() |
5f694c2bed | ||
![]() |
7ff74bb747 | ||
![]() |
a5de63397a | ||
![]() |
13cc62f3f1 | ||
![]() |
95dfac5294 | ||
![]() |
14e95107c9 | ||
![]() |
caf5b2b54e | ||
![]() |
285483034e | ||
![]() |
1cdd8b63b4 | ||
![]() |
4c748df7ac | ||
![]() |
e1229b5a4b | ||
![]() |
b937fb1a37 | ||
![]() |
87ab99b363 | ||
![]() |
e3a47393f4 | ||
![]() |
75725314ad | ||
![]() |
d3ed2b21b9 | ||
![]() |
ec34dfbb16 | ||
![]() |
bd76e3a651 | ||
![]() |
278c8f23be | ||
![]() |
42568db2ab | ||
![]() |
b033c59b00 | ||
![]() |
04a43a3f11 | ||
![]() |
54214cdd0b | ||
![]() |
fa870b6228 | ||
![]() |
8a49d3437b | ||
![]() |
f19b889deb | ||
![]() |
d55f6e29d9 | ||
![]() |
8e4af31f26 | ||
![]() |
58a4b3e043 | ||
![]() |
576bf3bb48 | ||
![]() |
d282b81546 | ||
![]() |
d6dcd8851c | ||
![]() |
bbea9c4cee | ||
![]() |
b1ccb1fd53 | ||
![]() |
0f2ad3c71d | ||
![]() |
0e6851e691 | ||
![]() |
75b9956261 | ||
![]() |
401c397fc8 | ||
![]() |
94b314e91f | ||
![]() |
a6573d9336 | ||
![]() |
abf5357f49 | ||
![]() |
aab353cca3 | ||
![]() |
e13071c4db | ||
![]() |
e071c20214 | ||
![]() |
bf73ce333b | ||
![]() |
d7a60002d4 | ||
![]() |
61f657322d | ||
![]() |
33223dc02e | ||
![]() |
0661c59877 | ||
![]() |
7c74e10857 | ||
![]() |
db935bfb5f | ||
![]() |
dc6b7bb21e | ||
![]() |
d32f3cb768 | ||
![]() |
a32ab30444 | ||
![]() |
aa0d40c758 | ||
![]() |
5239b89326 | ||
![]() |
53f0ecb6c1 | ||
![]() |
ea9447d0ea | ||
![]() |
0c6d59f53e | ||
![]() |
9986e22d18 | ||
![]() |
7deb049769 | ||
![]() |
fce8bd4d60 | ||
![]() |
7ec14c670f | ||
![]() |
efe3d2b25b | ||
![]() |
2107282368 | ||
![]() |
fb26ea2297 | ||
![]() |
9ac150425f | ||
![]() |
36d2c146b0 | ||
![]() |
774b73bbbf | ||
![]() |
32366eb127 | ||
![]() |
a843617e5f | ||
![]() |
46b139f7cf | ||
![]() |
e6ff1dec3f | ||
![]() |
4429732010 | ||
![]() |
60a5caebae | ||
![]() |
f7c95d8216 | ||
![]() |
98dcb80942 | ||
![]() |
30192d52cf | ||
![]() |
b7b2262d53 | ||
![]() |
d1c93f15d6 | ||
![]() |
1ac1ff7d56 | ||
![]() |
863788f303 | ||
![]() |
74790c7486 | ||
![]() |
1c223c8ad0 | ||
![]() |
e75b3a4e66 | ||
![]() |
91b0995911 | ||
![]() |
14c886ea8f | ||
![]() |
9babc0ba85 | ||
![]() |
f8056aef32 | ||
![]() |
846dc4d4bc | ||
![]() |
03ab7fb353 | ||
![]() |
3c11980e7c | ||
![]() |
c23aa0bd71 | ||
![]() |
df76d0b77a | ||
![]() |
35d2012546 | ||
![]() |
c96d20a89a | ||
![]() |
5787d0f9dc | ||
![]() |
8b35c8ca07 | ||
![]() |
e5456da482 | ||
![]() |
144ba9f222 | ||
![]() |
889fbdc8e1 | ||
![]() |
0d177e6868 | ||
![]() |
29135228be | ||
![]() |
c1a1de73cd | ||
![]() |
612e9b7eba | ||
![]() |
5ed6b8c378 | ||
![]() |
06dad5a68a | ||
![]() |
9e0462043c | ||
![]() |
c1178e284b | ||
![]() |
8e3dd138a6 | ||
![]() |
405253c507 | ||
![]() |
66ef50a9e0 | ||
![]() |
413f4dcda7 | ||
![]() |
75fdb4d7de | ||
![]() |
1ada1e4e33 | ||
![]() |
3848b6aaaa | ||
![]() |
2b33b405c0 | ||
![]() |
6766b930d7 | ||
![]() |
526baf45fa | ||
![]() |
a473971cfb | ||
![]() |
51effd2c97 | ||
![]() |
4205220b64 | ||
![]() |
e95c18ca67 | ||
![]() |
f5571716a2 | ||
![]() |
d61a8dfa4b | ||
![]() |
068ec709d0 | ||
![]() |
8b26b790ee | ||
![]() |
da35b4306b | ||
![]() |
ee2147e3d9 | ||
![]() |
a273e9d5f8 | ||
![]() |
31d4d6c0b8 | ||
![]() |
6a6f0591f1 | ||
![]() |
9cd0bac81e | ||
![]() |
4abe10eecf | ||
![]() |
bd10297dfa | ||
![]() |
7ce72b8aa8 | ||
![]() |
c6ecb48944 | ||
![]() |
c178f8bb75 | ||
![]() |
d4f160d284 | ||
![]() |
d87653a9fb | ||
![]() |
04a5a0e048 | ||
![]() |
c825f4d920 | ||
![]() |
b3e59b92d6 | ||
![]() |
37af620d71 | ||
![]() |
493172a925 | ||
![]() |
1382db8c61 | ||
![]() |
72b7149aa1 | ||
![]() |
0c51ae7bdd | ||
![]() |
79dc302f14 | ||
![]() |
95e4dd93a8 | ||
![]() |
2b310c00f2 | ||
![]() |
fbb26ea9ea | ||
![]() |
bff8f20af3 | ||
![]() |
0c7ce5f28c | ||
![]() |
31fc9abdf9 | ||
![]() |
a087f40e8a | ||
![]() |
7b2fc7ce9f | ||
![]() |
dc4bb8cc9c | ||
![]() |
0c8f3a5f92 | ||
![]() |
a69f53592e | ||
![]() |
8757e1f550 | ||
![]() |
336d4b2eb9 | ||
![]() |
e0be062006 | ||
![]() |
5e69129667 | ||
![]() |
4c39ab79ca | ||
![]() |
a710d1cb76 | ||
![]() |
8472839394 | ||
![]() |
4988c99d31 | ||
![]() |
4fa62b2d79 | ||
![]() |
f549514d7c | ||
![]() |
f588038848 | ||
![]() |
9e0e3ebd50 | ||
![]() |
b07ef836a9 | ||
![]() |
7a1d593195 | ||
![]() |
bc113c5c6e | ||
![]() |
753bbdfb82 | ||
![]() |
6a6d33c536 | ||
![]() |
57a014a83a | ||
![]() |
9d1a8e8c9d | ||
![]() |
86c26cd21b | ||
![]() |
b60b9fff42 | ||
![]() |
990e17a6cc | ||
![]() |
be531bfad0 | ||
![]() |
6c46f3e5fd | ||
![]() |
e4b48e3c58 | ||
![]() |
68e5b1d482 |
122
.gitignore
vendored
122
.gitignore
vendored
@@ -1,119 +1,15 @@
|
||||
# This .gitignore file should be placed at the root of your Unity project directory
|
||||
#
|
||||
# Get latest from https://github.com/github/gitignore/blob/master/Unity.gitignore
|
||||
#
|
||||
/[Ll]ibrary/
|
||||
/[Tt]emp/
|
||||
/[Oo]bj/
|
||||
/[Bb]uild/
|
||||
/[Bb]uilds/
|
||||
/[Ll]ogs/
|
||||
/[Mm]emoryCaptures/
|
||||
/EditorBuild/
|
||||
# /[Aa]ssets/TResources/DLL/
|
||||
/[Aa]ssets/StreamingAssets
|
||||
/BuildBundleInfo/
|
||||
[Aa]ssets/AATest/
|
||||
[Aa]ssets/AATest.meta
|
||||
|
||||
# Asset meta data should only be ignored when the corresponding asset is also ignored
|
||||
!/[Aa]ssets/**/*.meta
|
||||
|
||||
# Uncomment this line if you wish to ignore the asset store tools plugin
|
||||
# /[Aa]ssets/AssetStoreTools*
|
||||
|
||||
# Autogenerated Jetbrains Rider plugin
|
||||
[Aa]ssets/Plugins/Editor/JetBrains*
|
||||
|
||||
# Visual Studio cache directory
|
||||
.vs/
|
||||
|
||||
# Gradle cache directory
|
||||
.gradle/
|
||||
|
||||
# Autogenerated VS/MD/Consulo solution and project files
|
||||
ExportedObj/
|
||||
.consulo/
|
||||
*.csproj
|
||||
*.unityproj
|
||||
*.sln
|
||||
*.suo
|
||||
*.tmp
|
||||
*.user
|
||||
*.userprefs
|
||||
*.pidb
|
||||
*.booproj
|
||||
*.svd
|
||||
*.pdb
|
||||
*.mdb
|
||||
*.opendb
|
||||
*.VC.db
|
||||
|
||||
# Unity3D generated meta files
|
||||
*.pidb.meta
|
||||
*.pdb.meta
|
||||
*.mdb.meta
|
||||
|
||||
# Unity3D generated file on crash reports
|
||||
sysinfo.txt
|
||||
|
||||
# Builds
|
||||
*.apk
|
||||
|
||||
# Crashlytics generated file
|
||||
crashlytics-build.properties
|
||||
|
||||
# TEnginePersistentDataPath
|
||||
TEnginePersistentDataPath/
|
||||
|
||||
# Hotfix
|
||||
TEngineHotUpdate/bin
|
||||
TEngineHotUpdate/obj
|
||||
|
||||
#HybirdCLR(HuaTuo)
|
||||
/HybirdCLRData/
|
||||
[Hh]ybridCLRData/
|
||||
|
||||
|
||||
#AATemp
|
||||
[Aa]ssets/AATemp/
|
||||
[Aa]ssets/AATemp.meta
|
||||
|
||||
#Rider
|
||||
/.idea/
|
||||
|
||||
# ABConfig
|
||||
[Aa]ssets/BuildConfig/
|
||||
[Aa]ssets/BuildConfig.meta
|
||||
|
||||
[Aa]ssets/StreamingAssets/
|
||||
[Aa]ssets/StreamingAssets.meta
|
||||
Assets/HybridCLRBuildCache/AssetBundleOutput.meta
|
||||
Assets/HybridCLRBuildCache/AssetBundleOutput/StandaloneWindows.meta
|
||||
Assets/HybridCLRBuildCache.meta
|
||||
Assets/HybridCLRBuildCache/AssetBundleSourceData.meta
|
||||
Assets/HybridCLRBuildCache/AssetBundleSourceData/StandaloneWindows.meta
|
||||
|
||||
#Bundles
|
||||
Bundles/
|
||||
|
||||
#Sandbox
|
||||
Sandbox/
|
||||
UnityProject/UserSettings/Layouts/default-2021.dwlt
|
||||
UnityProject/UserSettings/Search.settings
|
||||
|
||||
#Luban
|
||||
Luban/.cache.meta
|
||||
Tools/Luban/
|
||||
Tools/Luban.ClientServer/
|
||||
Configs/.cache.meta
|
||||
GenerateDatas/
|
||||
|
||||
#HybridCLR
|
||||
Assets/HybridCLRData.meta
|
||||
UserSettings/Search.settings
|
||||
#FileServer
|
||||
Tools/FileServer/AssetRoot
|
||||
|
||||
#Unity UserSettings
|
||||
UserSettings/Search.index
|
||||
UserSettings/Layouts/default-2021.dwlt
|
||||
|
||||
#UnityOnlineServiceData
|
||||
Assets/UnityOnlineServiceData.meta
|
||||
Assets/UnityOnlineServiceData
|
||||
|
||||
.DS_Store
|
||||
UnityProject/ProjectSettings/CommonBurstAotSettings.json
|
||||
UnityProject/ProjectSettings/BurstAotSettings_StandaloneWindows.json
|
||||
|
@@ -1,8 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1a8431d2a64361c4d821790ed88aeb3d
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 4343727234628468602
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -1,62 +0,0 @@
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace TEngine.Editor.Inspector
|
||||
{
|
||||
[CustomEditor(typeof(Network))]
|
||||
internal sealed class NetworkInspector : GameFrameworkInspector
|
||||
{
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
base.OnInspectorGUI();
|
||||
|
||||
if (!EditorApplication.isPlaying)
|
||||
{
|
||||
EditorGUILayout.HelpBox("Available during runtime only.", MessageType.Info);
|
||||
return;
|
||||
}
|
||||
|
||||
Network t = (Network)target;
|
||||
|
||||
if (IsPrefabInHierarchy(t.gameObject))
|
||||
{
|
||||
EditorGUILayout.LabelField("Network Channel Count", t.NetworkChannelCount.ToString());
|
||||
|
||||
INetworkChannel[] networkChannels = t.GetAllNetworkChannels();
|
||||
foreach (INetworkChannel networkChannel in networkChannels)
|
||||
{
|
||||
DrawNetworkChannel(networkChannel);
|
||||
}
|
||||
}
|
||||
|
||||
Repaint();
|
||||
}
|
||||
|
||||
private void DrawNetworkChannel(INetworkChannel networkChannel)
|
||||
{
|
||||
EditorGUILayout.BeginVertical("box");
|
||||
{
|
||||
EditorGUILayout.LabelField(networkChannel.Name, networkChannel.Connected ? "Connected" : "Disconnected");
|
||||
EditorGUILayout.LabelField("Service Type", networkChannel.ServiceType.ToString());
|
||||
EditorGUILayout.LabelField("Address Family", networkChannel.AddressFamily.ToString());
|
||||
EditorGUILayout.LabelField("Local Address", networkChannel.Connected ? networkChannel.Socket.LocalEndPoint.ToString() : "Unavailable");
|
||||
EditorGUILayout.LabelField("Remote Address", networkChannel.Connected ? networkChannel.Socket.RemoteEndPoint.ToString() : "Unavailable");
|
||||
EditorGUILayout.LabelField("Send Packet", Utility.Text.Format("{0} / {1}", networkChannel.SendPacketCount, networkChannel.SentPacketCount));
|
||||
EditorGUILayout.LabelField("Receive Packet", Utility.Text.Format("{0} / {1}", networkChannel.ReceivePacketCount, networkChannel.ReceivedPacketCount));
|
||||
EditorGUILayout.LabelField("Miss Heart Beat Count", networkChannel.MissHeartBeatCount.ToString());
|
||||
EditorGUILayout.LabelField("Heart Beat", Utility.Text.Format("{0:F2} / {1:F2}", networkChannel.HeartBeatElapseSeconds, networkChannel.HeartBeatInterval));
|
||||
EditorGUI.BeginDisabledGroup(!networkChannel.Connected);
|
||||
{
|
||||
if (GUILayout.Button("Disconnect"))
|
||||
{
|
||||
networkChannel.Close();
|
||||
}
|
||||
}
|
||||
EditorGUI.EndDisabledGroup();
|
||||
}
|
||||
EditorGUILayout.EndVertical();
|
||||
|
||||
EditorGUILayout.Separator();
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,3 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3301ba180cdc446bbdf823c860ae7a68
|
||||
timeCreated: 1682045195
|
@@ -1,3 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 890abd3a957e406ab422fc468ba6c169
|
||||
timeCreated: 1682353243
|
@@ -1,6 +0,0 @@
|
||||
namespace BattleCore.Runtime
|
||||
{
|
||||
public static class EntityExtension
|
||||
{
|
||||
}
|
||||
}
|
@@ -1,27 +0,0 @@
|
||||
using UnityEngine;
|
||||
using Unity.Mathematics;
|
||||
|
||||
namespace BattleCore.Runtime
|
||||
{
|
||||
public static class MathematicsExt
|
||||
{
|
||||
public static int2 ToInt2(this Vector2Int vec) => new int2(vec.x, vec.y);
|
||||
public static int3 ToInt3(this Vector3Int vec) => new int3(vec.x, vec.y, vec.z);
|
||||
public static float2 ToFloat2(this Vector2 vec) => new float2(vec.x, vec.y);
|
||||
public static float3 ToFloat3(this Vector3 vec) => new float3(vec.x, vec.y, vec.z);
|
||||
|
||||
public static bool IsEquals(this int2 a, int2 b) => math.all(a == b);
|
||||
public static bool IsEquals(this int3 a, int3 b) => math.all(a == b);
|
||||
|
||||
|
||||
public static Vector2Int ToVec2(this int2 vec) => new Vector2Int(vec.x, vec.y);
|
||||
public static Vector3Int ToVec3(this int2 vec) => new Vector3Int(vec.x, vec.y, 0);
|
||||
public static Vector3Int ToVec3(this int3 vec) => new Vector3Int(vec.x, vec.y, vec.z);
|
||||
public static Vector2 ToVec2(this float2 vec) => new Vector2(vec.x, vec.y);
|
||||
public static Vector3 ToVec3(this float3 vec) => new Vector3(vec.x, vec.y, vec.z);
|
||||
public static int ManhattanDist(this int2 vec) => vec.x + vec.y;
|
||||
public static int ManhattanDist(this int3 vec) => vec.x + vec.y + vec.z;
|
||||
public static float ManhattanDist(this float2 vec) => vec.x + vec.y;
|
||||
public static float ManhattanDist(this float3 vec) => vec.x + vec.y + vec.z;
|
||||
}
|
||||
}
|
@@ -1,3 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2b0e288b918a41698571ec3d36059851
|
||||
timeCreated: 1682353251
|
@@ -1,21 +0,0 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace BattleCore.Runtime
|
||||
{
|
||||
public class Empty : MonoBehaviour
|
||||
{
|
||||
// Start is called before the first frame update
|
||||
void Start()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
// Update is called once per frame
|
||||
void Update()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,33 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace TEngine
|
||||
{
|
||||
#region Attribute
|
||||
|
||||
[AttributeUsage(AttributeTargets.Class)]
|
||||
public class UpdateAttribute : Attribute
|
||||
{
|
||||
}
|
||||
|
||||
[AttributeUsage(AttributeTargets.Class)]
|
||||
public class FixedUpdateAttribute : Attribute
|
||||
{
|
||||
}
|
||||
|
||||
[AttributeUsage(AttributeTargets.Class)]
|
||||
public class LateUpdateAttribute : Attribute
|
||||
{
|
||||
}
|
||||
|
||||
[AttributeUsage(AttributeTargets.Class)]
|
||||
public class RoleLoginAttribute : Attribute
|
||||
{
|
||||
}
|
||||
|
||||
[AttributeUsage(AttributeTargets.Class)]
|
||||
public class RoleLogoutAttribute : Attribute
|
||||
{
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
@@ -1,3 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 80791cccf63d44faa348884ea0c506a2
|
||||
timeCreated: 1684553027
|
@@ -1,16 +0,0 @@
|
||||
namespace GameBase
|
||||
{
|
||||
public class BaseClsTemplate<T>
|
||||
{
|
||||
protected static T Imp;
|
||||
|
||||
/// <summary>
|
||||
/// Unity工程,注册处理函数。
|
||||
/// </summary>
|
||||
/// <param name="imp">实现类。</param>
|
||||
public static void RegisterImp(T imp)
|
||||
{
|
||||
Imp = imp;
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,3 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b062b3e32edd4536a4308a3d180842e0
|
||||
timeCreated: 1681989133
|
@@ -1,190 +0,0 @@
|
||||
using UnityEngine;
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Linq;
|
||||
|
||||
/*******************************************************************************
|
||||
//开启一个Loom进程
|
||||
Loom.RunAsync(() =>
|
||||
{
|
||||
aucThread = new Thread(ReceiveMsg);
|
||||
aucThread.Start();
|
||||
}
|
||||
|
||||
//进程调用主线程方法
|
||||
MainPack pack = (MainPack)MainPack.Descriptor.Parser.ParseFrom(buffer, 0, len);
|
||||
Loom.QueueOnMainThread((param) =>
|
||||
{
|
||||
UdpHandleResponse(pack);
|
||||
}, null);
|
||||
|
||||
*******************************************************************************/
|
||||
namespace GameBase
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Loom多线程通信。
|
||||
/// <remarks></remarks>
|
||||
/// </summary>
|
||||
public class Loom : MonoBehaviour
|
||||
{
|
||||
public Dictionary<string, CancellationTokenSource> TokenSourcesDictionary = new Dictionary<string, CancellationTokenSource>();
|
||||
private static readonly int MaxThreads = 8;
|
||||
private static int _numThreads;
|
||||
private static Loom _current;
|
||||
|
||||
public static Loom Current
|
||||
{
|
||||
get
|
||||
{
|
||||
Initialize();
|
||||
return _current;
|
||||
}
|
||||
}
|
||||
|
||||
public void Awake()
|
||||
{
|
||||
_current = this;
|
||||
_initialized = true;
|
||||
}
|
||||
|
||||
protected void OnDestroy()
|
||||
{
|
||||
}
|
||||
|
||||
private static bool _initialized;
|
||||
|
||||
private static void Initialize()
|
||||
{
|
||||
if (!_initialized)
|
||||
{
|
||||
if (!Application.isPlaying)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_initialized = true;
|
||||
|
||||
var obj = new GameObject("[Loom]");
|
||||
|
||||
_current = obj.AddComponent<Loom>();
|
||||
|
||||
DontDestroyOnLoad(obj);
|
||||
}
|
||||
}
|
||||
|
||||
public struct NoDelayedQueueItem
|
||||
{
|
||||
public Action<object> Action;
|
||||
public object Param;
|
||||
}
|
||||
|
||||
private readonly List<NoDelayedQueueItem> _actions = new List<NoDelayedQueueItem>();
|
||||
|
||||
public struct DelayedQueueItem
|
||||
{
|
||||
public float Time;
|
||||
public Action<object> Action;
|
||||
public object Param;
|
||||
}
|
||||
|
||||
private readonly List<DelayedQueueItem> _delayed = new List<DelayedQueueItem>();
|
||||
|
||||
private readonly List<DelayedQueueItem> _currentDelayed = new List<DelayedQueueItem>();
|
||||
|
||||
public static void QueueOnMainThread(Action<object> taction, object param, float time = 0f)
|
||||
{
|
||||
if (time != 0f)
|
||||
{
|
||||
lock (Current._delayed)
|
||||
{
|
||||
Current._delayed.Add(new DelayedQueueItem { Time = Time.time + time, Action = taction, Param = param });
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
lock (Current._actions)
|
||||
{
|
||||
Current._actions.Add(new NoDelayedQueueItem { Action = taction, Param = param });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static Thread RunAsync(Action action)
|
||||
{
|
||||
Initialize();
|
||||
while (_numThreads >= MaxThreads)
|
||||
{
|
||||
Thread.Sleep(100);
|
||||
}
|
||||
|
||||
Interlocked.Increment(ref _numThreads);
|
||||
ThreadPool.QueueUserWorkItem(RunAction, action);
|
||||
return null;
|
||||
}
|
||||
|
||||
private static void RunAction(object action)
|
||||
{
|
||||
try
|
||||
{
|
||||
((Action)action)();
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
finally
|
||||
{
|
||||
Interlocked.Decrement(ref _numThreads);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void OnDisable()
|
||||
{
|
||||
if (_current == this)
|
||||
{
|
||||
_current = null;
|
||||
}
|
||||
}
|
||||
|
||||
private readonly List<NoDelayedQueueItem> _currentActions = new List<NoDelayedQueueItem>();
|
||||
|
||||
void Update()
|
||||
{
|
||||
if (_actions.Count > 0)
|
||||
{
|
||||
lock (_actions)
|
||||
{
|
||||
_currentActions.Clear();
|
||||
_currentActions.AddRange(_actions);
|
||||
_actions.Clear();
|
||||
}
|
||||
|
||||
for (int i = 0; i < _currentActions.Count; i++)
|
||||
{
|
||||
_currentActions[i].Action(_currentActions[i].Param);
|
||||
}
|
||||
}
|
||||
|
||||
if (_delayed.Count > 0)
|
||||
{
|
||||
lock (_delayed)
|
||||
{
|
||||
_currentDelayed.Clear();
|
||||
_currentDelayed.AddRange(_delayed.Where(d => d.Time <= Time.time));
|
||||
for (int i = 0; i < _currentDelayed.Count; i++)
|
||||
{
|
||||
_delayed.Remove(_currentDelayed[i]);
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < _currentDelayed.Count; i++)
|
||||
{
|
||||
_currentDelayed[i].Action(_currentDelayed[i].Param);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@@ -1,3 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 464445719fff48a78021df4a4a6b05a5
|
||||
timeCreated: 1684552621
|
@@ -1,3 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 00565f0d362f4c36836804455e19c3df
|
||||
timeCreated: 1681990210
|
@@ -1,214 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using TEngine;
|
||||
using UnityEngine;
|
||||
using UnityEngine.SceneManagement;
|
||||
|
||||
namespace GameBase
|
||||
{
|
||||
/// <summary>
|
||||
/// 单例接口。
|
||||
/// </summary>
|
||||
public interface ISingleton
|
||||
{
|
||||
void Active();
|
||||
|
||||
void Release();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 单例管理器(统一化持久和释放)。
|
||||
/// </summary>
|
||||
public static class SingletonMgr
|
||||
{
|
||||
private static List<ISingleton> _singletonList;
|
||||
private static Dictionary<string, GameObject> _gameObjects;
|
||||
private static GameObject _root;
|
||||
|
||||
public static GameObject Root
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_root == null)
|
||||
{
|
||||
_root = GameObject.Find("[GameModule]");
|
||||
|
||||
if (_root == null)
|
||||
{
|
||||
_root = new GameObject("[GameModule]")
|
||||
{
|
||||
transform =
|
||||
{
|
||||
position = Vector3.zero
|
||||
}
|
||||
};
|
||||
}
|
||||
Object.DontDestroyOnLoad(_root);
|
||||
}
|
||||
return _root;
|
||||
}
|
||||
}
|
||||
|
||||
public static void Retain(ISingleton go)
|
||||
{
|
||||
if (_singletonList == null)
|
||||
{
|
||||
_singletonList = new List<ISingleton>();
|
||||
}
|
||||
|
||||
_singletonList.Add(go);
|
||||
}
|
||||
|
||||
public static void Retain(GameObject go)
|
||||
{
|
||||
if (_gameObjects == null)
|
||||
{
|
||||
_gameObjects = new Dictionary<string, GameObject>();
|
||||
}
|
||||
|
||||
if (!_gameObjects.ContainsKey(go.name))
|
||||
{
|
||||
_gameObjects.Add(go.name, go);
|
||||
if (Application.isPlaying)
|
||||
{
|
||||
Object.DontDestroyOnLoad(go);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void Release(GameObject go)
|
||||
{
|
||||
if (_gameObjects != null && _gameObjects.ContainsKey(go.name))
|
||||
{
|
||||
_gameObjects.Remove(go.name);
|
||||
Object.Destroy(go);
|
||||
}
|
||||
}
|
||||
|
||||
public static void Release(ISingleton go)
|
||||
{
|
||||
if (_singletonList != null && _singletonList.Contains(go))
|
||||
{
|
||||
_singletonList.Remove(go);
|
||||
}
|
||||
}
|
||||
|
||||
public static void Release()
|
||||
{
|
||||
if (_gameObjects != null)
|
||||
{
|
||||
foreach (var item in _gameObjects)
|
||||
{
|
||||
UnityEngine.Object.Destroy(item.Value);
|
||||
}
|
||||
|
||||
_gameObjects.Clear();
|
||||
}
|
||||
|
||||
if (_singletonList != null)
|
||||
{
|
||||
for (int i = 0; i < _singletonList.Count; ++i)
|
||||
{
|
||||
_singletonList[i].Release();
|
||||
}
|
||||
|
||||
_singletonList.Clear();
|
||||
}
|
||||
|
||||
Resources.UnloadUnusedAssets();
|
||||
}
|
||||
|
||||
public static GameObject GetGameObject(string name)
|
||||
{
|
||||
GameObject go = null;
|
||||
if (_gameObjects != null)
|
||||
{
|
||||
_gameObjects.TryGetValue(name, out go);
|
||||
}
|
||||
|
||||
return go;
|
||||
}
|
||||
|
||||
internal static bool ContainsKey(string name)
|
||||
{
|
||||
if (_gameObjects != null)
|
||||
{
|
||||
return _gameObjects.ContainsKey(name);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
internal static ISingleton GetSingleton(string name)
|
||||
{
|
||||
for (int i = 0; i < _singletonList.Count; ++i)
|
||||
{
|
||||
if (_singletonList[i].ToString() == name)
|
||||
{
|
||||
return _singletonList[i];
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 释放所有单例。
|
||||
/// </summary>
|
||||
public static void ReStart()
|
||||
{
|
||||
Release();
|
||||
|
||||
SceneManager.LoadScene(0);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 全局单例对象(非线程安全)。
|
||||
/// </summary>
|
||||
/// <typeparam name="T">泛型T。</typeparam>
|
||||
public abstract class TSingleton<T> : ISingleton where T : TSingleton<T>, new()
|
||||
{
|
||||
private static T _instance;
|
||||
|
||||
public static T Instance
|
||||
{
|
||||
get
|
||||
{
|
||||
if (null == _instance)
|
||||
{
|
||||
_instance = new T();
|
||||
_instance.Init();
|
||||
#if UNITY_EDITOR
|
||||
Log.Info($"TSingleton Instance:{typeof(T).Name}");
|
||||
#endif
|
||||
SingletonMgr.Retain(_instance);
|
||||
}
|
||||
|
||||
return _instance;
|
||||
}
|
||||
}
|
||||
|
||||
public static bool IsValid => _instance != null;
|
||||
|
||||
protected TSingleton()
|
||||
{
|
||||
}
|
||||
|
||||
protected virtual void Init()
|
||||
{
|
||||
}
|
||||
|
||||
public virtual void Active()
|
||||
{
|
||||
}
|
||||
|
||||
public virtual void Release()
|
||||
{
|
||||
if (_instance != null)
|
||||
{
|
||||
SingletonMgr.Release(_instance);
|
||||
_instance = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,3 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: aa2cb5bb622045d7a6f2a4c4faea3ca6
|
||||
timeCreated: 1681989590
|
@@ -1,114 +0,0 @@
|
||||
using TEngine;
|
||||
using UnityEngine;
|
||||
|
||||
namespace GameBase
|
||||
{
|
||||
/// <summary>
|
||||
/// 具备Unity完整生命周期的单例。
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
public abstract class UnitySingleton<T> : MonoBehaviour where T : MonoBehaviour
|
||||
{
|
||||
private static T _instance;
|
||||
|
||||
public static T Instance
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_instance == null)
|
||||
{
|
||||
var ins = FindObjectOfType<T>();
|
||||
if (ins != null)
|
||||
{
|
||||
var obj = ins.gameObject;
|
||||
obj.name = typeof(T).Name;
|
||||
_instance = ins;
|
||||
SingletonMgr.Retain(obj);
|
||||
return Instance;
|
||||
}
|
||||
|
||||
System.Type thisType = typeof(T);
|
||||
string instName = thisType.Name;
|
||||
GameObject go = SingletonMgr.GetGameObject(instName);
|
||||
if (go == null)
|
||||
{
|
||||
go = GameObject.Find($"[{instName}]");
|
||||
if (go == null)
|
||||
{
|
||||
go = new GameObject($"[{instName}]")
|
||||
{
|
||||
transform =
|
||||
{
|
||||
position = Vector3.zero
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
_instance = go.GetComponent<T>();
|
||||
if (_instance == null)
|
||||
{
|
||||
_instance = go.AddComponent<T>();
|
||||
}
|
||||
|
||||
if (_instance == null)
|
||||
{
|
||||
Log.Error($"Can't create UnitySingleton<{typeof(T)}>");
|
||||
}
|
||||
}
|
||||
|
||||
return _instance;
|
||||
}
|
||||
}
|
||||
|
||||
public static T Active()
|
||||
{
|
||||
return Instance;
|
||||
}
|
||||
|
||||
public static bool IsValid => _instance != null;
|
||||
|
||||
private bool CheckInstance()
|
||||
{
|
||||
if (this == Instance)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
GameObject.Destroy(gameObject);
|
||||
return false;
|
||||
}
|
||||
|
||||
protected virtual void OnLoad()
|
||||
{
|
||||
}
|
||||
|
||||
public virtual void Awake()
|
||||
{
|
||||
if (CheckInstance())
|
||||
{
|
||||
OnLoad();
|
||||
}
|
||||
#if UNITY_EDITOR
|
||||
Log.Debug($"UnitySingleton Instance:{typeof(T).Name}");
|
||||
#endif
|
||||
GameObject tEngine = SingletonMgr.Root;
|
||||
if (tEngine != null)
|
||||
{
|
||||
this.gameObject.transform.SetParent(tEngine.transform);
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void OnDestroy()
|
||||
{
|
||||
Release();
|
||||
}
|
||||
|
||||
public static void Release()
|
||||
{
|
||||
if (_instance == null) return;
|
||||
SingletonMgr.Release(_instance.gameObject);
|
||||
_instance = null;
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,3 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 25c99243aa534df5870e36fdf9d36afd
|
||||
timeCreated: 1681990223
|
@@ -1,8 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1d862c80fdc1e684e8ff3b6ae7707b79
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -1,24 +0,0 @@
|
||||
using TEngine;
|
||||
using UnityEngine;
|
||||
#if ENABLE_URP
|
||||
using UnityEngine.Rendering.Universal;
|
||||
#endif
|
||||
|
||||
namespace GameLogic
|
||||
{
|
||||
public class CameraUtils
|
||||
{
|
||||
public static void AddCameraStack(Camera camera,Camera mainCamera)
|
||||
{
|
||||
#if ENABLE_URP
|
||||
if (mainCamera != null)
|
||||
{
|
||||
// 通过脚本的方式,只要能找到 camera 不轮是否跨 base 相机的场景,都可以 Add 进 Stack
|
||||
mainCamera.GetComponent<UniversalAdditionalCameraData>().cameraStack.Add(GameModule.UI.UICamera);
|
||||
}
|
||||
#else
|
||||
Log.Fatal("Could not add camera stack because had no URP-Render-Pip");
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,11 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6375b5490bbdcc145a24706a6c4e9cb7
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -1,11 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4703541a565f5ec4bb35edd81c28958c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -1,3 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 145b951be40d41dea06e76bd967a5d15
|
||||
timeCreated: 1682045847
|
@@ -1,166 +0,0 @@
|
||||
using TEngine;
|
||||
|
||||
namespace GameLogic
|
||||
{
|
||||
public enum ClientConnectWatcherStatus
|
||||
{
|
||||
StatusInit,
|
||||
StatusReconnectAuto,
|
||||
StatusReconnectConfirm,
|
||||
StatusWaitExit
|
||||
}
|
||||
|
||||
public class ClientConnectWatcher
|
||||
{
|
||||
private readonly GameClient _client;
|
||||
private ClientConnectWatcherStatus _status;
|
||||
private float _statusTime;
|
||||
private int _reconnectCnt = 0;
|
||||
private int _disconnectReason = 0;
|
||||
|
||||
private bool _enable = false;
|
||||
|
||||
public bool Enable
|
||||
{
|
||||
get => _enable;
|
||||
set
|
||||
{
|
||||
if (_enable != value)
|
||||
{
|
||||
_enable = value;
|
||||
if (_enable)
|
||||
{
|
||||
OnEnable();
|
||||
}
|
||||
else
|
||||
{
|
||||
OnDisable();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private ClientConnectWatcherStatus Status
|
||||
{
|
||||
get => _status;
|
||||
set
|
||||
{
|
||||
if (_status == value) return;
|
||||
_status = value;
|
||||
_statusTime = NowTime;
|
||||
}
|
||||
}
|
||||
|
||||
private float NowTime => GameTime.unscaledTime;
|
||||
|
||||
public ClientConnectWatcher(GameClient client)
|
||||
{
|
||||
_client = client;
|
||||
_statusTime = NowTime;
|
||||
_status = ClientConnectWatcherStatus.StatusInit;
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
if (!_enable)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_client.IsEntered)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
switch (_status)
|
||||
{
|
||||
case ClientConnectWatcherStatus.StatusInit:
|
||||
UpdateOnInitStatus();
|
||||
break;
|
||||
case ClientConnectWatcherStatus.StatusReconnectAuto:
|
||||
UpdateOnReconnectAuto();
|
||||
break;
|
||||
case ClientConnectWatcherStatus.StatusReconnectConfirm:
|
||||
UpdateOnReconnectConfirm();
|
||||
break;
|
||||
case ClientConnectWatcherStatus.StatusWaitExit:
|
||||
UpdateOnWaitExit();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public void OnReConnect()
|
||||
{
|
||||
if (_status == ClientConnectWatcherStatus.StatusReconnectConfirm)
|
||||
{
|
||||
Status = ClientConnectWatcherStatus.StatusReconnectAuto;
|
||||
}
|
||||
}
|
||||
|
||||
void UpdateOnInitStatus()
|
||||
{
|
||||
int autoReconnectMaxCount = 4;
|
||||
if (_reconnectCnt <= autoReconnectMaxCount)
|
||||
{
|
||||
if (_reconnectCnt == 0)
|
||||
{
|
||||
_disconnectReason = _client.LastNetErrCode;
|
||||
}
|
||||
|
||||
Status = ClientConnectWatcherStatus.StatusReconnectAuto;
|
||||
_reconnectCnt++;
|
||||
|
||||
//重连
|
||||
_client.Reconnect();
|
||||
}
|
||||
else
|
||||
{
|
||||
Status = ClientConnectWatcherStatus.StatusReconnectConfirm;
|
||||
_reconnectCnt++;
|
||||
// UISys.Mgr.ShowUI(GAME_UI_TYPE.Tip_NetDisconn, UISys.Mgr.GetUIWindowParam().SetParam("errCode", m_disconnectReason));
|
||||
}
|
||||
}
|
||||
|
||||
void UpdateOnReconnectAuto()
|
||||
{
|
||||
if (_client.IsEntered)
|
||||
{
|
||||
Status = ClientConnectWatcherStatus.StatusInit;
|
||||
_reconnectCnt = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
float nowTime = NowTime;
|
||||
var timeoutTime = 10f;
|
||||
|
||||
if (_statusTime + timeoutTime < nowTime)
|
||||
{
|
||||
Log.Error("UpdateOnReconnectAuto timeout: {0}", timeoutTime);
|
||||
|
||||
//切换到默认的,下一帧继续判断是否需要自动还是手动
|
||||
Status = ClientConnectWatcherStatus.StatusInit;
|
||||
}
|
||||
}
|
||||
|
||||
void UpdateOnReconnectConfirm()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void UpdateOnWaitExit()
|
||||
{
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
Status = ClientConnectWatcherStatus.StatusInit;
|
||||
_reconnectCnt = 0;
|
||||
}
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
Status = ClientConnectWatcherStatus.StatusInit;
|
||||
_reconnectCnt = 0;
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,3 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3e4e637f3da340dd9512150c3e2ff087
|
||||
timeCreated: 1684334948
|
@@ -1,428 +0,0 @@
|
||||
using System.Net.Sockets;
|
||||
using GameBase;
|
||||
using GameProto;
|
||||
using TEngine;
|
||||
using CSPkg = GameProto.CSPkg;
|
||||
|
||||
namespace GameLogic
|
||||
{
|
||||
public enum GameClientStatus
|
||||
{
|
||||
StatusInit, //初始化
|
||||
StatusReconnect, //重新连接
|
||||
StatusClose, //断开连接
|
||||
StatusLogin, //登录中
|
||||
StatusEnter, //AccountLogin成功,进入服务器了
|
||||
}
|
||||
|
||||
public enum CsMsgResult
|
||||
{
|
||||
NoError = 0,
|
||||
NetworkError = 1,
|
||||
InternalError = 2,
|
||||
MsgTimeOut = 3,
|
||||
PingTimeOut = 4,
|
||||
}
|
||||
|
||||
//定义消息回报的回调接口
|
||||
public delegate void CsMsgDelegate(CsMsgResult result, CSPkg msg);
|
||||
|
||||
/// <summary>
|
||||
/// 统计网络协议的接口
|
||||
/// </summary>
|
||||
public delegate void CsMsgStatDelegate(int cmdID, int pkgSize);
|
||||
|
||||
public class GameClient : Singleton<GameClient>
|
||||
{
|
||||
private readonly INetworkChannel _channel;
|
||||
|
||||
private GameClientStatus _status = GameClientStatus.StatusInit;
|
||||
|
||||
private readonly MsgDispatcher _dispatcher;
|
||||
|
||||
private readonly ClientConnectWatcher _connectWatcher;
|
||||
|
||||
private float _lastLogDisconnectErrTime = 0f;
|
||||
|
||||
private int _lastNetErrCode = 0;
|
||||
|
||||
public int LastNetErrCode => _lastNetErrCode;
|
||||
|
||||
public GameClientStatus Status
|
||||
{
|
||||
get => _status;
|
||||
set => _status = value;
|
||||
}
|
||||
|
||||
public bool IsEntered => _status == GameClientStatus.StatusEnter;
|
||||
|
||||
/// <summary>
|
||||
/// 连续心跳超时
|
||||
/// </summary>
|
||||
private int _heatBeatTimeoutNum = 0;
|
||||
|
||||
private int _ping = -1;
|
||||
|
||||
private float NowTime => GameTime.unscaledTime;
|
||||
|
||||
private string _lastHost = null;
|
||||
private int _lastPort = 0;
|
||||
|
||||
public GameClient()
|
||||
{
|
||||
_connectWatcher = new ClientConnectWatcher(this);
|
||||
_dispatcher = new MsgDispatcher();
|
||||
_dispatcher.SetTimeout(5f);
|
||||
GameEvent.AddEventListener<INetworkChannel,object>(NetworkEvent.NetworkConnectedEvent,OnNetworkConnected);
|
||||
GameEvent.AddEventListener<INetworkChannel>(NetworkEvent.NetworkClosedEvent,OnNetworkClosed);
|
||||
GameEvent.AddEventListener<INetworkChannel,NetworkErrorCode,SocketError,string>(NetworkEvent.NetworkErrorEvent,OnNetworkError);
|
||||
GameEvent.AddEventListener<INetworkChannel,object>(NetworkEvent.NetworkCustomErrorEvent,OnNetworkCustomError);
|
||||
_channel = Network.Instance.CreateNetworkChannel("GameClient", ServiceType.Tcp, new NetworkChannelHelper());
|
||||
}
|
||||
|
||||
private void OnNetworkConnected(INetworkChannel channel, object userdata)
|
||||
{
|
||||
bool isReconnect = (_status == GameClientStatus.StatusReconnect);
|
||||
//准备登录
|
||||
Status = GameClientStatus.StatusLogin;
|
||||
|
||||
OnServerConnected(isReconnect);
|
||||
}
|
||||
|
||||
private void OnNetworkClosed(INetworkChannel channel)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
private void OnNetworkError(INetworkChannel channel, NetworkErrorCode networkErrorCode, SocketError socketError, string errorMessage)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
private void OnNetworkCustomError(INetworkChannel channel, object userData)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public void Connect(string host, int port, bool reconnect = false)
|
||||
{
|
||||
ResetParam();
|
||||
if (!reconnect)
|
||||
{
|
||||
SetWatchReconnect(false);
|
||||
}
|
||||
|
||||
if (reconnect)
|
||||
{
|
||||
// GameEvent.Get<ICommUI>().ShowWaitUITip(WaitUISeq.LOGINWORLD_SEQID, G.R(TextDefine.ID_TIPS_RECONNECTING));
|
||||
}
|
||||
else
|
||||
{
|
||||
// GameEvent.Get<ICommUI>().ShowWaitUI(WaitUISeq.LOGINWORLD_SEQID);
|
||||
}
|
||||
|
||||
_lastHost = host;
|
||||
_lastPort = port;
|
||||
|
||||
Status = reconnect ? GameClientStatus.StatusReconnect : GameClientStatus.StatusInit;
|
||||
|
||||
_channel.Connect(host, port);
|
||||
}
|
||||
|
||||
public void Reconnect()
|
||||
{
|
||||
if (string.IsNullOrEmpty(_lastHost) || _lastPort <= 0)
|
||||
{
|
||||
// GameModule.UI.ShowTipMsg("Invalid reconnect param");
|
||||
return;
|
||||
}
|
||||
|
||||
_connectWatcher.OnReConnect();
|
||||
Connect(_lastHost, _lastPort, true);
|
||||
}
|
||||
|
||||
|
||||
public void Shutdown()
|
||||
{
|
||||
_channel.Close();
|
||||
_status = GameClientStatus.StatusInit;
|
||||
}
|
||||
|
||||
public void OnServerConnected(bool isReconnect)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public bool SendCsMsg(CSPkg reqPkg)
|
||||
{
|
||||
if (!IsStatusCanSendMsg(reqPkg.Head.MsgId))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return DoSendData(reqPkg);
|
||||
}
|
||||
|
||||
public bool IsStatusCanSendMsg(uint msgId)
|
||||
{
|
||||
bool canSend = false;
|
||||
if (_status == GameClientStatus.StatusLogin)
|
||||
{
|
||||
canSend = (msgId == (uint)CSMsgID.CsCmdActLoginReq);
|
||||
}
|
||||
|
||||
if (_status == GameClientStatus.StatusEnter)
|
||||
{
|
||||
canSend = true;
|
||||
}
|
||||
|
||||
if (!canSend)
|
||||
{
|
||||
float nowTime = NowTime;
|
||||
if (_lastLogDisconnectErrTime + 5 < nowTime)
|
||||
{
|
||||
Log.Error("GameClient not connected, send msg failed, msgId[{0}]", msgId);
|
||||
_lastLogDisconnectErrTime = nowTime;
|
||||
}
|
||||
|
||||
//UISys.Mgr.ShowTipMsg(TextDefine.ID_ERR_NETWORKD_DISCONNECT);
|
||||
}
|
||||
|
||||
return canSend;
|
||||
}
|
||||
|
||||
public bool SendCsMsg(CSPkg reqPkg, uint resCmd, CsMsgDelegate resHandler = null, bool needShowWaitUI = true)
|
||||
{
|
||||
if (!IsStatusCanSendMsg(reqPkg.Head.MsgId))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var ret = DoSendData(reqPkg);
|
||||
if (!ret)
|
||||
{
|
||||
if (resHandler != null)
|
||||
{
|
||||
resHandler(CsMsgResult.InternalError, null);
|
||||
}
|
||||
|
||||
_dispatcher.NotifyCmdError(resCmd, CsMsgResult.InternalError);
|
||||
}
|
||||
else
|
||||
{
|
||||
//注册消息
|
||||
if (resHandler != null)
|
||||
{
|
||||
_dispatcher.RegSeqHandle(reqPkg.Head.Echo, resCmd, resHandler);
|
||||
if (reqPkg.Head.Echo > 0 && IsWaitingCmd(resCmd) && needShowWaitUI)
|
||||
{
|
||||
// TODO
|
||||
// GameEvent.Get<ICommUI>().ShowWaitUI(reqPkg.Head.Echo);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
private bool DoSendData(CSPkg reqPkg)
|
||||
{
|
||||
if (!IsIgnoreLog(reqPkg.Head.MsgId))
|
||||
{
|
||||
Log.Debug("[c-s] CmdId[{0}]\n{1}", reqPkg.Head.MsgId, reqPkg.Body.ToString());
|
||||
}
|
||||
var sendRet = _channel.Send(reqPkg);
|
||||
return sendRet;
|
||||
}
|
||||
|
||||
private bool IsIgnoreLog(uint msgId)
|
||||
{
|
||||
bool ignoreLog = false;
|
||||
switch (msgId)
|
||||
{
|
||||
case (uint)CSMsgID.CsCmdHeatbeatReq:
|
||||
case (uint)CSMsgID.CsCmdHeatbeatRes:
|
||||
ignoreLog = true;
|
||||
break;
|
||||
}
|
||||
return ignoreLog;
|
||||
}
|
||||
|
||||
public static bool IsWaitingCmd(uint msgId)
|
||||
{
|
||||
//心跳包不需要读条等待
|
||||
if (msgId == (uint)CSMsgID.CsCmdHeatbeatRes)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void ResetParam()
|
||||
{
|
||||
_lastLogDisconnectErrTime = 0f;
|
||||
_heatBeatTimeoutNum = 0;
|
||||
_lastHbTime = 0f;
|
||||
_ping = -1;
|
||||
_lastNetErrCode = 0;
|
||||
}
|
||||
|
||||
public void OnUpdate()
|
||||
{
|
||||
_dispatcher.Update();
|
||||
TickHeartBeat();
|
||||
CheckHeatBeatTimeout();
|
||||
_connectWatcher.Update();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 注册静态消息
|
||||
/// </summary>
|
||||
/// <param name="iCmdID"></param>
|
||||
/// <param name="msgDelegate"></param>
|
||||
public void RegCmdHandle(int iCmdID, CsMsgDelegate msgDelegate)
|
||||
{
|
||||
_dispatcher.RegCmdHandle((uint)iCmdID, msgDelegate);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 移除消息处理函数
|
||||
/// </summary>
|
||||
/// <param name="cmdId"></param>
|
||||
/// <param name="msgDelegate"></param>
|
||||
public void RmvCmdHandle(int cmdId, CsMsgDelegate msgDelegate)
|
||||
{
|
||||
_dispatcher.RmvCmdHandle((uint)cmdId, msgDelegate);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置加密密钥
|
||||
/// </summary>
|
||||
/// <param name="key"></param>
|
||||
public void SetEncryptKey(string key)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置是否需要监控网络重连。
|
||||
/// 登录成功后,开启监控,可以自动重连或者提示玩家重连。
|
||||
/// </summary>
|
||||
/// <param name="needWatch"></param>
|
||||
public void SetWatchReconnect(bool needWatch)
|
||||
{
|
||||
_connectWatcher.Enable = needWatch;
|
||||
}
|
||||
|
||||
public bool IsNetworkOkAndLogin()
|
||||
{
|
||||
return _status == GameClientStatus.StatusEnter;
|
||||
}
|
||||
|
||||
#region 心跳处理
|
||||
|
||||
/// <summary>
|
||||
/// 最近一次心跳的时间
|
||||
/// </summary>
|
||||
private float _lastHbTime = 0f;
|
||||
|
||||
/// <summary>
|
||||
/// 心跳间隔
|
||||
/// </summary>
|
||||
private readonly float _heartBeatDurTime = 5;
|
||||
|
||||
/// <summary>
|
||||
/// 心跳超时的最大次数
|
||||
/// </summary>
|
||||
private const int HeatBeatTimeoutMaxCount = 2;
|
||||
|
||||
private bool CheckHeatBeatTimeout()
|
||||
{
|
||||
if (_heatBeatTimeoutNum >= HeatBeatTimeoutMaxCount)
|
||||
{
|
||||
//断开连接
|
||||
Shutdown();
|
||||
|
||||
//准备重连
|
||||
_heatBeatTimeoutNum = 0;
|
||||
Status = GameClientStatus.StatusClose;
|
||||
Log.Error("heat beat detect timeout");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void TickHeartBeat()
|
||||
{
|
||||
if (Status != GameClientStatus.StatusEnter)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var nowTime = NowTime;
|
||||
if (_lastHbTime + _heartBeatDurTime < nowTime)
|
||||
{
|
||||
_lastHbTime = nowTime;
|
||||
|
||||
CSPkg heatPkg = ProtobufUtility.BuildCsMsg((int)CSMsgID.CsCmdHeatbeatReq);
|
||||
heatPkg.Body.HeatBeatReq = new CSHeatBeatReq { HeatEchoTime = _lastHbTime };
|
||||
SendCsMsg(heatPkg, (int)CSMsgID.CsCmdHeatbeatRes, HandleHeatBeatRes);
|
||||
}
|
||||
}
|
||||
|
||||
void HandleHeatBeatRes(CsMsgResult result, CSPkg msg)
|
||||
{
|
||||
if (result != CsMsgResult.NoError)
|
||||
{
|
||||
//如果是超时了,则标记最近收到包的次数
|
||||
if (result == CsMsgResult.MsgTimeOut)
|
||||
{
|
||||
_heatBeatTimeoutNum++;
|
||||
Log.Warning("heat beat timeout: {0}", _heatBeatTimeoutNum);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var resBody = msg.Body.HeatBeatRes;
|
||||
float diffTime = NowTime - resBody.HeatEchoTime;
|
||||
_ping = (int)(diffTime * 1000);
|
||||
_heatBeatTimeoutNum = 0;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Ping值
|
||||
|
||||
/// <summary>
|
||||
/// ping值
|
||||
/// </summary>
|
||||
public int Ping
|
||||
{
|
||||
get
|
||||
{
|
||||
if (IsPingValid())
|
||||
{
|
||||
return _ping / 4;
|
||||
}
|
||||
else
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsPingValid()
|
||||
{
|
||||
if (IsNetworkOkAndLogin())
|
||||
{
|
||||
return _ping >= 0;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
@@ -1,3 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8c5441725c9f4d98a7790dc76a6a0c48
|
||||
timeCreated: 1684331687
|
@@ -1,265 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using GameProto;
|
||||
using TEngine;
|
||||
using CSPkg = GameProto.CSPkg;
|
||||
|
||||
namespace GameLogic
|
||||
{
|
||||
internal class MsgHandleDataToRmv
|
||||
{
|
||||
public uint MsgId;
|
||||
public CsMsgDelegate Handle;
|
||||
};
|
||||
|
||||
class MsgDispatcher
|
||||
{
|
||||
const int CheckTimeoutPerframe = 10;
|
||||
const int MaxMsgHandle = 256;
|
||||
|
||||
private readonly CsMsgDelegate[] _aMsgHandles = new CsMsgDelegate[MaxMsgHandle];
|
||||
private readonly float[] _fMsgRegTime = new float[MaxMsgHandle];
|
||||
private readonly UInt32[] _adwMsgRegSeq = new UInt32[MaxMsgHandle]; //因为_aiMsgRegResCmdID存储的是hash,不能保证一定seqid一样,所以这儿存储下,用来校验
|
||||
private readonly uint[] _aiMsgRegResCmdID = new uint[MaxMsgHandle];
|
||||
|
||||
UInt32 _dwLastCheckIndex = 0;
|
||||
|
||||
private readonly Dictionary<uint, List<CsMsgDelegate>> _mapCmdHandle = new Dictionary<uint, List<CsMsgDelegate>>();
|
||||
private readonly List<CsMsgStatDelegate> _listStatHandle = new List<CsMsgStatDelegate>();
|
||||
|
||||
//防止在处理消息的时候又删除了消息映射,所以这儿加了个队列来做个保护
|
||||
private readonly List<MsgHandleDataToRmv> _rmvList = new List<MsgHandleDataToRmv>();
|
||||
private bool _isInHandleLoop = false;
|
||||
private float _timeout = 5;
|
||||
|
||||
// 清理所有的网络消息
|
||||
public void CleanAllNetMsg()
|
||||
{
|
||||
_mapCmdHandle.Clear();
|
||||
}
|
||||
|
||||
public void SetTimeout(float timeout)
|
||||
{
|
||||
_timeout = timeout;
|
||||
}
|
||||
|
||||
public void RegSeqHandle(UInt32 dwMsgSeqID, uint iResCmdID, CsMsgDelegate msgDelegate)
|
||||
{
|
||||
UInt32 hashIndex = dwMsgSeqID % MaxMsgHandle;
|
||||
if (_aMsgHandles[hashIndex] != null)
|
||||
{
|
||||
OnCallSeqHandle(_adwMsgRegSeq[hashIndex], _aiMsgRegResCmdID[hashIndex]);
|
||||
NotifyTimeout(_aMsgHandles[hashIndex]);
|
||||
RmvReg((int)hashIndex);
|
||||
}
|
||||
|
||||
_aMsgHandles[hashIndex] = msgDelegate;
|
||||
_fMsgRegTime[hashIndex] = NowTime;
|
||||
_adwMsgRegSeq[hashIndex] = dwMsgSeqID;
|
||||
_aiMsgRegResCmdID[hashIndex] = iResCmdID;
|
||||
}
|
||||
|
||||
public void RegCmdHandle(uint iCmdID, CsMsgDelegate msgDelegate)
|
||||
{
|
||||
if (!_mapCmdHandle.TryGetValue(iCmdID, out var listHandle))
|
||||
{
|
||||
listHandle = new List<CsMsgDelegate>();
|
||||
_mapCmdHandle[iCmdID] = listHandle;
|
||||
}
|
||||
|
||||
if (listHandle != null)
|
||||
{
|
||||
if (listHandle.Contains(msgDelegate))
|
||||
{
|
||||
Log.Error("-------------repeat RegCmdHandle:{0}-----------", iCmdID);
|
||||
}
|
||||
|
||||
listHandle.Add(msgDelegate);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 注册统计处理接口
|
||||
/// </summary>
|
||||
/// <param name="handler"></param>
|
||||
public void RegCmdStatHandle(CsMsgStatDelegate handler)
|
||||
{
|
||||
_listStatHandle.Add(handler);
|
||||
}
|
||||
|
||||
public void DispatchCmdStat(int cmdID, int pkgSize)
|
||||
{
|
||||
foreach (CsMsgStatDelegate handle in _listStatHandle)
|
||||
{
|
||||
handle(cmdID, pkgSize);
|
||||
}
|
||||
}
|
||||
|
||||
public void RmvCmdHandle(uint iCmdID, CsMsgDelegate msgDelegate)
|
||||
{
|
||||
if (_isInHandleLoop)
|
||||
{
|
||||
MsgHandleDataToRmv toRmvData = new MsgHandleDataToRmv();
|
||||
toRmvData.MsgId = iCmdID;
|
||||
toRmvData.Handle = msgDelegate;
|
||||
|
||||
_rmvList.Add(toRmvData);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_mapCmdHandle.TryGetValue(iCmdID, out var listHandle))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (listHandle != null)
|
||||
{
|
||||
listHandle.Remove(msgDelegate);
|
||||
}
|
||||
}
|
||||
|
||||
public void NotifyCmdError(uint iCmdID, CsMsgResult result)
|
||||
{
|
||||
NotifyCmdHandle(iCmdID, result, default(CSPkg));
|
||||
}
|
||||
|
||||
protected bool NotifyCmdHandle(uint cmdID, CsMsgResult result, CSPkg pkg)
|
||||
{
|
||||
bool ret = false;
|
||||
if (_mapCmdHandle.TryGetValue(cmdID, out var listHandle))
|
||||
{
|
||||
_isInHandleLoop = true;
|
||||
|
||||
var rmvList = _rmvList;
|
||||
rmvList.Clear();
|
||||
foreach (CsMsgDelegate handle in listHandle)
|
||||
{
|
||||
ret = true;
|
||||
|
||||
TProfiler.BeginSample("handle");
|
||||
handle(result, pkg);
|
||||
TProfiler.EndSample();
|
||||
}
|
||||
|
||||
_isInHandleLoop = false;
|
||||
|
||||
//再统一删除掉
|
||||
int rmvCnt = rmvList.Count;
|
||||
for (int i = 0; i < rmvCnt; i++)
|
||||
{
|
||||
var rmvItem = rmvList[i];
|
||||
Log.Error("-------------remove cmd handle on loop:{0}-----------", rmvItem.MsgId);
|
||||
RmvCmdHandle(rmvItem.MsgId, rmvItem.Handle);
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
protected void OnCallSeqHandle(UInt32 echoSeq, uint resCmdID)
|
||||
{
|
||||
if (echoSeq > 0)
|
||||
{
|
||||
// TODO
|
||||
// GameEvent.Get<ICommUI>().FinWaitUI(echoSeq);
|
||||
}
|
||||
}
|
||||
|
||||
protected void NotifyTimeout(CsMsgDelegate msgHandler)
|
||||
{
|
||||
msgHandler(CsMsgResult.MsgTimeOut, default(CSPkg));
|
||||
}
|
||||
|
||||
public void NotifySeqError(UInt32 dwSeqID, CsMsgResult result)
|
||||
{
|
||||
UInt32 hashIndex = dwSeqID % MaxMsgHandle;
|
||||
|
||||
//先判断是否有注册的指定消息
|
||||
if (_aMsgHandles[hashIndex] != null &&
|
||||
_adwMsgRegSeq[hashIndex] == dwSeqID)
|
||||
{
|
||||
OnCallSeqHandle(dwSeqID, _aiMsgRegResCmdID[hashIndex]);
|
||||
_aMsgHandles[hashIndex](result, null);
|
||||
|
||||
RmvReg((int)hashIndex);
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsCmdFilterNoLog(int cmdID)
|
||||
{
|
||||
switch (cmdID)
|
||||
{
|
||||
case (int)CSMsgID.CsCmdHeatbeatRes:
|
||||
return true;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public void NotifyMsg(CSPkg msg)
|
||||
{
|
||||
UInt32 dwSeq = msg.Head.Echo;
|
||||
UInt32 hashIndex = dwSeq % MaxMsgHandle;
|
||||
//判断是否有固定的消息处理流程
|
||||
bool bHaveHandle = NotifyCmdHandle(msg.Head.MsgId, CsMsgResult.NoError, msg);
|
||||
|
||||
//再判断是否有注册的指定消息
|
||||
if (_aMsgHandles[hashIndex] != null &&
|
||||
_adwMsgRegSeq[hashIndex] == dwSeq &&
|
||||
_aiMsgRegResCmdID[hashIndex] == (int)msg.Head.MsgId)
|
||||
{
|
||||
OnCallSeqHandle(_adwMsgRegSeq[hashIndex], _aiMsgRegResCmdID[hashIndex]);
|
||||
_aMsgHandles[hashIndex](CsMsgResult.NoError, msg);
|
||||
RmvReg((int)hashIndex);
|
||||
bHaveHandle = true;
|
||||
}
|
||||
|
||||
if (!bHaveHandle)
|
||||
{
|
||||
Log.Debug("there is no handle for Msg[{0}]", msg.Head.MsgId);
|
||||
}
|
||||
}
|
||||
|
||||
private float NowTime => GameTime.unscaledTime;
|
||||
|
||||
public void Update()
|
||||
{
|
||||
CheckTimeOut();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 定时检查是否请求超时。
|
||||
/// </summary>
|
||||
private void CheckTimeOut()
|
||||
{
|
||||
float timeout = _timeout;
|
||||
float nowTime = NowTime;
|
||||
for (int i = 0; i < CheckTimeoutPerframe; i++)
|
||||
{
|
||||
_dwLastCheckIndex = (_dwLastCheckIndex + 1) % MaxMsgHandle;
|
||||
if (_aMsgHandles[_dwLastCheckIndex] != null)
|
||||
{
|
||||
if (_fMsgRegTime[_dwLastCheckIndex] + timeout < nowTime)
|
||||
{
|
||||
Log.Error("msg timeout, resCmdID[{0}], reqSeq[{1}]", _aiMsgRegResCmdID[_dwLastCheckIndex],
|
||||
_adwMsgRegSeq[_dwLastCheckIndex]);
|
||||
|
||||
OnCallSeqHandle(_adwMsgRegSeq[_dwLastCheckIndex], _aiMsgRegResCmdID[_dwLastCheckIndex]);
|
||||
NotifyTimeout(_aMsgHandles[_dwLastCheckIndex]);
|
||||
|
||||
RmvReg((int)_dwLastCheckIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void RmvReg(int index)
|
||||
{
|
||||
_aMsgHandles[index] = null;
|
||||
_adwMsgRegSeq[index] = 0;
|
||||
_aiMsgRegResCmdID[index] = 0;
|
||||
_fMsgRegTime[index] = 0;
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,3 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7d667fb84fed4c5f93a06c464585512f
|
||||
timeCreated: 1684333223
|
@@ -1,226 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net.Sockets;
|
||||
using GameProto;
|
||||
using Google.Protobuf;
|
||||
using TEngine;
|
||||
|
||||
namespace GameLogic
|
||||
{
|
||||
public class NetworkChannelHelper : INetworkChannelHelper, IMemory
|
||||
{
|
||||
private readonly Dictionary<int, Type> _serverToClientPacketTypes = new Dictionary<int, Type>();
|
||||
private readonly MemoryStream _cachedStream = new MemoryStream(1024 * 8);
|
||||
private INetworkChannel _networkChannel = null;
|
||||
|
||||
/// <summary>
|
||||
/// 获取消息包头长度。
|
||||
/// <remarks>4。</remarks>
|
||||
/// </summary>
|
||||
public int PacketHeaderLength => sizeof(int);
|
||||
|
||||
/// <summary>
|
||||
/// 初始化网络频道辅助器。
|
||||
/// </summary>
|
||||
/// <param name="networkChannel">网络频道。</param>
|
||||
public void Initialize(INetworkChannel networkChannel)
|
||||
{
|
||||
_networkChannel = networkChannel;
|
||||
|
||||
GameEvent.AddEventListener<INetworkChannel, object>(NetworkEvent.NetworkConnectedEvent, OnNetworkConnected);
|
||||
GameEvent.AddEventListener<INetworkChannel>(NetworkEvent.NetworkClosedEvent, OnNetworkClosed);
|
||||
GameEvent.AddEventListener<INetworkChannel, int>(NetworkEvent.NetworkMissHeartBeatEvent, OnNetworkMissHeartBeat);
|
||||
GameEvent.AddEventListener<INetworkChannel, NetworkErrorCode, SocketError, string>(NetworkEvent.NetworkErrorEvent, OnNetworkError);
|
||||
GameEvent.AddEventListener<INetworkChannel, object>(NetworkEvent.NetworkCustomErrorEvent, OnNetworkCustomError);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 关闭并清理网络频道辅助器。
|
||||
/// </summary>
|
||||
public void Shutdown()
|
||||
{
|
||||
GameEvent.RemoveEventListener<INetworkChannel, object>(NetworkEvent.NetworkConnectedEvent, OnNetworkConnected);
|
||||
GameEvent.RemoveEventListener<INetworkChannel>(NetworkEvent.NetworkClosedEvent, OnNetworkClosed);
|
||||
GameEvent.RemoveEventListener<INetworkChannel, int>(NetworkEvent.NetworkMissHeartBeatEvent, OnNetworkMissHeartBeat);
|
||||
GameEvent.RemoveEventListener<INetworkChannel, NetworkErrorCode, SocketError, string>(NetworkEvent.NetworkErrorEvent, OnNetworkError);
|
||||
GameEvent.RemoveEventListener<INetworkChannel, object>(NetworkEvent.NetworkCustomErrorEvent, OnNetworkCustomError);
|
||||
|
||||
_networkChannel = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 准备进行连接。
|
||||
/// </summary>
|
||||
public void PrepareForConnecting()
|
||||
{
|
||||
_networkChannel.Socket.ReceiveBufferSize = 1024 * 64;
|
||||
_networkChannel.Socket.SendBufferSize = 1024 * 64;
|
||||
}
|
||||
|
||||
public CSPkg HeartBeatPack = new CSPkg { Head = new CSPkgHead(), Body = new CSPkgBody() };
|
||||
|
||||
/// <summary>
|
||||
/// 发送心跳消息包。
|
||||
/// </summary>
|
||||
/// <returns>是否发送心跳消息包成功。</returns>
|
||||
public bool SendHeartBeat()
|
||||
{
|
||||
HeartBeatPack.Head.MsgId = (uint)CSMsgID.CsCmdHeatbeatReq;
|
||||
_networkChannel.Send(HeartBeatPack);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 序列化消息包。
|
||||
/// </summary>
|
||||
/// <typeparam name="T">消息包类型。</typeparam>
|
||||
/// <param name="packet">要序列化的消息包。</param>
|
||||
/// <param name="destination">要序列化的目标流。</param>
|
||||
/// <returns>是否序列化成功。</returns>
|
||||
public bool Serialize(CSPkg packet, Stream destination)
|
||||
{
|
||||
if (packet == null)
|
||||
{
|
||||
Log.Warning("Packet is invalid.");
|
||||
return false;
|
||||
}
|
||||
|
||||
_cachedStream.SetLength(_cachedStream.Capacity); // 此行防止 Array.Copy 的数据无法写入
|
||||
_cachedStream.Position = 0L;
|
||||
global::ProtobufUtility.ToStreamWithHead(packet,_cachedStream);
|
||||
_cachedStream.WriteTo(destination);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 反序列化消息包头。
|
||||
/// </summary>
|
||||
/// <param name="source">要反序列化的来源流。</param>
|
||||
/// <param name="customErrorData">用户自定义错误数据。</param>
|
||||
/// <returns>反序列化后的消息包头。</returns>
|
||||
public IPacketHeader DeserializePacketHeader(Stream source, out object customErrorData)
|
||||
{
|
||||
// 注意:此函数并不在主线程调用!
|
||||
customErrorData = null;
|
||||
PacketHeader packetHeader = MemoryPool.Acquire<PacketHeader>();
|
||||
packetHeader.PacketLength = ((MemoryStream)source).GetBuffer()[0];
|
||||
return packetHeader;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 反序列化消息包。
|
||||
/// </summary>
|
||||
/// <param name="packetHeader">消息包头。</param>
|
||||
/// <param name="source">要反序列化的来源流。</param>
|
||||
/// <param name="customErrorData">用户自定义错误数据。</param>
|
||||
/// <returns>反序列化后的消息包。</returns>
|
||||
public CSPkg DeserializePacket(IPacketHeader packetHeader, Stream source, out object customErrorData)
|
||||
{
|
||||
// 注意:此函数并不在主线程调用!
|
||||
customErrorData = null;
|
||||
|
||||
PacketHeader scPacketHeader = packetHeader as PacketHeader;
|
||||
if (scPacketHeader == null)
|
||||
{
|
||||
Log.Warning("Packet header is invalid.");
|
||||
return null;
|
||||
}
|
||||
|
||||
CSPkg csPkg = null;
|
||||
if (scPacketHeader.IsValid)
|
||||
{
|
||||
try
|
||||
{
|
||||
csPkg = global::ProtobufUtility.Deserialize(((MemoryStream)source).GetBuffer(),0,scPacketHeader.PacketLength);
|
||||
Log.Debug("[s-c] CmdId[{0}]\n{1}", csPkg.Head.MsgId, csPkg.ToString());
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Warning(e);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Warning("Packet header is invalid.");
|
||||
}
|
||||
|
||||
MemoryPool.Release(scPacketHeader);
|
||||
return csPkg;
|
||||
}
|
||||
|
||||
private Type GetServerToClientPacketType(int id)
|
||||
{
|
||||
if (_serverToClientPacketTypes.TryGetValue(id, out var type))
|
||||
{
|
||||
return type;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private void OnNetworkConnected(INetworkChannel channel, object userdata)
|
||||
{
|
||||
if (channel != _networkChannel)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Log.Info("Network channel '{0}' connected, local address '{1}', remote address '{2}'.",
|
||||
channel.Name, channel.Socket.LocalEndPoint.ToString(),
|
||||
channel.Socket.RemoteEndPoint.ToString());
|
||||
}
|
||||
|
||||
private void OnNetworkClosed(INetworkChannel channel)
|
||||
{
|
||||
if (channel != _networkChannel)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Log.Info("Network channel '{0}' closed.", channel.Name);
|
||||
}
|
||||
|
||||
private void OnNetworkMissHeartBeat(INetworkChannel channel, int missCount)
|
||||
{
|
||||
if (channel != _networkChannel)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Log.Fatal("Network channel '{0}' miss heart beat '{1}' times.", channel.Name, missCount.ToString());
|
||||
|
||||
if (missCount < 2)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
channel.Close();
|
||||
}
|
||||
|
||||
private void OnNetworkError(INetworkChannel channel, NetworkErrorCode networkErrorCode, SocketError socketError, string errorMessage)
|
||||
{
|
||||
if (channel != _networkChannel)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Log.Fatal("Network channel '{0}' error, error code is '{1}', error message is '{2}'.", channel.Name, networkErrorCode.ToString(), errorMessage);
|
||||
|
||||
channel.Close();
|
||||
}
|
||||
|
||||
private void OnNetworkCustomError(INetworkChannel channel, object userData)
|
||||
{
|
||||
if (channel != _networkChannel)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,3 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: bf86ce2ddfb5429abecfb06257c86acd
|
||||
timeCreated: 1682045961
|
@@ -1,3 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e77590d750cf480baae2468038654bc0
|
||||
timeCreated: 1681991042
|
@@ -1,23 +0,0 @@
|
||||
namespace TEngine
|
||||
{
|
||||
/// <summary>
|
||||
/// 网络地址类型。
|
||||
/// </summary>
|
||||
public enum AddressFamily : byte
|
||||
{
|
||||
/// <summary>
|
||||
/// 未知。
|
||||
/// </summary>
|
||||
Unknown = 0,
|
||||
|
||||
/// <summary>
|
||||
/// IP 版本 4。
|
||||
/// </summary>
|
||||
IPv4,
|
||||
|
||||
/// <summary>
|
||||
/// IP 版本 6。
|
||||
/// </summary>
|
||||
IPv6
|
||||
}
|
||||
}
|
@@ -1,3 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: dd8e805430d24bdb8c2679b59ba7c2d6
|
||||
timeCreated: 1681993653
|
@@ -1,3 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9aefe7f3e0fd485091a20579b29872a7
|
||||
timeCreated: 1681994393
|
@@ -1,180 +0,0 @@
|
||||
using System;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using GameProto;
|
||||
using Google.Protobuf;
|
||||
|
||||
namespace TEngine
|
||||
{
|
||||
/// <summary>
|
||||
/// 网络频道接口。
|
||||
/// </summary>
|
||||
public interface INetworkChannel
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取网络频道名称。
|
||||
/// </summary>
|
||||
string Name
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取网络频道所使用的 Socket。
|
||||
/// </summary>
|
||||
Socket Socket
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取是否已连接。
|
||||
/// </summary>
|
||||
bool Connected
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取网络服务类型。
|
||||
/// </summary>
|
||||
ServiceType ServiceType
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取网络地址类型。
|
||||
/// </summary>
|
||||
AddressFamily AddressFamily
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取要发送的消息包数量。
|
||||
/// </summary>
|
||||
int SendPacketCount
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取累计发送的消息包数量。
|
||||
/// </summary>
|
||||
int SentPacketCount
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取已接收未处理的消息包数量。
|
||||
/// </summary>
|
||||
int ReceivePacketCount
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取累计已接收的消息包数量。
|
||||
/// </summary>
|
||||
int ReceivedPacketCount
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置当收到消息包时是否重置心跳流逝时间。
|
||||
/// </summary>
|
||||
bool ResetHeartBeatElapseSecondsWhenReceivePacket
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取丢失心跳的次数。
|
||||
/// </summary>
|
||||
int MissHeartBeatCount
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置心跳间隔时长,以秒为单位。
|
||||
/// </summary>
|
||||
float HeartBeatInterval
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取心跳等待时长,以秒为单位。
|
||||
/// </summary>
|
||||
float HeartBeatElapseSeconds
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 注册网络消息包处理函数。
|
||||
/// </summary>
|
||||
/// <param name="msgId">网络消息包id。</param>
|
||||
/// <param name="msgDelegate">要注册的网络消息包处理函数。</param>
|
||||
/// <param name="checkRepeat">是否检测重复。</param>
|
||||
void RegisterMsgHandler(int msgId, CsMsgDelegate msgDelegate, bool checkRepeat = false);
|
||||
|
||||
/// <summary>
|
||||
/// 移除网络消息包处理函数。
|
||||
/// </summary>
|
||||
/// <param name="msgId">网络消息包id。</param>
|
||||
/// <param name="msgDelegate">要注册的网络消息包处理函数。</param>
|
||||
void RemoveMsgHandler(int msgId, CsMsgDelegate msgDelegate);
|
||||
|
||||
/// <summary>
|
||||
/// 连接到远程主机。
|
||||
/// </summary>
|
||||
/// <param name="ipAddress">远程主机的 IP 地址。</param>
|
||||
/// <param name="port">远程主机的端口号。</param>
|
||||
void Connect(string ipAddress, int port);
|
||||
|
||||
/// <summary>
|
||||
/// 连接到远程主机。
|
||||
/// </summary>
|
||||
/// <param name="ipAddress">远程主机的 IP 地址。</param>
|
||||
/// <param name="port">远程主机的端口号。</param>
|
||||
void Connect(IPAddress ipAddress, int port);
|
||||
|
||||
/// <summary>
|
||||
/// 连接到远程主机。
|
||||
/// </summary>
|
||||
/// <param name="ipAddress">远程主机的 IP 地址。</param>
|
||||
/// <param name="port">远程主机的端口号。</param>
|
||||
/// <param name="userData">用户自定义数据。</param>
|
||||
void Connect(IPAddress ipAddress, int port, object userData);
|
||||
|
||||
/// <summary>
|
||||
/// 关闭网络频道。
|
||||
/// </summary>
|
||||
void Close();
|
||||
|
||||
/// <summary>
|
||||
/// 向远程主机发送消息包。
|
||||
/// </summary>
|
||||
/// <typeparam name="T">消息包类型。</typeparam>
|
||||
/// <param name="packet">要发送的消息包。</param>
|
||||
/// <returns>消息包是否发送成功。</returns>
|
||||
bool Send(CSPkg packet);
|
||||
|
||||
/// <summary>
|
||||
/// 向远程主机发送消息包并注册消息回调。
|
||||
/// </summary>
|
||||
/// <typeparam name="T">消息包类型。</typeparam>
|
||||
/// <param name="packet">要发送的消息包。</param>
|
||||
/// <param name="resHandler">要注册的回调。</param>
|
||||
/// <param name="needShowWaitUI">是否需要等待UI。</param>
|
||||
/// <returns>消息包是否发送成功。</returns>
|
||||
bool Send(CSPkg packet, CsMsgDelegate resHandler, bool needShowWaitUI = false);
|
||||
}
|
||||
}
|
@@ -1,3 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 09b554ed41c546a1bc40e5be392f836a
|
||||
timeCreated: 1681993830
|
@@ -1,67 +0,0 @@
|
||||
using System.IO;
|
||||
using GameProto;
|
||||
|
||||
namespace TEngine
|
||||
{
|
||||
/// <summary>
|
||||
/// 网络频道辅助器接口。
|
||||
/// </summary>
|
||||
public interface INetworkChannelHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取消息包头长度。
|
||||
/// </summary>
|
||||
int PacketHeaderLength
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 初始化网络频道辅助器。
|
||||
/// </summary>
|
||||
/// <param name="networkChannel">网络频道。</param>
|
||||
void Initialize(INetworkChannel networkChannel);
|
||||
|
||||
/// <summary>
|
||||
/// 关闭并清理网络频道辅助器。
|
||||
/// </summary>
|
||||
void Shutdown();
|
||||
|
||||
/// <summary>
|
||||
/// 准备进行连接。
|
||||
/// </summary>
|
||||
void PrepareForConnecting();
|
||||
|
||||
/// <summary>
|
||||
/// 发送心跳消息包。
|
||||
/// </summary>
|
||||
/// <returns>是否发送心跳消息包成功。</returns>
|
||||
bool SendHeartBeat();
|
||||
|
||||
/// <summary>
|
||||
/// 序列化消息包。
|
||||
/// </summary>
|
||||
/// <typeparam name="T">消息包类型。</typeparam>
|
||||
/// <param name="packet">要序列化的消息包。</param>
|
||||
/// <param name="destination">要序列化的目标流。</param>
|
||||
/// <returns>是否序列化成功。</returns>
|
||||
bool Serialize(CSPkg packet, Stream destination);
|
||||
|
||||
/// <summary>
|
||||
/// 反序列化消息包头。
|
||||
/// </summary>
|
||||
/// <param name="source">要反序列化的来源流。</param>
|
||||
/// <param name="customErrorData">用户自定义错误数据。</param>
|
||||
/// <returns>反序列化后的消息包头。</returns>
|
||||
IPacketHeader DeserializePacketHeader(Stream source, out object customErrorData);
|
||||
|
||||
/// <summary>
|
||||
/// 反序列化消息包。
|
||||
/// </summary>
|
||||
/// <param name="packetHeader">消息包头。</param>
|
||||
/// <param name="source">要反序列化的来源流。</param>
|
||||
/// <param name="customErrorData">用户自定义错误数据。</param>
|
||||
/// <returns>反序列化后的消息包。</returns>
|
||||
CSPkg DeserializePacket(IPacketHeader packetHeader, Stream source, out object customErrorData);
|
||||
}
|
||||
}
|
@@ -1,3 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1f847791db5a4e6cbbc39dd56a6888d9
|
||||
timeCreated: 1681993713
|
@@ -1,84 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net.Sockets;
|
||||
|
||||
namespace TEngine
|
||||
{
|
||||
/// <summary>
|
||||
/// 网络管理器接口。
|
||||
/// </summary>
|
||||
public interface INetworkManager
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取网络频道数量。
|
||||
/// </summary>
|
||||
int NetworkChannelCount { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 网络连接成功事件。
|
||||
/// </summary>
|
||||
event Action<INetworkChannel, object> NetworkConnected;
|
||||
|
||||
/// <summary>
|
||||
/// 网络连接关闭事件。
|
||||
/// </summary>
|
||||
event Action<INetworkChannel> NetworkClosed;
|
||||
|
||||
/// <summary>
|
||||
/// 网络心跳包丢失事件。
|
||||
/// </summary>
|
||||
event Action<INetworkChannel, int> NetworkMissHeartBeat;
|
||||
|
||||
/// <summary>
|
||||
/// 网络错误事件。
|
||||
/// </summary>
|
||||
event Action<INetworkChannel, NetworkErrorCode, SocketError, string> NetworkError;
|
||||
|
||||
/// <summary>
|
||||
/// 用户自定义网络错误事件。
|
||||
/// </summary>
|
||||
event Action<INetworkChannel, object> NetworkCustomError;
|
||||
|
||||
/// <summary>
|
||||
/// 检查是否存在网络频道。
|
||||
/// </summary>
|
||||
/// <param name="name">网络频道名称。</param>
|
||||
/// <returns>是否存在网络频道。</returns>
|
||||
bool HasNetworkChannel(string name);
|
||||
|
||||
/// <summary>
|
||||
/// 获取网络频道。
|
||||
/// </summary>
|
||||
/// <param name="name">网络频道名称。</param>
|
||||
/// <returns>要获取的网络频道。</returns>
|
||||
INetworkChannel GetNetworkChannel(string name);
|
||||
|
||||
/// <summary>
|
||||
/// 获取所有网络频道。
|
||||
/// </summary>
|
||||
/// <returns>所有网络频道。</returns>
|
||||
INetworkChannel[] GetAllNetworkChannels();
|
||||
|
||||
/// <summary>
|
||||
/// 获取所有网络频道。
|
||||
/// </summary>
|
||||
/// <param name="results">所有网络频道。</param>
|
||||
void GetAllNetworkChannels(List<INetworkChannel> results);
|
||||
|
||||
/// <summary>
|
||||
/// 创建网络频道。
|
||||
/// </summary>
|
||||
/// <param name="name">网络频道名称。</param>
|
||||
/// <param name="serviceType">网络服务类型。</param>
|
||||
/// <param name="networkChannelHelper">网络频道辅助器。</param>
|
||||
/// <returns>要创建的网络频道。</returns>
|
||||
INetworkChannel CreateNetworkChannel(string name, ServiceType serviceType, INetworkChannelHelper networkChannelHelper);
|
||||
|
||||
/// <summary>
|
||||
/// 销毁网络频道。
|
||||
/// </summary>
|
||||
/// <param name="name">网络频道名称。</param>
|
||||
/// <returns>是否销毁网络频道成功。</returns>
|
||||
bool DestroyNetworkChannel(string name);
|
||||
}
|
||||
}
|
@@ -1,3 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7de8458fc50e4cd3b49ef73d35ad9763
|
||||
timeCreated: 1681993806
|
@@ -1,16 +0,0 @@
|
||||
namespace TEngine
|
||||
{
|
||||
/// <summary>
|
||||
/// 网络消息包头接口。
|
||||
/// </summary>
|
||||
public interface IPacketHeader
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取网络消息包长度。
|
||||
/// </summary>
|
||||
int PacketLength
|
||||
{
|
||||
get;
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,3 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7659e4a617d84126b0a548077fb4fdcc
|
||||
timeCreated: 1681994226
|
@@ -1,3 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6d988d26345f402f9177488d8921184f
|
||||
timeCreated: 1682092063
|
@@ -1,71 +0,0 @@
|
||||
using System.Buffers;
|
||||
using Cysharp.Threading.Tasks;
|
||||
|
||||
namespace System.Net.Sockets.Kcp
|
||||
{
|
||||
/// <summary>
|
||||
/// 用于调试的KCP IO 类,没有Kcp功能
|
||||
/// </summary>
|
||||
public class FakeKcpIO : IKcpIO
|
||||
{
|
||||
QueuePipe<byte[]> recv = new QueuePipe<byte[]>();
|
||||
public int Input(ReadOnlySpan<byte> span)
|
||||
{
|
||||
byte[] buffer = new byte[span.Length];
|
||||
span.CopyTo(buffer);
|
||||
recv.Write(buffer);
|
||||
return 0;
|
||||
}
|
||||
|
||||
public int Input(ReadOnlySequence<byte> span)
|
||||
{
|
||||
byte[] buffer = new byte[span.Length];
|
||||
span.CopyTo(buffer);
|
||||
return Input(buffer);
|
||||
}
|
||||
|
||||
public async UniTask RecvAsync(IBufferWriter<byte> writer, object options = null)
|
||||
{
|
||||
var buffer = await recv.ReadAsync().ConfigureAwait(false);
|
||||
var target = writer.GetMemory(buffer.Length);
|
||||
buffer.AsSpan().CopyTo(target.Span);
|
||||
writer.Advance(buffer.Length);
|
||||
}
|
||||
|
||||
public async UniTask<int> RecvAsync(ArraySegment<byte> buffer, object options = null)
|
||||
{
|
||||
var temp = await recv.ReadAsync().ConfigureAwait(false);
|
||||
temp.AsSpan().CopyTo(buffer);
|
||||
return temp.Length;
|
||||
}
|
||||
|
||||
QueuePipe<byte[]> send = new QueuePipe<byte[]>();
|
||||
public int Send(ReadOnlySpan<byte> span, object options = null)
|
||||
{
|
||||
byte[] buffer = new byte[span.Length];
|
||||
span.CopyTo(buffer);
|
||||
send.Write(buffer);
|
||||
return 0;
|
||||
}
|
||||
|
||||
public int Send(ReadOnlySequence<byte> span, object options = null)
|
||||
{
|
||||
byte[] buffer = new byte[span.Length];
|
||||
span.CopyTo(buffer);
|
||||
return Send(buffer);
|
||||
}
|
||||
|
||||
public async UniTask OutputAsync(IBufferWriter<byte> writer, object options = null)
|
||||
{
|
||||
var buffer = await send.ReadAsync().ConfigureAwait(false);
|
||||
Write(writer, buffer);
|
||||
}
|
||||
|
||||
private static void Write(IBufferWriter<byte> writer, byte[] buffer)
|
||||
{
|
||||
var span = writer.GetSpan(buffer.Length);
|
||||
buffer.AsSpan().CopyTo(span);
|
||||
writer.Advance(buffer.Length);
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,11 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e3c2f9de3c34ffc409549b86ac3fc530
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -1,147 +0,0 @@
|
||||
using BufferOwner = System.Buffers.IMemoryOwner<byte>;
|
||||
using System.Buffers;
|
||||
using Cysharp.Threading.Tasks;
|
||||
|
||||
namespace System.Net.Sockets.Kcp
|
||||
{
|
||||
/// <summary>
|
||||
/// Kcp回调
|
||||
/// </summary>
|
||||
public interface IKcpCallback
|
||||
{
|
||||
/// <summary>
|
||||
/// kcp 发送方向输出
|
||||
/// </summary>
|
||||
/// <param name="buffer">kcp 交出发送缓冲区控制权,缓冲区来自<see cref="RentBuffer(int)"/></param>
|
||||
/// <param name="avalidLength">数据的有效长度</param>
|
||||
/// <returns>不需要返回值</returns>
|
||||
/// <remarks>通过增加 avalidLength 能够在协议栈中有效的减少数据拷贝</remarks>
|
||||
void Output(BufferOwner buffer, int avalidLength);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Kcp回调
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 失败设计,<see cref="KcpOutputWriter.Output(BufferOwner, int)"/>。IMemoryOwner是没有办法代替的。
|
||||
/// 这里只相当于把 IKcpCallback 和 IRentable 和并。
|
||||
/// </remarks>
|
||||
public interface IKcpOutputWriter : IBufferWriter<byte>
|
||||
{
|
||||
int UnflushedBytes { get; }
|
||||
void Flush();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 外部提供缓冲区,可以在外部链接一个内存池
|
||||
/// </summary>
|
||||
public interface IRentable
|
||||
{
|
||||
/// <summary>
|
||||
/// 外部提供缓冲区,可以在外部链接一个内存池
|
||||
/// </summary>
|
||||
BufferOwner RentBuffer(int length);
|
||||
}
|
||||
|
||||
public interface IKcpSetting
|
||||
{
|
||||
int Interval(int interval);
|
||||
/// <summary>
|
||||
/// fastest: ikcp_nodelay(kcp, 1, 20, 2, 1)
|
||||
/// </summary>
|
||||
/// <param name="nodelay">0:disable(default), 1:enable</param>
|
||||
/// <param name="interval">internal update timer interval in millisec, default is 100ms</param>
|
||||
/// <param name="resend">0:disable fast resend(default), 1:enable fast resend</param>
|
||||
/// <param name="nc">0:normal congestion control(default), 1:disable congestion control</param>
|
||||
/// <returns></returns>
|
||||
int NoDelay(int nodelay, int interval, int resend, int nc);
|
||||
/// <summary>
|
||||
/// change MTU size, default is 1400
|
||||
/// <para>** 这个方法不是线程安全的。请在没有发送和接收时调用 。</para>
|
||||
/// </summary>
|
||||
/// <param name="mtu"></param>
|
||||
/// <returns></returns>
|
||||
/// <remarks>
|
||||
/// 如果没有必要,不要修改Mtu。过小的Mtu会导致分片数大于接收窗口,造成kcp阻塞冻结。
|
||||
/// </remarks>
|
||||
int SetMtu(int mtu = 1400);
|
||||
/// <summary>
|
||||
/// set maximum window size: sndwnd=32, rcvwnd=128 by default
|
||||
/// </summary>
|
||||
/// <param name="sndwnd"></param>
|
||||
/// <param name="rcvwnd"></param>
|
||||
/// <returns></returns>
|
||||
/// <remarks>
|
||||
/// 如果没有必要请不要修改。注意确保接收窗口必须大于最大分片数。
|
||||
/// </remarks>
|
||||
int WndSize(int sndwnd = 32, int rcvwnd = 128);
|
||||
}
|
||||
|
||||
public interface IKcpUpdate
|
||||
{
|
||||
void Update(in DateTimeOffset time);
|
||||
}
|
||||
|
||||
public interface IKcpSendable
|
||||
{
|
||||
/// <summary>
|
||||
/// 将要发送到网络的数据Send到kcp协议中
|
||||
/// </summary>
|
||||
/// <param name="span"></param>
|
||||
/// <param name="options"></param>
|
||||
int Send(ReadOnlySpan<byte> span, object options = null);
|
||||
/// <summary>
|
||||
/// 将要发送到网络的数据Send到kcp协议中
|
||||
/// </summary>
|
||||
/// <param name="span"></param>
|
||||
/// <param name="options"></param>
|
||||
int Send(ReadOnlySequence<byte> span, object options = null);
|
||||
}
|
||||
|
||||
public interface IKcpInputable
|
||||
{
|
||||
/// <summary>
|
||||
/// 下层收到数据后添加到kcp协议中
|
||||
/// </summary>
|
||||
/// <param name="span"></param>
|
||||
int Input(ReadOnlySpan<byte> span);
|
||||
/// <summary>
|
||||
/// 下层收到数据后添加到kcp协议中
|
||||
/// </summary>
|
||||
/// <param name="span"></param>
|
||||
int Input(ReadOnlySequence<byte> span);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// kcp协议输入输出标准接口
|
||||
/// </summary>
|
||||
public interface IKcpIO : IKcpSendable, IKcpInputable
|
||||
{
|
||||
/// <summary>
|
||||
/// 从kcp中取出一个整合完毕的数据包
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
UniTask RecvAsync(IBufferWriter<byte> writer, object options = null);
|
||||
|
||||
/// <summary>
|
||||
/// 从kcp中取出一个整合完毕的数据包
|
||||
/// </summary>
|
||||
/// <param name="buffer"></param>
|
||||
/// <param name="options"></param>
|
||||
/// <returns>接收数据长度</returns>
|
||||
UniTask<int> RecvAsync(ArraySegment<byte> buffer, object options = null);
|
||||
|
||||
/// <summary>
|
||||
/// 从kcp协议中取出需要发送到网络的数据。
|
||||
/// </summary>
|
||||
/// <param name="writer"></param>
|
||||
/// <param name="options"></param>
|
||||
/// <returns></returns>
|
||||
UniTask OutputAsync(IBufferWriter<byte> writer, object options = null);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
@@ -1,11 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b0ca98e42147b3948bc8d30f24e9c366
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -1,88 +0,0 @@
|
||||
namespace System.Net.Sockets.Kcp
|
||||
{
|
||||
/// <summary>
|
||||
/// Kcp报头
|
||||
/// https://zhuanlan.zhihu.com/p/559191428
|
||||
/// </summary>
|
||||
public interface IKcpHeader
|
||||
{
|
||||
/// <summary>
|
||||
/// 会话编号,两方一致才会通信
|
||||
/// </summary>
|
||||
uint conv { get; set; }
|
||||
/// <summary>
|
||||
/// 指令类型
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para/> IKCP_CMD_PUSH = 81 // cmd: push data 数据报文
|
||||
/// <para/> IKCP_CMD_ACK = 82 // cmd: ack 确认报文
|
||||
/// <para/> IKCP_CMD_WASK = 83 // cmd: window probe (ask) 窗口探测报文,询问对端剩余接收窗口的大小.
|
||||
/// <para/> IKCP_CMD_WINS = 84 // cmd: window size (tell) 窗口通知报文,通知对端剩余接收窗口的大小.
|
||||
/// </remarks>
|
||||
byte cmd { get; set; }
|
||||
/// <summary>
|
||||
/// 剩余分片数量,表示随后还有多少个报文属于同一个包。
|
||||
/// </summary>
|
||||
byte frg { get; set; }
|
||||
/// <summary>
|
||||
/// 自己可用窗口大小
|
||||
/// </summary>
|
||||
ushort wnd { get; set; }
|
||||
/// <summary>
|
||||
/// 发送时的时间戳 <seealso cref="DateTimeOffset.ToUnixTimeMilliseconds"/>
|
||||
/// </summary>
|
||||
uint ts { get; set; }
|
||||
/// <summary>
|
||||
/// 编号 确认编号或者报文编号
|
||||
/// </summary>
|
||||
uint sn { get; set; }
|
||||
/// <summary>
|
||||
/// 代表编号前面的所有报都收到了的标志
|
||||
/// </summary>
|
||||
uint una { get; set; }
|
||||
/// <summary>
|
||||
/// 数据内容长度
|
||||
/// </summary>
|
||||
uint len { get; }
|
||||
}
|
||||
public interface IKcpSegment : IKcpHeader
|
||||
{
|
||||
/// <summary>
|
||||
/// 重传的时间戳。超过当前时间重发这个包
|
||||
/// </summary>
|
||||
uint resendts { get; set; }
|
||||
/// <summary>
|
||||
/// 超时重传时间,根据网络去定
|
||||
/// </summary>
|
||||
uint rto { get; set; }
|
||||
/// <summary>
|
||||
/// 快速重传机制,记录被跳过的次数,超过次数进行快速重传
|
||||
/// </summary>
|
||||
uint fastack { get; set; }
|
||||
/// <summary>
|
||||
/// 重传次数
|
||||
/// </summary>
|
||||
uint xmit { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 数据内容
|
||||
/// </summary>
|
||||
Span<byte> data { get; }
|
||||
/// <summary>
|
||||
/// 将IKcpSegment编码成字节数组,并返回总长度(包括Kcp报头)
|
||||
/// </summary>
|
||||
/// <param name="buffer"></param>
|
||||
/// <returns></returns>
|
||||
int Encode(Span<byte> buffer);
|
||||
}
|
||||
|
||||
public interface ISegmentManager<Segment> where Segment : IKcpSegment
|
||||
{
|
||||
Segment Alloc(int appendDateSize);
|
||||
void Free(Segment seg);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
@@ -1,11 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a9259bb6b548e46459cc766d8fe2faeb
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -1,387 +0,0 @@
|
||||
using System.Buffers;
|
||||
using BufferOwner = System.Buffers.IMemoryOwner<byte>;
|
||||
|
||||
namespace System.Net.Sockets.Kcp
|
||||
{
|
||||
public class Kcp<Segment> : KcpCore<Segment>
|
||||
where Segment : IKcpSegment
|
||||
{
|
||||
/// <summary>
|
||||
/// create a new kcp control object, 'conv' must equal in two endpoint
|
||||
/// from the same connection.
|
||||
/// </summary>
|
||||
/// <param name="conv_"></param>
|
||||
/// <param name="callback"></param>
|
||||
/// <param name="rentable">可租用内存的回调</param>
|
||||
public Kcp(uint conv_, IKcpCallback callback, IRentable rentable = null)
|
||||
: base(conv_)
|
||||
{
|
||||
callbackHandle = callback;
|
||||
this.rentable = rentable;
|
||||
}
|
||||
|
||||
|
||||
//extension 重构和新增加的部分============================================
|
||||
|
||||
IRentable rentable;
|
||||
/// <summary>
|
||||
/// 如果外部能够提供缓冲区则使用外部缓冲区,否则new byte[]
|
||||
/// </summary>
|
||||
/// <param name="needSize"></param>
|
||||
/// <returns></returns>
|
||||
internal protected override BufferOwner CreateBuffer(int needSize)
|
||||
{
|
||||
var res = rentable?.RentBuffer(needSize);
|
||||
if (res == null)
|
||||
{
|
||||
return base.CreateBuffer(needSize);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (res.Memory.Length < needSize)
|
||||
{
|
||||
throw new ArgumentException($"{nameof(rentable.RentBuffer)} 指定的委托不符合标准,返回的" +
|
||||
$"BufferOwner.Memory.Length 小于 {nameof(needSize)}");
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// TryRecv Recv设计上同一时刻只允许一个线程调用。
|
||||
/// <para/>因为要保证数据顺序,多个线程同时调用Recv也没有意义。
|
||||
/// <para/>所以只需要部分加锁即可。
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public (BufferOwner buffer, int avalidLength) TryRecv()
|
||||
{
|
||||
var peekSize = -1;
|
||||
lock (rcv_queueLock)
|
||||
{
|
||||
if (rcv_queue.Count == 0)
|
||||
{
|
||||
///没有可用包
|
||||
return (null, -1);
|
||||
}
|
||||
|
||||
var seq = rcv_queue[0];
|
||||
|
||||
if (seq.frg == 0)
|
||||
{
|
||||
peekSize = (int)seq.len;
|
||||
}
|
||||
|
||||
if (rcv_queue.Count < seq.frg + 1)
|
||||
{
|
||||
///没有足够的包
|
||||
return (null, -1);
|
||||
}
|
||||
|
||||
uint length = 0;
|
||||
|
||||
foreach (var item in rcv_queue)
|
||||
{
|
||||
length += item.len;
|
||||
if (item.frg == 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
peekSize = (int)length;
|
||||
|
||||
if (peekSize <= 0)
|
||||
{
|
||||
return (null, -2);
|
||||
}
|
||||
}
|
||||
|
||||
var buffer = CreateBuffer(peekSize);
|
||||
var recvlength = UncheckRecv(buffer.Memory.Span);
|
||||
return (buffer, recvlength);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// TryRecv Recv设计上同一时刻只允许一个线程调用。
|
||||
/// <para/>因为要保证数据顺序,多个线程同时调用Recv也没有意义。
|
||||
/// <para/>所以只需要部分加锁即可。
|
||||
/// </summary>
|
||||
/// <param name="writer"></param>
|
||||
/// <returns></returns>
|
||||
public int TryRecv(IBufferWriter<byte> writer)
|
||||
{
|
||||
var peekSize = -1;
|
||||
lock (rcv_queueLock)
|
||||
{
|
||||
if (rcv_queue.Count == 0)
|
||||
{
|
||||
///没有可用包
|
||||
return -1;
|
||||
}
|
||||
|
||||
var seq = rcv_queue[0];
|
||||
|
||||
if (seq.frg == 0)
|
||||
{
|
||||
peekSize = (int)seq.len;
|
||||
}
|
||||
|
||||
if (rcv_queue.Count < seq.frg + 1)
|
||||
{
|
||||
///没有足够的包
|
||||
return -1;
|
||||
}
|
||||
|
||||
uint length = 0;
|
||||
|
||||
foreach (var item in rcv_queue)
|
||||
{
|
||||
length += item.len;
|
||||
if (item.frg == 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
peekSize = (int)length;
|
||||
|
||||
if (peekSize <= 0)
|
||||
{
|
||||
return -2;
|
||||
}
|
||||
}
|
||||
|
||||
return UncheckRecv(writer);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// user/upper level recv: returns size, returns below zero for EAGAIN
|
||||
/// </summary>
|
||||
/// <param name="buffer"></param>
|
||||
/// <returns></returns>
|
||||
public int Recv(Span<byte> buffer)
|
||||
{
|
||||
if (0 == rcv_queue.Count)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
var peekSize = PeekSize();
|
||||
if (peekSize < 0)
|
||||
{
|
||||
return -2;
|
||||
}
|
||||
|
||||
if (peekSize > buffer.Length)
|
||||
{
|
||||
return -3;
|
||||
}
|
||||
|
||||
/// 拆分函数
|
||||
var recvLength = UncheckRecv(buffer);
|
||||
|
||||
return recvLength;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// user/upper level recv: returns size, returns below zero for EAGAIN
|
||||
/// </summary>
|
||||
/// <param name="writer"></param>
|
||||
/// <returns></returns>
|
||||
public int Recv(IBufferWriter<byte> writer)
|
||||
{
|
||||
if (0 == rcv_queue.Count)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
var peekSize = PeekSize();
|
||||
if (peekSize < 0)
|
||||
{
|
||||
return -2;
|
||||
}
|
||||
|
||||
//if (peekSize > buffer.Length)
|
||||
//{
|
||||
// return -3;
|
||||
//}
|
||||
|
||||
/// 拆分函数
|
||||
var recvLength = UncheckRecv(writer);
|
||||
|
||||
return recvLength;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 这个函数不检查任何参数
|
||||
/// </summary>
|
||||
/// <param name="buffer"></param>
|
||||
/// <returns></returns>
|
||||
int UncheckRecv(Span<byte> buffer)
|
||||
{
|
||||
var recover = false;
|
||||
if (rcv_queue.Count >= rcv_wnd)
|
||||
{
|
||||
recover = true;
|
||||
}
|
||||
|
||||
#region merge fragment.
|
||||
/// merge fragment.
|
||||
|
||||
var recvLength = 0;
|
||||
lock (rcv_queueLock)
|
||||
{
|
||||
var count = 0;
|
||||
foreach (var seg in rcv_queue)
|
||||
{
|
||||
seg.data.CopyTo(buffer.Slice(recvLength));
|
||||
recvLength += (int)seg.len;
|
||||
|
||||
count++;
|
||||
int frg = seg.frg;
|
||||
|
||||
SegmentManager.Free(seg);
|
||||
if (frg == 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (count > 0)
|
||||
{
|
||||
rcv_queue.RemoveRange(0, count);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
Move_Rcv_buf_2_Rcv_queue();
|
||||
|
||||
#region fast recover
|
||||
/// fast recover
|
||||
if (rcv_queue.Count < rcv_wnd && recover)
|
||||
{
|
||||
// ready to send back IKCP_CMD_WINS in ikcp_flush
|
||||
// tell remote my window size
|
||||
probe |= IKCP_ASK_TELL;
|
||||
}
|
||||
#endregion
|
||||
return recvLength;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 这个函数不检查任何参数
|
||||
/// </summary>
|
||||
/// <param name="writer"></param>
|
||||
/// <returns></returns>
|
||||
int UncheckRecv(IBufferWriter<byte> writer)
|
||||
{
|
||||
var recover = false;
|
||||
if (rcv_queue.Count >= rcv_wnd)
|
||||
{
|
||||
recover = true;
|
||||
}
|
||||
|
||||
#region merge fragment.
|
||||
/// merge fragment.
|
||||
|
||||
var recvLength = 0;
|
||||
lock (rcv_queueLock)
|
||||
{
|
||||
var count = 0;
|
||||
foreach (var seg in rcv_queue)
|
||||
{
|
||||
var len = (int)seg.len;
|
||||
var destination = writer.GetSpan(len);
|
||||
|
||||
seg.data.CopyTo(destination);
|
||||
writer.Advance(len);
|
||||
|
||||
recvLength += len;
|
||||
|
||||
count++;
|
||||
int frg = seg.frg;
|
||||
|
||||
SegmentManager.Free(seg);
|
||||
if (frg == 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (count > 0)
|
||||
{
|
||||
rcv_queue.RemoveRange(0, count);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
Move_Rcv_buf_2_Rcv_queue();
|
||||
|
||||
#region fast recover
|
||||
/// fast recover
|
||||
if (rcv_queue.Count < rcv_wnd && recover)
|
||||
{
|
||||
// ready to send back IKCP_CMD_WINS in ikcp_flush
|
||||
// tell remote my window size
|
||||
probe |= IKCP_ASK_TELL;
|
||||
}
|
||||
#endregion
|
||||
return recvLength;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// check the size of next message in the recv queue
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public int PeekSize()
|
||||
{
|
||||
lock (rcv_queueLock)
|
||||
{
|
||||
if (rcv_queue.Count == 0)
|
||||
{
|
||||
///没有可用包
|
||||
return -1;
|
||||
}
|
||||
|
||||
var seq = rcv_queue[0];
|
||||
|
||||
if (seq.frg == 0)
|
||||
{
|
||||
return (int)seq.len;
|
||||
}
|
||||
|
||||
if (rcv_queue.Count < seq.frg + 1)
|
||||
{
|
||||
///没有足够的包
|
||||
return -1;
|
||||
}
|
||||
|
||||
uint length = 0;
|
||||
|
||||
foreach (var seg in rcv_queue)
|
||||
{
|
||||
length += seg.len;
|
||||
if (seg.frg == 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return (int)length;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
@@ -1,11 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b3840e90e37f0194bbfafb564ff80d45
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
File diff suppressed because it is too large
Load Diff
@@ -1,11 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fcc0172c723e55a4389ad2f62b68a3fe
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -1,262 +0,0 @@
|
||||
using System.Buffers;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Cysharp.Threading.Tasks;
|
||||
using BufferOwner = System.Buffers.IMemoryOwner<byte>;
|
||||
|
||||
namespace System.Net.Sockets.Kcp
|
||||
{
|
||||
/// <summary>
|
||||
/// <inheritdoc cref="IPipe{T}"/>
|
||||
/// <para></para>这是个简单的实现,更复杂使用微软官方实现<see cref="Channel.CreateBounded{T}(int)"/>
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
internal class QueuePipe<T> : Queue<T>
|
||||
{
|
||||
readonly object _innerLock = new object();
|
||||
private TaskCompletionSource<T> source;
|
||||
|
||||
//线程同步上下文由Task机制保证,无需额外处理
|
||||
//SynchronizationContext callbackContext;
|
||||
//public bool UseSynchronizationContext { get; set; } = true;
|
||||
|
||||
public virtual void Write(T item)
|
||||
{
|
||||
lock (_innerLock)
|
||||
{
|
||||
if (source == null)
|
||||
{
|
||||
Enqueue(item);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (Count > 0)
|
||||
{
|
||||
throw new Exception("内部顺序错误,不应该出现,请联系作者");
|
||||
}
|
||||
|
||||
var next = source;
|
||||
source = null;
|
||||
next.TrySetResult(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public new void Enqueue(T item)
|
||||
{
|
||||
lock (_innerLock)
|
||||
{
|
||||
base.Enqueue(item);
|
||||
}
|
||||
}
|
||||
|
||||
public void Flush()
|
||||
{
|
||||
lock (_innerLock)
|
||||
{
|
||||
if (Count > 0)
|
||||
{
|
||||
var res = Dequeue();
|
||||
var next = source;
|
||||
source = null;
|
||||
next?.TrySetResult(res);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public virtual Task<T> ReadAsync()
|
||||
{
|
||||
lock (_innerLock)
|
||||
{
|
||||
if (this.Count > 0)
|
||||
{
|
||||
var next = Dequeue();
|
||||
return Task.FromResult(next);
|
||||
}
|
||||
else
|
||||
{
|
||||
source = new TaskCompletionSource<T>();
|
||||
return source.Task;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public UniTask<T> ReadValueTaskAsync()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
public class KcpIO<Segment> : KcpCore<Segment>, IKcpIO
|
||||
where Segment : IKcpSegment
|
||||
{
|
||||
OutputQ outq;
|
||||
|
||||
public KcpIO(uint conv_) : base(conv_)
|
||||
{
|
||||
outq = new OutputQ();
|
||||
callbackHandle = outq;
|
||||
}
|
||||
|
||||
internal override void Parse_data(Segment newseg)
|
||||
{
|
||||
base.Parse_data(newseg);
|
||||
|
||||
lock (rcv_queueLock)
|
||||
{
|
||||
var recover = false;
|
||||
if (rcv_queue.Count >= rcv_wnd)
|
||||
{
|
||||
recover = true;
|
||||
}
|
||||
|
||||
while (TryRecv(out var arraySegment) > 0)
|
||||
{
|
||||
recvSignal.Enqueue(arraySegment);
|
||||
}
|
||||
|
||||
recvSignal.Flush();
|
||||
|
||||
#region fast recover
|
||||
|
||||
/// fast recover
|
||||
if (rcv_queue.Count < rcv_wnd && recover)
|
||||
{
|
||||
// ready to send back IKCP_CMD_WINS in ikcp_flush
|
||||
// tell remote my window size
|
||||
probe |= IKCP_ASK_TELL;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
QueuePipe<ArraySegment<Segment>> recvSignal = new QueuePipe<ArraySegment<Segment>>();
|
||||
|
||||
internal int TryRecv(out ArraySegment<Segment> package)
|
||||
{
|
||||
package = default;
|
||||
lock (rcv_queueLock)
|
||||
{
|
||||
var peekSize = -1;
|
||||
if (rcv_queue.Count == 0)
|
||||
{
|
||||
///没有可用包
|
||||
return -1;
|
||||
}
|
||||
|
||||
var seq = rcv_queue[0];
|
||||
|
||||
if (seq.frg == 0)
|
||||
{
|
||||
peekSize = (int)seq.len;
|
||||
}
|
||||
|
||||
if (rcv_queue.Count < seq.frg + 1)
|
||||
{
|
||||
///没有足够的包
|
||||
return -1;
|
||||
}
|
||||
|
||||
uint length = 0;
|
||||
|
||||
Segment[] kcpSegments = ArrayPool<Segment>.Shared.Rent(seq.frg + 1);
|
||||
|
||||
var index = 0;
|
||||
foreach (var item in rcv_queue)
|
||||
{
|
||||
kcpSegments[index] = item;
|
||||
index++;
|
||||
length += item.len;
|
||||
if (item.frg == 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (index > 0)
|
||||
{
|
||||
rcv_queue.RemoveRange(0, index);
|
||||
}
|
||||
|
||||
package = new ArraySegment<Segment>(kcpSegments, 0, index);
|
||||
|
||||
peekSize = (int)length;
|
||||
|
||||
if (peekSize <= 0)
|
||||
{
|
||||
return -2;
|
||||
}
|
||||
|
||||
return peekSize;
|
||||
}
|
||||
}
|
||||
|
||||
public async UniTask RecvAsync(IBufferWriter<byte> writer, object options = null)
|
||||
{
|
||||
var arraySegment = await recvSignal.ReadAsync().ConfigureAwait(false);
|
||||
for (int i = arraySegment.Offset; i < arraySegment.Count; i++)
|
||||
{
|
||||
WriteRecv(writer, arraySegment.Array[i]);
|
||||
}
|
||||
|
||||
ArrayPool<Segment>.Shared.Return(arraySegment.Array, true);
|
||||
}
|
||||
|
||||
private void WriteRecv(IBufferWriter<byte> writer, Segment seg)
|
||||
{
|
||||
var curCount = (int)seg.len;
|
||||
var target = writer.GetSpan(curCount);
|
||||
seg.data.CopyTo(target);
|
||||
SegmentManager.Free(seg);
|
||||
writer.Advance(curCount);
|
||||
}
|
||||
|
||||
public async UniTask<int> RecvAsync(ArraySegment<byte> buffer, object options = null)
|
||||
{
|
||||
var arraySegment = await recvSignal.ReadAsync().ConfigureAwait(false);
|
||||
int start = buffer.Offset;
|
||||
for (int i = arraySegment.Offset; i < arraySegment.Count; i++)
|
||||
{
|
||||
var target = new Memory<byte>(buffer.Array, start, buffer.Array.Length - start);
|
||||
|
||||
var seg = arraySegment.Array[i];
|
||||
seg.data.CopyTo(target.Span);
|
||||
start += seg.data.Length;
|
||||
|
||||
SegmentManager.Free(seg);
|
||||
}
|
||||
|
||||
ArrayPool<Segment>.Shared.Return(arraySegment.Array, true);
|
||||
return start - buffer.Offset;
|
||||
}
|
||||
|
||||
public async UniTask OutputAsync(IBufferWriter<byte> writer, object options = null)
|
||||
{
|
||||
var (Owner, Count) = await outq.ReadAsync().ConfigureAwait(false);
|
||||
WriteOut(writer, Owner, Count);
|
||||
}
|
||||
|
||||
private static void WriteOut(IBufferWriter<byte> writer, BufferOwner Owner, int Count)
|
||||
{
|
||||
var target = writer.GetSpan(Count);
|
||||
Owner.Memory.Span.Slice(0, Count).CopyTo(target);
|
||||
writer.Advance(Count);
|
||||
Owner.Dispose();
|
||||
}
|
||||
|
||||
protected internal override BufferOwner CreateBuffer(int needSize)
|
||||
{
|
||||
return MemoryPool<byte>.Shared.Rent(needSize);
|
||||
}
|
||||
|
||||
internal class OutputQ : QueuePipe<(BufferOwner Owner, int Count)>,
|
||||
IKcpCallback
|
||||
{
|
||||
public void Output(BufferOwner buffer, int avalidLength)
|
||||
{
|
||||
Write((buffer, avalidLength));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,11 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3c30d30fa46372948b60bd56eec47fe6
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -1,50 +0,0 @@
|
||||
using System.Buffers;
|
||||
|
||||
namespace System.Net.Sockets.Kcp
|
||||
{
|
||||
public abstract class KcpOutputWriter : IKcpOutputWriter
|
||||
{
|
||||
public int UnflushedBytes { get; set; }
|
||||
public IMemoryOwner<byte> MemoryOwner { get; set; }
|
||||
public void Flush()
|
||||
{
|
||||
Output(MemoryOwner, UnflushedBytes);
|
||||
MemoryOwner = null;
|
||||
UnflushedBytes = 0;
|
||||
}
|
||||
|
||||
public void Advance(int count)
|
||||
{
|
||||
UnflushedBytes += count;
|
||||
}
|
||||
|
||||
public Memory<byte> GetMemory(int sizeHint = 0)
|
||||
{
|
||||
if (MemoryOwner == null)
|
||||
{
|
||||
MemoryOwner = MemoryPool<byte>.Shared.Rent(2048);
|
||||
}
|
||||
return MemoryOwner.Memory.Slice(UnflushedBytes);
|
||||
}
|
||||
|
||||
public Span<byte> GetSpan(int sizeHint = 0)
|
||||
{
|
||||
if (MemoryOwner == null)
|
||||
{
|
||||
MemoryOwner = MemoryPool<byte>.Shared.Rent(2048);
|
||||
}
|
||||
return MemoryOwner.Memory.Span.Slice(UnflushedBytes);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Socket发送是要pin byte[],为了不阻塞KcpFlush,动态缓存是必须的。
|
||||
/// </summary>
|
||||
/// <param name="buffer"></param>
|
||||
/// <param name="avalidLength"></param>
|
||||
public abstract void Output(IMemoryOwner<byte> buffer, int avalidLength);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
@@ -1,11 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a058d8fa8a92dc94395bfd6e1f6fdb8f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -1,402 +0,0 @@
|
||||
using System.Buffers.Binary;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace System.Net.Sockets.Kcp
|
||||
{
|
||||
/// <summary>
|
||||
/// 调整了没存布局,直接拷贝块提升性能。
|
||||
/// <para>结构体保存内容只有一个指针,不用担心参数传递过程中的性能</para>
|
||||
/// https://github.com/skywind3000/kcp/issues/118#issuecomment-338133930
|
||||
/// <para>不要对没有初始化的KcpSegment(内部指针为0,所有属性都将指向位置区域) 进行任何赋值操作,可能导致内存损坏。
|
||||
/// 出于性能考虑,没有对此项进行安全检查。</para>
|
||||
/// </summary>
|
||||
public struct KcpSegment : IKcpSegment
|
||||
{
|
||||
internal readonly unsafe byte* ptr;
|
||||
public unsafe KcpSegment(byte* intPtr, uint appendDateSize)
|
||||
{
|
||||
this.ptr = intPtr;
|
||||
len = appendDateSize;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 使用完必须显示释放,否则内存泄漏
|
||||
/// </summary>
|
||||
/// <param name="appendDateSize"></param>
|
||||
/// <returns></returns>
|
||||
public static KcpSegment AllocHGlobal(int appendDateSize)
|
||||
{
|
||||
var total = LocalOffset + HeadOffset + appendDateSize;
|
||||
IntPtr intPtr = Marshal.AllocHGlobal(total);
|
||||
unsafe
|
||||
{
|
||||
///清零 不知道是不是有更快的清0方法?
|
||||
Span<byte> span = new Span<byte>(intPtr.ToPointer(), total);
|
||||
span.Clear();
|
||||
|
||||
return new KcpSegment((byte*)intPtr.ToPointer(), (uint)appendDateSize);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 释放非托管内存
|
||||
/// </summary>
|
||||
/// <param name="seg"></param>
|
||||
public static void FreeHGlobal(KcpSegment seg)
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
Marshal.FreeHGlobal((IntPtr)seg.ptr);
|
||||
}
|
||||
}
|
||||
|
||||
/// 以下为本机使用的参数
|
||||
/// <summary>
|
||||
/// offset = 0
|
||||
/// </summary>
|
||||
public uint resendts
|
||||
{
|
||||
get
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
return *(uint*)(ptr + 0);
|
||||
}
|
||||
}
|
||||
set
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
*(uint*)(ptr + 0) = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// offset = 4
|
||||
/// </summary>
|
||||
public uint rto
|
||||
{
|
||||
get
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
return *(uint*)(ptr + 4);
|
||||
}
|
||||
}
|
||||
set
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
*(uint*)(ptr + 4) = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// offset = 8
|
||||
/// </summary>
|
||||
public uint fastack
|
||||
{
|
||||
get
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
return *(uint*)(ptr + 8);
|
||||
}
|
||||
}
|
||||
set
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
*(uint*)(ptr + 8) = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// offset = 12
|
||||
/// </summary>
|
||||
public uint xmit
|
||||
{
|
||||
get
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
return *(uint*)(ptr + 12);
|
||||
}
|
||||
}
|
||||
set
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
*(uint*)(ptr + 12) = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
///以下为需要网络传输的参数
|
||||
public const int LocalOffset = 4 * 4;
|
||||
public const int HeadOffset = KcpConst.IKCP_OVERHEAD;
|
||||
|
||||
/// <summary>
|
||||
/// offset = <see cref="LocalOffset"/>
|
||||
/// </summary>
|
||||
/// https://github.com/skywind3000/kcp/issues/134
|
||||
public uint conv
|
||||
{
|
||||
get
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
return *(uint*)(LocalOffset + 0 + ptr);
|
||||
}
|
||||
}
|
||||
set
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
*(uint*)(LocalOffset + 0 + ptr) = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// offset = <see cref="LocalOffset"/> + 4
|
||||
/// </summary>
|
||||
public byte cmd
|
||||
{
|
||||
get
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
return *(LocalOffset + 4 + ptr);
|
||||
}
|
||||
}
|
||||
set
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
*(LocalOffset + 4 + ptr) = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// offset = <see cref="LocalOffset"/> + 5
|
||||
/// </summary>
|
||||
public byte frg
|
||||
{
|
||||
get
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
return *(LocalOffset + 5 + ptr);
|
||||
}
|
||||
}
|
||||
set
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
*(LocalOffset + 5 + ptr) = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// offset = <see cref="LocalOffset"/> + 6
|
||||
/// </summary>
|
||||
public ushort wnd
|
||||
{
|
||||
get
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
return *(ushort*)(LocalOffset + 6 + ptr);
|
||||
}
|
||||
}
|
||||
set
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
*(ushort*)(LocalOffset + 6 + ptr) = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// offset = <see cref="LocalOffset"/> + 8
|
||||
/// </summary>
|
||||
public uint ts
|
||||
{
|
||||
get
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
return *(uint*)(LocalOffset + 8 + ptr);
|
||||
}
|
||||
}
|
||||
set
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
*(uint*)(LocalOffset + 8 + ptr) = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <para> SendNumber? </para>
|
||||
/// offset = <see cref="LocalOffset"/> + 12
|
||||
/// </summary>
|
||||
public uint sn
|
||||
{
|
||||
get
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
return *(uint*)(LocalOffset + 12 + ptr);
|
||||
}
|
||||
}
|
||||
set
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
*(uint*)(LocalOffset + 12 + ptr) = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// offset = <see cref="LocalOffset"/> + 16
|
||||
/// </summary>
|
||||
public uint una
|
||||
{
|
||||
get
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
return *(uint*)(LocalOffset + 16 + ptr);
|
||||
}
|
||||
}
|
||||
set
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
*(uint*)(LocalOffset + 16 + ptr) = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <para> AppendDateSize </para>
|
||||
/// offset = <see cref="LocalOffset"/> + 20
|
||||
/// </summary>
|
||||
public uint len
|
||||
{
|
||||
get
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
return *(uint*)(LocalOffset + 20 + ptr);
|
||||
}
|
||||
}
|
||||
private set
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
*(uint*)(LocalOffset + 20 + ptr) = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// https://github.com/skywind3000/kcp/issues/35#issuecomment-263770736
|
||||
public Span<byte> data
|
||||
{
|
||||
get
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
return new Span<byte>(LocalOffset + HeadOffset + ptr, (int)len);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 将片段中的要发送的数据拷贝到指定缓冲区
|
||||
/// </summary>
|
||||
/// <param name="buffer"></param>
|
||||
/// <returns></returns>
|
||||
public int Encode(Span<byte> buffer)
|
||||
{
|
||||
var datelen = (int)(HeadOffset + len);
|
||||
|
||||
///备用偏移值 现阶段没有使用
|
||||
const int offset = 0;
|
||||
|
||||
if (KcpConst.IsLittleEndian)
|
||||
{
|
||||
if (BitConverter.IsLittleEndian)
|
||||
{
|
||||
///小端可以一次拷贝
|
||||
unsafe
|
||||
{
|
||||
///要发送的数据从LocalOffset开始。
|
||||
///本结构体调整了要发送字段和单机使用字段的位置,让报头数据和数据连续,节约一次拷贝。
|
||||
Span<byte> sendDate = new Span<byte>(ptr + LocalOffset, datelen);
|
||||
sendDate.CopyTo(buffer);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
BinaryPrimitives.WriteUInt32LittleEndian(buffer.Slice(offset), conv);
|
||||
buffer[offset + 4] = cmd;
|
||||
buffer[offset + 5] = frg;
|
||||
BinaryPrimitives.WriteUInt16LittleEndian(buffer.Slice(offset + 6), wnd);
|
||||
|
||||
BinaryPrimitives.WriteUInt32LittleEndian(buffer.Slice(offset + 8), ts);
|
||||
BinaryPrimitives.WriteUInt32LittleEndian(buffer.Slice(offset + 12), sn);
|
||||
BinaryPrimitives.WriteUInt32LittleEndian(buffer.Slice(offset + 16), una);
|
||||
BinaryPrimitives.WriteUInt32LittleEndian(buffer.Slice(offset + 20), len);
|
||||
|
||||
data.CopyTo(buffer.Slice(HeadOffset));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (BitConverter.IsLittleEndian)
|
||||
{
|
||||
BinaryPrimitives.WriteUInt32BigEndian(buffer.Slice(offset), conv);
|
||||
buffer[offset + 4] = cmd;
|
||||
buffer[offset + 5] = frg;
|
||||
BinaryPrimitives.WriteUInt16BigEndian(buffer.Slice(offset + 6), wnd);
|
||||
|
||||
BinaryPrimitives.WriteUInt32BigEndian(buffer.Slice(offset + 8), ts);
|
||||
BinaryPrimitives.WriteUInt32BigEndian(buffer.Slice(offset + 12), sn);
|
||||
BinaryPrimitives.WriteUInt32BigEndian(buffer.Slice(offset + 16), una);
|
||||
BinaryPrimitives.WriteUInt32BigEndian(buffer.Slice(offset + 20), len);
|
||||
|
||||
data.CopyTo(buffer.Slice(HeadOffset));
|
||||
}
|
||||
else
|
||||
{
|
||||
///大端可以一次拷贝
|
||||
unsafe
|
||||
{
|
||||
///要发送的数据从LocalOffset开始。
|
||||
///本结构体调整了要发送字段和单机使用字段的位置,让报头数据和数据连续,节约一次拷贝。
|
||||
Span<byte> sendDate = new Span<byte>(ptr + LocalOffset, datelen);
|
||||
sendDate.CopyTo(buffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return datelen;
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,11 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: daab08fc1f00355429c23a1e36993942
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -1,79 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace System.Net.Sockets.Kcp
|
||||
{
|
||||
public partial class KcpCore<Segment>
|
||||
{
|
||||
public KcpLogMask LogMask { get; set; } = KcpLogMask.IKCP_LOG_PARSE_DATA | KcpLogMask.IKCP_LOG_NEED_SEND | KcpLogMask.IKCP_LOG_DEAD_LINK;
|
||||
|
||||
public virtual bool CanLog(KcpLogMask mask)
|
||||
{
|
||||
if ((mask & LogMask) == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
#if NETSTANDARD2_0_OR_GREATER || NET5_0_OR_GREATER
|
||||
if (TraceListener != null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
|
||||
#if NETSTANDARD2_0_OR_GREATER || NET5_0_OR_GREATER
|
||||
public System.Diagnostics.TraceListener TraceListener { get; set; }
|
||||
#endif
|
||||
|
||||
public virtual void LogFail(string message)
|
||||
{
|
||||
#if NETSTANDARD2_0_OR_GREATER || NET5_0_OR_GREATER
|
||||
TraceListener?.Fail(message);
|
||||
#endif
|
||||
}
|
||||
|
||||
public virtual void LogWriteLine(string message, string category)
|
||||
{
|
||||
#if NETSTANDARD2_0_OR_GREATER || NET5_0_OR_GREATER
|
||||
TraceListener?.WriteLine(message, category);
|
||||
#endif
|
||||
}
|
||||
|
||||
[Obsolete("一定要先判断CanLog 内部判断是否存在TraceListener,避免在没有TraceListener时生成字符串", true)]
|
||||
public virtual void LogWriteLine(string message, KcpLogMask mask)
|
||||
{
|
||||
#if NETSTANDARD2_0_OR_GREATER || NET5_0_OR_GREATER
|
||||
if (CanLog(mask))
|
||||
{
|
||||
LogWriteLine(message, mask.ToString());
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum KcpLogMask
|
||||
{
|
||||
IKCP_LOG_OUTPUT = 1 << 0,
|
||||
IKCP_LOG_INPUT = 1 << 1,
|
||||
IKCP_LOG_SEND = 1 << 2,
|
||||
IKCP_LOG_RECV = 1 << 3,
|
||||
IKCP_LOG_IN_DATA = 1 << 4,
|
||||
IKCP_LOG_IN_ACK = 1 << 5,
|
||||
IKCP_LOG_IN_PROBE = 1 << 6,
|
||||
IKCP_LOG_IN_WINS = 1 << 7,
|
||||
IKCP_LOG_OUT_DATA = 1 << 8,
|
||||
IKCP_LOG_OUT_ACK = 1 << 9,
|
||||
IKCP_LOG_OUT_PROBE = 1 << 10,
|
||||
IKCP_LOG_OUT_WINS = 1 << 11,
|
||||
|
||||
IKCP_LOG_PARSE_DATA = 1 << 12,
|
||||
IKCP_LOG_NEED_SEND = 1 << 13,
|
||||
IKCP_LOG_DEAD_LINK = 1 << 14,
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -1,11 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 39791b0c45bf4864e87385c477d9b50c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -1,265 +0,0 @@
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Buffers.Binary;
|
||||
|
||||
namespace System.Net.Sockets.Kcp
|
||||
{
|
||||
/// <summary>
|
||||
/// 动态申请非托管内存
|
||||
/// </summary>
|
||||
public class SimpleSegManager : ISegmentManager<KcpSegment>
|
||||
{
|
||||
public static SimpleSegManager Default { get; } = new SimpleSegManager();
|
||||
public KcpSegment Alloc(int appendDateSize)
|
||||
{
|
||||
return KcpSegment.AllocHGlobal(appendDateSize);
|
||||
}
|
||||
|
||||
public void Free(KcpSegment seg)
|
||||
{
|
||||
KcpSegment.FreeHGlobal(seg);
|
||||
}
|
||||
|
||||
public class Kcp : Kcp<KcpSegment>
|
||||
{
|
||||
public Kcp(uint conv_, IKcpCallback callback, IRentable rentable = null)
|
||||
: base(conv_, callback, rentable)
|
||||
{
|
||||
SegmentManager = Default;
|
||||
}
|
||||
}
|
||||
|
||||
public class KcpIO : KcpIO<KcpSegment>
|
||||
{
|
||||
public KcpIO(uint conv_)
|
||||
: base(conv_)
|
||||
{
|
||||
SegmentManager = Default;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 申请固定大小非托管内存。使用这个就不能SetMtu了,大小已经写死。
|
||||
/// </summary>
|
||||
/// <remarks>需要大量测试</remarks>
|
||||
public unsafe class UnSafeSegManager : ISegmentManager<KcpSegment>
|
||||
{
|
||||
public static UnSafeSegManager Default { get; } = new UnSafeSegManager();
|
||||
/// <summary>
|
||||
/// 因为默认mtu是1400,并且内存需要内存行/内存页对齐。这里直接512对齐。
|
||||
/// </summary>
|
||||
public const int blockSize = 512 * 3;
|
||||
public HashSet<IntPtr> header = new HashSet<IntPtr>();
|
||||
public Stack<IntPtr> blocks = new Stack<IntPtr>();
|
||||
public readonly object locker = new object();
|
||||
public UnSafeSegManager()
|
||||
{
|
||||
Alloc();
|
||||
}
|
||||
|
||||
void Alloc()
|
||||
{
|
||||
int count = 50;
|
||||
IntPtr intPtr = Marshal.AllocHGlobal(blockSize * count);
|
||||
header.Add(intPtr);
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
blocks.Push(intPtr + blockSize * i);
|
||||
}
|
||||
}
|
||||
|
||||
~UnSafeSegManager()
|
||||
{
|
||||
foreach (var item in header)
|
||||
{
|
||||
Marshal.FreeHGlobal(item);
|
||||
}
|
||||
}
|
||||
|
||||
public KcpSegment Alloc(int appendDateSize)
|
||||
{
|
||||
lock (locker)
|
||||
{
|
||||
var total = KcpSegment.LocalOffset + KcpSegment.HeadOffset + appendDateSize;
|
||||
if (total > blockSize)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
|
||||
if (blocks.Count > 0)
|
||||
{
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
Alloc();
|
||||
}
|
||||
|
||||
var ptr = blocks.Pop();
|
||||
Span<byte> span = new Span<byte>(ptr.ToPointer(), blockSize);
|
||||
span.Clear();
|
||||
return new KcpSegment((byte*)ptr.ToPointer(), (uint)appendDateSize);
|
||||
}
|
||||
}
|
||||
|
||||
public void Free(KcpSegment seg)
|
||||
{
|
||||
IntPtr ptr = (IntPtr)seg.ptr;
|
||||
blocks.Push(ptr);
|
||||
}
|
||||
|
||||
public class Kcp : Kcp<KcpSegment>
|
||||
{
|
||||
public Kcp(uint conv_, IKcpCallback callback, IRentable rentable = null)
|
||||
: base(conv_, callback, rentable)
|
||||
{
|
||||
SegmentManager = Default;
|
||||
}
|
||||
}
|
||||
|
||||
public class KcpIO : KcpIO<KcpSegment>
|
||||
{
|
||||
public KcpIO(uint conv_)
|
||||
: base(conv_)
|
||||
{
|
||||
SegmentManager = Default;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 使用内存池,而不是非托管内存,有内存alloc,但是不多。可以解决Marshal.AllocHGlobal 内核调用带来的性能问题
|
||||
/// </summary>
|
||||
public class PoolSegManager : ISegmentManager<PoolSegManager.Seg>
|
||||
{
|
||||
public static PoolSegManager Default { get; } = new PoolSegManager();
|
||||
|
||||
/// <summary>
|
||||
/// 因为默认mtu是1400,并且内存需要内存行/内存页对齐。这里直接512对齐。
|
||||
/// </summary>
|
||||
public const int blockSize = 512 * 3;
|
||||
public class Seg : IKcpSegment
|
||||
{
|
||||
byte[] cache;
|
||||
public Seg(int blockSize)
|
||||
{
|
||||
cache = Buffers.ArrayPool<byte>.Shared.Rent(blockSize);
|
||||
}
|
||||
|
||||
///以下为需要网络传输的参数
|
||||
public const int LocalOffset = 4 * 4;
|
||||
public const int HeadOffset = Kcp.IKCP_OVERHEAD;
|
||||
|
||||
public byte cmd { get; set; }
|
||||
public uint conv { get; set; }
|
||||
public Span<byte> data => cache.AsSpan().Slice(0, (int)len);
|
||||
public uint fastack { get; set; }
|
||||
public byte frg { get; set; }
|
||||
public uint len { get; internal set; }
|
||||
public uint resendts { get; set; }
|
||||
public uint rto { get; set; }
|
||||
public uint sn { get; set; }
|
||||
public uint ts { get; set; }
|
||||
public uint una { get; set; }
|
||||
public ushort wnd { get; set; }
|
||||
public uint xmit { get; set; }
|
||||
|
||||
public int Encode(Span<byte> buffer)
|
||||
{
|
||||
var datelen = (int)(HeadOffset + len);
|
||||
|
||||
///备用偏移值 现阶段没有使用
|
||||
const int offset = 0;
|
||||
|
||||
if (BitConverter.IsLittleEndian)
|
||||
{
|
||||
BinaryPrimitives.WriteUInt32LittleEndian(buffer.Slice(offset), conv);
|
||||
buffer[offset + 4] = cmd;
|
||||
buffer[offset + 5] = frg;
|
||||
BinaryPrimitives.WriteUInt16LittleEndian(buffer.Slice(offset + 6), wnd);
|
||||
|
||||
BinaryPrimitives.WriteUInt32LittleEndian(buffer.Slice(offset + 8), ts);
|
||||
BinaryPrimitives.WriteUInt32LittleEndian(buffer.Slice(offset + 12), sn);
|
||||
BinaryPrimitives.WriteUInt32LittleEndian(buffer.Slice(offset + 16), una);
|
||||
BinaryPrimitives.WriteUInt32LittleEndian(buffer.Slice(offset + 20), len);
|
||||
|
||||
data.CopyTo(buffer.Slice(HeadOffset));
|
||||
}
|
||||
else
|
||||
{
|
||||
BinaryPrimitives.WriteUInt32BigEndian(buffer.Slice(offset), conv);
|
||||
buffer[offset + 4] = cmd;
|
||||
buffer[offset + 5] = frg;
|
||||
BinaryPrimitives.WriteUInt16BigEndian(buffer.Slice(offset + 6), wnd);
|
||||
|
||||
BinaryPrimitives.WriteUInt32BigEndian(buffer.Slice(offset + 8), ts);
|
||||
BinaryPrimitives.WriteUInt32BigEndian(buffer.Slice(offset + 12), sn);
|
||||
BinaryPrimitives.WriteUInt32BigEndian(buffer.Slice(offset + 16), una);
|
||||
BinaryPrimitives.WriteUInt32BigEndian(buffer.Slice(offset + 20), len);
|
||||
|
||||
data.CopyTo(buffer.Slice(HeadOffset));
|
||||
}
|
||||
|
||||
return datelen;
|
||||
}
|
||||
}
|
||||
ConcurrentStack<Seg> Pool = new ConcurrentStack<Seg>();
|
||||
public Seg Alloc(int appendDateSize)
|
||||
{
|
||||
if (appendDateSize > blockSize)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
if (Pool.TryPop(out var ret))
|
||||
{
|
||||
}
|
||||
else
|
||||
{
|
||||
ret = new Seg(blockSize);
|
||||
}
|
||||
ret.len = (uint)appendDateSize;
|
||||
return ret;
|
||||
}
|
||||
|
||||
public void Free(Seg seg)
|
||||
{
|
||||
seg.cmd = 0;
|
||||
seg.conv = 0;
|
||||
seg.fastack = 0;
|
||||
seg.frg = 0;
|
||||
seg.len = 0;
|
||||
seg.resendts = 0;
|
||||
seg.rto = 0;
|
||||
seg.sn = 0;
|
||||
seg.ts = 0;
|
||||
seg.una = 0;
|
||||
seg.wnd = 0;
|
||||
seg.xmit = 0;
|
||||
Pool.Push(seg);
|
||||
}
|
||||
|
||||
public class Kcp : Kcp<Seg>
|
||||
{
|
||||
public Kcp(uint conv_, IKcpCallback callback, IRentable rentable = null)
|
||||
: base(conv_, callback, rentable)
|
||||
{
|
||||
SegmentManager = Default;
|
||||
}
|
||||
}
|
||||
|
||||
public class KcpIO : KcpIO<Seg>
|
||||
{
|
||||
public KcpIO(uint conv_)
|
||||
: base(conv_)
|
||||
{
|
||||
SegmentManager = Default;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@@ -1,11 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e7f0d71aa1362b6488c3173d525fd284
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -1,65 +0,0 @@
|
||||
using System.Buffers;
|
||||
using System.Threading.Tasks;
|
||||
using Cysharp.Threading.Tasks;
|
||||
|
||||
namespace System.Net.Sockets.Kcp.Simple
|
||||
{
|
||||
/// <summary>
|
||||
/// 简单例子。
|
||||
/// </summary>
|
||||
public class SimpleKcpClient : IKcpCallback
|
||||
{
|
||||
UdpClient client;
|
||||
|
||||
public SimpleKcpClient(int port)
|
||||
: this(port, null)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public SimpleKcpClient(int port, IPEndPoint endPoint)
|
||||
{
|
||||
client = new UdpClient(port);
|
||||
kcp = new SimpleSegManager.Kcp(2001, this);
|
||||
this.EndPoint = endPoint;
|
||||
BeginRecv();
|
||||
}
|
||||
|
||||
public SimpleSegManager.Kcp kcp { get; }
|
||||
public IPEndPoint EndPoint { get; set; }
|
||||
|
||||
public void Output(IMemoryOwner<byte> buffer, int avalidLength)
|
||||
{
|
||||
var s = buffer.Memory.Span.Slice(0, avalidLength).ToArray();
|
||||
client.SendAsync(s, s.Length, EndPoint);
|
||||
buffer.Dispose();
|
||||
}
|
||||
|
||||
public void SendAsync(byte[] datagram, int bytes)
|
||||
{
|
||||
kcp.Send(datagram.AsSpan().Slice(0, bytes));
|
||||
}
|
||||
|
||||
public async UniTask<byte[]> ReceiveAsync()
|
||||
{
|
||||
var (buffer, avalidLength) = kcp.TryRecv();
|
||||
while (buffer == null)
|
||||
{
|
||||
await Task.Delay(10);
|
||||
(buffer, avalidLength) = kcp.TryRecv();
|
||||
}
|
||||
|
||||
var s = buffer.Memory.Span.Slice(0, avalidLength).ToArray();
|
||||
return s;
|
||||
}
|
||||
|
||||
private async void BeginRecv()
|
||||
{
|
||||
var res = await client.ReceiveAsync();
|
||||
EndPoint = res.RemoteEndPoint;
|
||||
kcp.Input(res.Buffer);
|
||||
BeginRecv();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,11 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ff16ed9b89525df4aa6501c0edc679d5
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -1,48 +0,0 @@
|
||||
using System.Net.Sockets.Kcp.Simple;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace System.Net.Sockets.Kcp
|
||||
{
|
||||
namespace TestServer
|
||||
{
|
||||
/// <summary>
|
||||
/// 简单例子。
|
||||
/// </summary>
|
||||
class SimpleKcpServer
|
||||
{
|
||||
static void Main(string[] args)
|
||||
{
|
||||
Console.WriteLine("Hello World!");
|
||||
|
||||
SimpleKcpClient kcpClient = new SimpleKcpClient(40001);
|
||||
Task.Run(async () =>
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
kcpClient.kcp.Update(DateTimeOffset.UtcNow);
|
||||
await Task.Delay(10);
|
||||
}
|
||||
});
|
||||
|
||||
StartRecv(kcpClient);
|
||||
Console.ReadLine();
|
||||
}
|
||||
|
||||
static async void StartRecv(SimpleKcpClient client)
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
var res = await client.ReceiveAsync();
|
||||
var str = System.Text.Encoding.UTF8.GetString(res);
|
||||
if ("发送一条消息" == str)
|
||||
{
|
||||
Console.WriteLine(str);
|
||||
|
||||
var buffer = System.Text.Encoding.UTF8.GetBytes("回复一条消息");
|
||||
client.SendAsync(buffer, buffer.Length);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,3 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 97e221b7388e412da59b0f4d200cb891
|
||||
timeCreated: 1682095200
|
@@ -1,73 +0,0 @@
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
|
||||
//[assembly: InternalsVisibleTo("UnitTestProject1")]
|
||||
|
||||
namespace System.Net.Sockets.Kcp
|
||||
{
|
||||
public static class KcpExtension_FDF71D0BC31D49C48EEA8FAA51F017D4
|
||||
{
|
||||
private static readonly DateTime utc_time = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
|
||||
[Obsolete("", true)]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static uint ConvertTime(this in DateTime time)
|
||||
{
|
||||
return (uint)(Convert.ToInt64(time.Subtract(utc_time).TotalMilliseconds) & 0xffffffff);
|
||||
}
|
||||
|
||||
private static readonly DateTimeOffset utc1970 = new DateTimeOffset(1970, 1, 1, 0, 0, 0, TimeSpan.Zero);
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static uint ConvertTimeOld(this in DateTimeOffset time)
|
||||
{
|
||||
return (uint)(Convert.ToInt64(time.Subtract(utc1970).TotalMilliseconds) & 0xffffffff);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static uint ConvertTime2(this in DateTimeOffset time)
|
||||
{
|
||||
#if NETSTANDARD2_0_OR_GREATER || NET5_0_OR_GREATER
|
||||
return (uint)(time.ToUnixTimeMilliseconds() & 0xffffffff);
|
||||
#else
|
||||
return (uint)(Convert.ToInt64(time.Subtract(utc1970).TotalMilliseconds) & 0xffffffff);
|
||||
#endif
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static uint ConvertTime(this in DateTimeOffset time)
|
||||
{
|
||||
#if NETSTANDARD2_0_OR_GREATER || NET5_0_OR_GREATER
|
||||
return (uint)(time.ToUnixTimeMilliseconds());
|
||||
#else
|
||||
return (uint)(Convert.ToInt64(time.Subtract(utc1970).TotalMilliseconds) & 0xffffffff);
|
||||
#endif
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static string ToLogString<T>(this T segment, bool local = false)
|
||||
where T : IKcpSegment
|
||||
{
|
||||
if (local)
|
||||
{
|
||||
return $"sn:{segment.sn,2} una:{segment.una,2} frg:{segment.frg,2} cmd:{segment.cmd,2} len:{segment.len,2} wnd:{segment.wnd} [ LocalValue: xmit:{segment.xmit} fastack:{segment.fastack} rto:{segment.rto} ]";
|
||||
}
|
||||
else
|
||||
{
|
||||
return $"sn:{segment.sn,2} una:{segment.una,2} frg:{segment.frg,2} cmd:{segment.cmd,2} len:{segment.len,2} wnd:{segment.wnd}";
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static int Encode<T>(this T Seg, IBufferWriter<byte> writer)
|
||||
where T : IKcpSegment
|
||||
{
|
||||
var totalLength = (int)(KcpSegment.HeadOffset + Seg.len);
|
||||
var span = writer.GetSpan(totalLength);
|
||||
Seg.Encode(span);
|
||||
writer.Advance(totalLength);
|
||||
return totalLength;
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,287 +0,0 @@
|
||||
using System;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
|
||||
namespace TEngine
|
||||
{
|
||||
internal sealed partial class NetworkManager
|
||||
{
|
||||
/// <summary>
|
||||
/// Kcp 网络频道。
|
||||
/// </summary>
|
||||
private sealed class KcpNetworkChannel : NetworkChannelBase
|
||||
{
|
||||
private readonly AsyncCallback _connectCallback;
|
||||
private readonly AsyncCallback _sendCallback;
|
||||
private readonly AsyncCallback _receiveCallback;
|
||||
|
||||
/// <summary>
|
||||
/// 获取网络服务类型。
|
||||
/// </summary>
|
||||
public override ServiceType ServiceType => ServiceType.Kcp;
|
||||
|
||||
|
||||
public KcpNetworkChannel(string name, INetworkChannelHelper networkChannelHelper)
|
||||
: base(name, networkChannelHelper)
|
||||
{
|
||||
_connectCallback = ConnectCallback;
|
||||
_sendCallback = SendCallback;
|
||||
_receiveCallback = ReceiveCallback;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 连接到远程主机。
|
||||
/// </summary>
|
||||
/// <param name="ipAddress">远程主机的 IP 地址。</param>
|
||||
/// <param name="port">远程主机的端口号。</param>
|
||||
/// <param name="userData">用户自定义数据。</param>
|
||||
public override void Connect(IPAddress ipAddress, int port, object userData)
|
||||
{
|
||||
base.Connect(ipAddress, port, userData);
|
||||
MSocket = new Socket(ipAddress.AddressFamily, SocketType.Dgram, ProtocolType.Udp);
|
||||
if (MSocket == null)
|
||||
{
|
||||
string errorMessage = "Initialize network channel failure.";
|
||||
if (NetworkChannelError != null)
|
||||
{
|
||||
NetworkChannelError(this, NetworkErrorCode.SocketError, SocketError.Success, errorMessage);
|
||||
return;
|
||||
}
|
||||
|
||||
throw new GameFrameworkException(errorMessage);
|
||||
}
|
||||
|
||||
NetworkChannelHelper.PrepareForConnecting();
|
||||
ConnectAsync(ipAddress, port, userData);
|
||||
}
|
||||
|
||||
private void ConnectAsync(IPAddress ipAddress, int port, object userData)
|
||||
{
|
||||
try
|
||||
{
|
||||
MSocket.BeginConnect(ipAddress, port, _connectCallback, new ConnectState(MSocket, userData));
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
if (NetworkChannelError != null)
|
||||
{
|
||||
SocketException socketException = exception as SocketException;
|
||||
NetworkChannelError(this, NetworkErrorCode.ConnectError,
|
||||
socketException?.SocketErrorCode ?? SocketError.Success,
|
||||
exception.ToString());
|
||||
return;
|
||||
}
|
||||
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
protected override bool ProcessSend()
|
||||
{
|
||||
if (base.ProcessSend())
|
||||
{
|
||||
SendAsync();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void ConnectCallback(IAsyncResult ar)
|
||||
{
|
||||
ConnectState socketUserData = (ConnectState)ar.AsyncState;
|
||||
try
|
||||
{
|
||||
socketUserData.Socket.EndConnect(ar);
|
||||
}
|
||||
catch (ObjectDisposedException)
|
||||
{
|
||||
return;
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Active = false;
|
||||
if (NetworkChannelError != null)
|
||||
{
|
||||
SocketException socketException = exception as SocketException;
|
||||
NetworkChannelError(this, NetworkErrorCode.ConnectError,
|
||||
socketException?.SocketErrorCode ?? SocketError.Success,
|
||||
exception.ToString());
|
||||
return;
|
||||
}
|
||||
|
||||
throw;
|
||||
}
|
||||
|
||||
MSentPacketCount = 0;
|
||||
MReceivedPacketCount = 0;
|
||||
|
||||
lock (SendPacketPool)
|
||||
{
|
||||
SendPacketPool.Clear();
|
||||
}
|
||||
|
||||
lock (MHeartBeatState)
|
||||
{
|
||||
MHeartBeatState.Reset(true);
|
||||
}
|
||||
|
||||
if (NetworkChannelConnected != null)
|
||||
{
|
||||
NetworkChannelConnected(this, socketUserData.UserData);
|
||||
}
|
||||
|
||||
Active = true;
|
||||
ReceiveAsync();
|
||||
}
|
||||
|
||||
private void SendAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
MSocket.BeginSend(MSendState.Stream.GetBuffer(), (int)MSendState.Stream.Position,
|
||||
(int)(MSendState.Stream.Length - MSendState.Stream.Position), SocketFlags.None, _sendCallback,
|
||||
MSocket);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Active = false;
|
||||
if (NetworkChannelError != null)
|
||||
{
|
||||
SocketException socketException = exception as SocketException;
|
||||
NetworkChannelError(this, NetworkErrorCode.SendError,
|
||||
socketException?.SocketErrorCode ?? SocketError.Success,
|
||||
exception.ToString());
|
||||
return;
|
||||
}
|
||||
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
private void SendCallback(IAsyncResult ar)
|
||||
{
|
||||
Socket socket = (Socket)ar.AsyncState;
|
||||
if (!socket.Connected)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int bytesSent = 0;
|
||||
try
|
||||
{
|
||||
bytesSent = socket.EndSend(ar);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Active = false;
|
||||
if (NetworkChannelError != null)
|
||||
{
|
||||
SocketException socketException = exception as SocketException;
|
||||
NetworkChannelError(this, NetworkErrorCode.SendError,
|
||||
socketException?.SocketErrorCode ?? SocketError.Success,
|
||||
exception.ToString());
|
||||
return;
|
||||
}
|
||||
|
||||
throw;
|
||||
}
|
||||
|
||||
MSendState.Stream.Position += bytesSent;
|
||||
if (MSendState.Stream.Position < MSendState.Stream.Length)
|
||||
{
|
||||
SendAsync();
|
||||
return;
|
||||
}
|
||||
|
||||
MSentPacketCount++;
|
||||
MSendState.Reset();
|
||||
}
|
||||
|
||||
private void ReceiveAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
MSocket.BeginReceive(MReceiveState.Stream.GetBuffer(), (int)MReceiveState.Stream.Position,
|
||||
(int)(MReceiveState.Stream.Length - MReceiveState.Stream.Position), SocketFlags.None,
|
||||
_receiveCallback, MSocket);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Active = false;
|
||||
if (NetworkChannelError != null)
|
||||
{
|
||||
SocketException socketException = exception as SocketException;
|
||||
NetworkChannelError(this, NetworkErrorCode.ReceiveError,
|
||||
socketException?.SocketErrorCode ?? SocketError.Success,
|
||||
exception.ToString());
|
||||
return;
|
||||
}
|
||||
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
private void ReceiveCallback(IAsyncResult ar)
|
||||
{
|
||||
Socket socket = (Socket)ar.AsyncState;
|
||||
if (!socket.Connected)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int bytesReceived = 0;
|
||||
try
|
||||
{
|
||||
bytesReceived = socket.EndReceive(ar);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Active = false;
|
||||
if (NetworkChannelError != null)
|
||||
{
|
||||
SocketException socketException = exception as SocketException;
|
||||
NetworkChannelError(this, NetworkErrorCode.ReceiveError,
|
||||
socketException?.SocketErrorCode ?? SocketError.Success,
|
||||
exception.ToString());
|
||||
return;
|
||||
}
|
||||
|
||||
throw;
|
||||
}
|
||||
|
||||
if (bytesReceived <= 0)
|
||||
{
|
||||
Close();
|
||||
return;
|
||||
}
|
||||
|
||||
MReceiveState.Stream.Position += bytesReceived;
|
||||
if (MReceiveState.Stream.Position < MReceiveState.Stream.Length)
|
||||
{
|
||||
ReceiveAsync();
|
||||
return;
|
||||
}
|
||||
|
||||
MReceiveState.Stream.Position = 0L;
|
||||
|
||||
bool processSuccess = false;
|
||||
if (MReceiveState.PacketHeader != null)
|
||||
{
|
||||
processSuccess = ProcessPacket();
|
||||
MReceivedPacketCount++;
|
||||
}
|
||||
else
|
||||
{
|
||||
processSuccess = ProcessPacketHeader();
|
||||
}
|
||||
|
||||
if (processSuccess)
|
||||
{
|
||||
ReceiveAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,3 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 70ff4fca885c401ea776223622deab75
|
||||
timeCreated: 1682092071
|
@@ -1,54 +0,0 @@
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
|
||||
namespace TEngine
|
||||
{
|
||||
public class NetUtil
|
||||
{
|
||||
public static bool IsHaveIpV6Address(IPAddress[] ipAddresses, ref IPAddress[] outIPs)
|
||||
{
|
||||
int v6Count = 0;
|
||||
for (int i = 0; i < ipAddresses.Length; i++)
|
||||
{
|
||||
if (System.Net.Sockets.AddressFamily.InterNetworkV6.Equals(ipAddresses[i].AddressFamily))
|
||||
{
|
||||
v6Count++;
|
||||
}
|
||||
}
|
||||
|
||||
if (v6Count > 0)
|
||||
{
|
||||
outIPs = new IPAddress[v6Count];
|
||||
int resIndex = 0;
|
||||
for (int i = 0; i < ipAddresses.Length; i++)
|
||||
{
|
||||
if (System.Net.Sockets.AddressFamily.InterNetworkV6.Equals(ipAddresses[i].AddressFamily))
|
||||
{
|
||||
outIPs[resIndex++] = ipAddresses[i];
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static IPEndPoint GetEndPoint(string server, int port)
|
||||
{
|
||||
IPAddress[] ps = Dns.GetHostAddresses(server);
|
||||
IPAddress[] finalIps = ps;
|
||||
if (Socket.OSSupportsIPv6 && NetUtil.IsHaveIpV6Address(ps, ref finalIps))
|
||||
{
|
||||
Log.Error("socket use addr ipV6: {0}, IP count:{1} AddressFamily[{2}]", server, finalIps.Length, finalIps[0].AddressFamily);
|
||||
}
|
||||
|
||||
if (finalIps.Length > 0)
|
||||
{
|
||||
return new IPEndPoint(finalIps[0], port);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,3 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 26846cca9e0f4d3c965d58882c1b503e
|
||||
timeCreated: 1681991073
|
@@ -1,144 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Net.Sockets;
|
||||
using GameBase;
|
||||
using UnityEngine;
|
||||
|
||||
namespace TEngine
|
||||
{
|
||||
/// <summary>
|
||||
/// 网络组件。
|
||||
/// </summary>
|
||||
[DisallowMultipleComponent]
|
||||
public sealed class Network : UnitySingleton<Network>
|
||||
{
|
||||
private NetworkManager m_NetworkManager = null;
|
||||
|
||||
/// <summary>
|
||||
/// 获取网络频道数量。
|
||||
/// </summary>
|
||||
public int NetworkChannelCount => m_NetworkManager.NetworkChannelCount;
|
||||
|
||||
/// <summary>
|
||||
/// 游戏框架组件初始化。
|
||||
/// </summary>
|
||||
public override void Awake()
|
||||
{
|
||||
base.Awake();
|
||||
|
||||
// m_NetworkManager = GameFrameworkEntry.GetModule<INetworkManager>();
|
||||
m_NetworkManager = new NetworkManager();
|
||||
if (m_NetworkManager == null)
|
||||
{
|
||||
Log.Fatal("Network manager is invalid.");
|
||||
return;
|
||||
}
|
||||
|
||||
m_NetworkManager.NetworkConnected += OnNetworkConnected;
|
||||
m_NetworkManager.NetworkClosed += OnNetworkClosed;
|
||||
m_NetworkManager.NetworkMissHeartBeat += OnNetworkMissHeartBeat;
|
||||
m_NetworkManager.NetworkError += OnNetworkError;
|
||||
m_NetworkManager.NetworkCustomError += OnNetworkCustomError;
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
m_NetworkManager.Update(GameTime.deltaTime, GameTime.unscaledDeltaTime);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检查是否存在网络频道。
|
||||
/// </summary>
|
||||
/// <param name="name">网络频道名称。</param>
|
||||
/// <returns>是否存在网络频道。</returns>
|
||||
public bool HasNetworkChannel(string name)
|
||||
{
|
||||
return m_NetworkManager.HasNetworkChannel(name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取网络频道。
|
||||
/// </summary>
|
||||
/// <param name="name">网络频道名称。</param>
|
||||
/// <returns>要获取的网络频道。</returns>
|
||||
public INetworkChannel GetNetworkChannel(string name)
|
||||
{
|
||||
return m_NetworkManager.GetNetworkChannel(name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取所有网络频道。
|
||||
/// </summary>
|
||||
/// <returns>所有网络频道。</returns>
|
||||
public INetworkChannel[] StaticGetAllNetworkChannels()
|
||||
{
|
||||
return m_NetworkManager.GetAllNetworkChannels();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取所有网络频道。
|
||||
/// </summary>
|
||||
/// <returns>所有网络频道。</returns>
|
||||
public INetworkChannel[] GetAllNetworkChannels()
|
||||
{
|
||||
return m_NetworkManager.GetAllNetworkChannels();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取所有网络频道。
|
||||
/// </summary>
|
||||
/// <param name="results">所有网络频道。</param>
|
||||
public void GetAllNetworkChannels(List<INetworkChannel> results)
|
||||
{
|
||||
m_NetworkManager.GetAllNetworkChannels(results);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建网络频道。
|
||||
/// </summary>
|
||||
/// <param name="name">网络频道名称。</param>
|
||||
/// <param name="serviceType">网络服务类型。</param>
|
||||
/// <param name="networkChannelHelper">网络频道辅助器。</param>
|
||||
/// <returns>要创建的网络频道。</returns>
|
||||
public INetworkChannel CreateNetworkChannel(string name, ServiceType serviceType,
|
||||
INetworkChannelHelper networkChannelHelper)
|
||||
{
|
||||
return m_NetworkManager.CreateNetworkChannel(name, serviceType, networkChannelHelper);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 销毁网络频道。
|
||||
/// </summary>
|
||||
/// <param name="channelName">网络频道名称。</param>
|
||||
/// <returns>是否销毁网络频道成功。</returns>
|
||||
public bool DestroyNetworkChannel(string channelName)
|
||||
{
|
||||
return m_NetworkManager.DestroyNetworkChannel(channelName);
|
||||
}
|
||||
|
||||
private void OnNetworkConnected(INetworkChannel channel, object userdata)
|
||||
{
|
||||
GameEvent.Send(NetworkEvent.NetworkConnectedEvent, channel, userdata);
|
||||
}
|
||||
|
||||
private void OnNetworkClosed(INetworkChannel channel)
|
||||
{
|
||||
GameEvent.Send(NetworkEvent.NetworkClosedEvent, channel);
|
||||
}
|
||||
|
||||
private void OnNetworkMissHeartBeat(INetworkChannel channel, int missCount)
|
||||
{
|
||||
GameEvent.Send(NetworkEvent.NetworkMissHeartBeatEvent, channel, missCount);
|
||||
}
|
||||
|
||||
private void OnNetworkError(INetworkChannel channel, NetworkErrorCode networkErrorCode, SocketError socketError,
|
||||
string errorMessage)
|
||||
{
|
||||
GameEvent.Send(NetworkEvent.NetworkErrorEvent, channel, networkErrorCode, socketError, errorMessage);
|
||||
}
|
||||
|
||||
private void OnNetworkCustomError(INetworkChannel channel, object userData)
|
||||
{
|
||||
GameEvent.Send(NetworkEvent.NetworkCustomErrorEvent, channel, userData);
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,3 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8edbc7d090fd4bb1ad2c6aa967ff0b76
|
||||
timeCreated: 1682044812
|
@@ -1,53 +0,0 @@
|
||||
namespace TEngine
|
||||
{
|
||||
/// <summary>
|
||||
/// 网络错误码。
|
||||
/// </summary>
|
||||
public enum NetworkErrorCode : byte
|
||||
{
|
||||
/// <summary>
|
||||
/// 未知错误。
|
||||
/// </summary>
|
||||
Unknown = 0,
|
||||
|
||||
/// <summary>
|
||||
/// 地址族错误。
|
||||
/// </summary>
|
||||
AddressFamilyError,
|
||||
|
||||
/// <summary>
|
||||
/// Socket 错误。
|
||||
/// </summary>
|
||||
SocketError,
|
||||
|
||||
/// <summary>
|
||||
/// 连接错误。
|
||||
/// </summary>
|
||||
ConnectError,
|
||||
|
||||
/// <summary>
|
||||
/// 发送错误。
|
||||
/// </summary>
|
||||
SendError,
|
||||
|
||||
/// <summary>
|
||||
/// 接收错误。
|
||||
/// </summary>
|
||||
ReceiveError,
|
||||
|
||||
/// <summary>
|
||||
/// 序列化错误。
|
||||
/// </summary>
|
||||
SerializeError,
|
||||
|
||||
/// <summary>
|
||||
/// 反序列化消息包头错误。
|
||||
/// </summary>
|
||||
DeserializePacketHeaderError,
|
||||
|
||||
/// <summary>
|
||||
/// 反序列化消息包错误。
|
||||
/// </summary>
|
||||
DeserializePacketError
|
||||
}
|
||||
}
|
@@ -1,3 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0bb8ca2ece7d49d28b1b4fd1bfa027e6
|
||||
timeCreated: 1681993877
|
@@ -1,30 +0,0 @@
|
||||
namespace TEngine
|
||||
{
|
||||
public class NetworkEvent
|
||||
{
|
||||
/// <summary>
|
||||
/// 网网络连接成功事件。
|
||||
/// </summary>
|
||||
public static int NetworkConnectedEvent = StringId.StringToHash("NetworkEvent.NetworkConnectedEvent");
|
||||
|
||||
/// <summary>
|
||||
/// 网络连接关闭事件。
|
||||
/// </summary>
|
||||
public static int NetworkClosedEvent = StringId.StringToHash("NetworkEvent.NetworkClosedEvent");
|
||||
|
||||
/// <summary>
|
||||
/// 网络错误事件。
|
||||
/// </summary>
|
||||
public static int NetworkErrorEvent = StringId.StringToHash("NetworkEvent.NetworkErrorEvent");
|
||||
|
||||
/// <summary>
|
||||
/// 用户自定义网络错误事件。
|
||||
/// </summary>
|
||||
public static int NetworkCustomErrorEvent = StringId.StringToHash("NetworkEvent.NetworkCustomErrorEvent");
|
||||
|
||||
/// <summary>
|
||||
/// 网络心跳包丢失事件。
|
||||
/// </summary>
|
||||
public static int NetworkMissHeartBeatEvent = StringId.StringToHash("NetworkEvent.NetworkMissHeartBeatEvent");
|
||||
}
|
||||
}
|
@@ -1,3 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8e3b5eb5b76244498181c6117f0e0b1a
|
||||
timeCreated: 1681993978
|
@@ -1,23 +0,0 @@
|
||||
using System.Net.Sockets;
|
||||
|
||||
namespace TEngine
|
||||
{
|
||||
internal sealed partial class NetworkManager
|
||||
{
|
||||
private sealed class ConnectState
|
||||
{
|
||||
private readonly Socket _socket;
|
||||
private readonly object _userData;
|
||||
|
||||
public ConnectState(Socket socket, object userData)
|
||||
{
|
||||
_socket = socket;
|
||||
_userData = userData;
|
||||
}
|
||||
|
||||
public Socket Socket => _socket;
|
||||
|
||||
public object UserData => _userData;
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,3 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 36ed7026c9b6441cb8a6438b2048d7f6
|
||||
timeCreated: 1681994450
|
@@ -1,39 +0,0 @@
|
||||
namespace TEngine
|
||||
{
|
||||
internal sealed partial class NetworkManager
|
||||
{
|
||||
private sealed class HeartBeatState
|
||||
{
|
||||
private float _heartBeatElapseSeconds;
|
||||
private int _missHeartBeatCount;
|
||||
|
||||
public HeartBeatState()
|
||||
{
|
||||
_heartBeatElapseSeconds = 0f;
|
||||
_missHeartBeatCount = 0;
|
||||
}
|
||||
|
||||
public float HeartBeatElapseSeconds
|
||||
{
|
||||
get => _heartBeatElapseSeconds;
|
||||
set => _heartBeatElapseSeconds = value;
|
||||
}
|
||||
|
||||
public int MissHeartBeatCount
|
||||
{
|
||||
get => _missHeartBeatCount;
|
||||
set => _missHeartBeatCount = value;
|
||||
}
|
||||
|
||||
public void Reset(bool resetHeartBeatElapseSeconds)
|
||||
{
|
||||
if (resetHeartBeatElapseSeconds)
|
||||
{
|
||||
_heartBeatElapseSeconds = 0f;
|
||||
}
|
||||
|
||||
_missHeartBeatCount = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,3 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5368d77f60554db99e9c4d0862660442
|
||||
timeCreated: 1681994450
|
@@ -1,684 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using GameProto;
|
||||
using Google.Protobuf;
|
||||
|
||||
namespace TEngine
|
||||
{
|
||||
internal sealed partial class NetworkManager
|
||||
{
|
||||
/// <summary>
|
||||
/// 网络频道基类。
|
||||
/// </summary>
|
||||
private abstract class NetworkChannelBase : INetworkChannel, IDisposable
|
||||
{
|
||||
private const float DefaultHeartBeatInterval = 30f;
|
||||
|
||||
private readonly string _name;
|
||||
protected readonly Queue<CSPkg> SendPacketPool;
|
||||
protected readonly INetworkChannelHelper NetworkChannelHelper;
|
||||
protected AddressFamily MAddressFamily;
|
||||
protected bool MResetHeartBeatElapseSecondsWhenReceivePacket;
|
||||
protected float MHeartBeatInterval;
|
||||
protected Socket MSocket;
|
||||
protected readonly SendState MSendState;
|
||||
protected readonly ReceiveState MReceiveState;
|
||||
protected readonly HeartBeatState MHeartBeatState;
|
||||
protected int MSentPacketCount;
|
||||
protected int MReceivedPacketCount;
|
||||
protected bool Active;
|
||||
private bool _disposed;
|
||||
|
||||
public Action<NetworkChannelBase, object> NetworkChannelConnected;
|
||||
public Action<NetworkChannelBase> NetworkChannelClosed;
|
||||
public Action<NetworkChannelBase, int> NetworkChannelMissHeartBeat;
|
||||
public Action<NetworkChannelBase, NetworkErrorCode, SocketError, string> NetworkChannelError;
|
||||
public Action<NetworkChannelBase, object> NetworkChannelCustomError;
|
||||
|
||||
/// <summary>
|
||||
/// 消息注册Map。
|
||||
/// </summary>
|
||||
private readonly Dictionary<int, List<CsMsgDelegate>> _msgHandlerMap = new Dictionary<int, List<CsMsgDelegate>>();
|
||||
|
||||
/// <summary>
|
||||
/// 委托缓存堆栈。
|
||||
/// </summary>
|
||||
private readonly Queue<List<CsMsgDelegate>> _cacheHandlerQueue = new Queue<List<CsMsgDelegate>>();
|
||||
|
||||
/// <summary>
|
||||
/// 消息包缓存堆栈。
|
||||
/// </summary>
|
||||
private readonly Queue<CSPkg> _packsQueue = new Queue<CSPkg>();
|
||||
|
||||
/// <summary>
|
||||
/// 初始化网络频道基类的新实例。
|
||||
/// </summary>
|
||||
/// <param name="name">网络频道名称。</param>
|
||||
/// <param name="networkChannelHelper">网络频道辅助器。</param>
|
||||
public NetworkChannelBase(string name, INetworkChannelHelper networkChannelHelper)
|
||||
{
|
||||
_name = name ?? string.Empty;
|
||||
SendPacketPool = new Queue<CSPkg>();
|
||||
NetworkChannelHelper = networkChannelHelper;
|
||||
MAddressFamily = AddressFamily.Unknown;
|
||||
MResetHeartBeatElapseSecondsWhenReceivePacket = false;
|
||||
MHeartBeatInterval = DefaultHeartBeatInterval;
|
||||
MSocket = null;
|
||||
MSendState = new SendState();
|
||||
MReceiveState = new ReceiveState();
|
||||
MHeartBeatState = new HeartBeatState();
|
||||
MSentPacketCount = 0;
|
||||
MReceivedPacketCount = 0;
|
||||
Active = false;
|
||||
_disposed = false;
|
||||
|
||||
NetworkChannelConnected = null;
|
||||
NetworkChannelClosed = null;
|
||||
NetworkChannelMissHeartBeat = null;
|
||||
NetworkChannelError = null;
|
||||
NetworkChannelCustomError = null;
|
||||
|
||||
networkChannelHelper.Initialize(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取网络频道名称。
|
||||
/// </summary>
|
||||
public string Name => _name;
|
||||
|
||||
/// <summary>
|
||||
/// 获取网络频道所使用的 Socket。
|
||||
/// </summary>
|
||||
public Socket Socket => MSocket;
|
||||
|
||||
/// <summary>
|
||||
/// 获取是否已连接。
|
||||
/// </summary>
|
||||
public bool Connected
|
||||
{
|
||||
get
|
||||
{
|
||||
if (MSocket != null)
|
||||
{
|
||||
return MSocket.Connected;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取网络服务类型。
|
||||
/// </summary>
|
||||
public abstract ServiceType ServiceType { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取网络地址类型。
|
||||
/// </summary>
|
||||
public AddressFamily AddressFamily => MAddressFamily;
|
||||
|
||||
/// <summary>
|
||||
/// 获取要发送的消息包数量。
|
||||
/// </summary>
|
||||
public int SendPacketCount => SendPacketPool.Count;
|
||||
|
||||
/// <summary>
|
||||
/// 获取累计发送的消息包数量。
|
||||
/// </summary>
|
||||
public int SentPacketCount => MSentPacketCount;
|
||||
|
||||
/// <summary>
|
||||
/// 获取已接收未处理的消息包数量。
|
||||
/// </summary>
|
||||
public int ReceivePacketCount => _cacheHandlerQueue.Count;
|
||||
|
||||
/// <summary>
|
||||
/// 获取累计已接收的消息包数量。
|
||||
/// </summary>
|
||||
public int ReceivedPacketCount => MReceivedPacketCount;
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置当收到消息包时是否重置心跳流逝时间。
|
||||
/// </summary>
|
||||
public bool ResetHeartBeatElapseSecondsWhenReceivePacket
|
||||
{
|
||||
get => MResetHeartBeatElapseSecondsWhenReceivePacket;
|
||||
set => MResetHeartBeatElapseSecondsWhenReceivePacket = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取丢失心跳的次数。
|
||||
/// </summary>
|
||||
public int MissHeartBeatCount => MHeartBeatState.MissHeartBeatCount;
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置心跳间隔时长,以秒为单位。
|
||||
/// </summary>
|
||||
public float HeartBeatInterval
|
||||
{
|
||||
get => MHeartBeatInterval;
|
||||
set => MHeartBeatInterval = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取心跳等待时长,以秒为单位。
|
||||
/// </summary>
|
||||
public float HeartBeatElapseSeconds => MHeartBeatState.HeartBeatElapseSeconds;
|
||||
|
||||
/// <summary>
|
||||
/// 网络频道轮询。
|
||||
/// </summary>
|
||||
/// <param name="elapseSeconds">逻辑流逝时间,以秒为单位。</param>
|
||||
/// <param name="realElapseSeconds">真实流逝时间,以秒为单位。</param>
|
||||
public virtual void Update(float elapseSeconds, float realElapseSeconds)
|
||||
{
|
||||
if (MSocket == null || !Active)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ProcessSend();
|
||||
ProcessReceive();
|
||||
if (MSocket == null || !Active)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
HandleCsMsgOnUpdate();
|
||||
|
||||
if (MHeartBeatInterval > 0f)
|
||||
{
|
||||
bool sendHeartBeat = false;
|
||||
int missHeartBeatCount = 0;
|
||||
lock (MHeartBeatState)
|
||||
{
|
||||
if (MSocket == null || !Active)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
MHeartBeatState.HeartBeatElapseSeconds += realElapseSeconds;
|
||||
if (MHeartBeatState.HeartBeatElapseSeconds >= MHeartBeatInterval)
|
||||
{
|
||||
sendHeartBeat = true;
|
||||
missHeartBeatCount = MHeartBeatState.MissHeartBeatCount;
|
||||
MHeartBeatState.HeartBeatElapseSeconds = 0f;
|
||||
MHeartBeatState.MissHeartBeatCount++;
|
||||
}
|
||||
}
|
||||
|
||||
if (sendHeartBeat && NetworkChannelHelper.SendHeartBeat())
|
||||
{
|
||||
if (missHeartBeatCount > 0 && NetworkChannelMissHeartBeat != null)
|
||||
{
|
||||
NetworkChannelMissHeartBeat(this, missHeartBeatCount);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 关闭网络频道。
|
||||
/// </summary>
|
||||
public virtual void Shutdown()
|
||||
{
|
||||
Close();
|
||||
NetworkChannelHelper.Shutdown();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 注册网络消息包处理函数。
|
||||
/// </summary>
|
||||
/// <param name="msgId">网络消息包id。</param>
|
||||
/// <param name="msgDelegate">要注册的网络消息包处理函数。</param>
|
||||
/// <param name="checkRepeat">是否检测重复。</param>
|
||||
public void RegisterMsgHandler(int msgId, CsMsgDelegate msgDelegate,bool checkRepeat = true)
|
||||
{
|
||||
if (msgDelegate == null)
|
||||
{
|
||||
throw new GameFrameworkException("Msg handler is invalid.");
|
||||
}
|
||||
|
||||
lock (_msgHandlerMap)
|
||||
{
|
||||
if (!_msgHandlerMap.TryGetValue(msgId, out var listHandle))
|
||||
{
|
||||
listHandle = new List<CsMsgDelegate>();
|
||||
_msgHandlerMap[msgId] = listHandle;
|
||||
}
|
||||
|
||||
if (listHandle != null)
|
||||
{
|
||||
if (!listHandle.Contains(msgDelegate))
|
||||
{
|
||||
listHandle.Add(msgDelegate);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (checkRepeat)
|
||||
{
|
||||
Log.Error("-------------repeat RegCmdHandle MsgId:{0}-----------",msgId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 移除网络消息包处理函数。
|
||||
/// </summary>
|
||||
/// <param name="msgId">网络消息包id。</param>
|
||||
/// <param name="msgDelegate">要注册的网络消息包处理函数。</param>
|
||||
public void RemoveMsgHandler(int msgId, CsMsgDelegate msgDelegate)
|
||||
{
|
||||
lock (_msgHandlerMap)
|
||||
{
|
||||
if (!_msgHandlerMap.TryGetValue(msgId, out List<CsMsgDelegate> listHandle))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (listHandle != null)
|
||||
{
|
||||
listHandle.Remove(msgDelegate);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 连接到远程主机。
|
||||
/// </summary>
|
||||
/// <param name="ipAddress">远程主机的 IP 地址。</param>
|
||||
/// <param name="port">远程主机的端口号。</param>
|
||||
public void Connect(string ipAddress, int port)
|
||||
{
|
||||
IPAddress address = IPAddress.Parse(ipAddress);
|
||||
Connect(address, port, null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 连接到远程主机。
|
||||
/// </summary>
|
||||
/// <param name="ipAddress">远程主机的 IP 地址。</param>
|
||||
/// <param name="port">远程主机的端口号。</param>
|
||||
public void Connect(IPAddress ipAddress, int port)
|
||||
{
|
||||
Connect(ipAddress, port, null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 连接到远程主机。
|
||||
/// </summary>
|
||||
/// <param name="ipAddress">远程主机的 IP 地址。</param>
|
||||
/// <param name="port">远程主机的端口号。</param>
|
||||
/// <param name="userData">用户自定义数据。</param>
|
||||
public virtual void Connect(IPAddress ipAddress, int port, object userData)
|
||||
{
|
||||
if (MSocket != null)
|
||||
{
|
||||
Close();
|
||||
MSocket = null;
|
||||
}
|
||||
|
||||
switch (ipAddress.AddressFamily)
|
||||
{
|
||||
case System.Net.Sockets.AddressFamily.InterNetwork:
|
||||
MAddressFamily = AddressFamily.IPv4;
|
||||
break;
|
||||
|
||||
case System.Net.Sockets.AddressFamily.InterNetworkV6:
|
||||
MAddressFamily = AddressFamily.IPv6;
|
||||
break;
|
||||
|
||||
default:
|
||||
string errorMessage = Utility.Text.Format("Not supported address family '{0}'.", ipAddress.AddressFamily);
|
||||
if (NetworkChannelError != null)
|
||||
{
|
||||
NetworkChannelError(this, NetworkErrorCode.AddressFamilyError, SocketError.Success, errorMessage);
|
||||
return;
|
||||
}
|
||||
|
||||
throw new GameFrameworkException(errorMessage);
|
||||
}
|
||||
|
||||
MSendState.Reset();
|
||||
MReceiveState.PrepareForPacketHeader(NetworkChannelHelper.PacketHeaderLength);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 关闭连接并释放所有相关资源。
|
||||
/// </summary>
|
||||
public void Close()
|
||||
{
|
||||
lock (this)
|
||||
{
|
||||
if (MSocket == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Active = false;
|
||||
|
||||
try
|
||||
{
|
||||
MSocket.Shutdown(SocketShutdown.Both);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
finally
|
||||
{
|
||||
MSocket.Close();
|
||||
MSocket = null;
|
||||
|
||||
if (NetworkChannelClosed != null)
|
||||
{
|
||||
NetworkChannelClosed(this);
|
||||
}
|
||||
}
|
||||
|
||||
MSentPacketCount = 0;
|
||||
MReceivedPacketCount = 0;
|
||||
|
||||
lock (SendPacketPool)
|
||||
{
|
||||
SendPacketPool.Clear();
|
||||
}
|
||||
|
||||
lock (_packsQueue)
|
||||
{
|
||||
_packsQueue.Clear();
|
||||
}
|
||||
|
||||
lock (_msgHandlerMap)
|
||||
{
|
||||
_msgHandlerMap.Clear();
|
||||
}
|
||||
|
||||
lock (_cacheHandlerQueue)
|
||||
{
|
||||
_cacheHandlerQueue.Clear();
|
||||
}
|
||||
|
||||
lock (MHeartBeatState)
|
||||
{
|
||||
MHeartBeatState.Reset(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 向远程主机发送消息包。
|
||||
/// </summary>
|
||||
/// <param name="packet">要发送的消息包。</param>
|
||||
public bool Send(CSPkg packet)
|
||||
{
|
||||
if (MSocket == null)
|
||||
{
|
||||
string errorMessage = "You must connect first.";
|
||||
if (NetworkChannelError != null)
|
||||
{
|
||||
NetworkChannelError(this, NetworkErrorCode.SendError, SocketError.Success, errorMessage);
|
||||
return false;
|
||||
}
|
||||
|
||||
throw new GameFrameworkException(errorMessage);
|
||||
}
|
||||
|
||||
if (!Active)
|
||||
{
|
||||
string errorMessage = "Socket is not active.";
|
||||
if (NetworkChannelError != null)
|
||||
{
|
||||
NetworkChannelError(this, NetworkErrorCode.SendError, SocketError.Success, errorMessage);
|
||||
return false;
|
||||
}
|
||||
|
||||
throw new GameFrameworkException(errorMessage);
|
||||
}
|
||||
|
||||
if (packet == null)
|
||||
{
|
||||
string errorMessage = "Packet is invalid.";
|
||||
if (NetworkChannelError != null)
|
||||
{
|
||||
NetworkChannelError(this, NetworkErrorCode.SendError, SocketError.Success, errorMessage);
|
||||
return false;
|
||||
}
|
||||
|
||||
throw new GameFrameworkException(errorMessage);
|
||||
}
|
||||
|
||||
lock (SendPacketPool)
|
||||
{
|
||||
SendPacketPool.Enqueue(packet);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 向远程主机发送消息包并注册消息回调。
|
||||
/// </summary>
|
||||
/// <param name="packet">要发送的消息包。</param>
|
||||
/// <param name="resHandler">要注册的回调。</param>
|
||||
/// <param name="needShowWaitUI">是否需要等待UI。</param>
|
||||
/// <returns>消息包是否发送成功。</returns>
|
||||
public bool Send(CSPkg packet, CsMsgDelegate resHandler, bool needShowWaitUI = false)
|
||||
{
|
||||
RegisterMsgHandler((int)packet.Head.MsgId,resHandler,false);
|
||||
return Send(packet);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 释放资源。
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 释放资源。
|
||||
/// </summary>
|
||||
/// <param name="disposing">释放资源标记。</param>
|
||||
private void Dispose(bool disposing)
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (disposing)
|
||||
{
|
||||
Close();
|
||||
MSendState.Dispose();
|
||||
MReceiveState.Dispose();
|
||||
}
|
||||
|
||||
_disposed = true;
|
||||
}
|
||||
|
||||
protected virtual bool ProcessSend()
|
||||
{
|
||||
if (MSendState.Stream.Length > 0 || SendPacketPool.Count <= 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
while (SendPacketPool.Count > 0)
|
||||
{
|
||||
CSPkg csPkg = null;
|
||||
lock (SendPacketPool)
|
||||
{
|
||||
csPkg = SendPacketPool.Dequeue();
|
||||
}
|
||||
|
||||
bool serializeResult = false;
|
||||
try
|
||||
{
|
||||
serializeResult = NetworkChannelHelper.Serialize(csPkg, MSendState.Stream);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Active = false;
|
||||
if (NetworkChannelError != null)
|
||||
{
|
||||
SocketException socketException = exception as SocketException;
|
||||
NetworkChannelError(this, NetworkErrorCode.SerializeError, socketException?.SocketErrorCode ?? SocketError.Success,
|
||||
exception.ToString());
|
||||
return false;
|
||||
}
|
||||
|
||||
throw;
|
||||
}
|
||||
|
||||
if (!serializeResult)
|
||||
{
|
||||
string errorMessage = "Serialized packet failure.";
|
||||
if (NetworkChannelError != null)
|
||||
{
|
||||
NetworkChannelError(this, NetworkErrorCode.SerializeError, SocketError.Success, errorMessage);
|
||||
return false;
|
||||
}
|
||||
|
||||
throw new GameFrameworkException(errorMessage);
|
||||
}
|
||||
}
|
||||
|
||||
MSendState.Stream.Position = 0L;
|
||||
return true;
|
||||
}
|
||||
|
||||
protected virtual void ProcessReceive()
|
||||
{
|
||||
}
|
||||
|
||||
protected virtual bool ProcessPacketHeader()
|
||||
{
|
||||
try
|
||||
{
|
||||
IPacketHeader packetHeader = NetworkChannelHelper.DeserializePacketHeader(MReceiveState.Stream, out var customErrorData);
|
||||
|
||||
if (customErrorData != null && NetworkChannelCustomError != null)
|
||||
{
|
||||
NetworkChannelCustomError(this, customErrorData);
|
||||
}
|
||||
|
||||
if (packetHeader == null)
|
||||
{
|
||||
string errorMessage = "Packet header is invalid.";
|
||||
if (NetworkChannelError != null)
|
||||
{
|
||||
NetworkChannelError(this, NetworkErrorCode.DeserializePacketHeaderError, SocketError.Success, errorMessage);
|
||||
return false;
|
||||
}
|
||||
|
||||
throw new GameFrameworkException(errorMessage);
|
||||
}
|
||||
|
||||
MReceiveState.PrepareForPacket(packetHeader);
|
||||
if (packetHeader.PacketLength <= 0)
|
||||
{
|
||||
bool processSuccess = ProcessPacket();
|
||||
MReceivedPacketCount++;
|
||||
return processSuccess;
|
||||
}
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Active = false;
|
||||
if (NetworkChannelError != null)
|
||||
{
|
||||
SocketException socketException = exception as SocketException;
|
||||
NetworkChannelError(this, NetworkErrorCode.DeserializePacketHeaderError, socketException?.SocketErrorCode ?? SocketError.Success,
|
||||
exception.ToString());
|
||||
return false;
|
||||
}
|
||||
|
||||
throw;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected virtual bool ProcessPacket()
|
||||
{
|
||||
lock (MHeartBeatState)
|
||||
{
|
||||
MHeartBeatState.Reset(MResetHeartBeatElapseSecondsWhenReceivePacket);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
CSPkg csPkg = NetworkChannelHelper.DeserializePacket(MReceiveState.PacketHeader, MReceiveState.Stream, out var customErrorData);
|
||||
|
||||
if (customErrorData != null && NetworkChannelCustomError != null)
|
||||
{
|
||||
NetworkChannelCustomError(this, customErrorData);
|
||||
}
|
||||
|
||||
if (csPkg != null)
|
||||
{
|
||||
lock (_cacheHandlerQueue)
|
||||
{
|
||||
if (_msgHandlerMap.TryGetValue((int)csPkg.Head.MsgId, out var listHandle))
|
||||
{
|
||||
_cacheHandlerQueue.Enqueue(listHandle);
|
||||
|
||||
_packsQueue.Enqueue(csPkg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MReceiveState.PrepareForPacketHeader(NetworkChannelHelper.PacketHeaderLength);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Active = false;
|
||||
if (NetworkChannelError != null)
|
||||
{
|
||||
SocketException socketException = exception as SocketException;
|
||||
NetworkChannelError(this, NetworkErrorCode.DeserializePacketError, socketException?.SocketErrorCode ?? SocketError.Success,
|
||||
exception.ToString());
|
||||
return false;
|
||||
}
|
||||
|
||||
throw;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 主线程从消息包缓存堆栈/委托缓存堆栈中出列。
|
||||
/// </summary>
|
||||
private void HandleCsMsgOnUpdate()
|
||||
{
|
||||
if (_cacheHandlerQueue.Count <= 0 || _packsQueue.Count <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
try
|
||||
{
|
||||
foreach (CsMsgDelegate handle in _cacheHandlerQueue.Dequeue())
|
||||
{
|
||||
var pack = _packsQueue.Peek();
|
||||
|
||||
if (pack != null)
|
||||
{
|
||||
handle(pack);
|
||||
}
|
||||
}
|
||||
_packsQueue.Dequeue();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,3 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 399b677044924c6bb2a4e5d56628ca42
|
||||
timeCreated: 1681994450
|
@@ -1,91 +0,0 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace TEngine
|
||||
{
|
||||
internal sealed partial class NetworkManager
|
||||
{
|
||||
private sealed class ReceiveState : IDisposable
|
||||
{
|
||||
private const int DefaultBufferLength = 1024 * 64;
|
||||
private MemoryStream _stream;
|
||||
private IPacketHeader _packetHeader;
|
||||
private bool _disposed;
|
||||
|
||||
public ReceiveState()
|
||||
{
|
||||
_stream = new MemoryStream(DefaultBufferLength);
|
||||
_packetHeader = null;
|
||||
_disposed = false;
|
||||
}
|
||||
|
||||
public MemoryStream Stream
|
||||
{
|
||||
get
|
||||
{
|
||||
return _stream;
|
||||
}
|
||||
}
|
||||
|
||||
public IPacketHeader PacketHeader
|
||||
{
|
||||
get
|
||||
{
|
||||
return _packetHeader;
|
||||
}
|
||||
}
|
||||
|
||||
public void PrepareForPacketHeader(int packetHeaderLength)
|
||||
{
|
||||
Reset(packetHeaderLength, null);
|
||||
}
|
||||
|
||||
public void PrepareForPacket(IPacketHeader packetHeader)
|
||||
{
|
||||
if (packetHeader == null)
|
||||
{
|
||||
throw new GameFrameworkException("Packet header is invalid.");
|
||||
}
|
||||
|
||||
Reset(packetHeader.PacketLength, packetHeader);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
private void Dispose(bool disposing)
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (disposing)
|
||||
{
|
||||
if (_stream != null)
|
||||
{
|
||||
_stream.Dispose();
|
||||
_stream = null;
|
||||
}
|
||||
}
|
||||
|
||||
_disposed = true;
|
||||
}
|
||||
|
||||
private void Reset(int targetLength, IPacketHeader packetHeader)
|
||||
{
|
||||
if (targetLength < 0)
|
||||
{
|
||||
throw new GameFrameworkException("Target length is invalid.");
|
||||
}
|
||||
|
||||
_stream.Position = 0L;
|
||||
_stream.SetLength(targetLength);
|
||||
_packetHeader = packetHeader;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,3 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 89feb1bba2164efea12041106391c284
|
||||
timeCreated: 1681994450
|
@@ -1,60 +0,0 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace TEngine
|
||||
{
|
||||
internal sealed partial class NetworkManager
|
||||
{
|
||||
private sealed class SendState : IDisposable
|
||||
{
|
||||
private const int DefaultBufferLength = 1024 * 64;
|
||||
private MemoryStream _stream;
|
||||
private bool _disposed;
|
||||
|
||||
public SendState()
|
||||
{
|
||||
_stream = new MemoryStream(DefaultBufferLength);
|
||||
_disposed = false;
|
||||
}
|
||||
|
||||
public MemoryStream Stream
|
||||
{
|
||||
get
|
||||
{
|
||||
return _stream;
|
||||
}
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
_stream.Position = 0L;
|
||||
_stream.SetLength(0L);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
private void Dispose(bool disposing)
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (disposing)
|
||||
{
|
||||
if (_stream != null)
|
||||
{
|
||||
_stream.Dispose();
|
||||
_stream = null;
|
||||
}
|
||||
}
|
||||
|
||||
_disposed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,3 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: df8ba0ec679b463e880f8f599ba23d77
|
||||
timeCreated: 1681994450
|
@@ -1,281 +0,0 @@
|
||||
using System;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
|
||||
namespace TEngine
|
||||
{
|
||||
internal sealed partial class NetworkManager
|
||||
{
|
||||
/// <summary>
|
||||
/// TCP 网络频道。
|
||||
/// </summary>
|
||||
private sealed class TcpNetworkChannel : NetworkChannelBase
|
||||
{
|
||||
private readonly AsyncCallback _connectCallback;
|
||||
private readonly AsyncCallback _sendCallback;
|
||||
private readonly AsyncCallback _receiveCallback;
|
||||
|
||||
/// <summary>
|
||||
/// 初始化网络频道的新实例。
|
||||
/// </summary>
|
||||
/// <param name="name">网络频道名称。</param>
|
||||
/// <param name="networkChannelHelper">网络频道辅助器。</param>
|
||||
public TcpNetworkChannel(string name, INetworkChannelHelper networkChannelHelper)
|
||||
: base(name, networkChannelHelper)
|
||||
{
|
||||
_connectCallback = ConnectCallback;
|
||||
_sendCallback = SendCallback;
|
||||
_receiveCallback = ReceiveCallback;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取网络服务类型。
|
||||
/// </summary>
|
||||
public override ServiceType ServiceType
|
||||
{
|
||||
get
|
||||
{
|
||||
return ServiceType.Tcp;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 连接到远程主机。
|
||||
/// </summary>
|
||||
/// <param name="ipAddress">远程主机的 IP 地址。</param>
|
||||
/// <param name="port">远程主机的端口号。</param>
|
||||
/// <param name="userData">用户自定义数据。</param>
|
||||
public override void Connect(IPAddress ipAddress, int port, object userData)
|
||||
{
|
||||
base.Connect(ipAddress, port, userData);
|
||||
MSocket = new Socket(ipAddress.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
|
||||
if (MSocket == null)
|
||||
{
|
||||
string errorMessage = "Initialize network channel failure.";
|
||||
if (NetworkChannelError != null)
|
||||
{
|
||||
NetworkChannelError(this, NetworkErrorCode.SocketError, SocketError.Success, errorMessage);
|
||||
return;
|
||||
}
|
||||
|
||||
throw new GameFrameworkException(errorMessage);
|
||||
}
|
||||
|
||||
NetworkChannelHelper.PrepareForConnecting();
|
||||
ConnectAsync(ipAddress, port, userData);
|
||||
}
|
||||
|
||||
protected override bool ProcessSend()
|
||||
{
|
||||
if (base.ProcessSend())
|
||||
{
|
||||
SendAsync();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void ConnectAsync(IPAddress ipAddress, int port, object userData)
|
||||
{
|
||||
try
|
||||
{
|
||||
MSocket.BeginConnect(ipAddress, port, _connectCallback, new ConnectState(MSocket, userData));
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
if (NetworkChannelError != null)
|
||||
{
|
||||
SocketException socketException = exception as SocketException;
|
||||
NetworkChannelError(this, NetworkErrorCode.ConnectError, socketException != null ? socketException.SocketErrorCode : SocketError.Success, exception.ToString());
|
||||
return;
|
||||
}
|
||||
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
private void ConnectCallback(IAsyncResult ar)
|
||||
{
|
||||
ConnectState socketUserData = (ConnectState)ar.AsyncState;
|
||||
try
|
||||
{
|
||||
socketUserData.Socket.EndConnect(ar);
|
||||
}
|
||||
catch (ObjectDisposedException)
|
||||
{
|
||||
return;
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Active = false;
|
||||
if (NetworkChannelError != null)
|
||||
{
|
||||
SocketException socketException = exception as SocketException;
|
||||
NetworkChannelError(this, NetworkErrorCode.ConnectError, socketException != null ? socketException.SocketErrorCode : SocketError.Success, exception.ToString());
|
||||
return;
|
||||
}
|
||||
|
||||
throw;
|
||||
}
|
||||
|
||||
MSentPacketCount = 0;
|
||||
MReceivedPacketCount = 0;
|
||||
|
||||
lock (SendPacketPool)
|
||||
{
|
||||
SendPacketPool.Clear();
|
||||
}
|
||||
|
||||
lock (MHeartBeatState)
|
||||
{
|
||||
MHeartBeatState.Reset(true);
|
||||
}
|
||||
|
||||
if (NetworkChannelConnected != null)
|
||||
{
|
||||
NetworkChannelConnected(this, socketUserData.UserData);
|
||||
}
|
||||
|
||||
Active = true;
|
||||
ReceiveAsync();
|
||||
}
|
||||
|
||||
private void SendAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
MSocket.BeginSend(MSendState.Stream.GetBuffer(), (int)MSendState.Stream.Position, (int)(MSendState.Stream.Length - MSendState.Stream.Position), SocketFlags.None, _sendCallback, MSocket);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Active = false;
|
||||
if (NetworkChannelError != null)
|
||||
{
|
||||
SocketException socketException = exception as SocketException;
|
||||
NetworkChannelError(this, NetworkErrorCode.SendError, socketException != null ? socketException.SocketErrorCode : SocketError.Success, exception.ToString());
|
||||
return;
|
||||
}
|
||||
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
private void SendCallback(IAsyncResult ar)
|
||||
{
|
||||
Socket socket = (Socket)ar.AsyncState;
|
||||
if (!socket.Connected)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int bytesSent = 0;
|
||||
try
|
||||
{
|
||||
bytesSent = socket.EndSend(ar);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Active = false;
|
||||
if (NetworkChannelError != null)
|
||||
{
|
||||
SocketException socketException = exception as SocketException;
|
||||
NetworkChannelError(this, NetworkErrorCode.SendError, socketException != null ? socketException.SocketErrorCode : SocketError.Success, exception.ToString());
|
||||
return;
|
||||
}
|
||||
|
||||
throw;
|
||||
}
|
||||
|
||||
MSendState.Stream.Position += bytesSent;
|
||||
if (MSendState.Stream.Position < MSendState.Stream.Length)
|
||||
{
|
||||
SendAsync();
|
||||
return;
|
||||
}
|
||||
|
||||
MSentPacketCount++;
|
||||
MSendState.Reset();
|
||||
}
|
||||
|
||||
private void ReceiveAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
MSocket.BeginReceive(MReceiveState.Stream.GetBuffer(), (int)MReceiveState.Stream.Position, (int)(MReceiveState.Stream.Length - MReceiveState.Stream.Position), SocketFlags.None, _receiveCallback, MSocket);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Active = false;
|
||||
if (NetworkChannelError != null)
|
||||
{
|
||||
SocketException socketException = exception as SocketException;
|
||||
NetworkChannelError(this, NetworkErrorCode.ReceiveError, socketException != null ? socketException.SocketErrorCode : SocketError.Success, exception.ToString());
|
||||
return;
|
||||
}
|
||||
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
private void ReceiveCallback(IAsyncResult ar)
|
||||
{
|
||||
Socket socket = (Socket)ar.AsyncState;
|
||||
if (!socket.Connected)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int bytesReceived = 0;
|
||||
try
|
||||
{
|
||||
bytesReceived = socket.EndReceive(ar);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Active = false;
|
||||
if (NetworkChannelError != null)
|
||||
{
|
||||
SocketException socketException = exception as SocketException;
|
||||
NetworkChannelError(this, NetworkErrorCode.ReceiveError, socketException != null ? socketException.SocketErrorCode : SocketError.Success, exception.ToString());
|
||||
return;
|
||||
}
|
||||
|
||||
throw;
|
||||
}
|
||||
|
||||
if (bytesReceived <= 0)
|
||||
{
|
||||
Close();
|
||||
return;
|
||||
}
|
||||
|
||||
MReceiveState.Stream.Position += bytesReceived;
|
||||
if (MReceiveState.Stream.Position < MReceiveState.Stream.Length)
|
||||
{
|
||||
ReceiveAsync();
|
||||
return;
|
||||
}
|
||||
|
||||
MReceiveState.Stream.Position = 0L;
|
||||
|
||||
bool processSuccess = false;
|
||||
if (MReceiveState.PacketHeader != null)
|
||||
{
|
||||
processSuccess = ProcessPacket();
|
||||
MReceivedPacketCount++;
|
||||
}
|
||||
else
|
||||
{
|
||||
processSuccess = ProcessPacketHeader();
|
||||
}
|
||||
|
||||
if (processSuccess)
|
||||
{
|
||||
ReceiveAsync();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,3 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a9660c1a740a423d9c855619aedbebd5
|
||||
timeCreated: 1681994450
|
@@ -1,257 +0,0 @@
|
||||
using System;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
|
||||
namespace TEngine
|
||||
{
|
||||
internal sealed partial class NetworkManager
|
||||
{
|
||||
/// <summary>
|
||||
/// 使用同步接收的 TCP 网络频道。
|
||||
/// </summary>
|
||||
private sealed class TcpWithSyncReceiveNetworkChannel : NetworkChannelBase
|
||||
{
|
||||
private readonly AsyncCallback _connectCallback;
|
||||
private readonly AsyncCallback _sendCallback;
|
||||
|
||||
/// <summary>
|
||||
/// 初始化网络频道的新实例。
|
||||
/// </summary>
|
||||
/// <param name="name">网络频道名称。</param>
|
||||
/// <param name="networkChannelHelper">网络频道辅助器。</param>
|
||||
public TcpWithSyncReceiveNetworkChannel(string name, INetworkChannelHelper networkChannelHelper)
|
||||
: base(name, networkChannelHelper)
|
||||
{
|
||||
_connectCallback = ConnectCallback;
|
||||
_sendCallback = SendCallback;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取网络服务类型。
|
||||
/// </summary>
|
||||
public override ServiceType ServiceType
|
||||
{
|
||||
get
|
||||
{
|
||||
return ServiceType.TcpWithSyncReceive;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 连接到远程主机。
|
||||
/// </summary>
|
||||
/// <param name="ipAddress">远程主机的 IP 地址。</param>
|
||||
/// <param name="port">远程主机的端口号。</param>
|
||||
/// <param name="userData">用户自定义数据。</param>
|
||||
public override void Connect(IPAddress ipAddress, int port, object userData)
|
||||
{
|
||||
base.Connect(ipAddress, port, userData);
|
||||
MSocket = new Socket(ipAddress.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
|
||||
if (MSocket == null)
|
||||
{
|
||||
string errorMessage = "Initialize network channel failure.";
|
||||
if (NetworkChannelError != null)
|
||||
{
|
||||
NetworkChannelError(this, NetworkErrorCode.SocketError, SocketError.Success, errorMessage);
|
||||
return;
|
||||
}
|
||||
|
||||
throw new GameFrameworkException(errorMessage);
|
||||
}
|
||||
|
||||
NetworkChannelHelper.PrepareForConnecting();
|
||||
ConnectAsync(ipAddress, port, userData);
|
||||
}
|
||||
|
||||
protected override bool ProcessSend()
|
||||
{
|
||||
if (base.ProcessSend())
|
||||
{
|
||||
SendAsync();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
protected override void ProcessReceive()
|
||||
{
|
||||
base.ProcessReceive();
|
||||
while (MSocket.Available > 0)
|
||||
{
|
||||
if (!ReceiveSync())
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ConnectAsync(IPAddress ipAddress, int port, object userData)
|
||||
{
|
||||
try
|
||||
{
|
||||
MSocket.BeginConnect(ipAddress, port, _connectCallback, new ConnectState(MSocket, userData));
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
if (NetworkChannelError != null)
|
||||
{
|
||||
SocketException socketException = exception as SocketException;
|
||||
NetworkChannelError(this, NetworkErrorCode.ConnectError, socketException != null ? socketException.SocketErrorCode : SocketError.Success, exception.ToString());
|
||||
return;
|
||||
}
|
||||
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
private void ConnectCallback(IAsyncResult ar)
|
||||
{
|
||||
ConnectState socketUserData = (ConnectState)ar.AsyncState;
|
||||
try
|
||||
{
|
||||
socketUserData.Socket.EndConnect(ar);
|
||||
}
|
||||
catch (ObjectDisposedException)
|
||||
{
|
||||
return;
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Active = false;
|
||||
if (NetworkChannelError != null)
|
||||
{
|
||||
SocketException socketException = exception as SocketException;
|
||||
NetworkChannelError(this, NetworkErrorCode.ConnectError, socketException != null ? socketException.SocketErrorCode : SocketError.Success, exception.ToString());
|
||||
return;
|
||||
}
|
||||
|
||||
throw;
|
||||
}
|
||||
|
||||
MSentPacketCount = 0;
|
||||
MReceivedPacketCount = 0;
|
||||
|
||||
lock (SendPacketPool)
|
||||
{
|
||||
SendPacketPool.Clear();
|
||||
}
|
||||
|
||||
lock (MHeartBeatState)
|
||||
{
|
||||
MHeartBeatState.Reset(true);
|
||||
}
|
||||
|
||||
if (NetworkChannelConnected != null)
|
||||
{
|
||||
NetworkChannelConnected(this, socketUserData.UserData);
|
||||
}
|
||||
|
||||
Active = true;
|
||||
}
|
||||
|
||||
private void SendAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
MSocket.BeginSend(MSendState.Stream.GetBuffer(), (int)MSendState.Stream.Position, (int)(MSendState.Stream.Length - MSendState.Stream.Position), SocketFlags.None, _sendCallback, MSocket);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Active = false;
|
||||
if (NetworkChannelError != null)
|
||||
{
|
||||
SocketException socketException = exception as SocketException;
|
||||
NetworkChannelError(this, NetworkErrorCode.SendError, socketException != null ? socketException.SocketErrorCode : SocketError.Success, exception.ToString());
|
||||
return;
|
||||
}
|
||||
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
private void SendCallback(IAsyncResult ar)
|
||||
{
|
||||
Socket socket = (Socket)ar.AsyncState;
|
||||
if (!socket.Connected)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int bytesSent = 0;
|
||||
try
|
||||
{
|
||||
bytesSent = socket.EndSend(ar);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Active = false;
|
||||
if (NetworkChannelError != null)
|
||||
{
|
||||
SocketException socketException = exception as SocketException;
|
||||
NetworkChannelError(this, NetworkErrorCode.SendError, socketException != null ? socketException.SocketErrorCode : SocketError.Success, exception.ToString());
|
||||
return;
|
||||
}
|
||||
|
||||
throw;
|
||||
}
|
||||
|
||||
MSendState.Stream.Position += bytesSent;
|
||||
if (MSendState.Stream.Position < MSendState.Stream.Length)
|
||||
{
|
||||
SendAsync();
|
||||
return;
|
||||
}
|
||||
|
||||
MSentPacketCount++;
|
||||
MSendState.Reset();
|
||||
}
|
||||
|
||||
private bool ReceiveSync()
|
||||
{
|
||||
try
|
||||
{
|
||||
int bytesReceived = MSocket.Receive(MReceiveState.Stream.GetBuffer(), (int)MReceiveState.Stream.Position, (int)(MReceiveState.Stream.Length - MReceiveState.Stream.Position), SocketFlags.None);
|
||||
if (bytesReceived <= 0)
|
||||
{
|
||||
Close();
|
||||
return false;
|
||||
}
|
||||
|
||||
MReceiveState.Stream.Position += bytesReceived;
|
||||
if (MReceiveState.Stream.Position < MReceiveState.Stream.Length)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
MReceiveState.Stream.Position = 0L;
|
||||
|
||||
bool processSuccess = false;
|
||||
if (MReceiveState.PacketHeader != null)
|
||||
{
|
||||
processSuccess = ProcessPacket();
|
||||
MReceivedPacketCount++;
|
||||
}
|
||||
else
|
||||
{
|
||||
processSuccess = ProcessPacketHeader();
|
||||
}
|
||||
|
||||
return processSuccess;
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Active = false;
|
||||
if (NetworkChannelError != null)
|
||||
{
|
||||
SocketException socketException = exception as SocketException;
|
||||
NetworkChannelError(this, NetworkErrorCode.ReceiveError, socketException != null ? socketException.SocketErrorCode : SocketError.Success, exception.ToString());
|
||||
return false;
|
||||
}
|
||||
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,3 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ce9312d4603b4eda99cec01fa82364af
|
||||
timeCreated: 1681994450
|
@@ -1,291 +0,0 @@
|
||||
using System;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
|
||||
namespace TEngine
|
||||
{
|
||||
internal sealed partial class NetworkManager
|
||||
{
|
||||
/// <summary>
|
||||
/// Udp 网络频道。
|
||||
/// </summary>
|
||||
private sealed class UdpNetworkChannel : NetworkChannelBase
|
||||
{
|
||||
private readonly AsyncCallback _connectCallback;
|
||||
private readonly AsyncCallback _sendCallback;
|
||||
private readonly AsyncCallback _receiveCallback;
|
||||
|
||||
/// <summary>
|
||||
/// 获取网络服务类型。
|
||||
/// </summary>
|
||||
public override ServiceType ServiceType => ServiceType.Udp;
|
||||
|
||||
/// <summary>
|
||||
/// 初始化网络频道的新实例。
|
||||
/// </summary>
|
||||
/// <param name="name">网络频道名称。</param>
|
||||
/// <param name="networkChannelHelper">网络频道辅助器。</param>
|
||||
public UdpNetworkChannel(string name, INetworkChannelHelper networkChannelHelper)
|
||||
: base(name, networkChannelHelper)
|
||||
{
|
||||
_connectCallback = ConnectCallback;
|
||||
_sendCallback = SendCallback;
|
||||
_receiveCallback = ReceiveCallback;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 连接到远程主机。
|
||||
/// </summary>
|
||||
/// <param name="ipAddress">远程主机的 IP 地址。</param>
|
||||
/// <param name="port">远程主机的端口号。</param>
|
||||
/// <param name="userData">用户自定义数据。</param>
|
||||
public override void Connect(IPAddress ipAddress, int port, object userData)
|
||||
{
|
||||
base.Connect(ipAddress, port, userData);
|
||||
MSocket = new Socket(ipAddress.AddressFamily, SocketType.Dgram, ProtocolType.Udp);
|
||||
if (MSocket == null)
|
||||
{
|
||||
string errorMessage = "Initialize network channel failure.";
|
||||
if (NetworkChannelError != null)
|
||||
{
|
||||
NetworkChannelError(this, NetworkErrorCode.SocketError, SocketError.Success, errorMessage);
|
||||
return;
|
||||
}
|
||||
|
||||
throw new GameFrameworkException(errorMessage);
|
||||
}
|
||||
|
||||
NetworkChannelHelper.PrepareForConnecting();
|
||||
ConnectAsync(ipAddress, port, userData);
|
||||
}
|
||||
|
||||
private void ConnectAsync(IPAddress ipAddress, int port, object userData)
|
||||
{
|
||||
try
|
||||
{
|
||||
MSocket.BeginConnect(ipAddress, port, _connectCallback, new ConnectState(MSocket, userData));
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
if (NetworkChannelError != null)
|
||||
{
|
||||
SocketException socketException = exception as SocketException;
|
||||
NetworkChannelError(this, NetworkErrorCode.ConnectError,
|
||||
socketException?.SocketErrorCode ?? SocketError.Success,
|
||||
exception.ToString());
|
||||
return;
|
||||
}
|
||||
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
protected override bool ProcessSend()
|
||||
{
|
||||
if (base.ProcessSend())
|
||||
{
|
||||
SendAsync();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void ConnectCallback(IAsyncResult ar)
|
||||
{
|
||||
ConnectState socketUserData = (ConnectState)ar.AsyncState;
|
||||
try
|
||||
{
|
||||
socketUserData.Socket.EndConnect(ar);
|
||||
}
|
||||
catch (ObjectDisposedException)
|
||||
{
|
||||
return;
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Active = false;
|
||||
if (NetworkChannelError != null)
|
||||
{
|
||||
SocketException socketException = exception as SocketException;
|
||||
NetworkChannelError(this, NetworkErrorCode.ConnectError,
|
||||
socketException?.SocketErrorCode ?? SocketError.Success,
|
||||
exception.ToString());
|
||||
return;
|
||||
}
|
||||
|
||||
throw;
|
||||
}
|
||||
|
||||
MSentPacketCount = 0;
|
||||
MReceivedPacketCount = 0;
|
||||
|
||||
lock (SendPacketPool)
|
||||
{
|
||||
SendPacketPool.Clear();
|
||||
}
|
||||
|
||||
lock (MHeartBeatState)
|
||||
{
|
||||
MHeartBeatState.Reset(true);
|
||||
}
|
||||
|
||||
if (NetworkChannelConnected != null)
|
||||
{
|
||||
NetworkChannelConnected(this, socketUserData.UserData);
|
||||
}
|
||||
|
||||
Active = true;
|
||||
ReceiveAsync();
|
||||
}
|
||||
|
||||
private void SendAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
MSocket.BeginSend(MSendState.Stream.GetBuffer(), (int)MSendState.Stream.Position,
|
||||
(int)(MSendState.Stream.Length - MSendState.Stream.Position), SocketFlags.None, _sendCallback,
|
||||
MSocket);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Active = false;
|
||||
if (NetworkChannelError != null)
|
||||
{
|
||||
SocketException socketException = exception as SocketException;
|
||||
NetworkChannelError(this, NetworkErrorCode.SendError,
|
||||
socketException?.SocketErrorCode ?? SocketError.Success,
|
||||
exception.ToString());
|
||||
return;
|
||||
}
|
||||
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
private void SendCallback(IAsyncResult ar)
|
||||
{
|
||||
Socket socket = (Socket)ar.AsyncState;
|
||||
if (!socket.Connected)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int bytesSent = 0;
|
||||
try
|
||||
{
|
||||
bytesSent = socket.EndSend(ar);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Active = false;
|
||||
if (NetworkChannelError != null)
|
||||
{
|
||||
SocketException socketException = exception as SocketException;
|
||||
NetworkChannelError(this, NetworkErrorCode.SendError,
|
||||
socketException?.SocketErrorCode ?? SocketError.Success,
|
||||
exception.ToString());
|
||||
return;
|
||||
}
|
||||
|
||||
throw;
|
||||
}
|
||||
|
||||
MSendState.Stream.Position += bytesSent;
|
||||
if (MSendState.Stream.Position < MSendState.Stream.Length)
|
||||
{
|
||||
SendAsync();
|
||||
return;
|
||||
}
|
||||
|
||||
MSentPacketCount++;
|
||||
MSendState.Reset();
|
||||
}
|
||||
|
||||
private void ReceiveAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
MSocket.BeginReceive(MReceiveState.Stream.GetBuffer(), (int)MReceiveState.Stream.Position,
|
||||
(int)(MReceiveState.Stream.Length - MReceiveState.Stream.Position), SocketFlags.None,
|
||||
_receiveCallback, MSocket);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Active = false;
|
||||
if (NetworkChannelError != null)
|
||||
{
|
||||
SocketException socketException = exception as SocketException;
|
||||
NetworkChannelError(this, NetworkErrorCode.ReceiveError,
|
||||
socketException?.SocketErrorCode ?? SocketError.Success,
|
||||
exception.ToString());
|
||||
return;
|
||||
}
|
||||
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
private void ReceiveCallback(IAsyncResult ar)
|
||||
{
|
||||
Socket socket = (Socket)ar.AsyncState;
|
||||
if (!socket.Connected)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int bytesReceived = 0;
|
||||
try
|
||||
{
|
||||
bytesReceived = socket.EndReceive(ar);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Active = false;
|
||||
if (NetworkChannelError != null)
|
||||
{
|
||||
SocketException socketException = exception as SocketException;
|
||||
NetworkChannelError(this, NetworkErrorCode.ReceiveError,
|
||||
socketException?.SocketErrorCode ?? SocketError.Success,
|
||||
exception.ToString());
|
||||
return;
|
||||
}
|
||||
|
||||
throw;
|
||||
}
|
||||
|
||||
if (bytesReceived <= 0)
|
||||
{
|
||||
Close();
|
||||
return;
|
||||
}
|
||||
|
||||
MReceiveState.Stream.Position += bytesReceived;
|
||||
if (MReceiveState.Stream.Position < MReceiveState.Stream.Length)
|
||||
{
|
||||
ReceiveAsync();
|
||||
return;
|
||||
}
|
||||
|
||||
MReceiveState.Stream.Position = 0L;
|
||||
|
||||
bool processSuccess = false;
|
||||
if (MReceiveState.PacketHeader != null)
|
||||
{
|
||||
processSuccess = ProcessPacket();
|
||||
MReceivedPacketCount++;
|
||||
}
|
||||
else
|
||||
{
|
||||
processSuccess = ProcessPacketHeader();
|
||||
}
|
||||
|
||||
if (processSuccess)
|
||||
{
|
||||
ReceiveAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,3 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: cb75f9187f154d83a9fd92d8e1ab318d
|
||||
timeCreated: 1682091504
|
@@ -1,317 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net.Sockets;
|
||||
using GameBase;
|
||||
using GameProto;
|
||||
|
||||
namespace TEngine
|
||||
{
|
||||
/// <summary>
|
||||
/// 网络消息委托。
|
||||
/// </summary>
|
||||
public delegate void CsMsgDelegate(CSPkg csPkg);
|
||||
|
||||
/// <summary>
|
||||
/// 网络管理器。
|
||||
/// </summary>
|
||||
internal sealed partial class NetworkManager : Singleton<NetworkManager>, INetworkManager
|
||||
{
|
||||
private readonly Dictionary<string, NetworkChannelBase> _networkChannels;
|
||||
|
||||
private Action<INetworkChannel, object> _networkConnectedEventHandler;
|
||||
private Action<INetworkChannel> _networkClosedEventHandler;
|
||||
private Action<INetworkChannel, int> _networkMissHeartBeatEventHandler;
|
||||
private Action<INetworkChannel, NetworkErrorCode, SocketError, string> _networkErrorEventHandler;
|
||||
private Action<INetworkChannel, object> _networkCustomErrorEventHandler;
|
||||
|
||||
/// <summary>
|
||||
/// 初始化网络管理器的新实例。
|
||||
/// </summary>
|
||||
public NetworkManager()
|
||||
{
|
||||
_networkChannels = new Dictionary<string, NetworkChannelBase>(StringComparer.Ordinal);
|
||||
_networkConnectedEventHandler = null;
|
||||
_networkClosedEventHandler = null;
|
||||
_networkMissHeartBeatEventHandler = null;
|
||||
_networkErrorEventHandler = null;
|
||||
_networkCustomErrorEventHandler = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取网络频道数量。
|
||||
/// </summary>
|
||||
public int NetworkChannelCount => _networkChannels.Count;
|
||||
|
||||
/// <summary>
|
||||
/// 网络连接成功事件。
|
||||
/// </summary>
|
||||
public event Action<INetworkChannel, object> NetworkConnected
|
||||
{
|
||||
add => _networkConnectedEventHandler += value;
|
||||
remove => _networkConnectedEventHandler -= value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 网络连接关闭事件。
|
||||
/// </summary>
|
||||
public event Action<INetworkChannel> NetworkClosed
|
||||
{
|
||||
add => _networkClosedEventHandler += value;
|
||||
remove => _networkClosedEventHandler -= value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 网络心跳包丢失事件。
|
||||
/// </summary>
|
||||
public event Action<INetworkChannel, int> NetworkMissHeartBeat
|
||||
{
|
||||
add => _networkMissHeartBeatEventHandler += value;
|
||||
remove => _networkMissHeartBeatEventHandler -= value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 网络错误事件。
|
||||
/// </summary>
|
||||
public event Action<INetworkChannel, NetworkErrorCode, SocketError, string> NetworkError
|
||||
{
|
||||
add => _networkErrorEventHandler += value;
|
||||
remove => _networkErrorEventHandler -= value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 用户自定义网络错误事件。
|
||||
/// </summary>
|
||||
public event Action<INetworkChannel, object> NetworkCustomError
|
||||
{
|
||||
add => _networkCustomErrorEventHandler += value;
|
||||
remove => _networkCustomErrorEventHandler -= value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 网络管理器轮询。
|
||||
/// </summary>
|
||||
/// <param name="elapseSeconds">逻辑流逝时间,以秒为单位。</param>
|
||||
/// <param name="realElapseSeconds">真实流逝时间,以秒为单位。</param>
|
||||
public void Update(float elapseSeconds, float realElapseSeconds)
|
||||
{
|
||||
foreach (KeyValuePair<string, NetworkChannelBase> networkChannel in _networkChannels)
|
||||
{
|
||||
networkChannel.Value.Update(elapseSeconds, realElapseSeconds);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 关闭并清理网络管理器。
|
||||
/// </summary>
|
||||
public void Shutdown()
|
||||
{
|
||||
foreach (KeyValuePair<string, NetworkChannelBase> networkChannel in _networkChannels)
|
||||
{
|
||||
NetworkChannelBase networkChannelBase = networkChannel.Value;
|
||||
networkChannelBase.NetworkChannelConnected -= OnNetworkChannelConnected;
|
||||
networkChannelBase.NetworkChannelClosed -= OnNetworkChannelClosed;
|
||||
networkChannelBase.NetworkChannelMissHeartBeat -= OnNetworkChannelMissHeartBeat;
|
||||
networkChannelBase.NetworkChannelError -= OnNetworkChannelError;
|
||||
networkChannelBase.NetworkChannelCustomError -= OnNetworkChannelCustomError;
|
||||
networkChannelBase.Shutdown();
|
||||
}
|
||||
|
||||
_networkChannels.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检查是否存在网络频道。
|
||||
/// </summary>
|
||||
/// <param name="name">网络频道名称。</param>
|
||||
/// <returns>是否存在网络频道。</returns>
|
||||
public bool HasNetworkChannel(string name)
|
||||
{
|
||||
return _networkChannels.ContainsKey(name ?? string.Empty);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取网络频道。
|
||||
/// </summary>
|
||||
/// <param name="name">网络频道名称。</param>
|
||||
/// <returns>要获取的网络频道。</returns>
|
||||
public INetworkChannel GetNetworkChannel(string name)
|
||||
{
|
||||
if (_networkChannels.TryGetValue(name ?? string.Empty, out var networkChannel))
|
||||
{
|
||||
return networkChannel;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取所有网络频道。
|
||||
/// </summary>
|
||||
/// <returns>所有网络频道。</returns>
|
||||
public INetworkChannel[] GetAllNetworkChannels()
|
||||
{
|
||||
int index = 0;
|
||||
INetworkChannel[] results = new INetworkChannel[_networkChannels.Count];
|
||||
foreach (KeyValuePair<string, NetworkChannelBase> networkChannel in _networkChannels)
|
||||
{
|
||||
results[index++] = networkChannel.Value;
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取所有网络频道。
|
||||
/// </summary>
|
||||
/// <param name="results">所有网络频道。</param>
|
||||
public void GetAllNetworkChannels(List<INetworkChannel> results)
|
||||
{
|
||||
if (results == null)
|
||||
{
|
||||
throw new GameFrameworkException("Results is invalid.");
|
||||
}
|
||||
|
||||
results.Clear();
|
||||
foreach (KeyValuePair<string, NetworkChannelBase> networkChannel in _networkChannels)
|
||||
{
|
||||
results.Add(networkChannel.Value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建网络频道。
|
||||
/// </summary>
|
||||
/// <param name="name">网络频道名称。</param>
|
||||
/// <param name="serviceType">网络服务类型。</param>
|
||||
/// <param name="networkChannelHelper">网络频道辅助器。</param>
|
||||
/// <returns>要创建的网络频道。</returns>
|
||||
public INetworkChannel CreateNetworkChannel(string name, ServiceType serviceType,
|
||||
INetworkChannelHelper networkChannelHelper)
|
||||
{
|
||||
if (networkChannelHelper == null)
|
||||
{
|
||||
throw new GameFrameworkException("Network channel helper is invalid.");
|
||||
}
|
||||
|
||||
if (networkChannelHelper.PacketHeaderLength < 0)
|
||||
{
|
||||
throw new GameFrameworkException("Packet header length is invalid.");
|
||||
}
|
||||
|
||||
if (HasNetworkChannel(name))
|
||||
{
|
||||
throw new GameFrameworkException(Utility.Text.Format("Already exist network channel '{0}'.",
|
||||
name ?? string.Empty));
|
||||
}
|
||||
|
||||
NetworkChannelBase networkChannel = null;
|
||||
switch (serviceType)
|
||||
{
|
||||
case ServiceType.Tcp:
|
||||
networkChannel = new TcpNetworkChannel(name, networkChannelHelper);
|
||||
break;
|
||||
|
||||
case ServiceType.TcpWithSyncReceive:
|
||||
networkChannel = new TcpWithSyncReceiveNetworkChannel(name, networkChannelHelper);
|
||||
break;
|
||||
|
||||
case ServiceType.Udp:
|
||||
networkChannel = new UdpNetworkChannel(name, networkChannelHelper);
|
||||
break;
|
||||
|
||||
case ServiceType.Kcp:
|
||||
networkChannel = new KcpNetworkChannel(name, networkChannelHelper);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new GameFrameworkException(Utility.Text.Format("Not supported service type '{0}'.",
|
||||
serviceType));
|
||||
}
|
||||
|
||||
networkChannel.NetworkChannelConnected += OnNetworkChannelConnected;
|
||||
networkChannel.NetworkChannelClosed += OnNetworkChannelClosed;
|
||||
networkChannel.NetworkChannelMissHeartBeat += OnNetworkChannelMissHeartBeat;
|
||||
networkChannel.NetworkChannelError += OnNetworkChannelError;
|
||||
networkChannel.NetworkChannelCustomError += OnNetworkChannelCustomError;
|
||||
_networkChannels.Add(name, networkChannel);
|
||||
return networkChannel;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 销毁网络频道。
|
||||
/// </summary>
|
||||
/// <param name="name">网络频道名称。</param>
|
||||
/// <returns>是否销毁网络频道成功。</returns>
|
||||
public bool DestroyNetworkChannel(string name)
|
||||
{
|
||||
if (_networkChannels.TryGetValue(name ?? string.Empty, out var networkChannel))
|
||||
{
|
||||
networkChannel.NetworkChannelConnected -= OnNetworkChannelConnected;
|
||||
networkChannel.NetworkChannelClosed -= OnNetworkChannelClosed;
|
||||
networkChannel.NetworkChannelMissHeartBeat -= OnNetworkChannelMissHeartBeat;
|
||||
networkChannel.NetworkChannelError -= OnNetworkChannelError;
|
||||
networkChannel.NetworkChannelCustomError -= OnNetworkChannelCustomError;
|
||||
networkChannel.Shutdown();
|
||||
return name != null && _networkChannels.Remove(name);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void OnNetworkChannelConnected(NetworkChannelBase networkChannel, object userData)
|
||||
{
|
||||
if (_networkConnectedEventHandler != null)
|
||||
{
|
||||
lock (_networkConnectedEventHandler)
|
||||
{
|
||||
_networkConnectedEventHandler(networkChannel, userData);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnNetworkChannelClosed(NetworkChannelBase networkChannel)
|
||||
{
|
||||
if (_networkClosedEventHandler != null)
|
||||
{
|
||||
lock (_networkClosedEventHandler)
|
||||
{
|
||||
_networkClosedEventHandler(networkChannel);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnNetworkChannelMissHeartBeat(NetworkChannelBase networkChannel, int missHeartBeatCount)
|
||||
{
|
||||
if (_networkMissHeartBeatEventHandler != null)
|
||||
{
|
||||
lock (_networkMissHeartBeatEventHandler)
|
||||
{
|
||||
_networkMissHeartBeatEventHandler(networkChannel, missHeartBeatCount);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnNetworkChannelError(NetworkChannelBase networkChannel, NetworkErrorCode errorCode,
|
||||
SocketError socketErrorCode, string errorMessage)
|
||||
{
|
||||
if (_networkErrorEventHandler != null)
|
||||
{
|
||||
lock (_networkErrorEventHandler)
|
||||
{
|
||||
_networkErrorEventHandler(networkChannel, errorCode, socketErrorCode, errorMessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnNetworkChannelCustomError(NetworkChannelBase networkChannel, object customErrorData)
|
||||
{
|
||||
if (_networkCustomErrorEventHandler != null)
|
||||
{
|
||||
lock (_networkCustomErrorEventHandler)
|
||||
{
|
||||
_networkCustomErrorEventHandler(networkChannel, customErrorData);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,3 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 96c0584a53cd4b8e88949b77eac8e1ce
|
||||
timeCreated: 1681994450
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user