Przeglądaj źródła

把客户端的网络层代码独立出来

hexiaojie 7 miesięcy temu
rodzic
commit
1e43daf60a
29 zmienionych plików z 2121 dodań i 9 usunięć
  1. 8 6
      GameClient/Assets/Game/HotUpdate/ETCodes/Hotfix/App/Login/LoginHelper.cs
  2. 2 2
      GameClient/Assets/Game/HotUpdate/GameConfig.cs
  3. 8 0
      GameClient/Assets/Game/HotUpdate/Network.meta
  4. 66 0
      GameClient/Assets/Game/HotUpdate/Network/AChannel.cs
  5. 11 0
      GameClient/Assets/Game/HotUpdate/Network/AChannel.cs.meta
  6. 126 0
      GameClient/Assets/Game/HotUpdate/Network/AService.cs
  7. 11 0
      GameClient/Assets/Game/HotUpdate/Network/AService.cs.meta
  8. 306 0
      GameClient/Assets/Game/HotUpdate/Network/Circularbuffer.cs
  9. 11 0
      GameClient/Assets/Game/HotUpdate/Network/Circularbuffer.cs.meta
  10. 80 0
      GameClient/Assets/Game/HotUpdate/Network/ErrorCore.cs
  11. 11 0
      GameClient/Assets/Game/HotUpdate/Network/ErrorCore.cs.meta
  12. 8 0
      GameClient/Assets/Game/HotUpdate/NetworkTCP.meta
  13. 96 0
      GameClient/Assets/Game/HotUpdate/NetworkTCP/PacketParser.cs
  14. 11 0
      GameClient/Assets/Game/HotUpdate/NetworkTCP/PacketParser.cs.meta
  15. 423 0
      GameClient/Assets/Game/HotUpdate/NetworkTCP/TChannel.cs
  16. 11 0
      GameClient/Assets/Game/HotUpdate/NetworkTCP/TChannel.cs.meta
  17. 192 0
      GameClient/Assets/Game/HotUpdate/NetworkTCP/TService.cs
  18. 11 0
      GameClient/Assets/Game/HotUpdate/NetworkTCP/TService.cs.meta
  19. 245 0
      GameClient/Assets/Game/HotUpdate/NetworkTCP/WChannel.cs
  20. 3 0
      GameClient/Assets/Game/HotUpdate/NetworkTCP/WChannel.cs.meta
  21. 176 0
      GameClient/Assets/Game/HotUpdate/NetworkTCP/WChannel_WebGL.cs
  22. 11 0
      GameClient/Assets/Game/HotUpdate/NetworkTCP/WChannel_WebGL.cs.meta
  23. 164 0
      GameClient/Assets/Game/HotUpdate/NetworkTCP/WService.cs
  24. 3 0
      GameClient/Assets/Game/HotUpdate/NetworkTCP/WService.cs.meta
  25. 105 0
      GameClient/Assets/Game/HotUpdate/NetworkTCP/WService_WebGL.cs
  26. 11 0
      GameClient/Assets/Game/HotUpdate/NetworkTCP/WService_WebGL.cs.meta
  27. 1 1
      GameClient/Assets/Game/HotUpdate/ServerProxy/AccountSProxy.cs
  28. 9 0
      GameClient/Assets/Game/Launcher/LauncherConfig.cs
  29. 1 0
      GameClient/GameClient.sln.DotSettings.user

+ 8 - 6
GameClient/Assets/Game/HotUpdate/ETCodes/Hotfix/App/Login/LoginHelper.cs

