Browse Source

加上软路由

tanghai 3 năm trước cách đây
mục cha
commit
205de45626
66 tập tin đã thay đổi với 2142 bổ sung573 xóa
  1. 1 1
      .gitignore
  2. 1 1
      Apps/App/Program.cs
  3. 650 0
      Apps/Hotfix/Module/Router/RouterComponentSystem.cs
  4. 61 0
      Apps/Hotfix/Module/Router/RouterSystem.cs
  5. 17 0
      Apps/Hotfix/Server/Helper/HttpHelper.cs
  6. 2 2
      Apps/Hotfix/Server/Robot/RobotCaseSystem.cs
  7. 1 1
      Apps/Hotfix/Server/Robot/RobotManagerComponentSystem.cs
  8. 28 0
      Apps/Hotfix/Server/RouterManager/HttpGetRouterHandler.cs
  9. 10 2
      Apps/Hotfix/Server/Scene/SceneFactory.cs
  10. 3 2
      Apps/Hotfix/Server/Watcher/WatcherHelper.cs
  11. 10 0
      Apps/Model/Generate/ConfigPartial/StartSceneConfig.cs
  12. 24 0
      Apps/Model/Generate/Message/OuterMessage.cs
  13. 32 30
      Apps/Model/Generate/Message/OuterOpcode.cs
  14. 24 0
      Apps/Model/Module/Router/RouterComponent.cs
  15. 35 0
      Apps/Model/Module/Router/RouterNode.cs
  16. 1 2
      Config/StartConfig/Localhost/StartProcessConfigCategory.bytes
  17. 6 1
      Config/StartConfig/Localhost/StartSceneConfigCategory.bytes
  18. BIN
      Config/StartConfig/Localhost/StartZoneConfigCategory.bytes
  19. 2 0
      Config/StartConfig/RouterTest/StartMachineConfigCategory.bytes
  20. 8 0
      Config/StartConfig/RouterTest/StartProcessConfigCategory.bytes
  21. 13 0
      Config/StartConfig/RouterTest/StartSceneConfigCategory.bytes
  22. BIN
      Config/StartConfig/RouterTest/StartZoneConfigCategory.bytes
  23. 0 1
      Excel/Json/s/StartConfig/Localhost/StartProcessConfig.txt
  24. 5 0
      Excel/Json/s/StartConfig/Localhost/StartSceneConfig.txt
  25. 2 1
      Excel/Json/s/StartConfig/Localhost/StartZoneConfig.txt
  26. 3 0
      Excel/Json/s/StartConfig/RouterTest/StartMachineConfig.txt
  27. 9 0
      Excel/Json/s/StartConfig/RouterTest/StartProcessConfig.txt
  28. 14 0
      Excel/Json/s/StartConfig/RouterTest/StartSceneConfig.txt
  29. 5 0
      Excel/Json/s/StartConfig/RouterTest/StartZoneConfig.txt
  30. BIN
      Excel/StartConfig/Localhost/StartProcessConfig@s.xlsx
  31. BIN
      Excel/StartConfig/Localhost/StartSceneConfig@s.xlsx
  32. BIN
      Excel/StartConfig/Localhost/StartZoneConfig@s.xlsx
  33. BIN
      Excel/StartConfig/RouterTest/StartMachineConfig@s.xlsx
  34. BIN
      Excel/StartConfig/RouterTest/StartProcessConfig@s.xlsx
  35. BIN
      Excel/StartConfig/RouterTest/StartSceneConfig@s.xlsx
  36. BIN
      Excel/StartConfig/RouterTest/StartZoneConfig@s.xlsx
  37. 12 0
      Proto/OuterMessage.proto
  38. 16 0
      Unity/Assets/Editor/ServerCommandLineEditor/ServerCommandLineEditor.cs
  39. 17 4
      Unity/Assets/Mono/Core/Helper/ProcessHelper.cs
  40. 9 244
      Unity/Assets/Mono/Core/Helper/RandomHelper.cs
  41. 34 0
      Unity/Assets/Mono/Core/Helper/StringHashHelper.cs
  42. 11 0
      Unity/Assets/Mono/Core/Helper/StringHashHelper.cs.meta
  43. 4 0
      Unity/Assets/Mono/Core/Log/Log.cs
  44. 3 3
      Unity/Assets/Mono/Core/Options.cs
  45. 3 0
      Unity/Assets/Mono/Module/Network/ErrorCore.cs
  46. 60 0
      Unity/Assets/Mono/Module/Network/KService.cs
  47. 13 4
      Unity/Codes/Hotfix/Client/Login/LoginHelper.cs
  48. 24 0
      Unity/Codes/Hotfix/Client/Router/HttpClientHelper.cs
  49. 68 0
      Unity/Codes/Hotfix/Client/Router/RouterAddressComponentSystem.cs
  50. 80 0
      Unity/Codes/Hotfix/Client/Router/RouterCheckComponentSystem.cs
  51. 108 0
      Unity/Codes/Hotfix/Client/Router/RouterHelper.cs
  52. 13 2
      Unity/Codes/Hotfix/Module/Message/NetKcpComponentSystem.cs
  53. 1 1
      Unity/Codes/Hotfix/Module/Message/SessionIdleCheckerComponentSystem.cs
  54. 2 1
      Unity/Codes/Hotfix/Share/ConstValue.cs
  55. 0 1
      Unity/Codes/HotfixView/Client/UI/UILogin/UILoginComponentSystem.cs
  56. 12 0
      Unity/Codes/Model/Client/Router/RouterAddressComponent.cs
  57. 6 0
      Unity/Codes/Model/Client/Router/RouterCheckComponent.cs
  58. 2 0
      Unity/Codes/Model/Core/Entity/SceneType.cs
  59. 24 0
      Unity/Codes/Model/Generate/Message/OuterMessage.cs
  60. 32 30
      Unity/Codes/Model/Generate/Message/OuterOpcode.cs
  61. 1 1
      Unity/Codes/Model/Module/Message/Session.cs
  62. 1 1
      Unity/Unity.Hotfix.csproj
  63. 1 1
      Unity/Unity.HotfixView.csproj
  64. 1 1
      Unity/Unity.Model.csproj
  65. 1 1
      Unity/Unity.ModelView.csproj
  66. 616 234
      Unity/UserSettings/Layouts/default-2021.dwlt

+ 1 - 1
.gitignore

@@ -56,6 +56,6 @@ Server/.DS_Store
 /Unity/Assets/Bundles/Code/Code.pdb.bytes
 /Unity/Assets/Bundles/Code/Code.pdb.bytes.meta
 /Unity/Assembly-CSharp.csproj
-/Excel/~$*.xlsx
+~$*.xlsx
 /.vscode
 /Unity/UserSettings/Search.settings

+ 1 - 1
Apps/App/Program.cs

@@ -38,7 +38,7 @@ namespace ET
 				Log.ILog = new NLogger(Game.Options.AppType.ToString());
 				LogManager.Configuration.Variables["appIdFormat"] = $"{Game.Options.Process:000000}";
 				
-				Log.Info($"apps start........................ {Game.Scene.Id}");
+				Log.Console($"app start: {Game.Scene.Id} options: {JsonHelper.ToJson(Game.Options)} ");
 
 				Game.EventSystem.Publish(new EventType.AppStart());
 				

+ 650 - 0
Apps/Hotfix/Module/Router/RouterComponentSystem.cs

