NewLife/X

[feat]网络层增加Byte[]和ArraySegment的发送重载,直达底层,这是用户最常用的方法,也是性能最好的路径。
大石头 authored at 2024-11-19 10:06:42
a7ec85a
Tree
1 Parent(s) d5a2116
Summary: 9 changed files with 266 additions and 32 deletions.
Modified +5 -3
Modified +7 -0
Modified +18 -0
Modified +14 -0
Modified +44 -1
Modified +74 -11
Modified +75 -16
Modified +28 -0
Modified +1 -1
Modified +5 -3
diff --git a/NewLife.Core/Data/IPacket.cs b/NewLife.Core/Data/IPacket.cs
index 57f5af7..cbf3d47 100644
--- a/NewLife.Core/Data/IPacket.cs
+++ b/NewLife.Core/Data/IPacket.cs
@@ -150,7 +150,7 @@ public static class PacketHelper
         return sb.Return(true);
     }
 
-    /// <summary>拷贝</summary>
+    /// <summary>写入数据流,netfx中可能有二次拷贝</summary>
     /// <param name="pk"></param>
     /// <param name="stream"></param>
     public static void CopyTo(this IPacket pk, Stream stream)
@@ -162,6 +162,8 @@ public static class PacketHelper
 #else
             if (p is ArrayPacket ap)
                 stream.Write(ap.Buffer, ap.Offset, ap.Length);
+            else if (p.TryGetArray(out var segment))
+                stream.Write(segment.Array!, segment.Offset, segment.Count);
             else
                 stream.Write(p.GetMemory());
 #endif
@@ -202,7 +204,7 @@ public static class PacketHelper
         return ms;
     }
 
-    /// <summary>返回数据段</summary>
+    /// <summary>返回数据段,可能有拷贝</summary>
     /// <returns></returns>
     public static ArraySegment<Byte> ToSegment(this IPacket pk)
     {
@@ -215,7 +217,7 @@ public static class PacketHelper
         return new ArraySegment<Byte>(ms.Return(true));
     }
 
-    /// <summary>返回数据段集合</summary>
+    /// <summary>返回数据段集合,可能有拷贝</summary>
     /// <returns></returns>
     public static IList<ArraySegment<Byte>> ToSegments(this IPacket pk)
     {
Modified +7 -0
diff --git a/NewLife.Core/Net/INetSession.cs b/NewLife.Core/Net/INetSession.cs
index 8a3221d..75c26c2 100644
--- a/NewLife.Core/Net/INetSession.cs
+++ b/NewLife.Core/Net/INetSession.cs
@@ -52,6 +52,13 @@ public interface INetSession : IDisposable2
     INetSession Send(IPacket data);
 
     /// <summary>发送数据,直达网卡</summary>
+    /// <param name="data">字节数组</param>
+    /// <param name="offset">偏移</param>
+    /// <param name="count">字节数</param>
+    /// <returns></returns>
+    INetSession Send(Byte[] data, Int32 offset = 0, Int32 count = -1);
+
+    /// <summary>发送数据,直达网卡</summary>
     /// <param name="data">数据包</param>
     /// <returns></returns>
     INetSession Send(ReadOnlySpan<Byte> data);
Modified +18 -0
diff --git a/NewLife.Core/Net/ISocketRemote.cs b/NewLife.Core/Net/ISocketRemote.cs
index 61b6714..3c4f6b5 100644
--- a/NewLife.Core/Net/ISocketRemote.cs
+++ b/NewLife.Core/Net/ISocketRemote.cs
@@ -32,6 +32,24 @@ public interface ISocketRemote : ISocket, IExtend
     /// <remarks>
     /// 目标地址由<seealso cref="Remote"/>决定
     /// </remarks>
+    /// <param name="data">字节数组</param>
+    /// <param name="offset">偏移</param>
+    /// <param name="count">字节数</param>
+    /// <returns>是否成功</returns>
+    Int32 Send(Byte[] data, Int32 offset = 0, Int32 count = -1);
+
+    /// <summary>发送原始数据包</summary>
+    /// <remarks>
+    /// 目标地址由<seealso cref="Remote"/>决定
+    /// </remarks>
+    /// <param name="data">数据包</param>
+    /// <returns>是否成功</returns>
+    Int32 Send(ArraySegment<Byte> data);
+
+    /// <summary>发送原始数据包</summary>
+    /// <remarks>
+    /// 目标地址由<seealso cref="Remote"/>决定
+    /// </remarks>
     /// <param name="data">数据包</param>
     /// <returns>是否成功</returns>
     Int32 Send(ReadOnlySpan<Byte> data);
Modified +14 -0
diff --git a/NewLife.Core/Net/NetSession.cs b/NewLife.Core/Net/NetSession.cs
index c7b63a6..e5f5345 100644
--- a/NewLife.Core/Net/NetSession.cs
+++ b/NewLife.Core/Net/NetSession.cs
@@ -243,6 +243,20 @@ public class NetSession : DisposeBase, INetSession, IServiceProvider, IExtend
     }
 
     /// <summary>发送数据,直达网卡</summary>
