[feat]网络层增加Byte[]和ArraySegment的发送重载,直达底层,这是用户最常用的方法,也是性能最好的路径。大石头 authored at 2024-11-19 10:06:42
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)
{
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);
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);
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)
{
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)
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())
{
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())
{
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>
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;