@@ -0,0 +1,650 @@
+using System;
+using System.Net;
+using System.Net.Sockets;
+using System.Runtime.InteropServices;
+
+namespace ET
+{
+    [FriendClass(typeof (RouterComponent))]
+    [FriendClass(typeof (RouterNode))]
+    public static class RouterComponentSystem
+    {
+        [ObjectSystem]
+        public class RouterComponentAwakeSystem: AwakeSystem<RouterComponent, IPEndPoint, string>
+        {
+            public override void Awake(RouterComponent self, IPEndPoint outerAddress, string innerIP)
+            {
+                self.OuterSocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
+                self.OuterSocket.Bind(outerAddress);
+                self.OuterSocket.SendBufferSize = 16 * Kcp.OneM;
+                self.OuterSocket.ReceiveBufferSize = 16 * Kcp.OneM;
+
+                self.InnerSocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
+                self.InnerSocket.Bind(new IPEndPoint(IPAddress.Parse(innerIP), 0));
+                self.InnerSocket.SendBufferSize = 16 * Kcp.OneM;
+                self.InnerSocket.ReceiveBufferSize = 16 * Kcp.OneM;
+
+                if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
+                {
+                    const uint IOC_IN = 0x80000000;
+                    const uint IOC_VENDOR = 0x18000000;
+                    uint SIO_UDP_CONNRESET = IOC_IN | IOC_VENDOR | 12;
+
+                    self.OuterSocket.IOControl((int) SIO_UDP_CONNRESET, new[] { Convert.ToByte(false) }, null);
+                    self.InnerSocket.IOControl((int) SIO_UDP_CONNRESET, new[] { Convert.ToByte(false) }, null);
+                }
+            }
+        }
+
+        [ObjectSystem]
+        public class RouterComponentDestroySystem: DestroySystem<RouterComponent>
+        {
+            public override void Destroy(RouterComponent self)
+            {
+                self.OuterSocket.Dispose();
+                self.InnerSocket.Dispose();
+                self.OuterNodes.Clear();
+                self.IPEndPoint = null;
+            }
+        }
+
+        [ObjectSystem]
+        public class RouterComponentUpdateSystem: UpdateSystem<RouterComponent>
+        {
+            public override void Update(RouterComponent self)
+            {
+                long timeNow = TimeHelper.ClientNow();
+                self.RecvOuter(timeNow);
+                self.RecvInner(timeNow);
+
+                // 每秒钟检查一次
+                if (timeNow - self.LastCheckTime > 1000)
+                {
+                    self.CheckConnectTimeout(timeNow);
+                }
+            }
+        }
+
+        private static IPEndPoint CloneAddress(this RouterComponent self)
+        {
+            IPEndPoint ipEndPoint = (IPEndPoint) self.IPEndPoint;
+            return new IPEndPoint(ipEndPoint.Address, ipEndPoint.Port);
+        }
+
+        // 接收udp消息
+        private static void RecvOuter(this RouterComponent self, long timeNow)
+        {
+            while (self.OuterSocket != null && self.OuterSocket.Available > 0)
+            {
+                try
+                {
+                    int messageLength = self.OuterSocket.ReceiveFrom(self.Cache, ref self.IPEndPoint);
+                    self.RecvOuterHandler(messageLength, timeNow);
+                }
+                catch (Exception e)
+                {
+                    Log.Error(e);
+                }
+            }
+        }
+
+        private static void CheckConnectTimeout(this RouterComponent self, long timeNow)
+        {
+            // 检查连接过程超时
+            using (ListComponent<long> listComponent = ListComponent<long>.Create())
+            {
+                foreach (var kv in self.ConnectIdNodes)
+                {
+                    if (timeNow < kv.Value.LastRecvOuterTime + 10 * 1000)
+                    {
+                        continue;
+                    }
+
+                    listComponent.Add(kv.Value.Id);
+                }
+
+                foreach (long id in listComponent)
+                {
+                    self.OnError(id, ErrorCore.ERR_KcpRouterConnectFail);
+                }
+            }
+
+            // 外网消息超时就断开,内网因为会一直重发,没有重连之前内网连接一直存在,会导致router一直收到内网消息
+            using (ListComponent<long> listComponent = ListComponent<long>.Create())
+            {
+                foreach (var kv in self.OuterNodes)
+                {
+                    // 比session超时应该多10秒钟
+                    if (timeNow < kv.Value.LastRecvOuterTime + ConstValue.SessionTimeoutTime + 10 * 1000)
+                    {
+                        continue;
+                    }
+
+                    listComponent.Add(kv.Value.Id);
+                }
+
+                foreach (long id in listComponent)
+                {
+                    self.OnError(id, ErrorCore.ERR_KcpRouterTimeout);
+                }
+            }
+        }
+
+        private static void RecvInner(this RouterComponent self, long timeNow)
+        {
+            while (self.InnerSocket != null && self.InnerSocket.Available > 0)
+            {
+                try
+                {
+                    int messageLength = self.InnerSocket.ReceiveFrom(self.Cache, ref self.IPEndPoint);
+                    self.RecvInnerHandler(messageLength, timeNow);
+                }
+                catch (Exception e)
+                {
+                    Log.Error(e);
+                }
+            }
+        }
+
+        private static void RecvOuterHandler(this RouterComponent self, int messageLength, long timeNow)
+        {
+            // 长度小于1,不是正常的消息
+            if (messageLength < 1)
+            {
+                return;
+            }
+
+            // accept
+            byte flag = self.Cache[0];
+            switch (flag)
+            {
+                case KcpProtocalType.RouterReconnectSYN:
+                {
+                    if (messageLength < 13)
+                    {
+                        break;
+                    }
+
+                    uint outerConn = BitConverter.ToUInt32(self.Cache, 1);
+                    uint innerConn = BitConverter.ToUInt32(self.Cache, 5);
+                    uint connectId = BitConverter.ToUInt32(self.Cache, 9);
+                    string realAddress = self.Cache.ToStr(13, messageLength - 13);
+
+                    RouterNode routerNode;
+
+                    // RouterAck之后ConnectIdNodes会删除,加入到OuterNodes中来
+                    if (!self.OuterNodes.TryGetValue(outerConn, out routerNode))
+                    {
+                        self.ConnectIdNodes.TryGetValue(connectId, out routerNode);
+                        if (routerNode == null)
+                        {
+                            Log.Info($"router create reconnect: {self.IPEndPoint} {realAddress} {connectId} {outerConn} {innerConn}");
+                            routerNode = self.New(realAddress, connectId, outerConn, innerConn, self.CloneAddress());
+                            // self.OuterNodes 这里不能add,因为还没验证完成,要在RouterAck中加入
+                        }
+                    }
+
+                    if (routerNode.ConnectId != connectId)
+                    {
+                        Log.Warning($"kcp router router reconnect connectId diff1: {routerNode.SyncIpEndPoint} {(IPEndPoint) self.IPEndPoint}");
+                        break;
+                    }
+                    
+                    // 不是自己的,outerConn冲突, 直接break,也就是说这个软路由上有个跟自己outerConn冲突的连接,就不能连接了
+                    // 这个路由连接不上,客户端会换个软路由,所以没关系
+                    if (routerNode.InnerConn != innerConn)
+                    {
+                        Log.Warning($"kcp router router reconnect inner conn diff1: {routerNode.SyncIpEndPoint} {(IPEndPoint) self.IPEndPoint}");
+                        break;
+                    }
+                    
+                    if (routerNode.OuterConn != outerConn)
+                    {
+                        Log.Warning($"kcp router router reconnect outer conn diff1: {routerNode.SyncIpEndPoint} {(IPEndPoint) self.IPEndPoint}");
+                        break;
+                    }
+
+                    // 校验ip,连接过程中ip不能变化
+                    if (!Equals(routerNode.SyncIpEndPoint, self.IPEndPoint))
+                    {
+                        Log.Warning($"kcp router syn ip is diff1: {routerNode.SyncIpEndPoint} {(IPEndPoint) self.IPEndPoint}");
+                        break;
+                    }
+
+                    // 校验内网地址
+                    if (routerNode.InnerAddress != realAddress)
+                    {
+                        Log.Warning($"router sync error2: {routerNode.OuterConn} {routerNode.InnerAddress} {outerConn} {realAddress}");
+                        break;
+                    }
+                    
+                    if (++routerNode.RouterSyncCount > 40)
+                    {
+                        self.OnError(routerNode.Id, ErrorCore.ERR_KcpRouterRouterSyncCountTooMuchTimes);
+                        break;
+                    }
+
+                    // 转发到内网
+                    self.Cache.WriteTo(0, KcpProtocalType.RouterReconnectSYN);
+                    self.Cache.WriteTo(1, outerConn);
+                    self.Cache.WriteTo(5, innerConn);
+                    self.Cache.WriteTo(9, connectId);
+                    self.InnerSocket.SendTo(self.Cache, 0, 13, SocketFlags.None, routerNode.InnerIpEndPoint);
+
+                    if (!routerNode.CheckOuterCount(timeNow))
+                    {
+                        self.OnError(routerNode.Id, ErrorCore.ERR_KcpRouterTooManyPackets);
+                    }
+
+                    break;
+                }
+                case KcpProtocalType.RouterSYN:
+                {
+                    if (messageLength < 13)
+                    {
+                        break;
+                    }
+
+                    uint outerConn = BitConverter.ToUInt32(self.Cache, 1);
+                    uint innerConn = BitConverter.ToUInt32(self.Cache, 5);
+                    uint connectId = BitConverter.ToUInt32(self.Cache, 9);
+                    string realAddress = self.Cache.ToStr(13, messageLength - 13);
+
+                    RouterNode routerNode;
+
+                    self.ConnectIdNodes.TryGetValue(connectId, out routerNode);
+                    if (routerNode == null)
+                    {
+                        outerConn = RandomHelper.RandUInt32();
+                        routerNode = self.New(realAddress, connectId, outerConn, innerConn, self.CloneAddress());
+                        Log.Info($"router create: {realAddress} {connectId} {outerConn} {innerConn} {routerNode.SyncIpEndPoint}");
+                        self.OuterNodes.Add(routerNode.OuterConn, routerNode);
+                    }
+
+                    if (++routerNode.RouterSyncCount > 40)
+                    {
+                        self.OnError(routerNode.Id, ErrorCore.ERR_KcpRouterRouterSyncCountTooMuchTimes);
+                        break;
+                    }
+
+                    // 校验ip,连接过程中ip不能变化
+                    if (!Equals(routerNode.SyncIpEndPoint, self.IPEndPoint))
+                    {
+                        Log.Warning($"kcp router syn ip is diff1: {routerNode.SyncIpEndPoint} {self.IPEndPoint}");
+                        break;
+                    }
+
+                    // 校验内网地址
+                    if (routerNode.InnerAddress != realAddress)
+                    {
+                        Log.Warning($"router sync error2: {routerNode.OuterConn} {routerNode.InnerAddress} {outerConn} {realAddress}");
+                        break;
+                    }
+
+                    self.Cache.WriteTo(0, KcpProtocalType.RouterACK);
+                    self.Cache.WriteTo(1, routerNode.InnerConn);
+                    self.Cache.WriteTo(5, routerNode.OuterConn);
+                    self.OuterSocket.SendTo(self.Cache, 0, 9, SocketFlags.None, routerNode.SyncIpEndPoint);
+
+                    if (!routerNode.CheckOuterCount(timeNow))
+                    {
+                        self.OnError(routerNode.Id, ErrorCore.ERR_KcpRouterTooManyPackets);
+                    }
+
+                    break;
+                }
+                case KcpProtocalType.SYN:
+                {
+                    // 长度!=13,不是accpet消息
+                    if (messageLength != 9)
+                    {
+                        break;
+                    }
+
+                    uint outerConn = BitConverter.ToUInt32(self.Cache, 1); // remote
+                    uint innerConn = BitConverter.ToUInt32(self.Cache, 5);
+
+                    if (!self.OuterNodes.TryGetValue(outerConn, out RouterNode kcpRouter))
+                    {
+                        Log.Warning($"kcp router syn not found outer nodes: {outerConn} {innerConn}");
+                        break;
+                    }
+
+                    if (++kcpRouter.SyncCount > 40)
+                    {
+                        self.OnError(kcpRouter.Id, ErrorCore.ERR_KcpRouterSyncCountTooMuchTimes);
+                        break;
+                    }
+
+                    // 校验ip,连接过程中ip不能变化
+                    IPEndPoint ipEndPoint = (IPEndPoint) self.IPEndPoint;
+                    if (!Equals(kcpRouter.SyncIpEndPoint.Address, ipEndPoint.Address))
+                    {
+                        Log.Warning($"kcp router syn ip is diff3: {kcpRouter.SyncIpEndPoint.Address} {ipEndPoint.Address}");
+                        break;
+                    }
+
+                    kcpRouter.LastRecvOuterTime = timeNow;
+                    kcpRouter.OuterIpEndPoint = self.CloneAddress();
+                    // 转发到内网, 带上客户端的地址
+                    self.Cache.WriteTo(0, KcpProtocalType.SYN);
+                    self.Cache.WriteTo(1, outerConn);
+                    self.Cache.WriteTo(5, innerConn);
+                    byte[] addressBytes = ipEndPoint.ToString().ToByteArray();
+                    Array.Copy(addressBytes, 0, self.Cache, 9, addressBytes.Length);
+                    Log.Info($"kcp router syn: {outerConn} {innerConn} {kcpRouter.InnerIpEndPoint} {kcpRouter.OuterIpEndPoint}");
+                    self.InnerSocket.SendTo(self.Cache, 0, 9 + addressBytes.Length, SocketFlags.None, kcpRouter.InnerIpEndPoint);
+
+                    if (!kcpRouter.CheckOuterCount(timeNow))
+                    {
+                        self.OnError(kcpRouter.Id, ErrorCore.ERR_KcpRouterTooManyPackets);
+                    }
+
+                    break;
+                }
+                case KcpProtocalType.FIN: // 断开
+                {
+                    // 长度!=13,不是DisConnect消息
+                    if (messageLength != 13)
+                    {
+                        break;
+                    }
+
+                    uint outerConn = BitConverter.ToUInt32(self.Cache, 1);
+                    uint innerConn = BitConverter.ToUInt32(self.Cache, 5);
+
+                    if (!self.OuterNodes.TryGetValue(outerConn, out RouterNode kcpRouter))
+                    {
+                        Log.Warning($"kcp router outer fin not found outer nodes: {outerConn} {innerConn}");
+                        break;
+                    }
+
+                    // 比对innerConn
+                    if (kcpRouter.InnerConn != innerConn)
+                    {
+                        Log.Warning($"router node innerConn error: {innerConn} {outerConn} {kcpRouter.Status}");
+                        break;
+                    }
+
+                    kcpRouter.LastRecvOuterTime = timeNow;
+                    Log.Info($"kcp router outer fin: {outerConn} {innerConn} {kcpRouter.InnerIpEndPoint}");
+                    self.InnerSocket.SendTo(self.Cache, 0, messageLength, SocketFlags.None, kcpRouter.InnerIpEndPoint);
+
+                    if (!kcpRouter.CheckOuterCount(timeNow))
+                    {
+                        self.OnError(kcpRouter.Id, ErrorCore.ERR_KcpRouterTooManyPackets);
+                    }
+
+                    break;
+                }
+                case KcpProtocalType.MSG:
+                {
+                    // 长度<9,不是Msg消息
+                    if (messageLength < 9)
+                    {
+                        break;
+                    }
+
+                    // 处理chanel
+                    uint outerConn = BitConverter.ToUInt32(self.Cache, 1); // remote
+                    uint innerConn = BitConverter.ToUInt32(self.Cache, 5); // local
+
+                    if (!self.OuterNodes.TryGetValue(outerConn, out RouterNode kcpRouter))
+                    {
+                        Log.Warning($"kcp router msg not found outer nodes: {outerConn} {innerConn}");
+                        break;
+                    }
+
+                    if (kcpRouter.Status != RouterStatus.Msg)
+                    {
+                        Log.Warning($"router node status error: {innerConn} {outerConn} {kcpRouter.Status}");
+                        break;
+                    }
+
+                    // 比对innerConn
+                    if (kcpRouter.InnerConn != innerConn)
+                    {
+                        Log.Warning($"router node innerConn error: {innerConn} {outerConn} {kcpRouter.Status}");
+                        break;
+                    }
+
+                    // 重连的时候,没有经过syn阶段,可能没有设置OuterIpEndPoint,重连请求Router的Socket跟发送消息的Socket不是同一个,所以udp出来的公网地址可能会变化
+                    if (!Equals(kcpRouter.OuterIpEndPoint, self.IPEndPoint))
+                    {
+                        kcpRouter.OuterIpEndPoint = self.CloneAddress();
+                    }
+
+                    kcpRouter.LastRecvOuterTime = timeNow;
+
+                    self.InnerSocket.SendTo(self.Cache, 0, messageLength, SocketFlags.None, kcpRouter.InnerIpEndPoint);
+
+                    if (!kcpRouter.CheckOuterCount(timeNow))
+                    {
+                        self.OnError(kcpRouter.Id, ErrorCore.ERR_KcpRouterTooManyPackets);
+                    }
+
+                    break;
+                }
+            }
+        }
+
+        private static void RecvInnerHandler(this RouterComponent self, int messageLength, long timeNow)
+        {
+            // 长度小于1,不是正常的消息
+            if (messageLength < 1)
+            {
+                return;
+            }
+
+            // accept
+            byte flag = self.Cache[0];
+
+            switch (flag)
+            {
+                case KcpProtocalType.RouterReconnectACK:
+                {
+                    uint innerConn = BitConverter.ToUInt32(self.Cache, 1);
+                    uint outerConn = BitConverter.ToUInt32(self.Cache, 5);
+                    uint connectId = BitConverter.ToUInt32(self.Cache, 9);
+                    if (!self.ConnectIdNodes.TryGetValue(connectId, out RouterNode kcpRouterNode))
+                    {
+                        Log.Warning($"router node error: {innerConn} {connectId}");
+                        break;
+                    }
+
+                    // 必须校验innerConn,防止伪造
+                    if (innerConn != kcpRouterNode.InnerConn)
+                    {
+                        Log.Warning(
+                            $"router node innerConn error: {innerConn} {kcpRouterNode.InnerConn} {outerConn} {kcpRouterNode.OuterConn} {kcpRouterNode.Status}");
+                        break;
+                    }
+
+                    // 必须校验outerConn,防止伪造
+                    if (outerConn != kcpRouterNode.OuterConn)
+                    {
+                        Log.Warning(
+                            $"router node outerConn error: {innerConn} {kcpRouterNode.InnerConn} {outerConn} {kcpRouterNode.OuterConn} {kcpRouterNode.Status}");
+                        break;
+                    }
+
+                    kcpRouterNode.Status = RouterStatus.Msg;
+
+                    kcpRouterNode.LastRecvInnerTime = timeNow;
+
+                    // 校验成功才加到outerNodes中, 如果这里有冲突,外网将连接失败,不过几率极小
+                    if (!self.OuterNodes.ContainsKey(outerConn))
+                    {
+                        self.OuterNodes.Add(outerConn, kcpRouterNode);
+                        self.ConnectIdNodes.Remove(connectId);
+                    }
+
+                    // 转发出去
+                    self.Cache.WriteTo(0, KcpProtocalType.RouterReconnectACK);
+                    self.Cache.WriteTo(1, kcpRouterNode.InnerConn);
+                    self.Cache.WriteTo(5, kcpRouterNode.OuterConn);
+                    Log.Info($"kcp router RouterAck: {outerConn} {innerConn} {kcpRouterNode.SyncIpEndPoint}");
+                    self.OuterSocket.SendTo(self.Cache, 0, 9, SocketFlags.None, kcpRouterNode.SyncIpEndPoint);
+                    break;
+                }
+
+                case KcpProtocalType.ACK:
+                {
+                    uint innerConn = BitConverter.ToUInt32(self.Cache, 1); // remote
+                    uint outerConn = BitConverter.ToUInt32(self.Cache, 5); // local
+
+                    if (!self.OuterNodes.TryGetValue(outerConn, out RouterNode kcpRouterNode))
+                    {
+                        Log.Warning($"kcp router ack not found outer nodes: {outerConn} {innerConn}");
+                        break;
+                    }
+
+                    kcpRouterNode.Status = RouterStatus.Msg;
+
+                    kcpRouterNode.InnerConn = innerConn;
+
+                    kcpRouterNode.LastRecvInnerTime = timeNow;
+                    // 转发出去
+                    Log.Info($"kcp router ack: {outerConn} {innerConn} {kcpRouterNode.OuterIpEndPoint}");
+                    self.OuterSocket.SendTo(self.Cache, 0, messageLength, SocketFlags.None, kcpRouterNode.OuterIpEndPoint);
+                    break;
+                }
+                case KcpProtocalType.FIN: // 断开
+                {
+                    // 长度!=13,不是DisConnect消息
+                    if (messageLength != 13)
+                    {
+                        break;
+                    }
+
+                    uint innerConn = BitConverter.ToUInt32(self.Cache, 1);
+                    uint outerConn = BitConverter.ToUInt32(self.Cache, 5);
+
+                    if (!self.OuterNodes.TryGetValue(outerConn, out RouterNode kcpRouterNode))
+                    {
+                        Log.Warning($"kcp router inner fin not found outer nodes: {outerConn} {innerConn}");
+                        break;
+                    }
+
+                    // 比对innerConn
+                    if (kcpRouterNode.InnerConn != innerConn)
+                    {
+                        Log.Warning($"router node innerConn error: {innerConn} {outerConn} {kcpRouterNode.Status}");
+                        break;
+                    }
+
+                    // 重连,这个字段可能为空,需要客户端发送消息上来才能设置
+                    if (kcpRouterNode.OuterIpEndPoint == null)
+                    {
+                        break;
+                    }
+
+                    kcpRouterNode.LastRecvInnerTime = timeNow;
+                    Log.Info($"kcp router inner fin: {outerConn} {innerConn} {kcpRouterNode.OuterIpEndPoint}");
+                    self.OuterSocket.SendTo(self.Cache, 0, messageLength, SocketFlags.None, kcpRouterNode.OuterIpEndPoint);
+
+                    break;
+                }
+                case KcpProtocalType.MSG:
+                {
+                    // 长度<9,不是Msg消息
+                    if (messageLength < 9)
+                    {
+                        break;
+                    }
+
+                    // 处理chanel
+                    uint innerConn = BitConverter.ToUInt32(self.Cache, 1); // remote
+                    uint outerConn = BitConverter.ToUInt32(self.Cache, 5); // local
+
+                    if (!self.OuterNodes.TryGetValue(outerConn, out RouterNode kcpRouterNode))
+                    {
+                        Log.Warning($"kcp router inner msg not found outer nodes: {outerConn} {innerConn}");
+                        break;
+                    }
+
+                    // 比对innerConn
+                    if (kcpRouterNode.InnerConn != innerConn)
+                    {
+                        Log.Warning($"router node innerConn error: {innerConn} {outerConn} {kcpRouterNode.Status}");
+                        break;
+                    }
+
+                    // 重连,这个字段可能为空,需要客户端发送消息上来才能设置
+                    if (kcpRouterNode.OuterIpEndPoint == null)
+                    {
+                        break;
+                    }
+
+                    kcpRouterNode.LastRecvInnerTime = timeNow;
+                    self.OuterSocket.SendTo(self.Cache, 0, messageLength, SocketFlags.None, kcpRouterNode.OuterIpEndPoint);
+                    break;
+                }
+            }
+        }
+
+        public static RouterNode Get(this RouterComponent self, uint outerConn)
+        {
+            RouterNode routerNode = null;
+            self.OuterNodes.TryGetValue(outerConn, out routerNode);
+            return routerNode;
+        }
+
+        private static RouterNode New(this RouterComponent self, string innerAddress, uint connectId, uint outerConn, uint innerConn, IPEndPoint syncEndPoint)
+        {
+            RouterNode routerNode = self.AddChild<RouterNode>();
+            routerNode.ConnectId = connectId;
+            routerNode.OuterConn = outerConn;
+            routerNode.InnerConn = innerConn;
+
+            routerNode.InnerIpEndPoint = NetworkHelper.ToIPEndPoint(innerAddress);
+            routerNode.SyncIpEndPoint = syncEndPoint;
+            routerNode.InnerAddress = innerAddress;
+            routerNode.LastRecvInnerTime = TimeHelper.ClientNow();
+
+            self.ConnectIdNodes.Add(connectId, routerNode);
+
+            routerNode.Status = RouterStatus.Sync;
+
+            Log.Info($"router new: outerConn: {outerConn} innerConn: {innerConn} {syncEndPoint}");
+
+            return routerNode;
+        }
+
+        public static void OnError(this RouterComponent self, long id, int error)
+        {
+            RouterNode routerNode = self.GetChild<RouterNode>(id);
+            if (routerNode == null)
+            {
+                return;
+            }
+
+            Log.Info($"router node remove: {routerNode.OuterConn} {routerNode.InnerConn} {error}");
+            self.Remove(id);
+        }
+
+        private static void Remove(this RouterComponent self, long id)
+        {
+            RouterNode routerNode = self.GetChild<RouterNode>(id);
+            if (routerNode == null)
+            {
+                return;
+            }
+
+            self.OuterNodes.Remove(routerNode.OuterConn);
+
+            RouterNode connectRouterNode;
+            if (self.ConnectIdNodes.TryGetValue(routerNode.ConnectId, out connectRouterNode))
+            {
+                if (connectRouterNode.Id == routerNode.Id)
+                {
+                    self.ConnectIdNodes.Remove(routerNode.ConnectId);
+                }
+            }
+
+            Log.Info($"router remove: {routerNode.Id} outerConn: {routerNode.OuterConn} innerConn: {routerNode.InnerConn}");
+
+            routerNode.Dispose();
+        }
+    }
+}