+    /// <param name="data">字节数组</param>
+    /// <param name="offset">偏移</param>
+    /// <param name="count">字节数</param>
+    public virtual INetSession Send(Byte[] data, Int32 offset = 0, Int32 count = -1)
+    {
+        var ns = (this as INetSession).Host;
+        using var span = ns?.Tracer?.NewSpan($"net:{ns.Name}:Send", data.ToHex(offset, count), count > 0 ? count : data.Length - offset);
+
+        Session.Send(data, offset, count);
+
+        return this;
+    }
+
+    /// <summary>发送数据,直达网卡</summary>
     /// <param name="data">数据包</param>
     public virtual INetSession Send(ReadOnlySpan<Byte> data)
     {
Modified +44 -1
diff --git a/NewLife.Core/Net/SessionBase.cs b/NewLife.Core/Net/SessionBase.cs
index 6805f08..65a9823 100644
--- a/NewLife.Core/Net/SessionBase.cs
+++ b/NewLife.Core/Net/SessionBase.cs
@@ -1,4 +1,5 @@
-using System.Collections.Concurrent;
+using System;
+using System.Collections.Concurrent;
 using System.Diagnostics.CodeAnalysis;
 using System.Net;
 using System.Net.Sockets;
@@ -262,6 +263,48 @@ public abstract class SessionBase : DisposeBase, ISocketClient, ITransport, ILog
     /// <remarks>
     /// 目标地址由<seealso cref="Remote"/>决定
     /// </remarks>
+    /// <param name="data">字节数组</param>
+    /// <param name="offset">偏移</param>
+    /// <param name="count">字节数</param>
+    /// <returns>是否成功</returns>
+    public Int32 Send(Byte[] data, Int32 offset = 0, Int32 count = -1)
+    {
+        if (Disposed) throw new ObjectDisposedException(GetType().Name);
+        if (!Open()) return -1;
+
+#if NET6_0_OR_GREATER
+        return OnSend(new ReadOnlySpan<Byte>(data, offset, count));
+#else
+        return OnSend(new ArraySegment<Byte>(data, offset, count));
+#endif
+    }
+
+    /// <summary>直接发送数据包 Byte[]/Packet</summary>
+    /// <remarks>
+    /// 目标地址由<seealso cref="Remote"/>决定
+    /// </remarks>
+    /// <param name="data">数据包</param>
+    /// <returns>是否成功</returns>
+    public Int32 Send(ArraySegment<Byte> data)
+    {
+        if (Disposed) throw new ObjectDisposedException(GetType().Name);
+        if (!Open()) return -1;
+
+        return OnSend(data);
+    }
+
+    /// <summary>发送数据</summary>
+    /// <remarks>
+    /// 目标地址由<seealso cref="Remote"/>决定
+    /// </remarks>
+    /// <param name="data">数据包</param>
+    /// <returns>是否成功</returns>
+    protected abstract Int32 OnSend(ArraySegment<Byte> data);
+
+    /// <summary>直接发送数据包 Byte[]/Packet</summary>
+    /// <remarks>
+    /// 目标地址由<seealso cref="Remote"/>决定
+    /// </remarks>
     /// <param name="data">数据包</param>
     /// <returns>是否成功</returns>
     public Int32 Send(ReadOnlySpan<Byte> data)
Modified +74 -11
diff --git a/NewLife.Core/Net/TcpSession.cs b/NewLife.Core/Net/TcpSession.cs
index 70c6dbd..7cf6100 100644
--- a/NewLife.Core/Net/TcpSession.cs
+++ b/NewLife.Core/Net/TcpSession.cs
@@ -316,15 +316,14 @@ public class TcpSession : SessionBase, ISocketSession
             {
                 if (count == 0)
                     rs = sock.Send(Pool.Empty);
+                else if (pk.TryGetArray(out var segment))
+                    rs = sock.Send(segment.Array!, segment.Offset, segment.Count, SocketFlags.None);
                 else if (pk.TryGetSpan(out var data))
                 {
 #if NETCOREAPP || NETSTANDARD2_1
                     rs = sock.Send(data);
 #else
-                    if (pk.TryGetArray(out var segment))
-                        rs = sock.Send(segment.Array!, segment.Offset, segment.Count, SocketFlags.None);
-                    else
-                        rs = sock.Send(data.ToArray(), data.Length, SocketFlags.None);
+                    rs = sock.Send(data.ToArray(), data.Length, SocketFlags.None);
 #endif
                 }
                 else
@@ -337,12 +336,10 @@ public class TcpSession : SessionBase, ISocketSession
                 else
                     pk.CopyTo(_Stream);
             }
-
-            //// 检查返回值
-            //if (rs != count) throw new NetException($"发送[{count:n0}]而成功[{rs:n0}]");
         }
         catch (Exception ex)
         {
+            // 发生异常时,全量数据写入埋点
             span?.SetError(ex, pk);
 
             if (!ex.IsDisposed())
@@ -351,11 +348,76 @@ public class TcpSession : SessionBase, ISocketSession
 
                 // 发送异常可能是连接出了问题,需要关闭
                 Close("SendError");
+            }
+
+            return -1;
+        }
+        finally
+        {
+            if (gotLock) _spinLock.Exit();
+        }
 