@@ -14,7 +14,8 @@ namespace ET
             try
             try
             {
             {
                 Debug.Log($"address:{address}");
                 Debug.Log($"address:{address}");
-                accountSession = zoneScene.GetComponent<NetWSComponent>().Create(NetworkHelper.ToIPEndPoint(address));
+                accountSession = zoneScene.GetComponent<NetWSComponent>()
+                    .Create(NetworkHelper.ToIPEndPoint(address), address);
 
 
                 a2CLoginAccount = (A2C_LoginAccount)await accountSession.Call(new C2A_LoginTest()
                 a2CLoginAccount = (A2C_LoginAccount)await accountSession.Call(new C2A_LoginTest()
                 {
                 {
@@ -53,7 +54,8 @@ namespace ET
             Session accountSession = null;
             Session accountSession = null;
             try
             try
             {
             {
-                accountSession = zoneScene.GetComponent<NetWSComponent>().Create(NetworkHelper.ToIPEndPoint(address));
+                accountSession = zoneScene.GetComponent<NetWSComponent>()
+                    .Create(NetworkHelper.ToIPEndPoint(address), address);
                 var passwordMD5 = password;
                 var passwordMD5 = password;
                 //密码禁止明文传输
                 //密码禁止明文传输
                 if (!isMD5)
                 if (!isMD5)
@@ -111,7 +113,7 @@ namespace ET
             Session session = null;
             Session session = null;
             try
             try
             {
             {
-                session = zoneScene.GetComponent<NetWSComponent>().Create(NetworkHelper.ToIPEndPoint(address));
+                session = zoneScene.GetComponent<NetWSComponent>().Create(NetworkHelper.ToIPEndPoint(address), address);
                 {
                 {
                     //密码禁止明文传输
                     //密码禁止明文传输
                     var passwordMD5 = MD5Helper.stringMD5(password);
                     var passwordMD5 = MD5Helper.stringMD5(password);
@@ -327,7 +329,7 @@ namespace ET
             R2C_LoginRealm r2C_LoginRealm = null;
             R2C_LoginRealm r2C_LoginRealm = null;
 
 
             Session session = zoneScene.GetComponent<NetWSComponent>()
             Session session = zoneScene.GetComponent<NetWSComponent>()
-                .Create(NetworkHelper.ToIPEndPoint(realmAddress));
+                .Create(NetworkHelper.ToIPEndPoint(realmAddress), realmAddress);
             try
             try
             {
             {
                 r2C_LoginRealm = (R2C_LoginRealm)await session.Call(new C2R_LoginRealm()
                 r2C_LoginRealm = (R2C_LoginRealm)await session.Call(new C2R_LoginRealm()
@@ -352,7 +354,7 @@ namespace ET
 
 
             LogUtil.LogDev($"GateAddress : {r2C_LoginRealm.GateAddress}");
             LogUtil.LogDev($"GateAddress : {r2C_LoginRealm.GateAddress}");
             Session gateSession = zoneScene.GetComponent<NetWSComponent>()
             Session gateSession = zoneScene.GetComponent<NetWSComponent>()
-                .Create(NetworkHelper.ToIPEndPoint(r2C_LoginRealm.GateAddress));
+                .Create(NetworkHelper.ToIPEndPoint(r2C_LoginRealm.GateAddress), r2C_LoginRealm.GateAddress);
             gateSession.AddComponent<PingComponent>();
             gateSession.AddComponent<PingComponent>();
             gateSession.AddComponent<DisConnectedCompnent>().SessionState = SessionState.Gate;
             gateSession.AddComponent<DisConnectedCompnent>().SessionState = SessionState.Gate;
             zoneScene.GetComponent<SessionComponent>().GateSession = gateSession;
             zoneScene.GetComponent<SessionComponent>().GateSession = gateSession;
@@ -457,7 +459,7 @@ namespace ET
             {
             {
                 notLogin = true;
                 notLogin = true;
                 accountSession = GameGlobal.zoneScene.GetComponent<NetWSComponent>()
                 accountSession = GameGlobal.zoneScene.GetComponent<NetWSComponent>()
-                    .Create(NetworkHelper.ToIPEndPoint(GameConfig.LoginAddress));
+                    .Create(NetworkHelper.ToIPEndPoint(GameConfig.LoginAddress), GameConfig.LoginAddress);
             }
             }
 
 
             try
             try

+ 2 - 2
GameClient/Assets/Game/HotUpdate/GameConfig.cs

@@ -20,7 +20,7 @@ namespace GFGGame
         //开服时间
         //开服时间
         public static long openTime = 0;
         public static long openTime = 0;
         public static int tsStatus;
         public static int tsStatus;
-        public static int tsServer;
+        public static int tsServer;   
         //兑换码
         //兑换码
         public static int hCode;
         public static int hCode;
 
 
@@ -33,7 +33,7 @@ namespace GFGGame
             LoginAddress = result.loginApiUrl;
             LoginAddress = result.loginApiUrl;
             //LoginAddress = "43.139.184.240:10003";
             //LoginAddress = "43.139.184.240:10003";
             //LoginAddress = "129.204.4.238:11005";//测试地址
             //LoginAddress = "129.204.4.238:11005";//测试地址
-            //LoginAddress = "192.168.1.191:11005";//测试地址
+            //LoginAddress = "192.168.20.198:11005";//测试地址
             showGM = int.Parse(result.showGM);
             showGM = int.Parse(result.showGM);
             if(!string.IsNullOrEmpty(result.openTime))
             if(!string.IsNullOrEmpty(result.openTime))
             {
             {

+ 8 - 0
GameClient/Assets/Game/HotUpdate/Network.meta

@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: ea4f41c146138cf4f80a355b1de52fcd
+folderAsset: yes
+DefaultImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 66 - 0
GameClient/Assets/Game/HotUpdate/Network/AChannel.cs

@@ -0,0 +1,66 @@
+using System;
+using System.IO;
+using System.Net;
+
+namespace ET
+{
+    public enum ChannelType
+    {
+        Connect,
+        Accept,
+    }
+
+    public struct Packet
+    {
+        public const int MinPacketSize = 2;
+        public const int OpcodeIndex = 8;
+        public const int KcpOpcodeIndex = 0;
+        public const int OpcodeLength = 2;
+        public const int ActorIdIndex = 0;
+        public const int ActorIdLength = 8;
+        public const int MessageIndex = 10;
+
+        public ushort Opcode;
+        public long ActorId;
+        public MemoryStream MemoryStream;
+    }
+    
+    public enum ServiceType
+    {
+        Outer,
+        Inner,
+    }
+
+    public abstract class AChannel: IDisposable
+    {
+        public long Id;
+		
+        public ChannelType ChannelType { get; protected set; }
+
+        public int Error { get; set; }
+		
+        private IPEndPoint remoteAddress;
+
+        public IPEndPoint RemoteAddress
+        {
+            get
+            {
+                return this.remoteAddress;
+            }
+            set
+            {
+                this.remoteAddress = value;
+            }
+        }
+
+        public bool IsDisposed
+        {
+            get
+            {
+                return this.Id == 0;	
+            }
+        }
+
+        public abstract void Dispose();
+    }
+}

+ 11 - 0
GameClient/Assets/Game/HotUpdate/Network/AChannel.cs.meta

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

+ 126 - 0
GameClient/Assets/Game/HotUpdate/Network/AService.cs

@@ -0,0 +1,126 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Net;
+
+namespace ET
+{
+    public abstract class AService : IDisposable
+    {
+        public Action<long, IPEndPoint> AcceptCallback;
+        public Action<long, MemoryStream> ReadCallback;
+        public Action<long, int> ErrorCallback;
+
+        public long Id { get; set; } = IdGenerater.Instance.GenerateId();
+
+        public ServiceType ServiceType { get; protected set; }
+
+        private const int MaxMemoryBufferSize = 1024;
+
+        private readonly Queue<MemoryStream> pool = new Queue<MemoryStream>();
+
+        public MemoryStream Fetch(int size = 0)
+        {
+            if (size > MaxMemoryBufferSize)
+            {
+                return new MemoryStream(size);
+            }
+
+            if (size < MaxMemoryBufferSize)
+            {
+                size = MaxMemoryBufferSize;
+            }
+
+            if (this.pool.Count == 0)
+            {
+                return new MemoryStream(size);
+            }
+
+            return pool.Dequeue();
+        }
+
+        public void OnError(long channelId, int e)
+        {
+            this.Remove(channelId);
+
+            this.ErrorCallback?.Invoke(channelId, e);
+        }
+
+        public void Recycle(MemoryStream memoryBuffer)
+        {
+            if (memoryBuffer.Capacity > 1024)
+            {
+                return;
+            }
+
+            if (this.pool.Count > 10) // 这里不需要太大,其实Kcp跟Tcp,这里1就足够了
+            {
+                return;
+            }
+
+            memoryBuffer.Seek(0, SeekOrigin.Begin);
+            memoryBuffer.SetLength(0);
+
+            this.pool.Enqueue(memoryBuffer);
+        }
+
+
+        // public virtual void Dispose()
+        // {
+        //     this.Id = 0;
+        // }
+
+        public abstract void Dispose();
+
+        public abstract void Update();
+
+        public abstract void Remove(long id, int error = 0);
+
+        public abstract bool IsDisposed();
+
+        // public bool IsDisposed()
+        // {
+        //     return this.Id == 0;
+        // }
+
+        public abstract void Create(long id, IPEndPoint ipEndPoint, string address);
+
+        //public abstract void Send(long channelId, MemoryStream memoryBuffer);
+        public abstract void Send(long channelId, long actorId, MemoryStream stream);
+
+        public virtual (uint, uint) GetChannelConn(long channelId)
+        {
+            throw new Exception($"default conn throw Exception! {channelId}");
+        }
+
+        public virtual void ChangeAddress(long channelId, IPEndPoint ipEndPoint)
+        {
+        }
+
+        // localConn放在低32bit
+        private long acceptIdGenerater = 1;
+
+        public long CreateAcceptChannelId(uint localConn)
+        {
+            return (++this.acceptIdGenerater << 32) | localConn;
+        }
+
+        // localConn放在低32bit
+        private long connectIdGenerater = int.MaxValue;
+
+        public long CreateConnectChannelId(uint localConn)
+        {
+            return (--this.connectIdGenerater << 32) | localConn;
+        }
+
+        public uint CreateRandomLocalConn()
+        {
+            return (1u << 30) | RandomHelper.RandUInt32();
+        }
+
+        protected void OnAccept(long channelId, IPEndPoint ipEndPoint)
+        {
+            this.AcceptCallback.Invoke(channelId, ipEndPoint);
+        }
+    }
+}

+ 11 - 0
GameClient/Assets/Game/HotUpdate/Network/AService.cs.meta

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

+ 306 - 0
GameClient/Assets/Game/HotUpdate/Network/Circularbuffer.cs

@@ -0,0 +1,306 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+
+namespace ET
+{
+    public class CircularBuffer: Stream
+    {
+        public int ChunkSize = 8192;
+
+        private readonly Queue<byte[]> bufferQueue = new Queue<byte[]>();
+
+        private readonly Queue<byte[]> bufferCache = new Queue<byte[]>();
+
+        public int LastIndex { get; set; }
+
+        public int FirstIndex { get; set; }
+		
+        private byte[] lastBuffer;
+
+	    public CircularBuffer()
+	    {
+		    this.AddLast();
+	    }
+
+        public override long Length
+        {
+            get
+            {
+                int c = 0;
+                if (this.bufferQueue.Count == 0)
+                {
+                    c = 0;
+                }
+                else
+                {
+                    c = (this.bufferQueue.Count - 1) * ChunkSize + this.LastIndex - this.FirstIndex;
+                }
+                if (c < 0)
+                {
+					Log.Error("CircularBuffer count < 0: {0}, {1}, {2}".Fmt(this.bufferQueue.Count, this.LastIndex, this.FirstIndex));
+                }
+                return c;
+            }
+        }
+
+        public void AddLast()
+        {
+            byte[] buffer;
+            if (this.bufferCache.Count > 0)
+            {
+                buffer = this.bufferCache.Dequeue();
+            }
+            else
+            {
+                buffer = new byte[ChunkSize];
+            }
+            this.bufferQueue.Enqueue(buffer);
+            this.lastBuffer = buffer;
+        }
+
+        public void RemoveFirst()
+        {
+            this.bufferCache.Enqueue(bufferQueue.Dequeue());
+        }
+
+        public byte[] First
+        {
+            get
+            {
+                if (this.bufferQueue.Count == 0)
+                {
+                    this.AddLast();
+                }
+                return this.bufferQueue.Peek();
+            }
+        }
+
+        public byte[] Last
+        {
+            get
+            {
+                if (this.bufferQueue.Count == 0)
+                {
+                    this.AddLast();
+                }
+                return this.lastBuffer;
+            }
+        }
+
+		/// <summary>
+		/// 从CircularBuffer读到stream中
+		/// </summary>
+		/// <param name="stream"></param>
+		/// <returns></returns>
+		//public async ETTask ReadAsync(Stream stream)
+	    //{
+		//    long buffLength = this.Length;
+		//	int sendSize = this.ChunkSize - this.FirstIndex;
+		//    if (sendSize > buffLength)
+		//    {
+		//	    sendSize = (int)buffLength;
+		//    }
+		//	
+		//    await stream.WriteAsync(this.First, this.FirstIndex, sendSize);
+		//    
+		//    this.FirstIndex += sendSize;
+		//    if (this.FirstIndex == this.ChunkSize)
+		//    {
+		//	    this.FirstIndex = 0;
+		//	    this.RemoveFirst();
+		//    }
+		//}
+
+	    // 从CircularBuffer读到stream
+	    public void Read(Stream stream, int count)
+	    {
+		    if (count > this.Length)
+		    {
+			    throw new Exception($"bufferList length < count, {Length} {count}");
+		    }
+
+		    int alreadyCopyCount = 0;
+		    while (alreadyCopyCount < count)
+		    {
+			    int n = count - alreadyCopyCount;
+			    if (ChunkSize - this.FirstIndex > n)
+			    {
+				    stream.Write(this.First, this.FirstIndex, n);
+				    this.FirstIndex += n;
+				    alreadyCopyCount += n;
+			    }
+			    else
+			    {
+				    stream.Write(this.First, this.FirstIndex, ChunkSize - this.FirstIndex);
+				    alreadyCopyCount += ChunkSize - this.FirstIndex;
+				    this.FirstIndex = 0;
+				    this.RemoveFirst();
+			    }
+		    }
+	    }
+	    
+	    // 从stream写入CircularBuffer
+	    public void Write(Stream stream)
+		{
+			int count = (int)(stream.Length - stream.Position);
+			
+			int alreadyCopyCount = 0;
+			while (alreadyCopyCount < count)
+			{
+				if (this.LastIndex == ChunkSize)
+				{
+					this.AddLast();
+					this.LastIndex = 0;
+				}
+
+				int n = count - alreadyCopyCount;
+				if (ChunkSize - this.LastIndex > n)
+				{
+					stream.Read(this.lastBuffer, this.LastIndex, n);
+					this.LastIndex += count - alreadyCopyCount;
+					alreadyCopyCount += n;
+				}
+				else
+				{
+					stream.Read(this.lastBuffer, this.LastIndex, ChunkSize - this.LastIndex);
+					alreadyCopyCount += ChunkSize - this.LastIndex;
+					this.LastIndex = ChunkSize;
+				}
+			}
+		}
+	    
+
+	    /// <summary>
+		///  从stream写入CircularBuffer
+		/// </summary>
+		/// <param name="stream"></param>
+		/// <returns></returns>
+		//public async ETTask<int> WriteAsync(Stream stream)
+	    //{
+		//    int size = this.ChunkSize - this.LastIndex;
+		//    
+		//    int n = await stream.ReadAsync(this.Last, this.LastIndex, size);
+//
+		//    if (n == 0)
+		//    {
+		//	    return 0;
+		//    }
+//
+		//    this.LastIndex += n;
+//
+		//    if (this.LastIndex == this.ChunkSize)
+		//    {
+		//	    this.AddLast();
+		//	    this.LastIndex = 0;
+		//    }
+//
+		//    return n;
+	    //}
+
+	    // 把CircularBuffer中数据写入buffer
+        public override int Read(byte[] buffer, int offset, int count)
+        {
+	        if (buffer.Length < offset + count)
+	        {
+		        throw new Exception($"bufferList length < coutn, buffer length: {buffer.Length} {offset} {count}");
+	        }
+
+	        long length = this.Length;
+			if (length < count)
+            {
+	            count = (int)length;
+            }
+
+            int alreadyCopyCount = 0;
+            while (alreadyCopyCount < count)
+            {
+                int n = count - alreadyCopyCount;
+				if (ChunkSize - this.FirstIndex > n)
+                {
+                    Array.Copy(this.First, this.FirstIndex, buffer, alreadyCopyCount + offset, n);
+                    this.FirstIndex += n;
+                    alreadyCopyCount += n;
+                }
+                else
+                {
+                    Array.Copy(this.First, this.FirstIndex, buffer, alreadyCopyCount + offset, ChunkSize - this.FirstIndex);
+                    alreadyCopyCount += ChunkSize - this.FirstIndex;
+                    this.FirstIndex = 0;
+                    this.RemoveFirst();
+                }
+            }
+
+	        return count;
+        }
+
+	    // 把buffer写入CircularBuffer中
+        public override void Write(byte[] buffer, int offset, int count)
+        {
+	        int alreadyCopyCount = 0;
+            while (alreadyCopyCount < count)
+            {
+                if (this.LastIndex == ChunkSize)
+                {
+                    this.AddLast();
+                    this.LastIndex = 0;
+                }
+
+                int n = count - alreadyCopyCount;
+                if (ChunkSize - this.LastIndex > n)
+                {
+                    Array.Copy(buffer, alreadyCopyCount + offset, this.lastBuffer, this.LastIndex, n);
+                    this.LastIndex += count - alreadyCopyCount;
+                    alreadyCopyCount += n;
+                }
+                else
+                {
+                    Array.Copy(buffer, alreadyCopyCount + offset, this.lastBuffer, this.LastIndex, ChunkSize - this.LastIndex);
+                    alreadyCopyCount += ChunkSize - this.LastIndex;
+                    this.LastIndex = ChunkSize;
+                }
+            }
+        }
+
+	    public override void Flush()
+	    {
+		    throw new NotImplementedException();
+		}
+
+	    public override long Seek(long offset, SeekOrigin origin)
+	    {
+			throw new NotImplementedException();
+	    }
+
+	    public override void SetLength(long value)
+	    {
+		    throw new NotImplementedException();
+		}
+
+	    public override bool CanRead
+	    {
+		    get
+		    {
+			    return true;
+		    }
+	    }
+
+	    public override bool CanSeek
+	    {
+		    get
+		    {
+			    return false;
+		    }
+	    }
+
+	    public override bool CanWrite
+	    {
+		    get
+		    {
+			    return true;
+		    }
+	    }
+
+	    public override long Position { get; set; }
+    }
+}

+ 11 - 0
GameClient/Assets/Game/HotUpdate/Network/Circularbuffer.cs.meta

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

+ 80 - 0
GameClient/Assets/Game/HotUpdate/Network/ErrorCore.cs

@@ -0,0 +1,80 @@
+namespace ET
+{
+    public static class ErrorCore
+    {
+        public const int ERR_MyErrorCode = 110000;
+        
+        public const int ERR_KcpConnectTimeout = 100205;
+        public const int ERR_PeerDisconnect = 100208;
+        public const int ERR_SocketCantSend = 100209;
+        public const int ERR_SocketError = 100210;
+        public const int ERR_KcpWaitSendSizeTooLarge = 100211;
+        public const int ERR_KcpCreateError = 100212;
+        public const int ERR_SendMessageNotFoundTChannel = 100213;
+        public const int ERR_TChannelRecvError = 100214;
+        public const int ERR_MessageSocketParserError = 100215;
+        public const int ERR_KcpNotFoundChannel = 100216;
+
+        public const int ERR_WebsocketSendError = 100217;
+        public const int ERR_WebsocketPeerReset = 100218;
+        public const int ERR_WebsocketMessageTooBig = 100219;
+        public const int ERR_WebsocketRecvError = 100220;
+        
+        public const int ERR_KcpReadNotSame = 100230;
+        public const int ERR_KcpSplitError = 100231;
+        public const int ERR_KcpSplitCountError = 100232;
+
+        public const int ERR_ActorLocationSenderTimeout = 110004;
+        public const int ERR_PacketParserError = 110005;
+        public const int ERR_KcpChannelAcceptTimeout = 110206;
+        public const int ERR_KcpRemoteDisconnect = 110207;
+        public const int ERR_WebsocketError = 110303;
+        public const int ERR_WebsocketConnectError = 110304;
+        public const int ERR_RpcFail = 110307;
+        public const int ERR_ReloadFail = 110308;
+        public const int ERR_ConnectGateKeyError = 110309;
+        public const int ERR_SessionSendOrRecvTimeout = 110311;
+        public const int ERR_OuterSessionRecvInnerMessage = 110312;
+        public const int ERR_NotFoundActor = 110313;
+        public const int ERR_ActorTimeout = 110315;
+        public const int ERR_UnverifiedSessionSendMessage = 110316;
+        public const int ERR_ActorLocationSenderTimeout2 = 110317;
+        public const int ERR_ActorLocationSenderTimeout3 = 110318;
+        public const int ERR_ActorLocationSenderTimeout4 = 110319;
+        public const int ERR_ActorLocationSenderTimeout5 = 110320;
+        
+        public const int ERR_KcpRouterTimeout = 110401;
+        public const int ERR_KcpRouterTooManyPackets = 110402;
+        public const int ERR_KcpRouterSame = 110402;
+        
+        // 110000 以上,避免跟SocketError冲突
+
+
+        //-----------------------------------
+
+        // 小于这个Rpc会抛异常,大于这个异常的error需要自己判断处理,也就是说需要处理的错误应该要大于该值
+        public const int ERR_Exception = 200000;
+
+        public const int ERR_Cancel = 200001;
+
+        public static bool IsRpcNeedThrowException(int error)
+        {
+            if (error == 0)
+            {
+                return false;
+            }
+            // ws平台返回错误专用的值
+            if (error == -1)
+            {
+                return false;
+            }
+
+            if (error > ERR_Exception)
+            {
+                return false;
+            }
+
+            return true;
+        }
+    }
+}

+ 11 - 0
GameClient/Assets/Game/HotUpdate/Network/ErrorCore.cs.meta

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

+ 8 - 0
GameClient/Assets/Game/HotUpdate/NetworkTCP.meta

@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: fef5522411c0ce34296cb376e7dddfdd
+folderAsset: yes
+DefaultImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 96 - 0
GameClient/Assets/Game/HotUpdate/NetworkTCP/PacketParser.cs

@@ -0,0 +1,96 @@
+using System;
+using System.IO;
+
+namespace ET
+{
+	public enum ParserState
+	{
+		PacketSize,
+		PacketBody
+	}
+
+	public class PacketParser
+	{
+		private readonly CircularBuffer buffer;
+		private int packetSize;
+		private ParserState state;
+		private readonly AService service;
+		private readonly byte[] cache = new byte[8];
+		public const int InnerPacketSizeLength = 4;
+		public const int OuterPacketSizeLength = 2;
+		public MemoryStream MemoryBuffer;
+
+		public PacketParser(CircularBuffer buffer, AService service)
+		{
+			this.buffer = buffer;
+			this.service = service;
+		}
+
+		public bool Parse(out MemoryStream memoryBuffer)
+		{
+			while (true)
+			{
+				switch (this.state)
+				{
+					case ParserState.PacketSize:
+					{
+						if (this.service.ServiceType == ServiceType.Inner)
+						{
+							if (this.buffer.Length < InnerPacketSizeLength)
+							{
+								memoryBuffer = null;
+								return false;
+							}
+
+							this.buffer.Read(this.cache, 0, InnerPacketSizeLength);
+
+							this.packetSize = BitConverter.ToInt32(this.cache, 0);
+							if (this.packetSize > ushort.MaxValue * 16 || this.packetSize < Packet.MinPacketSize)
+							{
+								throw new Exception($"recv packet size error, 可能是外网探测端口: {this.packetSize}");
+							}
+						}
+						else
+						{
+							if (this.buffer.Length < OuterPacketSizeLength)
+							{
+								memoryBuffer = null;
+								return false;
+							}
+
+							this.buffer.Read(this.cache, 0, OuterPacketSizeLength);
+
+							this.packetSize = BitConverter.ToUInt16(this.cache, 0);
+							if (this.packetSize < Packet.MinPacketSize)
+							{
+								throw new Exception($"recv packet size error, 可能是外网探测端口: {this.packetSize}");
+							}
+						}
+
+						this.state = ParserState.PacketBody;
+						break;
+					}
+					case ParserState.PacketBody:
+					{
+						if (this.buffer.Length < this.packetSize)
+						{
+							memoryBuffer = null;
+							return false;
+						}
+
+						memoryBuffer = this.service.Fetch(this.packetSize);
+						this.buffer.Read(memoryBuffer, this.packetSize);
+						//memoryStream.SetLength(this.packetSize - Packet.MessageIndex);
+
+						memoryBuffer.Seek(0, SeekOrigin.Begin);
+
+						this.state = ParserState.PacketSize;
+						return true;
+					}
+					default:
+						throw new ArgumentOutOfRangeException();
+				}
+			}
+		}
+	}
+}

+ 11 - 0
GameClient/Assets/Game/HotUpdate/NetworkTCP/PacketParser.cs.meta

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

+ 423 - 0
GameClient/Assets/Game/HotUpdate/NetworkTCP/TChannel.cs

@@ -0,0 +1,423 @@
+using System;
+using System.IO;
+using System.Net;
+using System.Net.Sockets;
+
+namespace ET
+{
+    /// <summary>
+    /// 封装Socket,将回调push到主线程处理
+    /// </summary>
+    public sealed class TChannel : AChannel
+    {
+        private readonly TService Service;
+        private Socket socket;
+        private SocketAsyncEventArgs innArgs = new SocketAsyncEventArgs();
+        private SocketAsyncEventArgs outArgs = new SocketAsyncEventArgs();
+
+        private readonly CircularBuffer recvBuffer = new CircularBuffer();
+        private readonly CircularBuffer sendBuffer = new CircularBuffer();
+
+        private bool isSending;
+
+        private bool isConnected;
+
+        private readonly PacketParser parser;
+
+        private readonly byte[] sendCache = new byte[Packet.OpcodeLength + Packet.ActorIdLength];
+
+        private void OnComplete(object sender, SocketAsyncEventArgs e)
+        {
+            switch (e.LastOperation)
+            {
+                case SocketAsyncOperation.Connect:
+                    ThreadSynchronizationContext.Instance.Post(() => OnConnectComplete(e));
+                    break;
+                case SocketAsyncOperation.Receive:
+                    ThreadSynchronizationContext.Instance.Post(() => OnRecvComplete(e));
+                    break;
+                case SocketAsyncOperation.Send:
+                    ThreadSynchronizationContext.Instance.Post(() => OnSendComplete(e));
+                    break;
+                case SocketAsyncOperation.Disconnect:
+                    ThreadSynchronizationContext.Instance.Post(() => OnDisconnectComplete(e));
+                    break;
+                default:
+                    throw new Exception($"socket error: {e.LastOperation}");
+            }
+        }
+
+        #region 网络线程
+
+        public TChannel(long id, IPEndPoint ipEndPoint, TService service)
+        {
+            this.ChannelType = ChannelType.Connect;
+            this.Id = id;
+            this.Service = service;
+            this.socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
+            this.socket.NoDelay = true;
+            this.parser = new PacketParser(this.recvBuffer, this.Service);
+            this.innArgs.Completed += this.OnComplete;
+            this.outArgs.Completed += this.OnComplete;
+
+            this.RemoteAddress = ipEndPoint;
+            this.isConnected = false;
+            this.isSending = false;
+
+            ThreadSynchronizationContext.Instance.PostNext(this.ConnectAsync);
+        }
+
+        public TChannel(long id, Socket socket, TService service)
+        {
+            this.ChannelType = ChannelType.Accept;
+            this.Id = id;
+            this.Service = service;
+            this.socket = socket;
+            this.socket.NoDelay = true;
+            this.parser = new PacketParser(this.recvBuffer, this.Service);
+            this.innArgs.Completed += this.OnComplete;
+            this.outArgs.Completed += this.OnComplete;
+
+            this.RemoteAddress = (IPEndPoint)socket.RemoteEndPoint;
+            this.isConnected = true;
+            this.isSending = false;
+
+            // 下一帧再开始读写
+            ThreadSynchronizationContext.Instance.PostNext(() =>
+            {
+                this.StartRecv();
+                this.StartSend();
+            });
+        }
+
+
+        public override void Dispose()
+        {
+            if (this.IsDisposed)
+            {
+                return;
+            }
+
+            Log.Info($"channel dispose: {this.Id} {this.RemoteAddress}");
+
+            long id = this.Id;
+            this.Id = 0;
+            this.Service.Remove(id);
+            this.socket.Close();
+            this.innArgs.Dispose();
+            this.outArgs.Dispose();
+            this.innArgs = null;
+            this.outArgs = null;
+            this.socket = null;
+        }
+
+        public void Send(long actorId, MemoryStream stream)
+        {
+            if (this.IsDisposed)
+            {
+                throw new Exception("TChannel已经被Dispose, 不能发送消息");
+            }
+
+            switch (this.Service.ServiceType)
+            {
+                case ServiceType.Inner:
+                {
+                    int messageSize = (int)(stream.Length - stream.Position);
+                    if (messageSize > ushort.MaxValue * 16)
+                    {
+                        throw new Exception($"send packet too large: {stream.Length} {stream.Position}");
+                    }
+
+                    this.sendCache.WriteTo(0, messageSize);
+                    this.sendBuffer.Write(this.sendCache, 0, PacketParser.InnerPacketSizeLength);
+
+                    // actorId
+                    stream.GetBuffer().WriteTo(0, actorId);
+                    this.sendBuffer.Write(stream.GetBuffer(), (int)stream.Position,
+                        (int)(stream.Length - stream.Position));
+                    break;
+                }
+                case ServiceType.Outer:
+                {
+                    stream.Seek(Packet.ActorIdLength, SeekOrigin.Begin); // 外网不需要actorId
+                    ushort messageSize = (ushort)(stream.Length - stream.Position);
+
+                    this.sendCache.WriteTo(0, messageSize);
+                    this.sendBuffer.Write(this.sendCache, 0, PacketParser.OuterPacketSizeLength);
+
+                    this.sendBuffer.Write(stream.GetBuffer(), (int)stream.Position,
+                        (int)(stream.Length - stream.Position));
+                    break;
+                }
+            }
+
+
+            if (!this.isSending)
+            {
+                //this.StartSend();
+                this.Service.NeedStartSend.Add(this.Id);
+            }
+        }
+
+        private void ConnectAsync()
+        {
+            this.outArgs.RemoteEndPoint = this.RemoteAddress;
+            if (this.socket.ConnectAsync(this.outArgs))
+            {
+                return;
+            }
+
+            OnConnectComplete(this.outArgs);
+        }
+
+        private void OnConnectComplete(object o)
+        {
+            if (this.socket == null)
+            {
+                return;
+            }
+
+            SocketAsyncEventArgs e = (SocketAsyncEventArgs)o;
+
+            if (e.SocketError != SocketError.Success)
+            {
+                this.OnError((int)e.SocketError);
+                return;
+            }
+
+            e.RemoteEndPoint = null;
+            this.isConnected = true;
+            this.StartRecv();
+            this.StartSend();
+        }
+
+        private void OnDisconnectComplete(object o)
+        {
+            SocketAsyncEventArgs e = (SocketAsyncEventArgs)o;
+            this.OnError((int)e.SocketError);
+        }
+
+        private void StartRecv()
+        {
+            while (true)
+            {
+                try
+                {
+                    if (this.socket == null)
+                    {
+                        return;
+                    }
+
+                    int size = this.recvBuffer.ChunkSize - this.recvBuffer.LastIndex;
+                    this.innArgs.SetBuffer(this.recvBuffer.Last, this.recvBuffer.LastIndex, size);
+                }
+                catch (Exception e)
+                {
+                    Log.Error($"tchannel error: {this.Id}\n{e}");
+                    this.OnError(ErrorCore.ERR_TChannelRecvError);
+                    return;
+                }
+
+                if (this.socket.ReceiveAsync(this.innArgs))
+                {
+                    return;
+                }
+
+                this.HandleRecv(this.innArgs);
+            }
+        }
+
+        private void OnRecvComplete(object o)
+        {
+            this.HandleRecv(o);
+
+            if (this.socket == null)
+            {
+                return;
+            }
+
+            this.StartRecv();
+        }
+
+        private void HandleRecv(object o)
+        {
+            if (this.socket == null)
+            {
+                return;
+            }
+
+            SocketAsyncEventArgs e = (SocketAsyncEventArgs)o;
+
+            if (e.SocketError != SocketError.Success)
+            {
+                this.OnError((int)e.SocketError);
+                return;
+            }
+
+            if (e.BytesTransferred == 0)
+            {
+                this.OnError(ErrorCore.ERR_PeerDisconnect);
+                return;
+            }
+
+            this.recvBuffer.LastIndex += e.BytesTransferred;
+            if (this.recvBuffer.LastIndex == this.recvBuffer.ChunkSize)
+            {
+                this.recvBuffer.AddLast();
+                this.recvBuffer.LastIndex = 0;
+            }
+
+            // 收到消息回调
+            while (true)
+            {
+                // 这里循环解析消息执行,有可能,执行消息的过程中断开了session
+                if (this.socket == null)
+                {
+                    return;
+                }
+
+                try
+                {
+                    bool ret = this.parser.Parse(out MemoryStream memoryBuffer);
+                    if (!ret)
+                    {
+                        break;
+                    }
+
+                    this.OnRead(memoryBuffer);
+                }
+                catch (Exception ee)
+                {
+                    Log.DetectionError($"ip: {this.RemoteAddress} {ee}");
+                    this.OnError(ErrorCore.ERR_SocketError);
+                    return;
+                }
+            }
+        }
+
+        public void Update()
+        {
+            this.StartSend();
+        }
+
+        private void StartSend()
+        {
+            if (!this.isConnected)
+            {
+                return;
+            }
+
+            if (this.isSending)
+            {
+                return;
+            }
+
+            while (true)
+            {
+                try
+                {
+                    if (this.socket == null)
+                    {
+                        this.isSending = false;
+                        return;
+                    }
+
+                    // 没有数据需要发送
+                    if (this.sendBuffer.Length == 0)
+                    {
+                        this.isSending = false;
+                        return;
+                    }
+
+                    this.isSending = true;
+
+                    int sendSize = this.sendBuffer.ChunkSize - this.sendBuffer.FirstIndex;
+                    if (sendSize > this.sendBuffer.Length)
+                    {
+                        sendSize = (int)this.sendBuffer.Length;
+                    }
+
+                    this.outArgs.SetBuffer(this.sendBuffer.First, this.sendBuffer.FirstIndex, sendSize);
+
+                    if (this.socket.SendAsync(this.outArgs))
+                    {
+                        return;
+                    }
+
+                    HandleSend(this.outArgs);
+                }
+                catch (Exception e)
+                {
+                    throw new Exception(
+                        $"socket set buffer error: {this.sendBuffer.First.Length}, {this.sendBuffer.FirstIndex}", e);
+                }
+            }
+        }
+
+        private void OnSendComplete(object o)
+        {
+            HandleSend(o);
+
+            this.isSending = false;
+
+            this.StartSend();
+        }
+
+        private void HandleSend(object o)
+        {
+            if (this.socket == null)
+            {
+                return;
+            }
+
+            SocketAsyncEventArgs e = (SocketAsyncEventArgs)o;
+
+            if (e.SocketError != SocketError.Success)
+            {
+                this.OnError((int)e.SocketError);
+                return;
+            }
+
+            if (e.BytesTransferred == 0)
+            {
+                this.OnError(ErrorCore.ERR_PeerDisconnect);
+                return;
+            }
+
+            this.sendBuffer.FirstIndex += e.BytesTransferred;
+            if (this.sendBuffer.FirstIndex == this.sendBuffer.ChunkSize)
+            {
+                this.sendBuffer.FirstIndex = 0;
+                this.sendBuffer.RemoveFirst();
+            }
+        }
+
+        private void OnRead(MemoryStream memoryStream)
+        {
+            try
+            {
+                long channelId = this.Id;
+                this.Service.ReadCallback(channelId, memoryStream);
+            }
+            catch (Exception e)
+            {
+                Log.DetectionError($"{this.RemoteAddress} {memoryStream.Length} {e}");
+                // 出现任何消息解析异常都要断开Session,防止客户端伪造消息
+                this.OnError(ErrorCore.ERR_PacketParserError);
+            }
+        }
+
+        private void OnError(int error)
+        {
+            Log.Info($"TChannel OnError: {error} {this.RemoteAddress}");
+
+            long channelId = this.Id;
+
+            this.Service.Remove(channelId);
+
+            this.Service.ErrorCallback(channelId, error);
+        }
+
+        #endregion
+    }
+}

+ 11 - 0
GameClient/Assets/Game/HotUpdate/NetworkTCP/TChannel.cs.meta

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

+ 192 - 0
GameClient/Assets/Game/HotUpdate/NetworkTCP/TService.cs

@@ -0,0 +1,192 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Net;
+using System.Net.Sockets;
+
+namespace ET
+{
+    public sealed class TService : AService
+    {
+        private readonly Dictionary<long, TChannel> idChannels = new Dictionary<long, TChannel>();
+
+        private readonly SocketAsyncEventArgs innArgs = new SocketAsyncEventArgs();
+
+        private Socket acceptor;
+
+        public HashSet<long> NeedStartSend = new HashSet<long>();
+
+        // public TService(ThreadSynchronizationContext threadSynchronizationContext, ServiceType serviceType)
+        // {
+        // 	this.ServiceType = serviceType;
+        // 	this.ThreadSynchronizationContext = threadSynchronizationContext;
+        // }
+
+        public TService(IPEndPoint ipEndPoint, ServiceType serviceType)
+        {
+            this.ServiceType = serviceType;
+
+            this.acceptor = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
+            this.acceptor.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
+            this.innArgs.Completed += this.OnComplete;
+            this.acceptor.Bind(ipEndPoint);
+            this.acceptor.Listen(1000);
+
+            ThreadSynchronizationContext.Instance.PostNext(this.AcceptAsync);
+        }
+
+        private void OnComplete(object sender, SocketAsyncEventArgs e)
+        {
+            switch (e.LastOperation)
+            {
+                case SocketAsyncOperation.Accept:
+                    SocketError socketError = e.SocketError;
+                    Socket acceptSocket = e.AcceptSocket;
+                    ThreadSynchronizationContext.Instance.Post(() =>
+                    {
+                        this.OnAcceptComplete(socketError, acceptSocket);
+                    });
+                    break;
+                default:
+                    throw new Exception($"socket error: {e.LastOperation}");
+            }
+        }
+
+        #region 网络线程
+
+        private void OnAcceptComplete(SocketError socketError, Socket acceptSocket)
+        {
+            if (this.acceptor == null)
+            {
+                return;
+            }
+
+            if (socketError != SocketError.Success)
+            {
+                Log.Error($"accept error {socketError}");
+                return;
+            }
+
+            try
+            {
+                long id = this.CreateAcceptChannelId(0);
+                TChannel channel = new TChannel(id, acceptSocket, this);
+                this.idChannels.Add(channel.Id, channel);
+                long channelId = channel.Id;
+
+                this.OnAccept(channelId, channel.RemoteAddress);
+            }
+            catch (Exception exception)
+            {
+                Log.Error(exception);
+            }
+
+            // 开始新的accept
+            this.AcceptAsync();
+        }
+
+
+        private void AcceptAsync()
+        {
+            this.innArgs.AcceptSocket = null;
+            if (this.acceptor.AcceptAsync(this.innArgs))
+            {
+                return;
+            }
+
+            OnAcceptComplete(this.innArgs.SocketError, this.innArgs.AcceptSocket);
+        }
+
+        public override void Create(long id, IPEndPoint ipEndPoint, string address)
+        {
+            if (this.idChannels.TryGetValue(id, out TChannel _))
+            {
+                return;
+            }
+
+            TChannel channel = new TChannel(id, ipEndPoint, this);
+            this.idChannels.Add(channel.Id, channel);
+        }
+
+        // protected override void Get(long id, IPEndPoint address)
+        // {
+        // 	if (this.idChannels.TryGetValue(id, out TChannel _))
+        // 	{
+        // 		return;
+        // 	}
+        // 	this.Create(address, id);
+        // }
+
+        private TChannel Get(long id)
+        {
+            TChannel channel = null;
+            this.idChannels.TryGetValue(id, out channel);
+            return channel;
+        }
+
+        public override void Dispose()
+        {
+            this.acceptor?.Close();
+            this.acceptor = null;
+            this.innArgs.Dispose();
+            // ThreadSynchronizationContext = null;
+
+            foreach (long id in this.idChannels.Keys.ToArray())
+            {
+                TChannel channel = this.idChannels[id];
+                channel.Dispose();
+            }
+
+            this.idChannels.Clear();
+        }
+
+        public override void Remove(long id, int error = 0)
+        {
+            if (this.idChannels.TryGetValue(id, out TChannel channel))
+            {
+                channel.Error = error;
+                channel.Dispose();
+            }
+
+            this.idChannels.Remove(id);
+        }
+
+        public override void Send(long channelId, long actorId, MemoryStream stream)
+        {
+            try
+            {
+                TChannel aChannel = this.Get(channelId);
+                if (aChannel == null)
+                {
+                    this.OnError(channelId, ErrorCore.ERR_SendMessageNotFoundTChannel);
+                    return;
+                }
+
+                aChannel.Send(actorId, stream);
+            }
+            catch (Exception e)
+            {
+                Log.Error(e);
+            }
+        }
+
+        public override void Update()
+        {
+            foreach (long channelId in this.NeedStartSend)
+            {
+                TChannel tChannel = this.Get(channelId);
+                tChannel?.Update();
+            }
+
+            this.NeedStartSend.Clear();
+        }
+
+        public override bool IsDisposed()
+        {
+            return this.acceptor == null;
+        }
+
+        #endregion
+    }
+}

+ 11 - 0
GameClient/Assets/Game/HotUpdate/NetworkTCP/TService.cs.meta

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

+ 245 - 0
GameClient/Assets/Game/HotUpdate/NetworkTCP/WChannel.cs

@@ -0,0 +1,245 @@
+#if !UNITY_WEBGL
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Net;
+using System.Net.WebSockets;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace ET
+{
+    public class WChannel: AChannel
+    {
+        private readonly WService Service;
+
+        private readonly WebSocket webSocket;
+
+        private readonly Queue<MemoryStream> queue = new Queue<MemoryStream>();
+
+        private bool isSending;
+
+        private bool isConnected;
+
+        private readonly MemoryStream recvStream;
+
+        private CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
+
+        public WChannel(long id, WebSocket webSocket, WService service, IPEndPoint remoteAddress = null)
+        {
+            this.Id = id;
+            this.Service = service;
+            this.ChannelType = ChannelType.Accept;
+            this.webSocket = webSocket;
+            this.recvStream = new MemoryStream(ushort.MaxValue);
+            this.RemoteAddress = remoteAddress;
+
+            isConnected = true;
+            
+            this.Service.ThreadSynchronizationContext.PostNext(()=>
+            {
+                this.StartRecv().Coroutine();
+                this.StartSend().Coroutine();
+            });
+        }
+
+        public WChannel(long id, WebSocket webSocket, string connectUrl, WService service)
+        {
+            this.Id = id;
+            this.Service = service;
+            this.ChannelType = ChannelType.Connect;
+            this.webSocket = webSocket;
+            this.recvStream = new MemoryStream(ushort.MaxValue);
+
+            isConnected = false;
+
+            this.Service.ThreadSynchronizationContext.Post(() => this.ConnectAsync(connectUrl).Coroutine());
+        }
+
+        public override void Dispose()
+        {
+            if (this.IsDisposed)
+            {
+                return;
+            }
+
+            this.Id = 0;
+            this.cancellationTokenSource.Cancel();
+            this.cancellationTokenSource.Dispose();
+            this.webSocket.Dispose();
+        }
+
+        public async ETTask ConnectAsync(string url)
+        {
+            try
+            {
+                await ((ClientWebSocket) this.webSocket).ConnectAsync(new Uri(url), cancellationTokenSource.Token);
+                isConnected = true;
+                
+                this.StartRecv().Coroutine();
+                this.StartSend().Coroutine();
+            }
+            catch (Exception e)
+            {
+                Log.Error(e);
+                this.OnError(ErrorCore.ERR_WebsocketConnectError);
+            }
+        }
+
+        public void Send(MemoryStream stream)
+        {
+            switch (this.Service.ServiceType)
+            {
+                case ServiceType.Inner:
+                    break;
+                case ServiceType.Outer:
+                    stream.Seek(Packet.ActorIdLength, SeekOrigin.Begin);
+                    break;
+            }
+
+            this.queue.Enqueue(stream);
+
+            if (this.isConnected)
+            {
+                this.StartSend().Coroutine();
+            }
+        }
+
+        public async ETTask StartSend()
+        {
+            if (this.IsDisposed)
+            {
+                return;
+            }
+
+            try
+            {
+                if (this.isSending)
+                {
+                    return;
+                }
+
+                this.isSending = true;
+
+                while (true)
+                {
+                    if (this.queue.Count == 0)
+                    {
+                        this.isSending = false;
+                        return;
+                    }
+
+                    MemoryStream bytes = this.queue.Dequeue();
+                    try
+                    {
+                        await this.webSocket.SendAsync(new ReadOnlyMemory<byte>(bytes.GetBuffer(), (int)bytes.Position, (int)(bytes.Length - bytes.Position)), WebSocketMessageType.Binary, true, cancellationTokenSource.Token);
+                        if (this.IsDisposed)
+                        {
+                            return;
+                        }
+                    }
+                    catch (Exception e)
+                    {
+                        Log.Error(e);
+                        this.OnError(ErrorCore.ERR_WebsocketSendError);
+                        return;
+                    }
+                }
+            }
+            catch (Exception e)
+            {
+                Log.Error(e);
+            }
+        }
+
+        private byte[] cache = new byte[ushort.MaxValue];
+
+        public async ETTask StartRecv()
+        {
+            if (this.IsDisposed)
+            {
+                return;
+            }
+
+            try
+            {
+                while (true)
+                {
+                    ValueWebSocketReceiveResult receiveResult;
+                    int receiveCount = 0;
+                    do
+                    {
+                        receiveResult = await this.webSocket.ReceiveAsync(
+                            new Memory<byte>(cache, receiveCount, this.cache.Length - receiveCount),
+                            cancellationTokenSource.Token);
+                        if (this.IsDisposed)
+                        {
+                            return;
+                        }
+
+                        receiveCount += receiveResult.Count;
+                    }
+                    while (!receiveResult.EndOfMessage);
+
+                    if (receiveResult.MessageType == WebSocketMessageType.Close)
+                    {
+                        this.OnError(ErrorCore.ERR_WebsocketPeerReset);
+                        return;
+                    }
+
+                    if (receiveResult.Count > ushort.MaxValue)
+                    {
+                        await this.webSocket.CloseAsync(WebSocketCloseStatus.MessageTooBig, $"message too big: {receiveCount}",
+                            cancellationTokenSource.Token);
+                        this.OnError(ErrorCore.ERR_WebsocketMessageTooBig);
+                        return;
+                    }
+                    
+                    this.recvStream.SetLength(receiveCount);
+                    this.recvStream.Seek(2, SeekOrigin.Begin);
+                    Array.Copy(this.cache, 0, this.recvStream.GetBuffer(), 0, receiveCount);
+                    this.OnRead(this.recvStream);
+                }
+            }
+            catch (WebSocketException)
+            {
+                this.OnError(ErrorCore.ERR_WebsocketRecvError);
+            }
+            catch (TaskCanceledException)
+            {
+                this.OnError(ErrorCore.ERR_WebsocketTaskCanceledError);
+            }
+            catch (Exception e)
+            {
+                Log.Error(e);
+                this.OnError(ErrorCore.ERR_WebsocketOtherError);
+            }
+        }
+        
+        private void OnRead(MemoryStream memoryStream)
+        {
+            try
+            {
+                long channelId = this.Id;
+                this.Service.OnRead(channelId, memoryStream);
+            }
+            catch (Exception e)
+            {
+                Log.Error($"{this.RemoteAddress} {memoryStream.Length} {e}");
+                // 出现任何消息解析异常都要断开Session,防止客户端伪造消息
+                this.OnError(ErrorCore.ERR_PacketParserError);
+            }
+        }
+        
+        private void OnError(int error)
+        {
+            Log.Debug($"WChannel error: {error} {this.RemoteAddress}");
+			
+            long channelId = this.Id;
+			
+            this.Service.OnError(channelId, error);
+        }
+    }
+}
+
+#endif

+ 3 - 0
GameClient/Assets/Game/HotUpdate/NetworkTCP/WChannel.cs.meta

@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 0d1c9403500b422f88faf632888bb96b
+timeCreated: 1748937018

+ 176 - 0
GameClient/Assets/Game/HotUpdate/NetworkTCP/WChannel_WebGL.cs

@@ -0,0 +1,176 @@
+#if UNITY_WEBGL
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Net;
+using System.Runtime.InteropServices.ComTypes;
+using BestHTTP.WebSocket;
+using GFGGame;
+
+namespace ET
+{
+    public class WChannel : AChannel
+    {
+        private readonly WService Service;
+
+        private WebSocket webSocket;
+
+        private Queue<MemoryStream> waitSend = new Queue<MemoryStream>();
+
+        //address=http://webgltest.goufuguiwxw.com/ws
+        public WChannel(long id, IPEndPoint ipEndPoint, WService service, string address)
+        {
+            this.Service = service;
+            this.ChannelType = ChannelType.Connect;
+            this.Id = id;
+
+            string wsStr = $"ws://{ipEndPoint}";
+
+            if (LauncherConfig.isHttps)
+            {
+                Uri uri = new Uri(address);
+                string hostAndPath = uri.Host + uri.AbsolutePath;
+                wsStr = $"wss://{hostAndPath}";
+            }
+            
+            WebSocket ws = new WebSocket(new Uri(wsStr));
+
+            if (LauncherConfig.isHttps)
+            {
+                this.RemoteAddress = new IPEndPoint(IPAddress.Parse("0.0.0.0"), 0);
+            }
+            else
+            {
+                this.RemoteAddress = ipEndPoint;
+            }
+            
+            // Subscribe to the WS events
+            ws.OnOpen += OnOpen;
+            ws.OnClosed += OnClosed;
+            ws.OnError += OnError;
+            ws.OnBinary += OnRead;
+
+            // Start connecting to the server
+            ws.Open();
+        }
+
+        public override void Dispose()
+        {
+            if (this.IsDisposed)
+            {
+                return;
+            }
+
+            this.Id = 0;
+
+            this.webSocket?.Close();
+            this.webSocket = null;
+        }
+
+        public void Send(MemoryStream memoryBuffer)
+        {
+            switch (this.Service.ServiceType)
+            {
+                case ServiceType.Inner:
+                    break;
+                case ServiceType.Outer:
+                    memoryBuffer.Seek(Packet.ActorIdLength, SeekOrigin.Begin);
+                    ;
+                    break;
+            }
+
+            if (this.webSocket == null)
+            {
+                this.waitSend.Enqueue(memoryBuffer);
+                return;
+            }
+
+            SendOne(memoryBuffer);
+        }
+
+        private void SendOne(MemoryStream memoryBuffer)
+        {
+            this.webSocket.Send(memoryBuffer.GetBuffer(), (ulong)memoryBuffer.Position,
+                (ulong)(memoryBuffer.Length - memoryBuffer.Position));
+        }
+
+        private void OnOpen(WebSocket ws)
+        {
+            if (ws == null)
+            {
+                this.OnError(ErrorCore.ERR_WebsocketConnectError);
+                return;
+            }
+
+            if (this.IsDisposed)
+            {
+                return;
+            }
+
+            this.webSocket = ws;
+
+            while (this.waitSend.Count > 0)
+            {
+                MemoryStream memoryBuffer = this.waitSend.Dequeue();
+                this.SendOne(memoryBuffer);
+            }
+        }
+
+        /// <summary>
+        /// Called when we received a text message from the server
+        /// </summary>
+        private void OnRead(WebSocket ws, byte[] data)
+        {
+            if (this.IsDisposed)
+            {
+                return;
+            }
+
+            MemoryStream memoryBuffer = this.Service.Fetch();
+            memoryBuffer.Write(data, 0, data.Length); // 写入整个 data
+            memoryBuffer.Seek(2, SeekOrigin.Begin);
+            this.Service.ReadCallback(this.Id, memoryBuffer);
+        }
+
+        /// <summary>
+        /// Called when the web socket closed
+        /// </summary>
+        private void OnClosed(WebSocket ws, UInt16 code, string message)
+        {
+            if (this.IsDisposed)
+            {
+                return;
+            }
+
+            Log.Error($"wchannel closed: {code} {message}");
+            this.OnError(0);
+        }
+
+        /// <summary>
+        /// Called when an error occured on client side
+        /// </summary>
+        private void OnError(WebSocket ws, string error)
+        {
+            if (this.IsDisposed)
+            {
+                return;
+            }
+
+            //Log.Error($"WChannel error: {this.Id} {ws.GetHashCode()} {error}");
+
+            this.OnError(ErrorCore.ERR_WebsocketError);
+        }
+
+        private void OnError(int error)
+        {
+            Log.Info($"WChannel error: {this.Id} {error}");
+
+            long channelId = this.Id;
+
+            this.Service.Remove(channelId);
+
+            this.Service.ErrorCallback(channelId, error);
+        }
+    }
+}
+#endif

+ 11 - 0
GameClient/Assets/Game/HotUpdate/NetworkTCP/WChannel_WebGL.cs.meta

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

+ 164 - 0
GameClient/Assets/Game/HotUpdate/NetworkTCP/WService.cs

@@ -0,0 +1,164 @@
+#if !UNITY_WEBGL
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Net;
+using System.Net.WebSockets;
+
+namespace ET
+{
+    public class WService: AService
+    {
+        private long idGenerater = 200000000;
+        
+        private HttpListener httpListener;
+        
+        private readonly Dictionary<long, WChannel> channels = new Dictionary<long, WChannel>();
+
+        public WService(ThreadSynchronizationContext threadSynchronizationContext, IEnumerable<string> prefixs)
+        {
+            this.ThreadSynchronizationContext = threadSynchronizationContext;
+            
+            this.httpListener = new HttpListener();
+
+            StartAccept(prefixs).Coroutine();
+        }
+        
+        public WService(ThreadSynchronizationContext threadSynchronizationContext)
+        {
+            this.ThreadSynchronizationContext = threadSynchronizationContext;
+        }
+        
+        private long GetId
+        {
+            get
+            {
+                return ++this.idGenerater;
+            }
+        }
+        
+        public WChannel Create(string address, long id)
+        {
+			ClientWebSocket webSocket = new ClientWebSocket();
+            WChannel channel = new WChannel(id, webSocket, address, this);
+            this.channels[channel.Id] = channel;
+            return channel;
+        }
+
+        public override void Remove(long id)
+        {
+            WChannel channel;
+            if (!this.channels.TryGetValue(id, out channel))
+            {
+                return;
+            }
+
+            this.channels.Remove(id);
+            channel.Dispose();
+        }
+
+        public override bool IsDispose()
+        {
+            return this.httpListener == null;
+        }
+
+        protected void Get(long id, string address)
+        {
+            if (!this.channels.TryGetValue(id, out _))
+            {
+                this.Create(address, id);
+            }
+        }
+
+        public override void Dispose()
+        {
+            this.ThreadSynchronizationContext = null;
+            this.httpListener?.Close();
+            this.httpListener = null;
+        }
+
+        private async ETTask StartAccept(IEnumerable<string> prefixs)
+        {
+            try
+            {
+                foreach (string prefix in prefixs)
+                {
+                    this.httpListener.Prefixes.Add(prefix);
+                    Log.Console($"start {prefix}");
+                }
+
+                httpListener.Start();
+
+                while (true)
+                {
+                    var context = await this.httpListener.GetContextAsync();
+                    Log.Info($"Accept WebSocket {context.Request.RemoteEndPoint}");
+                    //跨域设置
+                    {
+                        context.Response.AddHeader("Access-Control-Allow-Credentials", "true");
+                        context.Response.AddHeader("Access-Control-Allow-Headers", "content-type, Signature, Timestamp, Token");
+                        context.Response.AddHeader("Access-Control-Allow-Methods", "POST");
+                        context.Response.AddHeader("Access-Control-Allow-Origin", "*");
+                        if (context.Request.HttpMethod == "OPTIONS")
+                        {
+                            context.Response.StatusCode = 204;
+                        }
+                    }
+                    if (context.Request.IsWebSocketRequest)
+                    {
+                        var webSocket = await context.AcceptWebSocketAsync(null);
+                        WChannel channel = new WChannel(this.GetId, webSocket.WebSocket, this, context.Request.RemoteEndPoint);
+                        this.channels[channel.Id] = channel;
+                        this.OnAccept(channel.Id, channel.RemoteAddress);
+                    }
+                    else
+                    {
+                        context.Response.StatusCode = 400;
+                        context.Response.Close();
+                    }
+                }
+            }
+            catch (HttpListenerException e)
+            {
+                if (e.ErrorCode == 5)
+                {
+                    throw new Exception($"CMD管理员中输入: netsh http add urlacl url=http://{string.Join(",", prefixs)}/ user=Everyone", e);
+                }
+
+                Log.Error(e);
+            }
+            catch (Exception e)
+            {
+                Log.Error(e);
+            }
+        }
+        
+        protected override void Get(long id, IPEndPoint address)
+        {
+           // 将IPEndPoint转换为WebSocket适用的地址格式(ws://或wss://)
+            string webSocketAddress = $"ws://{address.Address}:{address.Port}";
+
+            // 检查是否已存在该通道
+            if (!this.channels.TryGetValue(id, out _))
+            {
+                // 不存在则创建新的WebSocket连接
+                this.Create(webSocketAddress, id);
+            }        }
+
+        protected override void Send(long channelId, long actorId, MemoryStream stream)
+        {
+            this.channels.TryGetValue(channelId, out WChannel channel);
+            if (channel == null)
+            {
+                return;
+            }
+            channel.Send(stream);
+        }
+
+        public override void Update()
+        {
+        }
+    }
+}
+
+#endif

+ 3 - 0
GameClient/Assets/Game/HotUpdate/NetworkTCP/WService.cs.meta

@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 3110972deeec4f3c97be370e352c607b
+timeCreated: 1748937035

+ 105 - 0
GameClient/Assets/Game/HotUpdate/NetworkTCP/WService_WebGL.cs

@@ -0,0 +1,105 @@
+#if UNITY_WEBGL
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Net;
+
+namespace ET
+{
+    public class WService : AService
+    {
+        private long idGenerater = 200000000;
+
+        private readonly Dictionary<long, WChannel> channels = new Dictionary<long, WChannel>();
+
+        public override void Dispose()
+        {
+            if (this.IsDisposed())
+            {
+                return;
+            }
+
+            this.Id = 0;
+
+            foreach (var kv in this.channels.ToArray())
+            {
+                kv.Value.Dispose();
+            }
+        }
+
+        public WService(IEnumerable<string> prefixs)
+        {
+            this.ServiceType = ServiceType.Outer;
+        }
+
+        public WService()
+        {
+            this.ServiceType = ServiceType.Outer;
+        }
+
+        private long GetId
+        {
+            get { return ++this.idGenerater; }
+        }
+
+        public override void Create(long id, IPEndPoint ipEndPoint, string address)
+        {
+            if (!this.channels.TryGetValue(id, out _))
+            {
+                WChannel channel = new WChannel(id, ipEndPoint, this, address);
+                this.channels[channel.Id] = channel;
+            }
+        }
+
+        public override void Remove(long id, int error = 0)
+        {
+            WChannel channel;
+            if (!this.channels.TryGetValue(id, out channel))
+            {
+                return;
+            }
+
+            channel.Error = error;
+
+            this.channels.Remove(id);
+            channel.Dispose();
+        }
+
+        protected void Get(long id, IPEndPoint ipEndPoint)
+        {
+            // if (!this.channels.TryGetValue(id, out _))
+            // {
+            //     this.Create(id, ipEndPoint);
+            // }
+        }
+
+        public WChannel Get(long id)
+        {
+            WChannel channel = null;
+            this.channels.TryGetValue(id, out channel);
+            return channel;
+        }
+
+        public override void Send(long channelId, long actorId, MemoryStream stream)
+        {
+            this.channels.TryGetValue(channelId, out WChannel channel);
+            if (channel == null)
+            {
+                return;
+            }
+
+            channel.Send(stream);
+        }
+
+        public override void Update()
+        {
+        }
+
+        public override bool IsDisposed()
+        {
+            return this.Id == 0;
+        }
+    }
+}
+#endif

+ 11 - 0
GameClient/Assets/Game/HotUpdate/NetworkTCP/WService_WebGL.cs.meta

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

+ 1 - 1
GameClient/Assets/Game/HotUpdate/ServerProxy/AccountSProxy.cs

@@ -18,7 +18,7 @@ namespace GFGGame
             {
             {
                 notLogin = true;
                 notLogin = true;
                 accountSession = GameGlobal.zoneScene.GetComponent<NetWSComponent>()
                 accountSession = GameGlobal.zoneScene.GetComponent<NetWSComponent>()
-                    .Create(NetworkHelper.ToIPEndPoint(GameConfig.LoginAddress));
+                    .Create(NetworkHelper.ToIPEndPoint(GameConfig.LoginAddress), GameConfig.LoginAddress);
             }
             }
 
 
             try
             try

+ 9 - 0
GameClient/Assets/Game/Launcher/LauncherConfig.cs

@@ -71,6 +71,8 @@ namespace GFGGame
         //是否使用douYou的ios sdk 0不使用用douYouSdk 1使用douYouSdk
         //是否使用douYou的ios sdk 0不使用用douYouSdk 1使用douYouSdk
         public static string isUseDouYouIos;
         public static string isUseDouYouIos;
 
 
+        public static bool isHttps;
+
         public static void InitScriptCompilation()
         public static void InitScriptCompilation()
         {
         {
             launcherRootUrl = "https://cdn.goufuguiwxw.com/";
             launcherRootUrl = "https://cdn.goufuguiwxw.com/";
@@ -154,6 +156,11 @@ namespace GFGGame
                 LauncherConfig.updateEndTime = LauncherTimeUtil.GetTimestamp(result.updateEndTime);
                 LauncherConfig.updateEndTime = LauncherTimeUtil.GetTimestamp(result.updateEndTime);
             }
             }
 
 
+            if (!string.IsNullOrEmpty(result.isHttps))
+            {
+                LauncherConfig.isHttps = result.isHttps == "1";
+            }
+
             if (!string.IsNullOrEmpty(result.promptSizeMB))
             if (!string.IsNullOrEmpty(result.promptSizeMB))
             {
             {
                 LauncherConfig.promptSizeMB = int.Parse(result.promptSizeMB);
                 LauncherConfig.promptSizeMB = int.Parse(result.promptSizeMB);
@@ -179,6 +186,8 @@ namespace GFGGame
             public string showLog;
             public string showLog;
             public string updateEndTime; //更新最后时间
             public string updateEndTime; //更新最后时间
             public string isUseDouYouIos;
             public string isUseDouYouIos;
+
+            public string isHttps;
         }
         }
     }
     }
 }
 }

+ 1 - 0
GameClient/GameClient.sln.DotSettings.user

@@ -9,6 +9,7 @@
 	<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AFile_002Ecs_002Fl_003AC_0021_003FUsers_003Fss510_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F695d1cc93cca45069c528c15c9fdd7493e2800_003Fc3_003F83d8926e_003FFile_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
 	<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AFile_002Ecs_002Fl_003AC_0021_003FUsers_003Fss510_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F695d1cc93cca45069c528c15c9fdd7493e2800_003Fc3_003F83d8926e_003FFile_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
 	<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AIBufferWriter_00601_002Ecs_002Fl_003AC_0021_003FUsers_003Fadmin_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fa5006ea2ea344a8e8c71a2eb982aadc024518_003Fef_003F2e83f7a0_003FIBufferWriter_00601_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
 	<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AIBufferWriter_00601_002Ecs_002Fl_003AC_0021_003FUsers_003Fadmin_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fa5006ea2ea344a8e8c71a2eb982aadc024518_003Fef_003F2e83f7a0_003FIBufferWriter_00601_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
 	<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AIEnumerator_00601_002Ecs_002Fl_003AC_0021_003FUsers_003Fss510_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F3789ee403a53437cbb6b5d9ab6311f51573620_003F4e_003Faad6c3b2_003FIEnumerator_00601_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
 	<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AIEnumerator_00601_002Ecs_002Fl_003AC_0021_003FUsers_003Fss510_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F3789ee403a53437cbb6b5d9ab6311f51573620_003F4e_003Faad6c3b2_003FIEnumerator_00601_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
+	<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AIPEndPoint_002Ecs_002Fl_003AC_0021_003FUsers_003Fss510_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fd6e153aa051345dc96d9a55ee3adc812360448_003Fa6_003F47eb89a0_003FIPEndPoint_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
 	<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AList_00601_002Ecs_002Fl_003AC_0021_003FUsers_003Fss510_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F3789ee403a53437cbb6b5d9ab6311f51573620_003Fa3_003F60d291b6_003FList_00601_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
 	<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AList_00601_002Ecs_002Fl_003AC_0021_003FUsers_003Fss510_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F3789ee403a53437cbb6b5d9ab6311f51573620_003Fa3_003F60d291b6_003FList_00601_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
 	<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ALogType_002Ecs_002Fl_003AC_0021_003FUsers_003Fss510_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F683a2b31bf9142429c44f02c75dbc6c913ce00_003F75_003Fd87ba9d0_003FLogType_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
 	<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ALogType_002Ecs_002Fl_003AC_0021_003FUsers_003Fss510_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F683a2b31bf9142429c44f02c75dbc6c913ce00_003F75_003Fd87ba9d0_003FLogType_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
 	<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AMemberInfo_002Ecs_002Fl_003AC_0021_003FUsers_003Fss510_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F9c2967a135e648bdb993c5397a44991b573620_003F69_003F4bdfd6bb_003FMemberInfo_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
 	<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AMemberInfo_002Ecs_002Fl_003AC_0021_003FUsers_003Fss510_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F9c2967a135e648bdb993c5397a44991b573620_003F69_003F4bdfd6bb_003FMemberInfo_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>