+ 61 - 0
Apps/Hotfix/Module/Router/RouterSystem.cs

@@ -0,0 +1,61 @@
+namespace ET
+{
+    [FriendClass(typeof(RouterNode))]
+    public static class RouterSystem
+    {
+        [ObjectSystem]
+        public class RouterNodeAwakeSystem: AwakeSystem<RouterNode>
+        {
+            public override void Awake(RouterNode self)
+            {
+                long timeNow = TimeHelper.ServerNow();
+                self.LastRecvInnerTime = timeNow;
+                self.LastRecvOuterTime = timeNow;
+                self.OuterIpEndPoint = null;
+                self.InnerIpEndPoint = null;
+                self.RouterSyncCount = 0;
+                self.OuterConn = 0;
+                self.InnerConn = 0;
+            }
+        }
+
+        [ObjectSystem]
+        public class RouterNodeDestroySystem: DestroySystem<RouterNode>
+        {
+            public override void Destroy(RouterNode self)
+            {
+                self.OuterConn = 0;
+                self.InnerConn = 0;
+                self.LastRecvInnerTime = 0;
+                self.LastRecvOuterTime = 0;
+                self.OuterIpEndPoint = null;
+                self.InnerIpEndPoint = null;
+                self.InnerAddress = null;
+                self.RouterSyncCount = 0;
+                self.SyncCount = 0;
+            }
+        }
+        
+        public static bool CheckOuterCount(this RouterNode self, long timeNow)
+        {
+            if (self.LastCheckTime == 0)
+            {
+                self.LastCheckTime = timeNow;
+            }
+            
+            if (timeNow - self.LastCheckTime > 1000)
+            {
+                //Log.Debug($"router recv packet per second: {self.LimitCountPerSecond}");
+                self.LimitCountPerSecond = 0;
+                self.LastCheckTime = timeNow;
+            }
+
+            if (++self.LimitCountPerSecond > 1000)
+            {
+                return false;
+            }
+
+            return true;
+        }
+    }
+}

+ 17 - 0
Apps/Hotfix/Server/Helper/HttpHelper.cs

@@ -0,0 +1,17 @@
+using System.Net;
+using System.Text;
+
+namespace ET.Server
+{
+    public static class HttpHelper
+    {
+        public static void Response(HttpListenerContext context, object response)
+        {
+            byte[] bytes = JsonHelper.ToJson(response).ToUtf8();
+            context.Response.StatusCode = 200;
+            context.Response.ContentEncoding = Encoding.UTF8;
+            context.Response.ContentLength64 = bytes.Length;
+            context.Response.OutputStream.Write(bytes, 0, bytes.Length);
+        }
+    }
+}

+ 2 - 2
Apps/Hotfix/Server/Robot/RobotCaseSystem.cs