-                //// 异步重连
-                //ThreadPoolX.QueueUserWorkItem(Reconnect);
+        LastTime = DateTime.Now;
 
-                //if (ThrowException) throw;
+        return rs;
+    }
+
+    /// <summary>发送数据</summary>
+    /// <remarks>
+    /// 目标地址由<seealso cref="SessionBase.Remote"/>决定
+    /// </remarks>
+    /// <param name="data">数据包</param>
+    /// <returns>是否成功</returns>
+    protected override Int32 OnSend(ArraySegment<Byte> data)
+    {
+        var count = data.Count;
+        var logCount = count > LogDataLength ? count : LogDataLength;
+
+        if (Log != null && Log.Enable && LogSend)
+            WriteLog("Send [{0}]: {1}", count, data.Array.ToHex(data.Offset, logCount));
+
+        using var span = Tracer?.NewSpan($"net:{Name}:Send", count + "", count);
+
+        var rs = count;
+        var sock = Client;
+        if (sock == null) return -1;
+
+        var gotLock = false;
+        try
+        {
+            // 修改发送缓冲区,读取SendBufferSize耗时很大
+            if (_bsize == 0) _bsize = sock.SendBufferSize;
+            if (_bsize < count) sock.SendBufferSize = _bsize = count;
+
+            // 加锁发送
+            _spinLock.Enter(ref gotLock);
+
+            if (_Stream == null)
+            {
+                if (count == 0)
+                    rs = sock.Send(Pool.Empty);
+                else
+                    rs = sock.Send(data.Array!, data.Offset, data.Count, SocketFlags.None);
+            }
+            else
+            {
+                if (count == 0)
+                    _Stream.Write([]);
+                else
+                    _Stream.Write(data.Array!, data.Offset, data.Count);
+            }
+        }
+        catch (Exception ex)
+        {
+            // 发生异常时,全量数据写入埋点
+            span?.SetError(ex, data.Array.ToHex(data.Offset, data.Count));
+
+            if (!ex.IsDisposed())
+            {
+                OnError("Send", ex);
+
+                // 发送异常可能是连接出了问题,需要关闭
+                Close("SendError");
             }
 
             return -1;
@@ -423,7 +485,8 @@ public class TcpSession : SessionBase, ISocketSession
         }
         catch (Exception ex)
         {
-            span?.SetError(ex, data.ToHex(LogDataLength));
+            // 发生异常时,全量数据写入埋点
+            span?.SetError(ex, data.ToHex());
 
             if (!ex.IsDisposed())
             {
Modified +75 -16
diff --git a/NewLife.Core/Net/UdpServer.cs b/NewLife.Core/Net/UdpServer.cs
index 4e83646..b0f4258 100644
--- a/NewLife.Core/Net/UdpServer.cs
+++ b/NewLife.Core/Net/UdpServer.cs
@@ -1,10 +1,10 @@
 using System.Net;
 using System.Net.Sockets;
 using System.Text;
+using NewLife.Collections;
 using NewLife.Data;
 using NewLife.Log;
 using NewLife.Model;
-using NewLife.Collections;
 
 namespace NewLife.Net;
 
@@ -118,7 +118,7 @@ public class UdpServer : SessionBase, ISocketServer, ILogFeature
                 var remote = Remote;
                 if (remote != null && !remote.Address.IsAny() && remote.Port != 0)
                 {
-                    this.Send(Pool.Empty);
+                    Send(Pool.Empty);
                 }
 
                 Client = null;
@@ -155,6 +155,14 @@ public class UdpServer : SessionBase, ISocketServer, ILogFeature
     /// </remarks>
     /// <param name="data">数据包</param>
     /// <returns>是否成功</returns>
+    protected override Int32 OnSend(ArraySegment<Byte> data) => OnSend(data, Remote.EndPoint);
+
+    /// <summary>发送数据</summary>
+    /// <remarks>
+    /// 目标地址由<seealso cref="SessionBase.Remote"/>决定
+    /// </remarks>
+    /// <param name="data">数据包</param>
+    /// <returns>是否成功</returns>
     protected override Int32 OnSend(ReadOnlySpan<Byte> data) => OnSend(data, Remote.EndPoint);
 
     internal Int32 OnSend(IPacket pk, IPEndPoint remote)
@@ -173,15 +181,16 @@ public class UdpServer : SessionBase, ISocketServer, ILogFeature
                 {
                     if (Log.Enable && LogSend) WriteLog("Send [{0}]: {1}", count, pk.ToHex(LogDataLength));
 
-                    if (pk.TryGetSpan(out var data))
+                    if (count == 0)
+                        rs = sock.Send(Pool.Empty);
+                    else if (pk.TryGetArray(out var segment))
+                        rs = sock.Send(segment.Array!, segment.Offset, segment.Count, SocketFlags.None);
+                    else if (pk.TryGetSpan(out var data))
                     {
 #if NETCOREAPP || NETSTANDARD2_1
                         rs = sock.Send(data);
 #else
-                        if (pk.TryGetArray(out var segment))
-                            rs = sock.Send(segment.Array!, segment.Offset, segment.Count, SocketFlags.None);
-                        else
-                            rs = sock.Send(data.ToArray(), data.Length, SocketFlags.None);
+                        rs = sock.Send(data.ToArray(), 0, data.Length, SocketFlags.None);
 #endif
                     }
                     else
@@ -192,15 +201,16 @@ public class UdpServer : SessionBase, ISocketServer, ILogFeature
                     sock.CheckBroadcast(remote.Address);
                     if (Log.Enable && LogSend) WriteLog("Send {2} [{0}]: {1}", count, pk.ToHex(LogDataLength), remote);
 
-                    if (pk.TryGetSpan(out var data))
+                    if (count == 0)
+                        rs = sock.SendTo(Pool.Empty, remote);
+                    else if (pk.TryGetArray(out var segment))
+                        rs = sock.SendTo(segment.Array!, segment.Offset, segment.Count, SocketFlags.None, remote);
+                    else if (pk.TryGetSpan(out var data))
                     {
 #if NET6_0_OR_GREATER
                         rs = sock.SendTo(data, remote);
 #else
-                        if (pk.TryGetArray(out var segment))
-                            rs = sock.SendTo(segment.Array!, segment.Offset, segment.Count, SocketFlags.None, remote);
-                        else
-                            rs = sock.SendTo(data.ToArray(), 0, data.Length, SocketFlags.None, remote);
+                        rs = sock.SendTo(data.ToArray(), 0, data.Length, SocketFlags.None, remote);
 #endif
                     }
                     else
@@ -212,16 +222,64 @@ public class UdpServer : SessionBase, ISocketServer, ILogFeature
         }
         catch (Exception ex)
         {
+            // 发生异常时,全量数据写入埋点
             span?.SetError(ex, pk);
 
             if (!ex.IsDisposed())
             {
                 OnError("Send", ex);
+            }
+            return -1;
+        }
+    }
+
+    internal Int32 OnSend(Byte[] data, Int32 offset, Int32 count, IPEndPoint remote)
+    {
+#if NET6_0_OR_GREATER
+        return OnSend(new ReadOnlySpan<Byte>(data, offset, count), remote);
+#else
+        return OnSend(new ArraySegment<Byte>(data, offset, count), remote);
+#endif
+    }
 
-                // 发送异常可能是连接出了问题,UDP不需要关闭
-                //Close();
+    internal Int32 OnSend(ArraySegment<Byte> data, IPEndPoint remote)
+    {
+        var count = data.Count;
+        var logCount = count > LogDataLength ? count : LogDataLength;
 
-                //if (ThrowException) throw;
+        using var span = Tracer?.NewSpan($"net:{Name}:Send", count + "", count);
+
+        try
+        {
+            var rs = 0;
+            var sock = Client ?? throw new InvalidOperationException(nameof(OnSend));
+            lock (sock)
+            {
+                if (sock.Connected && !sock.EnableBroadcast)
+                {
+                    if (Log.Enable && LogSend) WriteLog("Send [{0}]: {1}", count, data.Array.ToHex(data.Offset, logCount));
+
+                    rs = sock.Send(data.Array!, data.Offset, data.Count, SocketFlags.None);
+                }
+                else
+                {
+                    sock.CheckBroadcast(remote.Address);
+                    if (Log.Enable && LogSend) WriteLog("Send {2} [{0}]: {1}", count, data.Array.ToHex(data.Offset, logCount), remote);
+
+                    rs = sock.SendTo(data.Array!, data.Offset, data.Count, SocketFlags.None, remote);
+                }
+            }
+
+            return rs;
+        }
+        catch (Exception ex)
+        {
+            // 发生异常时,全量数据写入埋点
+            span?.SetError(ex, data.Array.ToHex(data.Offset, data.Count));
+
+            if (!ex.IsDisposed())
+            {
+                OnError("Send", ex);
             }
             return -1;
         }
@@ -266,7 +324,8 @@ public class UdpServer : SessionBase, ISocketServer, ILogFeature
         }
         catch (Exception ex)
         {
-            span?.SetError(ex, data.ToHex(LogDataLength));
+            // 发生异常时,全量数据写入埋点
+            span?.SetError(ex, data.ToHex());
 
             if (!ex.IsDisposed())
             {
Modified +28 -0
diff --git a/NewLife.Core/Net/UdpSession.cs b/NewLife.Core/Net/UdpSession.cs
index ac79efb..8b42aa8 100644
--- a/NewLife.Core/Net/UdpSession.cs
+++ b/NewLife.Core/Net/UdpSession.cs
@@ -139,6 +139,34 @@ public class UdpSession : DisposeBase, ISocketSession, ITransport, ILogFeature
     }
 
     /// <summary>发送数据</summary>
+    /// <param name="data">字节数组</param>
+    /// <param name="offset">偏移</param>
+    /// <param name="count">字节数</param>
+    /// <returns></returns>
+    /// <exception cref="ObjectDisposedException"></exception>
+    public Int32 Send(Byte[] data, Int32 offset = 0, Int32 count = -1)
+    {
+        if (Disposed) throw new ObjectDisposedException(GetType().Name);
+
+#if NET6_0_OR_GREATER
+        return Server.OnSend(new ReadOnlySpan<Byte>(data, offset, count), Remote.EndPoint);
+#else
+        return Server.OnSend(new ArraySegment<Byte>(data, offset, count), Remote.EndPoint);
+#endif
+    }
+
+    /// <summary>发送数据</summary>
+    /// <param name="data"></param>
+    /// <returns></returns>
+    /// <exception cref="ObjectDisposedException"></exception>
+    public Int32 Send(ArraySegment<Byte> data)
+    {
+        if (Disposed) throw new ObjectDisposedException(GetType().Name);
+
+        return Server.OnSend(data, Remote.EndPoint);
+    }
+
+    /// <summary>发送数据</summary>
     /// <param name="data"></param>
     /// <returns></returns>
     /// <exception cref="ObjectDisposedException"></exception>
Modified +1 -1
diff --git a/NewLife.Core/Setting.cs b/NewLife.Core/Setting.cs
index 7f87e13..1a17214 100644
--- a/NewLife.Core/Setting.cs
+++ b/NewLife.Core/Setting.cs
@@ -84,7 +84,7 @@ public class Setting : Config<Setting>
         // 多应用项目,需要把基础目录向上提升一级
         var root = "../";
         var di = ".".AsDirectory();
-        if (di.Name.StartsWithIgnoreCase("netcoreapp", "net2", "net4", "net5", "net6", "net7", "net8", "net9", "net10"))
+        if (di.Name.StartsWithIgnoreCase("netcoreapp", "net2", "net4", "net5", "net6", "net7", "net8", "net9", "net10") && di.Parent != null)
         {
             root = "../../";
             di = di.Parent;