tanghai 2 lat temu
rodzic
commit
247a7d060e

+ 1 - 1
DotNet/Core/DotNet.Core.csproj

@@ -36,7 +36,7 @@
     
     <ItemGroup>
       <ProjectReference Include="..\..\Share\Analyzer\Share.Analyzer.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" /> 
-      <ProjectReference Include="..\..\Share\Share.SourceGenerator\Share.SourceGenerator.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false"/>
+      <ProjectReference Include="..\..\Share\Share.SourceGenerator\Share.SourceGenerator.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
       <ProjectReference Include="..\ThirdParty\DotNet.ThirdParty.csproj" />
     </ItemGroup>
 

+ 1 - 3
Unity/Assets/Scripts/Core/Module/Network/AChannel.cs

@@ -32,10 +32,8 @@ namespace ET
 		public ChannelType ChannelType { get; protected set; }
 
 		public int Error { get; set; }
-		
-		public IPEndPoint RemoteAddress { get; set; }
 
-		
+
 		public bool IsDisposed
 		{
 			get

+ 58 - 0
Unity/Assets/Scripts/Core/Module/Network/Extensions.cs

@@ -0,0 +1,58 @@
+using System.Net;
+using System.Net.Sockets;
+
+namespace ET
+{
+    public static class Extensions
+    {
+        // always pass the same IPEndPointNonAlloc instead of allocating a new
+        // one each time.
+        //
+        // use IPEndPointNonAlloc.temp to get the latest SocketAdddress written
+        // by ReceiveFrom_Internal!
+        //
+        // IMPORTANT: .temp will be overwritten in next call!
+        //            hash or manually copy it if you need to store it, e.g.
+        //            when adding a new connection.
+        public static int ReceiveFrom_NonAlloc(
+            this Socket socket,
+            byte[] buffer,
+            int offset,
+            int size,
+            SocketFlags socketFlags,
+            ref IPEndPointNonAlloc remoteEndPoint)
+        {
+            // call ReceiveFrom with IPEndPointNonAlloc.
+            // need to wrap this in ReceiveFrom_NonAlloc because it's not
+            // obvious that IPEndPointNonAlloc.Create does NOT create a new
+            // IPEndPoint. it saves the result in IPEndPointNonAlloc.temp!
+            EndPoint casted = remoteEndPoint;
+            return  socket.ReceiveFrom(buffer, offset, size, socketFlags, ref casted);
+        }
+
+        // same as above, different parameters
+        public static int ReceiveFrom_NonAlloc(this Socket socket, byte[] buffer, ref IPEndPointNonAlloc remoteEndPoint)
+        {
+            EndPoint casted = remoteEndPoint;
+            return socket.ReceiveFrom(buffer, ref casted);
+        }
+
+        // SendTo allocates too:
+        // https://github.com/mono/mono/blob/f74eed4b09790a0929889ad7fc2cf96c9b6e3757/mcs/class/System/System.Net.Sockets/Socket.cs#L2240
+        // -> the allocation is in EndPoint.Serialize()
+        // NOTE: technically this function isn't necessary.
+        //       could just pass IPEndPointNonAlloc.
+        //       still good for strong typing.
+        public static int SendTo_NonAlloc(
+            this Socket socket,
+            byte[] buffer,
+            int offset,
+            int size,
+            SocketFlags socketFlags,
+            IPEndPointNonAlloc remoteEndPoint)
+        {
+            EndPoint casted = remoteEndPoint;
+            return socket.SendTo(buffer, offset, size, socketFlags, casted);
+        }
+    }
+}

+ 11 - 0
Unity/Assets/Scripts/Core/Module/Network/Extensions.cs.meta

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

+ 242 - 0
Unity/Assets/Scripts/Core/Module/Network/IPEndPointNonAlloc.cs

@@ -0,0 +1,242 @@
+using System;
+using System.Net;
+using System.Net.Sockets;
+
+namespace ET
+{
+    public class IPEndPointNonAlloc : IPEndPoint
+    {
+#if UNITY
+        
+        // Two steps to remove allocations in ReceiveFrom_Internal:
+        //
+        // 1.) remoteEndPoint.Serialize():
+        //     https://github.com/mono/mono/blob/f74eed4b09790a0929889ad7fc2cf96c9b6e3757/mcs/class/System/System.Net.Sockets/Socket.cs#L1733
+        //     -> creates an EndPoint for ReceiveFrom_Internal to write into
+        //     -> it's never read from:
+        //        ReceiveFrom_Internal passes it to native:
+        //          https://github.com/mono/mono/blob/f74eed4b09790a0929889ad7fc2cf96c9b6e3757/mcs/class/System/System.Net.Sockets/Socket.cs#L1885
+        //        native recv populates 'sockaddr* from' with the remote address:
+        //          https://docs.microsoft.com/en-us/windows/win32/api/winsock/nf-winsock-recvfrom
+        //     -> can NOT be null. bricks both Unity and Unity Hub otherwise.
+        //     -> it seems as if Serialize() is only called to avoid allocating
+        //        a 'new SocketAddress' in ReceiveFrom. it's up to the EndPoint.
+        //
+        // 2.) EndPoint.Create(SocketAddress):
+        //     https://github.com/mono/mono/blob/f74eed4b09790a0929889ad7fc2cf96c9b6e3757/mcs/class/System/System.Net.Sockets/Socket.cs#L1761
+        //     -> SocketAddress is the remote's address that we want to return
+        //     -> to avoid 'new EndPoint(SocketAddress), it seems up to the user
+        //        to decide how to create a new EndPoint via .Create
+        //     -> SocketAddress is the object that was returned by Serialize()
+        //
+        // in other words, all we need is an extra SocketAddress field that we
+        // can pass to ReceiveFrom_Internal to write the result into.
+        // => callers can then get the result from the extra field!
+        // => no allocations
+        //
+        // IMPORTANT: remember that IPEndPointNonAlloc is always the same object
+        //            and never changes. only the helper field is changed.
+        public SocketAddress temp;
+
+        // constructors simply create the field once by calling the base method.
+        // (our overwritten method would create anything new)
+        public IPEndPointNonAlloc(long address, int port) : base(address, port)
+        {
+            temp = base.Serialize();
+        }
+        public IPEndPointNonAlloc(IPAddress address, int port) : base(address, port)
+        {
+            temp = base.Serialize();
+        }
+
+        // Serialize simply returns it
+        public override SocketAddress Serialize() => temp;
+
+        // Create doesn't need to create anything.
+        // SocketAddress object is already the one we returned in Serialize().
+        // ReceiveFrom_Internal simply wrote into it.
+        public override EndPoint Create(SocketAddress socketAddress)
+        {
+            // original IPEndPoint.Create validates:
+            if (socketAddress.Family != AddressFamily)
+                throw new ArgumentException($"Unsupported socketAddress.AddressFamily: {socketAddress.Family}. Expected: {AddressFamily}");
+            if (socketAddress.Size < 8)
+                throw new ArgumentException($"Unsupported socketAddress.Size: {socketAddress.Size}. Expected: <8");
+
+            // double check to guarantee that ReceiveFrom actually did write
+            // into our 'temp' field. just in case that's ever changed.
+            if (socketAddress != temp)
+            {
+                // well this is fun.
+                // in the latest mono from the above github links,
+                // the result of Serialize() is passed as 'ref' so ReceiveFrom
+                // does in fact write into it.
+                //
+                // in Unity 2019 LTS's mono version, it does create a new one
+                // each time. this is from ILSpy Receive_From:
+                //
+                //     SocketPal.CheckDualModeReceiveSupport(this);
+                //     ValidateBlockingMode();
+                //     if (NetEventSource.IsEnabled)
+                //     {
+                //         NetEventSource.Info(this, $"SRC{LocalEndPoint} size:{size} remoteEP:{remoteEP}", "ReceiveFrom");
+                //     }
+                //     EndPoint remoteEP2 = remoteEP;
+                //     System.Net.Internals.SocketAddress socketAddress = SnapshotAndSerialize(ref remoteEP2);
+                //     System.Net.Internals.SocketAddress socketAddress2 = IPEndPointExtensions.Serialize(remoteEP2);
+                //     int bytesTransferred;
+                //     SocketError socketError = SocketPal.ReceiveFrom(_handle, buffer, offset, size, socketFlags, socketAddress.Buffer, ref socketAddress.InternalSize, out bytesTransferred);
+                //     SocketException ex = null;
+                //     if (socketError != 0)
+                //     {
+                //         ex = new SocketException((int)socketError);
+                //         UpdateStatusAfterSocketError(ex);
+                //         if (NetEventSource.IsEnabled)
+                //         {
+                //             NetEventSource.Error(this, ex, "ReceiveFrom");
+                //         }
+                //         if (ex.SocketErrorCode != SocketError.MessageSize)
+                //         {
+                //             throw ex;
+                //         }
+                //     }
+                //     if (!socketAddress2.Equals(socketAddress))
+                //     {
+                //         try
+                //         {
+                //             remoteEP = remoteEP2.Create(socketAddress);
+                //         }
+                //         catch
+                //         {
+                //         }
+                //         if (_rightEndPoint == null)
+                //         {
+                //             _rightEndPoint = remoteEP2;
+                //         }
+                //     }
+                //     if (ex != null)
+                //     {
+                //         throw ex;
+                //     }
+                //     if (NetEventSource.IsEnabled)
+                //     {
+                //         NetEventSource.DumpBuffer(this, buffer, offset, size, "ReceiveFrom");
+                //         NetEventSource.Exit(this, bytesTransferred, "ReceiveFrom");
+                //     }
+                //     return bytesTransferred;
+                //
+
+                // so until they upgrade their mono version, we are stuck with
+                // some allocations.
+                //
+                // for now, let's pass the newly created on to our temp so at
+                // least we reuse it next time.
+                
+                temp = socketAddress;
+
+                // SocketAddress.GetHashCode() depends on SocketAddress.m_changed.
+                // ReceiveFrom only sets the buffer, it does not seem to set m_changed.
+                // we need to reset m_changed for two reasons:
+                // * if m_changed is false, GetHashCode() returns the cahced m_hash
+                //   which is '0'. that would be a problem.
+                //   https://github.com/mono/mono/blob/bdd772531d379b4e78593587d15113c37edd4a64/mcs/class/referencesource/System/net/System/Net/SocketAddress.cs#L262
+                // * if we have a cached m_hash, but ReceiveFrom modified the buffer
+                //   then the GetHashCode() should change too. so we need to reset
+                //   either way.
+                //
+                // the only way to do that is by _actually_ modifying the buffer:
+                // https://github.com/mono/mono/blob/bdd772531d379b4e78593587d15113c37edd4a64/mcs/class/referencesource/System/net/System/Net/SocketAddress.cs#L99
+                // so let's do that.
+                // -> unchecked in case it's byte.Max
+                unchecked
+                {
+                    temp[0] += 1;
+                    temp[0] -= 1;
+                }
+
+                // make sure this worked.
+                // at least throw an Exception to make it obvious if the trick does
+                // not work anymore, in case ReceiveFrom is ever changed.
+                if (temp.GetHashCode() == 0)
+                    throw new Exception($"SocketAddress GetHashCode() is 0 after ReceiveFrom. Does the m_changed trick not work anymore?");
+
+                // in the future, enable this again:
+                //throw new Exception($"Socket.ReceiveFrom(): passed SocketAddress={socketAddress} but expected {temp}. This should never happen. Did ReceiveFrom() change?");
+            }
+
+            // ReceiveFrom sets seed_endpoint to the result of Create():
+            // https://github.com/mono/mono/blob/f74eed4b09790a0929889ad7fc2cf96c9b6e3757/mcs/class/System/System.Net.Sockets/Socket.cs#L1764
+            // so let's return ourselves at least.
+            // (seed_endpoint only seems to matter for BeginSend etc.)
+            return this;
+        }
+
+        // we need to overwrite GetHashCode() for two reasons.
+        // https://github.com/mono/mono/blob/bdd772531d379b4e78593587d15113c37edd4a64/mcs/class/referencesource/System/net/System/Net/IPEndPoint.cs#L160
+        // * it uses m_Address. but our true SocketAddress is in m_temp.
+        //   m_Address might not be set at all.
+        // * m_Address.GetHashCode() allocates:
+        //   https://github.com/mono/mono/blob/bdd772531d379b4e78593587d15113c37edd4a64/mcs/class/referencesource/System/net/System/Net/IPAddress.cs#L699
+        public override int GetHashCode() => temp.GetHashCode();
+        
+        public int GetThisHashCode() => base.GetHashCode();
+
+        // helper function to create an ACTUAL new IPEndPoint from this.
+        // server needs it to store new connections as unique IPEndPoints.
+        public IPEndPoint DeepCopyIPEndPoint()
+        {
+            // we need to create a new IPEndPoint from 'temp' SocketAddress.
+            // there is no 'new IPEndPoint(SocketAddress) constructor.
+            // so we need to be a bit creative...
+
+            // allocate a placeholder IPAddress to copy
+            // our SocketAddress into.
+            // -> needs to be the same address family.
+            IPAddress ipAddress;
+            if (temp.Family == AddressFamily.InterNetworkV6)
+                ipAddress = IPAddress.IPv6Any;
+            else if (temp.Family == AddressFamily.InterNetwork)
+                ipAddress = IPAddress.Any;
+            else
+                throw new Exception($"Unexpected SocketAddress family: {temp.Family}");
+
+            // allocate a placeholder IPEndPoint
+            // with the needed size form IPAddress.
+            // (the real class. not NonAlloc)
+            IPEndPoint placeholder = new IPEndPoint(ipAddress, 0);
+
+            // the real IPEndPoint's .Create function can create a new IPEndPoint
+            // copy from a SocketAddress.
+            return (IPEndPoint)placeholder.Create(temp);
+        }
+#else
+        
+        public IPEndPointNonAlloc(long address, int port) : base(address, port)
+        {
+        }
+        public IPEndPointNonAlloc(IPAddress address, int port) : base(address, port)
+        {
+        }
+        
+        public IPEndPoint DeepCopyIPEndPoint()
+        {
+            return new IPEndPoint(this.Address, this.Port);
+        }
+#endif
+        
+        public bool Equals(IPEndPoint ipEndPoint)
+        {
+            if (!object.Equals(ipEndPoint.Address, this.Address))
+            {
+                return false;
+            }
+
+            if (ipEndPoint.Port != this.Port)
+            {
+                return false;
+            }
+
+            return true;
+        }
+    }
+}

+ 11 - 0
Unity/Assets/Scripts/Core/Module/Network/IPEndPointNonAlloc.cs.meta

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

+ 23 - 4
Unity/Assets/Scripts/Core/Module/Network/KChannel.cs

@@ -52,7 +52,25 @@ namespace ET
 
 		private MemoryBuffer readMemory;
 		private int needReadSplitCount;
+
+		private IPEndPoint remoteAddress;
 		
+		public IPEndPointNonAlloc RemoteAddressNonAlloc { get; private set; }
+
+		public IPEndPoint RemoteAddress
+		{
+			get
+			{
+				return this.remoteAddress;
+			}
+			set
+			{
+				Log.Debug($"111111111111111111111111111111 set RemoteAddress: {value}");
+				this.remoteAddress = value;
+				this.RemoteAddressNonAlloc = new IPEndPointNonAlloc(value.Address, value.Port);
+			}
+		}
+
 		private void InitKcp()
 		{
 			this.Service.KcpPtrChannels.Add(this.kcp, this);
@@ -131,7 +149,7 @@ namespace ET
 			{
 				if (this.Error != ErrorCore.ERR_PeerDisconnect)
 				{
-					this.Service.Disconnect(localConn, remoteConn, this.Error, this.RemoteAddress, 3);
+					this.Service.Disconnect(localConn, remoteConn, this.Error, this.RemoteAddressNonAlloc, 3);
 				}
 			}
 			catch (Exception e)
@@ -207,8 +225,9 @@ namespace ET
 				buffer.WriteTo(0, KcpProtocalType.SYN);
 				buffer.WriteTo(1, this.LocalConn);
 				buffer.WriteTo(5, this.RemoteConn);
-				this.socket.SendTo(buffer, 0, 9, SocketFlags.None, this.RemoteAddress);
-				Log.Info($"kchannel connect {this.LocalConn} {this.RemoteConn} {this.RealAddress} {this.socket.LocalEndPoint}");
+				this.socket.SendTo_NonAlloc(buffer, 0, 9, SocketFlags.None, this.RemoteAddressNonAlloc);
+				// 这里很奇怪 调用socket.LocalEndPoint会动到this.RemoteAddressNonAlloc里面的temp,这里就不仔细研究了
+				Log.Info($"kchannel connect {this.LocalConn} {this.RemoteConn} {this.RealAddress}");
 
 				this.lastConnectTime = timeNow;
 
@@ -382,7 +401,7 @@ namespace ET
 				// 每个消息头部写下该channel的id;
 				buffer.WriteTo(1, this.LocalConn);
 				Marshal.Copy(bytes, buffer, 5, count);
-				this.socket.SendTo(buffer, 0, count + 5, SocketFlags.None, this.RemoteAddress);
+				this.socket.SendTo_NonAlloc(buffer, 0, count + 5, SocketFlags.None, this.RemoteAddressNonAlloc);
 			}
 			catch (Exception e)
 			{

+ 15 - 10
Unity/Assets/Scripts/Core/Module/Network/KService.cs

@@ -144,7 +144,7 @@ namespace ET
         private readonly Dictionary<long, KChannel> waitAcceptChannels = new Dictionary<long, KChannel>();
 
         private readonly byte[] cache = new byte[2048];
-        private EndPoint ipEndPoint = new IPEndPoint(IPAddress.Any, 0);
+        private IPEndPointNonAlloc ipEndPoint = new IPEndPointNonAlloc(IPAddress.Any, 0);
 
         // 下帧要更新的channel
         private readonly HashSet<long> updateIds = new HashSet<long>();
@@ -197,8 +197,9 @@ namespace ET
 
         private IPEndPoint CloneAddress()
         {
-            IPEndPoint ip = (IPEndPoint) this.ipEndPoint;
-            return new IPEndPoint(ip.Address, ip.Port);
+            IPEndPoint ip = this.ipEndPoint.DeepCopyIPEndPoint();
+            Log.Debug($"111111111111111111111 clone: {this.ipEndPoint} {ip}");
+            return ip;
         }
 
         private void Recv()
@@ -210,7 +211,9 @@ namespace ET
 
             while (socket != null && this.socket.Available > 0)
             {
-                int messageLength = this.socket.ReceiveFrom(this.cache, ref this.ipEndPoint);
+                int messageLength = this.socket.ReceiveFrom_NonAlloc(this.cache, ref this.ipEndPoint);
+                
+                Log.Debug($"11111111111111111111111111111111 recv: {this.ipEndPoint}");
 
                 // 长度小于1,不是正常的消息
                 if (messageLength < 1)
@@ -264,7 +267,7 @@ namespace ET
                             }
 
                             // 重连的时候router地址变化, 这个不能放到msg中,必须经过严格的验证才能切换
-                            if (!Equals(kChannel.RemoteAddress, this.ipEndPoint))
+                            if (!this.ipEndPoint.Equals(kChannel.RemoteAddress))
                             {
                                 kChannel.RemoteAddress = this.CloneAddress();
                             }
@@ -276,7 +279,7 @@ namespace ET
                                 buffer.WriteTo(1, kChannel.LocalConn);
                                 buffer.WriteTo(5, kChannel.RemoteConn);
                                 buffer.WriteTo(9, connectId);
-                                this.socket.SendTo(buffer, 0, 13, SocketFlags.None, this.ipEndPoint);
+                                this.socket.SendTo_NonAlloc(buffer, 0, 13, SocketFlags.None, this.ipEndPoint);
                             }
                             catch (Exception e)
                             {
@@ -344,7 +347,9 @@ namespace ET
                                 buffer.WriteTo(1, kChannel.LocalConn);
                                 buffer.WriteTo(5, kChannel.RemoteConn);
                                 Log.Info($"kservice syn: {kChannel.Id} {remoteConn} {localConn}");
-                                this.socket.SendTo(buffer, 0, 9, SocketFlags.None, kChannel.RemoteAddress);
+                                
+                                Log.Debug($"11111111111111111111111111111111111111: {kChannel.RemoteAddressNonAlloc} {kChannel.RemoteAddress}");
+                                this.socket.SendTo_NonAlloc(buffer, 0, 9, SocketFlags.None, kChannel.RemoteAddressNonAlloc);
                             }
                             catch (Exception e)
                             {
@@ -414,7 +419,7 @@ namespace ET
                             if (kChannel == null)
                             {
                                 // 通知对方断开
-                                this.Disconnect(localConn, remoteConn, ErrorCore.ERR_KcpNotFoundChannel, (IPEndPoint) this.ipEndPoint, 1);
+                                this.Disconnect(localConn, remoteConn, ErrorCore.ERR_KcpNotFoundChannel, this.ipEndPoint, 1);
                                 break;
                             }
                             
@@ -494,7 +499,7 @@ namespace ET
             kChannel.Dispose();
         }
 
-        public void Disconnect(uint localConn, uint remoteConn, int error, IPEndPoint address, int times)
+        public void Disconnect(uint localConn, uint remoteConn, int error, IPEndPointNonAlloc address, int times)
         {
             try
             {
@@ -510,7 +515,7 @@ namespace ET
                 buffer.WriteTo(9, (uint) error);
                 for (int i = 0; i < times; ++i)
                 {
-                    this.socket.SendTo(buffer, 0, 13, SocketFlags.None, address);
+                    this.socket.SendTo_NonAlloc(buffer, 0, 13, SocketFlags.None, address);
                 }
             }
             catch (Exception e)

+ 2 - 0
Unity/Assets/Scripts/Core/Module/Network/TChannel.cs

@@ -23,6 +23,8 @@ namespace ET
 		private bool isConnected;
 
 		private readonly PacketParser parser;
+		
+		public IPEndPoint RemoteAddress { get; set; }
 
 		private readonly byte[] sendCache = new byte[Packet.OpcodeLength + Packet.ActorIdLength];
 

+ 3 - 0
Unity/Assets/Scripts/Core/Module/Network/WChannel.cs

@@ -1,6 +1,7 @@
 using System;
 using System.Collections.Generic;
 using System.IO;
+using System.Net;
 using System.Net.WebSockets;
 using System.Threading;
 
@@ -21,6 +22,8 @@ namespace ET
         private bool isConnected;
 
         private readonly MemoryBuffer recvStream;
+        
+        public IPEndPoint RemoteAddress { get; set; }
 
         private CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();