@@ -65,7 +65,7 @@ namespace ET.Server
             try
             {
                 zoneScene = Client.SceneFactory.CreateZoneScene(zone, name, self);
-                await Client.LoginHelper.Login(zoneScene, ConstValue.LoginAddress, zone.ToString(), zone.ToString());
+                await Client.LoginHelper.Login(zoneScene, zone.ToString(), zone.ToString());
                 await Client.EnterMapHelper.EnterMapAsync(zoneScene);
                 Log.Debug($"create robot ok: {zone}");
                 return zoneScene;
@@ -85,7 +85,7 @@ namespace ET.Server
             try
             {
                 zoneScene = Client.SceneFactory.CreateZoneScene(zone, $"Robot_{zone}", self);
-                await Client.LoginHelper.Login(zoneScene, ConstValue.LoginAddress, zone.ToString(), zone.ToString());
+                await Client.LoginHelper.Login(zoneScene, zone.ToString(), zone.ToString());
                 await Client.EnterMapHelper.EnterMapAsync(zoneScene);
                 Log.Debug($"create robot ok: {zone}");
                 return zoneScene;

+ 1 - 1
Apps/Hotfix/Server/Robot/RobotManagerComponentSystem.cs

@@ -11,7 +11,7 @@ namespace ET.Server
             try
             {
                 zoneScene = Client.SceneFactory.CreateZoneScene(zone, "Robot", self);
-                await Client.LoginHelper.Login(zoneScene, ConstValue.LoginAddress, zone.ToString(), zone.ToString());
+                await Client.LoginHelper.Login(zoneScene, zone.ToString(), zone.ToString());
                 await Client.EnterMapHelper.EnterMapAsync(zoneScene);
                 Log.Debug($"create robot ok: {zone}");
                 return zoneScene;

+ 28 - 0
Apps/Hotfix/Server/RouterManager/HttpGetRouterHandler.cs

@@ -0,0 +1,28 @@
+using System;
+using System.Collections.Generic;
+using System.Net;
+using System.Text;
+
+namespace ET.Server
+{
+    [HttpHandler(SceneType.RouterManager, "/get_router")]
+    public class HttpGetRouterHandler : IHttpHandler
+    {
+        public async ETTask Handle(Entity domain, HttpListenerContext context)
+        {
+            HttpGetRouterResponse response = new HttpGetRouterResponse();
+            response.Realms = new List<string>();
+            response.Routers = new List<string>();
+            foreach (StartSceneConfig startSceneConfig in StartSceneConfigCategory.Instance.Realms)
+            {
+                response.Realms.Add(startSceneConfig.InnerIPOutPort.ToString());
+            }
+            foreach (StartSceneConfig startSceneConfig in StartSceneConfigCategory.Instance.Routers)
+            {
+                response.Routers.Add($"{startSceneConfig.StartProcessConfig.OuterIP}:{startSceneConfig.OuterPort}");
+            }
+            HttpHelper.Response(context, response);
+            await ETTask.CompletedTask;
+        }
+    }
+}

+ 10 - 2
Apps/Hotfix/Server/Scene/SceneFactory.cs

@@ -19,11 +19,19 @@ namespace ET.Server
 
             switch (scene.SceneType)
             {
+                case SceneType.Router:
+                    scene.AddComponent<RouterComponent, IPEndPoint, string>(startSceneConfig.OuterIPPort,
+                        startSceneConfig.StartProcessConfig.InnerIP
+                    );
+                    break;
+                case SceneType.RouterManager:
+                    scene.AddComponent<HttpComponent, string>($"http://{startSceneConfig.OuterIPPort}/");
+                    break;
                 case SceneType.Realm:
-                    scene.AddComponent<NetKcpComponent, IPEndPoint, int>(startSceneConfig.OuterIPPort, SessionStreamDispatcherType.SessionStreamDispatcherServerOuter);
+                    scene.AddComponent<NetKcpComponent, IPEndPoint, int>(startSceneConfig.InnerIPOutPort, SessionStreamDispatcherType.SessionStreamDispatcherServerOuter);
                     break;
                 case SceneType.Gate:
-                    scene.AddComponent<NetKcpComponent, IPEndPoint, int>(startSceneConfig.OuterIPPort, SessionStreamDispatcherType.SessionStreamDispatcherServerOuter);
+                    scene.AddComponent<NetKcpComponent, IPEndPoint, int>(startSceneConfig.InnerIPOutPort, SessionStreamDispatcherType.SessionStreamDispatcherServerOuter);
                     scene.AddComponent<PlayerComponent>();
                     scene.AddComponent<GateSessionKeyComponent>();
                     break;

+ 3 - 2
Apps/Hotfix/Server/Watcher/WatcherHelper.cs

@@ -41,13 +41,14 @@ namespace ET.Server
         {
             StartProcessConfig startProcessConfig = StartProcessConfigCategory.Instance.Get(processId);
             const string exe = "dotnet";
-            string arguments = $"Apps.dll" + 
+            string arguments = $"App.dll" + 
                     $" --Process={startProcessConfig.Id}" +
                     $" --AppType=Server" +  
                     $" --StartConfig={Game.Options.StartConfig}" +
                     $" --Develop={Game.Options.Develop}" +
                     $" --CreateScenes={createScenes}" +
-                    $" --LogLevel={Game.Options.LogLevel}";
+                    $" --LogLevel={Game.Options.LogLevel}" +
+                    $" --Console={Game.Options.Console}";
             Log.Debug($"{exe} {arguments}");
             Process process = ProcessHelper.Run(exe, arguments);
             return process;

+ 10 - 0
Apps/Model/Generate/ConfigPartial/StartSceneConfig.cs

@@ -13,6 +13,10 @@ namespace ET
         public Dictionary<long, Dictionary<string, StartSceneConfig>> ZoneScenesByName = new Dictionary<long, Dictionary<string, StartSceneConfig>>();
 
         public StartSceneConfig LocationConfig;
+
+        public List<StartSceneConfig> Realms = new List<StartSceneConfig>();
+        
+        public List<StartSceneConfig> Routers = new List<StartSceneConfig>();
         
         public List<StartSceneConfig> Robots = new List<StartSceneConfig>();
         
@@ -40,6 +44,9 @@ namespace ET
                 
                 switch (startSceneConfig.Type)
                 {
+                    case SceneType.Realm:
+                        this.Realms.Add(startSceneConfig);
+                        break;
                     case SceneType.Gate:
                         this.Gates.Add(startSceneConfig.Zone, startSceneConfig);
                         break;
@@ -49,6 +56,9 @@ namespace ET
                     case SceneType.Robot:
                         this.Robots.Add(startSceneConfig);
                         break;
+                    case SceneType.Router:
+                        this.Routers.Add(startSceneConfig);
+                        break;
                 }
             }
         }

+ 24 - 0
Apps/Model/Generate/Message/OuterMessage.cs

@@ -3,6 +3,30 @@ using ProtoBuf;
 using System.Collections.Generic;
 namespace ET
 {
+	[Message(OuterOpcode.HttpGetRouterResponse)]
+	[ProtoContract]
+	public partial class HttpGetRouterResponse: Object
+	{
+		[ProtoMember(1)]
+		public List<string> Realms = new List<string>();
+
+		[ProtoMember(2)]
+		public List<string> Routers = new List<string>();
+
+	}
+
+	[Message(OuterOpcode.RouterSync)]
+	[ProtoContract]
+	public partial class RouterSync: Object
+	{
+		[ProtoMember(1)]
+		public uint ConnectId { get; set; }
+
+		[ProtoMember(2)]
+		public string Address { get; set; }
+
+	}
+
 	[ResponseType(nameof(M2C_TestResponse))]
 	[Message(OuterOpcode.C2M_TestRequest)]
 	[ProtoContract]

+ 32 - 30
Apps/Model/Generate/Message/OuterOpcode.cs

@@ -2,35 +2,37 @@ namespace ET
 {
 	public static partial class OuterOpcode
 	{
-		 public const ushort C2M_TestRequest = 10002;
-		 public const ushort M2C_TestResponse = 10003;
-		 public const ushort Actor_TransferRequest = 10004;
-		 public const ushort Actor_TransferResponse = 10005;
-		 public const ushort C2G_EnterMap = 10006;
-		 public const ushort G2C_EnterMap = 10007;
-		 public const ushort MoveInfo = 10008;
-		 public const ushort UnitInfo = 10009;
-		 public const ushort M2C_CreateUnits = 10010;
-		 public const ushort M2C_CreateMyUnit = 10011;
-		 public const ushort M2C_StartSceneChange = 10012;
-		 public const ushort M2C_RemoveUnits = 10013;
-		 public const ushort C2M_PathfindingResult = 10014;
-		 public const ushort C2M_Stop = 10015;
-		 public const ushort M2C_PathfindingResult = 10016;
-		 public const ushort M2C_Stop = 10017;
-		 public const ushort C2G_Ping = 10018;
-		 public const ushort G2C_Ping = 10019;
-		 public const ushort G2C_Test = 10020;
-		 public const ushort C2M_Reload = 10021;
-		 public const ushort M2C_Reload = 10022;
-		 public const ushort C2R_Login = 10023;
-		 public const ushort R2C_Login = 10024;
-		 public const ushort C2G_LoginGate = 10025;
-		 public const ushort G2C_LoginGate = 10026;
-		 public const ushort G2C_TestHotfixMessage = 10027;
-		 public const ushort C2M_TestRobotCase = 10028;
-		 public const ushort M2C_TestRobotCase = 10029;
-		 public const ushort C2M_TransferMap = 10030;
-		 public const ushort M2C_TransferMap = 10031;
+		 public const ushort HttpGetRouterResponse = 10002;
+		 public const ushort RouterSync = 10003;
+		 public const ushort C2M_TestRequest = 10004;
+		 public const ushort M2C_TestResponse = 10005;
+		 public const ushort Actor_TransferRequest = 10006;
+		 public const ushort Actor_TransferResponse = 10007;
+		 public const ushort C2G_EnterMap = 10008;
+		 public const ushort G2C_EnterMap = 10009;
+		 public const ushort MoveInfo = 10010;
+		 public const ushort UnitInfo = 10011;
+		 public const ushort M2C_CreateUnits = 10012;
+		 public const ushort M2C_CreateMyUnit = 10013;
+		 public const ushort M2C_StartSceneChange = 10014;
+		 public const ushort M2C_RemoveUnits = 10015;
+		 public const ushort C2M_PathfindingResult = 10016;
+		 public const ushort C2M_Stop = 10017;
+		 public const ushort M2C_PathfindingResult = 10018;
+		 public const ushort M2C_Stop = 10019;
+		 public const ushort C2G_Ping = 10020;
+		 public const ushort G2C_Ping = 10021;
+		 public const ushort G2C_Test = 10022;
+		 public const ushort C2M_Reload = 10023;
+		 public const ushort M2C_Reload = 10024;
+		 public const ushort C2R_Login = 10025;
+		 public const ushort R2C_Login = 10026;
+		 public const ushort C2G_LoginGate = 10027;
+		 public const ushort G2C_LoginGate = 10028;
+		 public const ushort G2C_TestHotfixMessage = 10029;
+		 public const ushort C2M_TestRobotCase = 10030;
+		 public const ushort M2C_TestRobotCase = 10031;
+		 public const ushort C2M_TransferMap = 10032;
+		 public const ushort M2C_TransferMap = 10033;
 	}
 }

+ 24 - 0
Apps/Model/Module/Router/RouterComponent.cs

@@ -0,0 +1,24 @@
+using System;
+using System.Collections.Generic;
+using System.Net;
+using System.Net.Sockets;
+
+namespace ET
+{
+    [ChildType(typeof(RouterNode))]
+    public class RouterComponent: Entity, IAwake<IPEndPoint, string>, IDestroy, IUpdate
+    {
+        public Socket OuterSocket;
+        public Socket InnerSocket;
+        public EndPoint IPEndPoint = new IPEndPoint(IPAddress.Any, 0);
+
+        public byte[] Cache = new byte[1500];
+
+        public Dictionary<uint, RouterNode> ConnectIdNodes = new Dictionary<uint, RouterNode>();
+
+        // 已经连接成功的,虽然跟id一样,但是没有经过验证的不会加到这里
+        public Dictionary<uint, RouterNode> OuterNodes = new Dictionary<uint, RouterNode>();
+
+        public long LastCheckTime = 0;
+    }
+}

+ 35 - 0
Apps/Model/Module/Router/RouterNode.cs

@@ -0,0 +1,35 @@
+using System.Net;
+
+namespace ET
+{
+    public enum RouterStatus
+    {
+        Sync,
+        Msg,
+    }
+
+    public class RouterNode: Entity, IDestroy, IAwake
+    {
+        public uint ConnectId;
+        public string InnerAddress;
+        public IPEndPoint InnerIpEndPoint;
+        public IPEndPoint OuterIpEndPoint;
+        public IPEndPoint SyncIpEndPoint;
+        public uint OuterConn;
+        public uint InnerConn;
+        public long LastRecvOuterTime;
+        public long LastRecvInnerTime;
+
+        public int RouterSyncCount;
+        public int SyncCount;
+
+#region 限制外网消息数量,一秒最多50个包
+
+        public long LastCheckTime;
+        public int LimitCountPerSecond;
+
+#endregion
+
+        public RouterStatus Status;
+    }
+}

+ 1 - 2
Config/StartConfig/Localhost/StartProcessConfigCategory.bytes

@@ -1,3 +1,2 @@
 
-�
-�
+�

+ 6 - 1
Config/StartConfig/Localhost/StartSceneConfigCategory.bytes

@@ -5,4 +5,9 @@
 "Location*Location
 "Map*Map1
 "Map*Map2
-È"Robot*Robot01
+È"Robot*Robot01
+(¬"
RouterManager*
RouterManager0¼P
+­"Router*Router010½P
+®"Router*Router020¾P
+¯"Router*Router030¿P
+°"Router*Router040ÀP

BIN
Config/StartConfig/Localhost/StartZoneConfigCategory.bytes


+ 2 - 0
Config/StartConfig/RouterTest/StartMachineConfigCategory.bytes

@@ -0,0 +1,2 @@
+
+	127.0.0.1	127.0.0.1"10000

+ 8 - 0
Config/StartConfig/RouterTest/StartProcessConfigCategory.bytes

@@ -0,0 +1,8 @@
+
+�
+�
+�
+�
+�
+�
+�

+ 13 - 0
Config/StartConfig/RouterTest/StartSceneConfigCategory.bytes

@@ -0,0 +1,13 @@
+
+"Realm*Realm0’N
+"Gate*Gate10“N
+"Gate*Gate20”N
+"Location*Location
+"Map*Map1
+"Map*Map2
+È"Robot*Robot01
+(�"
RouterManager*
RouterManager0¼P
+‘"Router*Router010½P
+’"Router*Router020¾P
+“"Router*Router030¿P
+”"Router*Router040ÀP

BIN
Config/StartConfig/RouterTest/StartZoneConfigCategory.bytes


+ 0 - 1
Excel/Json/s/StartConfig/Localhost/StartProcessConfig.txt

@@ -1,4 +1,3 @@
 {"list":[
 {"_t":"StartProcessConfig","_id":1,"MachineId":1,"InnerPort":20001},
-{"_t":"StartProcessConfig","_id":2,"MachineId":1,"InnerPort":20002},
 ]}

+ 5 - 0
Excel/Json/s/StartConfig/Localhost/StartSceneConfig.txt

@@ -6,4 +6,9 @@
 {"_t":"StartSceneConfig","_id":5,"Process":1,"Zone":1,"SceneType":"Map","Name":"Map1","OuterPort":0},
 {"_t":"StartSceneConfig","_id":6,"Process":1,"Zone":1,"SceneType":"Map","Name":"Map2","OuterPort":0},
 {"_t":"StartSceneConfig","_id":200,"Process":1,"Zone":2,"SceneType":"Robot","Name":"Robot01","OuterPort":0},
+{"_t":"StartSceneConfig","_id":300,"Process":1,"Zone":3,"SceneType":"RouterManager","Name":"RouterManager","OuterPort":10300},
+{"_t":"StartSceneConfig","_id":301,"Process":1,"Zone":3,"SceneType":"Router","Name":"Router01","OuterPort":10301},
+{"_t":"StartSceneConfig","_id":302,"Process":1,"Zone":3,"SceneType":"Router","Name":"Router02","OuterPort":10302},
+{"_t":"StartSceneConfig","_id":303,"Process":1,"Zone":3,"SceneType":"Router","Name":"Router03","OuterPort":10303},
+{"_t":"StartSceneConfig","_id":304,"Process":1,"Zone":3,"SceneType":"Router","Name":"Router04","OuterPort":10304},
 ]}

+ 2 - 1
Excel/Json/s/StartConfig/Localhost/StartZoneConfig.txt

@@ -1,4 +1,5 @@
 {"list":[
 {"_t":"StartZoneConfig","_id":1,"DBConnection":"mongodb://127.0.0.1","DBName":"ET1"},
-{"_t":"StartZoneConfig","_id":2,"DBConnection":"mongodb://127.0.0.1","DBName":"ET2"},
+{"_t":"StartZoneConfig","_id":2,"DBConnection":"","DBName":""},
+{"_t":"StartZoneConfig","_id":3,"DBConnection":"","DBName":""},
 ]}

+ 3 - 0
Excel/Json/s/StartConfig/RouterTest/StartMachineConfig.txt

@@ -0,0 +1,3 @@
+{"list":[
+{"_t":"StartMachineConfig","_id":1,"InnerIP":"127.0.0.1","OuterIP":"127.0.0.1","WatcherPort":"10000"},
+]}

+ 9 - 0
Excel/Json/s/StartConfig/RouterTest/StartProcessConfig.txt

@@ -0,0 +1,9 @@
+{"list":[
+{"_t":"StartProcessConfig","_id":1,"MachineId":1,"InnerPort":20001},
+{"_t":"StartProcessConfig","_id":2,"MachineId":1,"InnerPort":20002},
+{"_t":"StartProcessConfig","_id":3,"MachineId":1,"InnerPort":20003},
+{"_t":"StartProcessConfig","_id":4,"MachineId":1,"InnerPort":20004},
+{"_t":"StartProcessConfig","_id":5,"MachineId":1,"InnerPort":20005},
+{"_t":"StartProcessConfig","_id":6,"MachineId":1,"InnerPort":20006},
+{"_t":"StartProcessConfig","_id":7,"MachineId":1,"InnerPort":20007},
+]}

+ 14 - 0
Excel/Json/s/StartConfig/RouterTest/StartSceneConfig.txt

@@ -0,0 +1,14 @@
+{"list":[
+{"_t":"StartSceneConfig","_id":1,"Process":1,"Zone":1,"SceneType":"Realm","Name":"Realm","OuterPort":10002},
+{"_t":"StartSceneConfig","_id":2,"Process":1,"Zone":1,"SceneType":"Gate","Name":"Gate1","OuterPort":10003},
+{"_t":"StartSceneConfig","_id":3,"Process":1,"Zone":1,"SceneType":"Gate","Name":"Gate2","OuterPort":10004},
+{"_t":"StartSceneConfig","_id":4,"Process":1,"Zone":1,"SceneType":"Location","Name":"Location","OuterPort":0},
+{"_t":"StartSceneConfig","_id":5,"Process":1,"Zone":1,"SceneType":"Map","Name":"Map1","OuterPort":0},
+{"_t":"StartSceneConfig","_id":6,"Process":1,"Zone":1,"SceneType":"Map","Name":"Map2","OuterPort":0},
+{"_t":"StartSceneConfig","_id":200,"Process":2,"Zone":2,"SceneType":"Robot","Name":"Robot01","OuterPort":0},
+{"_t":"StartSceneConfig","_id":400,"Process":3,"Zone":3,"SceneType":"RouterManager","Name":"RouterManager","OuterPort":10300},
+{"_t":"StartSceneConfig","_id":401,"Process":4,"Zone":3,"SceneType":"Router","Name":"Router01","OuterPort":10301},
+{"_t":"StartSceneConfig","_id":402,"Process":5,"Zone":3,"SceneType":"Router","Name":"Router02","OuterPort":10302},
+{"_t":"StartSceneConfig","_id":403,"Process":6,"Zone":3,"SceneType":"Router","Name":"Router03","OuterPort":10303},
+{"_t":"StartSceneConfig","_id":404,"Process":7,"Zone":3,"SceneType":"Router","Name":"Router04","OuterPort":10304},
+]}

+ 5 - 0
Excel/Json/s/StartConfig/RouterTest/StartZoneConfig.txt

@@ -0,0 +1,5 @@
+{"list":[
+{"_t":"StartZoneConfig","_id":1,"DBConnection":"mongodb://127.0.0.1","DBName":"ET1"},
+{"_t":"StartZoneConfig","_id":2,"DBConnection":"","DBName":""},
+{"_t":"StartZoneConfig","_id":3,"DBConnection":"","DBName":""},
+]}

BIN
Excel/StartConfig/Localhost/StartProcessConfig@s.xlsx


BIN
Excel/StartConfig/Localhost/StartSceneConfig@s.xlsx


BIN
Excel/StartConfig/Localhost/StartZoneConfig@s.xlsx


BIN
Excel/StartConfig/RouterTest/StartMachineConfig@s.xlsx


BIN
Excel/StartConfig/RouterTest/StartProcessConfig@s.xlsx


BIN
Excel/StartConfig/RouterTest/StartSceneConfig@s.xlsx


BIN
Excel/StartConfig/RouterTest/StartZoneConfig@s.xlsx


+ 12 - 0
Proto/OuterMessage.proto

@@ -1,6 +1,18 @@
 syntax = "proto3";
 package ET;
 
+message HttpGetRouterResponse
+{
+	repeated string Realms = 1;
+	repeated string Routers = 2;
+}
+
+message RouterSync
+{
+	uint32 ConnectId = 1;
+	string Address = 2;
+}
+
 //ResponseType M2C_TestResponse
 message C2M_TestRequest // IActorLocationRequest
 {

+ 16 - 0
Unity/Assets/Editor/ServerCommandLineEditor/ServerCommandLineEditor.cs

@@ -6,6 +6,13 @@ using UnityEngine;
 
 namespace ET
 {
+    public enum DevelopMode
+    {
+        正式 = 0,
+        开发 = 1,
+        压测 = 2,
+    }
+    
     public class ServerCommandLineEditor: EditorWindow
     {
         [MenuItem("Tools/ServerTools")]
@@ -17,6 +24,7 @@ namespace ET
         private int selectStartConfigIndex;
         private string[] startConfigs;
         private string startConfig;
+        private DevelopMode developMode;
 
         public void OnEnable()
         {
@@ -28,12 +36,20 @@ namespace ET
         {
             selectStartConfigIndex = EditorGUILayout.Popup(selectStartConfigIndex, this.startConfigs);
             this.startConfig = this.startConfigs[this.selectStartConfigIndex];
+            this.developMode = (DevelopMode) EditorGUILayout.EnumPopup("起服模式:", this.developMode);
+            int develop = (int) this.developMode;
             
             if (GUILayout.Button("Start Server(Single Srocess)"))
             {
                 string arguments = $"App.dll --Process=1 --StartConfig=StartConfig/{this.startConfig} --Console=1";
                 ProcessHelper.Run("dotnet.exe", arguments, "../Bin/");
             }
+            
+            if (GUILayout.Button("Start Watcher"))
+            {
+                string arguments = $"App.dll --AppType=Watcher --StartConfig=StartConfig/{this.startConfig} --Console=1";
+                ProcessHelper.Run("dotnet.exe", arguments, "../Bin/");
+            }
 
             if (GUILayout.Button("Start Mongo"))
             {

+ 17 - 4
Unity/Assets/Mono/Core/Helper/ProcessHelper.cs

@@ -8,7 +8,7 @@ namespace ET
 {
     public static class ProcessHelper
     {
-        public static Process Run(string exe, string arguments, string workingDirectory = ".")
+        public static Process Run(string exe, string arguments, string workingDirectory = ".", bool waitExit = false)
         {
             //Log.Debug($"Process Run exe:{exe} ,arguments:{arguments} ,workingDirectory:{workingDirectory}");
             try
@@ -22,6 +22,16 @@ namespace ET
                     redirectStandardError = false;
                     useShellExecute = true;
                 }
+                
+                if (waitExit)
+                {
+                    redirectStandardOutput = true;
+                    redirectStandardError = true;
+                    useShellExecute = false;
+                }
+                
+                //Log.Debug($"1111111111111111111111111aaaa: {redirectStandardError} {redirectStandardOutput} {useShellExecute}");
+                
                 ProcessStartInfo info = new ProcessStartInfo
                 {
                     FileName = exe,
@@ -34,8 +44,11 @@ namespace ET
                 };
 
                 Process process = Process.Start(info);
-                
-                WaitExitAsync(process);
+
+                if (waitExit)
+                {
+                    WaitExitAsync(process).Coroutine();
+                }
 
                 return process;
             }
@@ -45,7 +58,7 @@ namespace ET
             }
         }
         
-        private static async void WaitExitAsync(Process process)
+        private static async ETTask WaitExitAsync(Process process)
         {
             await process.WaitForExitAsync();
 #if NOT_UNITY

+ 9 - 244
Unity/Assets/Mono/Core/Helper/RandomHelper.cs

@@ -1,44 +1,17 @@
 using System;
-using System.Collections;
 using System.Collections.Generic;
 using Random = System.Random;
 
 namespace ET
 {
-    public static class RandomEx
+    public static class RandomHelper
     {
-        public static ulong RandUInt64(this Random random)
-        {
-            byte[] byte8 = new byte[8];
-            random.NextBytes(byte8);
-            return BitConverter.ToUInt64(byte8, 0);
-        }
+        private static readonly Random random = new Random(Guid.NewGuid().GetHashCode());
 
-        public static int RandInt32(this Random random)
-        {
-            return random.Next();
-        }
-        
-        public static uint RandUInt32(this Random random)
-        {
-            return (uint)random.Next();
-        }
+        private static readonly byte[] byte8 = new byte[8];
 
-        public static long RandInt64(this Random random)
-        {
-            byte[] byte8 = new byte[8];
-            random.NextBytes(byte8);
-            return BitConverter.ToInt64(byte8, 0);
-        }
-    }
-    
-    public static class RandomHelper
-    {
-        public static Random random = new Random(Guid.NewGuid().GetHashCode());
-        
         public static ulong RandUInt64()
         {
-            byte[] byte8 = new byte[8];
             random.NextBytes(byte8);
             return BitConverter.ToUInt64(byte8, 0);
         }
@@ -47,15 +20,14 @@ namespace ET
         {
             return random.Next();
         }
-        
+
         public static uint RandUInt32()
         {
-            return (uint)random.Next();
+            return (uint) random.Next();
         }
 
         public static long RandInt64()
         {
-            byte[] byte8 = new byte[8];
             random.NextBytes(byte8);
             return BitConverter.ToInt64(byte8, 0);
         }
@@ -72,13 +44,13 @@ namespace ET
             return value;
         }
 
-        public static long NextLong(long minValue,long maxValue)
+        public static long NextLong(long minValue, long maxValue)
         {
             if (minValue > maxValue)
             {
-                throw new ArgumentException("minValue is great than maxValue", "minValue");
+                throw new ArgumentException("minValue is great than maxValue", nameof (minValue));
             }
-            
+
             long num = maxValue - minValue;
             return minValue + (long) (random.NextDouble() * num);
         }
@@ -93,11 +65,6 @@ namespace ET
             return array[RandomNumber(0, array.Length)];
         }
 
-        public static int RandomArray_Len2(this int[] array)
-        {
-            return RandomHelper.RandomNumber(array[0], array[1]);
-        }
-
         public static T RandomArray<T>(this List<T> array)
         {
             return array[RandomNumber(0, array.Count)];
@@ -118,73 +85,8 @@ namespace ET
             for (int i = 0; i < arr.Count; i++)
             {
                 int index = random.Next(0, arr.Count);
-                T temp = arr[index];
-                arr[index] = arr[i];
-                arr[i] = temp;
-            }
-        }
-
-        public static int[] GetRandoms(int sum, int min, int max)
-        {
-            int[] arr = new int[sum];
-            int j = 0;
-            //表示键和值对的集合。
-            Hashtable hashtable = new Hashtable();
-            Random rm = random;
-            while (hashtable.Count < sum) {
-                //返回一个min到max之间的随机数
-                int nValue = rm.Next(min, max);
-                // 是否包含特定值
-                if (!hashtable.ContainsValue(nValue))
-                {
-                    //把键和值添加到hashtable
-                    hashtable.Add(nValue, nValue);
-                    arr[j] = nValue;
-                    j++;
-                }
-            }
-
-            return arr;
-        }
-        
-        /// <summary>
-        /// 随机从数组中取若干个不重复的元素,
-        /// 为了降低算法复杂度,所以是伪随机,对随机要求不是非常高的逻辑可以用
-        /// </summary>
-        /// <typeparam name="T"></typeparam>
-        /// <param name="sourceList"></param>
-        /// <param name="destList"></param>
-        /// <param name="randCount"></param>
-        public static bool GetRandListByCount<T>(List<T> sourceList, List<T> destList, int randCount)
-        {
-            if (sourceList == null || destList == null || randCount < 0)
-            {
-                return false;
+                (arr[index], arr[i]) = (arr[i], arr[index]);
             }
-            
-            destList.Clear();
-
-            if (randCount >= sourceList.Count)
-            {
-                foreach (var val in sourceList)
-                {
-                    destList.Add(val);
-                }
-                
-                return true;
-            }
-
-            if (randCount == 0)
-            {
-                return true;
-            }
-            int beginIndex = random.Next(0, sourceList.Count - 1);
-            for (int i = beginIndex; i < beginIndex + randCount; i++)
-            {
-                destList.Add(sourceList[i % sourceList.Count]);
-            }
-
-            return true;
         }
 
         public static float RandFloat01()
@@ -192,142 +94,5 @@ namespace ET
             int a = RandomNumber(0, 1000000);
             return a / 1000000f;
         }
-
-        private static int Rand(int n)
-        {
-            // 注意,返回值是左闭右开,所以maxValue要加1
-            return random.Next(1, n + 1);
-        }
-
-        /// <summary>
-        /// 通过权重随机
-        /// </summary>
-        /// <param name="weights"></param>
-        /// <returns></returns>
-        public static int RandomByWeight(int[] weights)
-        {
-            int sum = 0;
-            for (int i = 0; i < weights.Length; i++)
-            {
-                sum += weights[i];
-            }
-
-            int number_rand = Rand(sum);
-
-            int sum_temp = 0;
-
-            for (int i = 0; i < weights.Length; i++)
-            {
-                sum_temp += weights[i];
-                if (number_rand <= sum_temp)
-                {
-                    return i;
-                }
-            }
-
-            return -1;
-        }
-
-        public static int RandomByWeight(List<int> weights)
-        {
-            if (weights.Count == 0)
-            {
-                return -1;
-            }
-
-            if (weights.Count == 1)
-            {
-                return 0;
-            }
-
-            int sum = 0;
-            for (int i = 0; i < weights.Count; i++)
-            {
-                sum += weights[i];
-            }
-
-            int number_rand = Rand(sum);
-
-            int sum_temp = 0;
-
-            for (int i = 0; i < weights.Count; i++)
-            {
-                sum_temp += weights[i];
-                if (number_rand <= sum_temp)
-                {
-                    return i;
-                }
-            }
-
-            return -1;
-        }
-        
-        public static int RandomByWeight(List<int> weights, int weightRandomMinVal)
-        {
-            if (weights.Count == 0)
-            {
-                return -1;
-            }
-
-            if (weights.Count == 1)
-            {
-                return 0;
-            }
-
-            int sum = 0;
-            for (int i = 0; i < weights.Count; i++)
-            {
-                sum += weights[i];
-            }
-
-            int number_rand = Rand(Math.Max(sum, weightRandomMinVal));
-
-            int sum_temp = 0;
-
-            for (int i = 0; i < weights.Count; i++)
-            {
-                sum_temp += weights[i];
-                if (number_rand <= sum_temp)
-                {
-                    return i;
-                }
-            }
-
-            return -1;
-        }
-        
-        public static int RandomByWeight(List<long> weights)
-        {
-            if (weights.Count == 0)
-            {
-                return -1;
-            }
-
-            if (weights.Count == 1)
-            {
-                return 0;
-            }
-
-            long sum = 0;
-            for (int i = 0; i < weights.Count; i++)
-            {
-                sum += weights[i];
-            }
-            
-            long number_rand = NextLong(1, sum + 1);
-
-            long sum_temp = 0;
-
-            for (int i = 0; i < weights.Count; i++)
-            {
-                sum_temp += weights[i];
-                if (number_rand <= sum_temp)
-                {
-                    return i;
-                }
-            }
-
-            return -1;
-        }
     }
 }

+ 34 - 0
Unity/Assets/Mono/Core/Helper/StringHashHelper.cs

@@ -0,0 +1,34 @@
+using System;
+using System.Text;
+
+namespace ET
+{
+    public static class StringHashHelper
+    {
+        // bkdr hash
+        public static long GetLongHashCode(this string str)
+        {
+            const uint seed = 1313; // 31 131 1313 13131 131313 etc..
+            
+            ulong hash = 0;
+            for (int i = 0; i < str.Length; ++i)
+            {
+                char c = str[i];
+                byte high = (byte)(c >> 8);
+                byte low = (byte)(c & byte.MaxValue);
+                hash = hash * seed + high;
+                hash = hash * seed + low;
+            }
+            return (long)hash;
+        }
+
+        public static int Mode(this string strText, int mode)
+        {
+            if (mode <= 0)
+            {
+                throw new Exception($"string mode < 0: {strText} {mode}");
+            }
+            return (int)((ulong)strText.GetLongHashCode() % (uint)mode);
+        }
+    }
+}

+ 11 - 0
Unity/Assets/Mono/Core/Helper/StringHashHelper.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 36104fdef2a076547afb95f2edee000c
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 4 - 0
Unity/Assets/Mono/Core/Log/Log.cs

@@ -20,6 +20,10 @@ namespace ET
 
         private static bool CheckLogLevel(int level)
         {
+            if (Options.Instance == null)
+            {
+                return true;
+            }
             return Options.Instance.LogLevel <= level;
         }
         

+ 3 - 3
Unity/Assets/Mono/Core/Options.cs

@@ -19,6 +19,9 @@ namespace ET
         
         [Option("AppType", Required = false, Default = AppType.Server, HelpText = "AppType enum")]
         public AppType AppType { get; set; }
+        
+        [Option("StartConfig", Required = false)]
+        public string StartConfig { get; set; }
 
         [Option("Process", Required = false, Default = 1)]
         public int Process { get; set; } = 1;
@@ -32,9 +35,6 @@ namespace ET
         [Option("Console", Required = false, Default = 0)]
         public int Console { get; set; } = 0;
 
-        [Option("StartConfig", Required = false, Default = "")]
-        public string StartConfig { get; set; } = "";
-        
         // 进程启动是否创建该进程的scenes
         [Option("CreateScenes", Required = false, Default = 1)]
         public int CreateScenes { get; set; } = 1;

+ 3 - 0
Unity/Assets/Mono/Module/Network/ErrorCore.cs

@@ -46,6 +46,9 @@
         public const int ERR_KcpRouterTimeout = 110401;
         public const int ERR_KcpRouterTooManyPackets = 110402;
         public const int ERR_KcpRouterSame = 110402;
+        public const int ERR_KcpRouterConnectFail = 110404;
+        public const int ERR_KcpRouterRouterSyncCountTooMuchTimes = 110405;
+        public const int ERR_KcpRouterSyncCountTooMuchTimes = 110406;
         
         // 110000 以上,避免跟SocketError冲突
 

+ 60 - 0
Unity/Assets/Mono/Module/Network/KService.cs

@@ -15,6 +15,10 @@ namespace ET
         public const byte ACK = 2;
         public const byte FIN = 3;
         public const byte MSG = 4;
+        public const byte RouterReconnectSYN = 5;
+        public const byte RouterReconnectACK = 6;
+        public const byte RouterSYN = 7;
+        public const byte RouterACK = 8;
     }
 
     public enum ServiceType
@@ -222,6 +226,62 @@ namespace ET
                     switch (flag)
                     {
 #if NOT_UNITY
+                        case KcpProtocalType.RouterReconnectSYN:
+                        {
+                            // 长度!=5,不是RouterReconnectSYN消息
+                            if (messageLength != 13)
+                            {
+                                break;
+                            }
+
+                            string realAddress = null;
+                            remoteConn = BitConverter.ToUInt32(this.cache, 1);
+                            localConn = BitConverter.ToUInt32(this.cache, 5);
+                            uint connectId = BitConverter.ToUInt32(this.cache, 9);
+
+                            this.localConnChannels.TryGetValue(localConn, out kChannel);
+                            if (kChannel == null)
+                            {
+                                Log.Warning($"kchannel reconnect not found channel: {localConn} {remoteConn} {realAddress}");
+                                break;
+                            }
+
+                            // 这里必须校验localConn,客户端重连,localConn一定是一样的
+                            if (localConn != kChannel.LocalConn)
+                            {
+                                Log.Warning($"kchannel reconnect localconn error: {kChannel.Id} {localConn} {remoteConn} {realAddress} {kChannel.LocalConn}");
+                                break;
+                            }
+
+                            if (remoteConn != kChannel.RemoteConn)
+                            {
+                                Log.Warning($"kchannel reconnect remoteconn error: {kChannel.Id} {localConn} {remoteConn} {realAddress} {kChannel.RemoteConn}");
+                                break;
+                            }
+
+                            // 重连的时候router地址变化, 这个不能放到msg中,必须经过严格的验证才能切换
+                            if (!Equals(kChannel.RemoteAddress, this.ipEndPoint))
+                            {
+                                kChannel.RemoteAddress = this.CloneAddress();
+                            }
+
+                            try
+                            {
+                                byte[] buffer = this.cache;
+                                buffer.WriteTo(0, KcpProtocalType.RouterReconnectACK);
+                                buffer.WriteTo(1, kChannel.LocalConn);
+                                buffer.WriteTo(5, kChannel.RemoteConn);
+                                buffer.WriteTo(9, connectId);
+                                this.socket.SendTo(buffer, 0, 13, SocketFlags.None, this.ipEndPoint);
+                            }
+                            catch (Exception e)
+                            {
+                                Log.Error(e);
+                                kChannel.OnError(ErrorCore.ERR_SocketCantSend);
+                            }
+
+                            break;
+                        }
                         case KcpProtocalType.SYN: // accept
                         {
                             // 长度!=5,不是SYN消息

+ 13 - 4
Unity/Codes/Hotfix/Client/Login/LoginHelper.cs

@@ -4,7 +4,7 @@ namespace ET.Client
 {
     public static class LoginHelper
     {
-        public static async ETTask Login(Scene zoneScene, string address, string account, string password)
+        public static async ETTask Login(Scene zoneScene, string account, string password)
         {
             try
             {
@@ -13,7 +13,17 @@ namespace ET.Client
                 Session session = null;
                 try
                 {
-                    session = zoneScene.GetComponent<NetKcpComponent>().Create(NetworkHelper.ToIPEndPoint(address));
+                    zoneScene.RemoveComponent<RouterAddressComponent>();
+                    // 获取路由跟realmDispatcher地址
+                    RouterAddressComponent routerAddressComponent = zoneScene.GetComponent<RouterAddressComponent>();
+                    if (routerAddressComponent == null)
+                    {
+                        routerAddressComponent = zoneScene.AddComponent<RouterAddressComponent, string>(ConstValue.RouterHttpAddress);
+                        await routerAddressComponent.Init();
+                    }
+                    string realmAddress = routerAddressComponent.GetRealmAddress(account);
+                    
+                    session = await RouterHelper.CreateRouterSession(zoneScene, realmAddress);
                     {
                         r2CLogin = (R2C_Login) await session.Call(new C2R_Login() { Account = account, Password = password });
                     }
@@ -24,8 +34,7 @@ namespace ET.Client
                 }
 
                 // 创建一个gate Session,并且保存到SessionComponent中
-                Session gateSession = zoneScene.GetComponent<NetKcpComponent>().Create(NetworkHelper.ToIPEndPoint(r2CLogin.Address));
-                gateSession.AddComponent<PingComponent>();
+                Session gateSession = await RouterHelper.CreateRouterSession(zoneScene, r2CLogin.Address);
                 zoneScene.AddComponent<SessionComponent>().Session = gateSession;
 				
                 G2C_LoginGate g2CLoginGate = (G2C_LoginGate)await gateSession.Call(

+ 24 - 0
Unity/Codes/Hotfix/Client/Router/HttpClientHelper.cs

@@ -0,0 +1,24 @@
+using System;
+using System.IO;
+using System.Net.Http;
+
+namespace ET
+{
+    public static class HttpClientHelper
+    {
+        public static async ETTask<string> Get(string link)
+        {
+            try
+            {
+                using HttpClient httpClient = new HttpClient();
+                HttpResponseMessage response =  await httpClient.GetAsync(link);
+                string result = await response.Content.ReadAsStringAsync();
+                return result;
+            }
+            catch (Exception e)
+            {
+                throw new Exception($"http request fail: {link.Substring(0,link.IndexOf('?'))}\n{e}");
+            }
+        }
+    }
+}

+ 68 - 0
Unity/Codes/Hotfix/Client/Router/RouterAddressComponentSystem.cs

@@ -0,0 +1,68 @@
+using System;
+using System.IO;
+using System.Net;
+using System.Net.Sockets;
+using System.Runtime.InteropServices;
+
+namespace ET
+{
+    [FriendClass(typeof(RouterAddressComponent))]
+    public static class RouterAddressComponentSystem
+    {
+        public class RouterAddressComponentAwakeSystem: AwakeSystem<RouterAddressComponent, string>
+        {
+            public override void Awake(RouterAddressComponent self, string routerManagerAddress)
+            {
+                self.RouterManagerAddress = routerManagerAddress;
+            }
+        }
+        
+        public static async ETTask Init(this RouterAddressComponent self)
+        {
+            await self.GetAllRouter();
+        }
+
+        public static async ETTask GetAllRouter(this RouterAddressComponent self)
+        {
+            string url = $"http://{self.RouterManagerAddress}/get_router?v={RandomHelper.RandUInt32()}";
+            Log.Debug($"start get router info: {url}");
+            string routerInfo = await HttpClientHelper.Get(url);
+            Log.Debug($"recv router info: {routerInfo}");
+            HttpGetRouterResponse httpGetRouterResponse = JsonHelper.FromJson<HttpGetRouterResponse>(routerInfo);
+            self.Info = httpGetRouterResponse;
+            Log.Debug($"start get router info finish: {JsonHelper.ToJson(httpGetRouterResponse)}");
+            
+            // 打乱顺序
+            RandomHelper.BreakRank(self.Info.Routers);
+            
+            self.WaitTenMinGetAllRouter().Coroutine();
+        }
+        
+        // 等10分钟再获取一次
+        public static async ETTask WaitTenMinGetAllRouter(this RouterAddressComponent self)
+        {
+            await TimerComponent.Instance.WaitAsync(5 * 60 * 1000);
+            if (self.IsDisposed)
+            {
+                return;
+            }
+            await self.GetAllRouter();
+        }
+
+        public static string GetAddress(this RouterAddressComponent self)
+        {
+            if (self.Info.Routers.Count == 0)
+            {
+                return null;
+            }
+
+            return self.Info.Routers[self.RouterIndex++ % self.Info.Routers.Count];
+        }
+        
+        public static string GetRealmAddress(this RouterAddressComponent self, string account)
+        {
+            int v = account.Mode(self.Info.Realms.Count);
+            return self.Info.Realms[v];
+        }
+    }
+}

+ 80 - 0
Unity/Codes/Hotfix/Client/Router/RouterCheckComponentSystem.cs

@@ -0,0 +1,80 @@
+using System;
+using System.Net;
+
+namespace ET.Client
+{
+    [ObjectSystem]
+    public class RouterCheckComponentAwakeSystem: AwakeSystem<RouterCheckComponent>
+    {
+        public override void Awake(RouterCheckComponent self)
+        {
+            CheckAsync(self).Coroutine();
+        }
+
+        private static async ETTask CheckAsync(RouterCheckComponent self)
+        {
+            Session session = self.GetParent<Session>();
+            long instanceId = self.InstanceId;
+            
+            while (true)
+            {
+                if (self.InstanceId != instanceId)
+                {
+                    return;
+                }
+
+                await TimerComponent.Instance.WaitAsync(1000);
+                
+                if (self.InstanceId != instanceId)
+                {
+                    return;
+                }
+
+                long time = TimeHelper.ClientFrameTime();
+
+                if (time - session.LastRecvTime < 7 * 1000)
+                {
+                    continue;
+                }
+                
+                try
+                {
+                    long sessionId = session.Id;
+                    uint localConn = 0;
+                    uint remoteConn = 0;
+                    KService service = session.AService as KService;
+                    KChannel kChannel = service.Get(sessionId);
+                    if (kChannel == null)
+                    {
+                        Log.Warning($"not found remoteConn: {sessionId}");
+                        continue;
+                    }
+
+                    localConn = kChannel.LocalConn;
+                    remoteConn = kChannel.RemoteConn;
+
+                    string realAddress = self.GetParent<Session>().RemoteAddress.ToString();
+                    Log.Info($"get recvLocalConn start: {self.ZoneScene().Id} {realAddress} {localConn} {remoteConn}");
+
+                    (uint recvLocalConn, string routerAddress) = await RouterHelper.GetRouterAddress(self.ZoneScene(), realAddress, localConn, remoteConn);
+                    if (recvLocalConn == 0)
+                    {
+                        Log.Error($"get recvLocalConn fail: {self.ZoneScene().Id} {routerAddress} {realAddress} {localConn} {remoteConn}");
+                        continue;
+                    }
+                    
+                    Log.Info($"get recvLocalConn ok: {self.ZoneScene().Id} {routerAddress} {realAddress} {recvLocalConn} {localConn} {remoteConn}");
+                    
+                    session.LastRecvTime = TimeHelper.ClientNow();
+                    
+                    IPEndPoint remoteAddress = NetworkHelper.ToIPEndPoint(routerAddress);
+                    ((KService)session.AService).ChangeAddress(sessionId, remoteAddress);
+                }
+                catch (Exception e)
+                {
+                    Log.Error(e);
+                }
+            }
+        }
+    }
+}

+ 108 - 0
Unity/Codes/Hotfix/Client/Router/RouterHelper.cs

@@ -0,0 +1,108 @@
+using System;
+using System.Net;
+using System.Net.Sockets;
+
+namespace ET.Client
+{
+    public static class RouterHelper
+    {
+        // 注册router
+        public static async ETTask<Session> CreateRouterSession(Scene zoneScene, string address)
+        {
+            (uint recvLocalConn, string routerAddress) = await GetRouterAddress(zoneScene, address, 0, 0);
+
+            if (recvLocalConn == 0)
+            {
+                throw new Exception($"get router fail: {zoneScene.Id} {address}");
+            }
+            
+            Log.Info($"get router: {recvLocalConn} {routerAddress}");
+
+            Session routerSession = zoneScene.GetComponent<NetKcpComponent>().Create(NetworkHelper.ToIPEndPoint(routerAddress), NetworkHelper.ToIPEndPoint(address), recvLocalConn);
+            routerSession.AddComponent<PingComponent>();
+            routerSession.AddComponent<RouterCheckComponent>();
+            
+            return routerSession;
+        }
+        
+        public static async ETTask<(uint, string)> GetRouterAddress(Scene zoneScene, string address, uint localConn, uint remoteConn)
+        {
+            Log.Info($"start get router address: {zoneScene.Id} {address} {localConn} {remoteConn}");
+            //return (RandomHelper.RandUInt32(), address);
+            RouterAddressComponent routerAddressComponent = zoneScene.GetComponent<RouterAddressComponent>();
+            string routerInfo = routerAddressComponent.GetAddress();
+            
+            uint recvLocalConn = await Connect(NetworkHelper.ToIPEndPoint(routerInfo), address, localConn, remoteConn);
+            
+            Log.Info($"finish get router address: {zoneScene.Id} {address} {localConn} {remoteConn} {recvLocalConn} {routerInfo}");
+            return (recvLocalConn, routerInfo);
+        }
+
+        // 向router申请
+        private static async ETTask<uint> Connect(IPEndPoint routerAddress, string realAddress, uint localConn, uint remoteConn)
+        {
+            uint connectId = RandomHelper.RandUInt32();
+            
+            using Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
+            
+            int count = 30;
+            byte[] sendCache = new byte[512];
+            byte[] recvCache = new byte[512];
+
+            uint synFlag = localConn == 0? KcpProtocalType.RouterSYN : KcpProtocalType.RouterReconnectSYN;
+            sendCache.WriteTo(0, synFlag);
+            sendCache.WriteTo(1, localConn);
+            sendCache.WriteTo(5, remoteConn);
+            sendCache.WriteTo(9, connectId);
+            byte[] addressBytes = realAddress.ToByteArray();
+            Array.Copy(addressBytes, 0, sendCache, 13, addressBytes.Length);
+
+            Log.Info($"router connect: {connectId} {localConn} {remoteConn} {routerAddress} {realAddress}");
+                
+            EndPoint recvIPEndPoint = new IPEndPoint(IPAddress.Any, 0);
+
+            long lastSendTimer = 0;
+
+            while (true)
+            {
+                long timeNow = TimeHelper.ClientFrameTime();
+                if (timeNow - lastSendTimer > 300)
+                {
+                    if (--count < 0)
+                    {
+                        Log.Error($"router connect timeout fail! {localConn} {remoteConn} {routerAddress} {realAddress}");
+                        return 0;
+                    }
+                    lastSendTimer = timeNow;
+                    // 发送
+                    socket.SendTo(sendCache, 0, addressBytes.Length + 13, SocketFlags.None, routerAddress);
+                }
+                    
+                await TimerComponent.Instance.WaitFrameAsync();
+                    
+                // 接收
+                if (socket.Available > 0)
+                {
+                    int messageLength = socket.ReceiveFrom(recvCache, ref recvIPEndPoint);
+                    if (messageLength != 9)
+                    {
+                        Log.Error($"router connect error1: {connectId} {messageLength} {localConn} {remoteConn} {routerAddress} {realAddress}");
+                        continue;
+                    }
+
+                    byte flag = recvCache[0];
+                    if (flag != KcpProtocalType.RouterReconnectACK && flag != KcpProtocalType.RouterACK)
+                    {
+                        Log.Error($"router connect error2: {connectId} {synFlag} {flag} {localConn} {remoteConn} {routerAddress} {realAddress}");
+                        continue;
+                    }
+
+                    uint recvRemoteConn = BitConverter.ToUInt32(recvCache, 1);
+                    uint recvLocalConn = BitConverter.ToUInt32(recvCache, 5);
+                    Log.Info($"router connect finish: {connectId} {recvRemoteConn} {recvLocalConn} {localConn} {remoteConn} {routerAddress} {realAddress}");
+                    return recvLocalConn;
+                }
+            }
+        }
+    }
+}

+ 13 - 2
Unity/Codes/Hotfix/Module/Message/NetKcpComponentSystem.cs

@@ -14,7 +14,7 @@ namespace ET
             {
                 self.SessionStreamDispatcherType = sessionStreamDispatcherType;
             
-                self.Service = new TService(NetThreadComponent.Instance.ThreadSynchronizationContext, ServiceType.Outer);
+                self.Service = new KService(NetThreadComponent.Instance.ThreadSynchronizationContext, ServiceType.Outer);
                 self.Service.ErrorCallback += (channelId, error) => self.OnError(channelId, error);
                 self.Service.ReadCallback += (channelId, Memory) => self.OnRead(channelId, Memory);
 
@@ -29,7 +29,7 @@ namespace ET
             {
                 self.SessionStreamDispatcherType = sessionStreamDispatcherType;
             
-                self.Service = new TService(NetThreadComponent.Instance.ThreadSynchronizationContext, address, ServiceType.Outer);
+                self.Service = new KService(NetThreadComponent.Instance.ThreadSynchronizationContext, address, ServiceType.Outer);
                 self.Service.ErrorCallback += (channelId, error) => self.OnError(channelId, error);
                 self.Service.ReadCallback += (channelId, Memory) => self.OnRead(channelId, Memory);
                 self.Service.AcceptCallback += (channelId, IPAddress) => self.OnAccept(channelId, IPAddress);
@@ -101,5 +101,16 @@ namespace ET
 
             return session;
         }
+        
+        public static Session Create(this NetKcpComponent self, IPEndPoint routerIPEndPoint, IPEndPoint realIPEndPoint, uint localConn)
+        {
+            long channelId = self.Service.CreateConnectChannelId(localConn);
+            Session session = self.AddChildWithId<Session, AService>(channelId, self.Service);
+            session.RemoteAddress = realIPEndPoint;
+            session.AddComponent<SessionIdleCheckerComponent, int>(NetThreadComponent.checkInteral);
+            self.Service.GetOrCreate(session.Id, routerIPEndPoint);
+
+            return session;
+        }
     }
 }

+ 1 - 1
Unity/Codes/Hotfix/Module/Message/SessionIdleCheckerComponentSystem.cs

@@ -43,7 +43,7 @@ namespace ET
             Session session = self.GetParent<Session>();
             long timeNow = TimeHelper.ClientNow();
 
-            if (timeNow - session.LastRecvTime < 30 * 1000 && timeNow - session.LastSendTime < 30 * 1000)
+            if (timeNow - session.LastRecvTime < ConstValue.SessionTimeoutTime && timeNow - session.LastSendTime < ConstValue.SessionTimeoutTime)
             {
                 return;
             }

+ 2 - 1
Unity/Codes/Hotfix/Share/ConstValue.cs

@@ -2,6 +2,7 @@ namespace ET
 {
     public static class ConstValue
     {
-        public const string LoginAddress = "127.0.0.1:10002";
+        public const string RouterHttpAddress = "127.0.0.1:10300";
+        public const int SessionTimeoutTime = 30 * 1000;
     }
 }

+ 0 - 1
Unity/Codes/HotfixView/Client/UI/UILogin/UILoginComponentSystem.cs

@@ -25,7 +25,6 @@ namespace ET.Client
 		{
 			LoginHelper.Login(
 				self.DomainScene(), 
-				ConstValue.LoginAddress, 
 				self.account.GetComponent<InputField>().text, 
 				self.password.GetComponent<InputField>().text).Coroutine();
 		}

+ 12 - 0
Unity/Codes/Model/Client/Router/RouterAddressComponent.cs

@@ -0,0 +1,12 @@
+using System.Collections.Generic;
+using System.Net;
+
+namespace ET
+{
+    public class RouterAddressComponent: Entity, IAwake<string>
+    {
+        public string RouterManagerAddress;
+        public HttpGetRouterResponse Info;
+        public int RouterIndex;
+    }
+}

+ 6 - 0
Unity/Codes/Model/Client/Router/RouterCheckComponent.cs

@@ -0,0 +1,6 @@
+namespace ET
+{
+    public class RouterCheckComponent: Entity, IAwake
+    {
+    }
+}

+ 2 - 0
Unity/Codes/Model/Core/Entity/SceneType.cs

@@ -9,6 +9,8 @@
 		Http = 4,
 		Location = 5,
 		Map = 6,
+		Router = 7,
+		RouterManager = 8,
 
 		// 客户端Model层
 		Client = 30,

+ 24 - 0
Unity/Codes/Model/Generate/Message/OuterMessage.cs

@@ -3,6 +3,30 @@ using ProtoBuf;
 using System.Collections.Generic;
 namespace ET
 {
+	[Message(OuterOpcode.HttpGetRouterResponse)]
+	[ProtoContract]
+	public partial class HttpGetRouterResponse: Object
+	{
+		[ProtoMember(1)]
+		public List<string> Realms = new List<string>();
+
+		[ProtoMember(2)]
+		public List<string> Routers = new List<string>();
+
+	}
+
+	[Message(OuterOpcode.RouterSync)]
+	[ProtoContract]
+	public partial class RouterSync: Object
+	{
+		[ProtoMember(1)]
+		public uint ConnectId { get; set; }
+
+		[ProtoMember(2)]
+		public string Address { get; set; }
+
+	}
+
 	[ResponseType(nameof(M2C_TestResponse))]
 	[Message(OuterOpcode.C2M_TestRequest)]
 	[ProtoContract]

+ 32 - 30
Unity/Codes/Model/Generate/Message/OuterOpcode.cs

@@ -2,35 +2,37 @@ namespace ET
 {
 	public static partial class OuterOpcode
 	{
-		 public const ushort C2M_TestRequest = 10002;
-		 public const ushort M2C_TestResponse = 10003;
-		 public const ushort Actor_TransferRequest = 10004;
-		 public const ushort Actor_TransferResponse = 10005;
-		 public const ushort C2G_EnterMap = 10006;
-		 public const ushort G2C_EnterMap = 10007;
-		 public const ushort MoveInfo = 10008;
-		 public const ushort UnitInfo = 10009;
-		 public const ushort M2C_CreateUnits = 10010;
-		 public const ushort M2C_CreateMyUnit = 10011;
-		 public const ushort M2C_StartSceneChange = 10012;
-		 public const ushort M2C_RemoveUnits = 10013;
-		 public const ushort C2M_PathfindingResult = 10014;
-		 public const ushort C2M_Stop = 10015;
-		 public const ushort M2C_PathfindingResult = 10016;
-		 public const ushort M2C_Stop = 10017;
-		 public const ushort C2G_Ping = 10018;
-		 public const ushort G2C_Ping = 10019;
-		 public const ushort G2C_Test = 10020;
-		 public const ushort C2M_Reload = 10021;
-		 public const ushort M2C_Reload = 10022;
-		 public const ushort C2R_Login = 10023;
-		 public const ushort R2C_Login = 10024;
-		 public const ushort C2G_LoginGate = 10025;
-		 public const ushort G2C_LoginGate = 10026;
-		 public const ushort G2C_TestHotfixMessage = 10027;
-		 public const ushort C2M_TestRobotCase = 10028;
-		 public const ushort M2C_TestRobotCase = 10029;
-		 public const ushort C2M_TransferMap = 10030;
-		 public const ushort M2C_TransferMap = 10031;
+		 public const ushort HttpGetRouterResponse = 10002;
+		 public const ushort RouterSync = 10003;
+		 public const ushort C2M_TestRequest = 10004;
+		 public const ushort M2C_TestResponse = 10005;
+		 public const ushort Actor_TransferRequest = 10006;
+		 public const ushort Actor_TransferResponse = 10007;
+		 public const ushort C2G_EnterMap = 10008;
+		 public const ushort G2C_EnterMap = 10009;
+		 public const ushort MoveInfo = 10010;
+		 public const ushort UnitInfo = 10011;
+		 public const ushort M2C_CreateUnits = 10012;
+		 public const ushort M2C_CreateMyUnit = 10013;
+		 public const ushort M2C_StartSceneChange = 10014;
+		 public const ushort M2C_RemoveUnits = 10015;
+		 public const ushort C2M_PathfindingResult = 10016;
+		 public const ushort C2M_Stop = 10017;
+		 public const ushort M2C_PathfindingResult = 10018;
+		 public const ushort M2C_Stop = 10019;
+		 public const ushort C2G_Ping = 10020;
+		 public const ushort G2C_Ping = 10021;
+		 public const ushort G2C_Test = 10022;
+		 public const ushort C2M_Reload = 10023;
+		 public const ushort M2C_Reload = 10024;
+		 public const ushort C2R_Login = 10025;
+		 public const ushort R2C_Login = 10026;
+		 public const ushort C2G_LoginGate = 10027;
+		 public const ushort G2C_LoginGate = 10028;
+		 public const ushort G2C_TestHotfixMessage = 10029;
+		 public const ushort C2M_TestRobotCase = 10030;
+		 public const ushort M2C_TestRobotCase = 10031;
+		 public const ushort C2M_TransferMap = 10032;
+		 public const ushort M2C_TransferMap = 10033;
 	}
 }

+ 1 - 1
Unity/Codes/Model/Module/Message/Session.cs

@@ -145,7 +145,7 @@ namespace ET
 
     public sealed class Session: Entity, IAwake<AService>, IDestroy
     {
-        public AService AService;
+        public AService AService { get; set; }
         
         public static int RpcId
         {

+ 1 - 1
Unity/Unity.Hotfix.csproj

@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="utf-8"?>
 <Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
   <PropertyGroup>
-    <LangVersion>7.3</LangVersion>
+    <LangVersion>8</LangVersion>
     <_TargetFrameworkDirectories>non_empty_path_generated_by_unity.rider.package</_TargetFrameworkDirectories>
     <_FullFrameworkReferenceAssemblyPaths>non_empty_path_generated_by_unity.rider.package</_FullFrameworkReferenceAssemblyPaths>
     <DisableHandlePackageFileConflicts>true</DisableHandlePackageFileConflicts>

+ 1 - 1
Unity/Unity.HotfixView.csproj

@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="utf-8"?>
 <Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
   <PropertyGroup>
-    <LangVersion>7.3</LangVersion>
+    <LangVersion>8</LangVersion>
     <_TargetFrameworkDirectories>non_empty_path_generated_by_unity.rider.package</_TargetFrameworkDirectories>
     <_FullFrameworkReferenceAssemblyPaths>non_empty_path_generated_by_unity.rider.package</_FullFrameworkReferenceAssemblyPaths>
     <DisableHandlePackageFileConflicts>true</DisableHandlePackageFileConflicts>

+ 1 - 1
Unity/Unity.Model.csproj

@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="utf-8"?>
 <Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
   <PropertyGroup>
-    <LangVersion>7.3</LangVersion>
+    <LangVersion>8</LangVersion>
     <_TargetFrameworkDirectories>non_empty_path_generated_by_unity.rider.package</_TargetFrameworkDirectories>
     <_FullFrameworkReferenceAssemblyPaths>non_empty_path_generated_by_unity.rider.package</_FullFrameworkReferenceAssemblyPaths>
     <DisableHandlePackageFileConflicts>true</DisableHandlePackageFileConflicts>

+ 1 - 1
Unity/Unity.ModelView.csproj

@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="utf-8"?>
 <Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
   <PropertyGroup>
-    <LangVersion>7.3</LangVersion>
+    <LangVersion>8</LangVersion>
     <_TargetFrameworkDirectories>non_empty_path_generated_by_unity.rider.package</_TargetFrameworkDirectories>
     <_FullFrameworkReferenceAssemblyPaths>non_empty_path_generated_by_unity.rider.package</_FullFrameworkReferenceAssemblyPaths>
     <DisableHandlePackageFileConflicts>true</DisableHandlePackageFileConflicts>

Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 616 - 234
Unity/UserSettings/Layouts/default-2021.dwlt


Một số tệp đã không được hiển thị bởi vì quá nhiều tập tin thay đổi trong này khác