using System.Net;
using System.Net.Security;
using System.Net.Sockets;
using System.Security.Authentication;
using System.Security.Cryptography.X509Certificates;
using NewLife.Data;
namespace NewLife.Net;
/// <summary>增强TCP客户端(平å°å·®å¼‚桥:本类全部 TFM æ¡ä»¶ç¼–译集ä¸åœ°ï¼‰</summary>
/// <remarks>
/// <para>æœ¬æ–‡ä»¶åªæ”¾â€œæ¡†æž¶ç‰ˆæœ¬å·®å¼‚â€çš„ç‰ä»·æ¡¥æŽ¥ï¼Œä¸å«ä¸šåŠ¡ç–略:连接桥(å¯å–消建连与 Unix 域)ã€SSL 桥(客户端/æœåŠ¡ç«¯è®¤è¯çš„è¶…æ—¶é™çº§ï¼‰ã€
/// 写入桥(Span/数组差异ã€å¼‚æ¥å†™é™çº§ã€å•段/链å¼è·¯å¾„ï¼‰ã€æ–‡ä»¶æ¡¥ï¼ˆå†…æ ¸é›¶æ‹·è´ï¼‰ã€ç‰å¾…åŽŸè¯æ¡¥ä¸Žæ³µå†…æ ‡è®°æ¡¥ã€‚</para>
/// <para>维护约定:æ¡ä»¶ç¼–译åªå…è®¸å‡ºçŽ°åœ¨æœ¬æ–‡ä»¶ï¼›æ–°å¢žå·®å¼‚åº”åœ¨å¯¹åº”æ¡¥æ–¹æ³•å†…æ•´å¯¹æ·»åŠ ï¼ˆ#if/#else ä¸å¾—è·¨æ–‡ä»¶æ‹†åˆ†ï¼‰ï¼Œä¸»é€»è¾‘ä¿æŒæ— #if。</para>
/// </remarks>
public partial class TcpSession
{
#region 连接桥
/// <summary>回填 Unix 域套接å—è·¯å¾„ï¼ˆä½Žç‰ˆæœ¬æ¡†æž¶æ— æ¤ç»ˆç»“点类型,空æ“作)</summary>
/// <param name="socket">已连接的套接å—</param>
/// <param name="local">本地æè¿°</param>
/// <param name="remote">远程æè¿°</param>
private static void TryFillUnixEndPoints(Socket socket, NetUri local, NetUri remote)
{
#if !NETFRAMEWORK && !NETSTANDARD2_0
// Unix域套接å—回填路径,便于日志显示
if (socket.LocalEndPoint is UnixDomainSocketEndPoint localUds && !localUds.ToString().IsNullOrEmpty())
{
local.Type = NetType.Unix;
local.Path = localUds.ToString();
}
if (socket.RemoteEndPoint is UnixDomainSocketEndPoint remoteUds && !remoteUds.ToString().IsNullOrEmpty())
{
remote.Type = NetType.Unix;
remote.Path = remoteUds.ToString();
}
#endif
}
/// <summary>创建 Unix 域套接å—ç»ˆç»“ç‚¹ã€‚ä½Žç‰ˆæœ¬æ¡†æž¶ä¸æ”¯æŒ Unix Domain Socket</summary>
/// <param name="path">本地文件路径</param>
/// <returns>远程终结点</returns>
private static EndPoint CreateUnixEndPoint(String path)
{
#if NETFRAMEWORK || NETSTANDARD2_0
throw new PlatformNotSupportedException(L.T("Unix domain sockets require a target framework of .NET Standard 2.1 or later."));
#else
return new UnixDomainSocketEndPoint(path);
#endif
}
/// <summary>异æ¥è¿žæŽ¥Unix域套接å—,支æŒè¶…æ—¶</summary>
/// <param name="sock">套接å—</param>
/// <param name="ep">远程终结点</param>
/// <param name="timeout">超时时间(毫秒)</param>
/// <param name="cancellationToken">å–æ¶ˆé€šçŸ¥</param>
private static Task ConnectAsync(Socket sock, EndPoint ep, Int32 timeout, CancellationToken cancellationToken)
=> ConnectCoreAsync(sock, timeout, cancellationToken, null, 0, ep, ep);
/// <summary>异æ¥è¿žæŽ¥IPåœ°å€æ•°ç»„ï¼ˆåŒæ—åœ°å€æŒ‰åºå›žé€€ï¼‰ï¼Œæ”¯æŒè¶…æ—¶</summary>
/// <param name="sock">套接å—</param>
/// <param name="addrs">ç›®æ ‡åœ°å€æ•°ç»„</param>
/// <param name="port">ç›®æ ‡ç«¯å£</param>
/// <param name="timeout">超时时间(毫秒)</param>
/// <param name="cancellationToken">å–æ¶ˆé€šçŸ¥</param>
/// <param name="remote">远程æè¿°ï¼Œä»…用于超时异常消æ¯</param>
private static Task ConnectAsync(Socket sock, IPAddress[] addrs, Int32 port, Int32 timeout, CancellationToken cancellationToken, Object remote)
=> ConnectCoreAsync(sock, timeout, cancellationToken, addrs, port, null, remote);
/// <summary>è¿žæŽ¥æ ¸å¿ƒï¼šç»Ÿä¸€TCP与Unix域套接å—çš„åŒæ¥/异æ¥å»ºè¿žåŠè¶…时处ç†</summary>
/// <remarks>
/// <para>æ— è¶…æ—¶ï¼ˆtimeoutå°äºŽç‰äºŽé›¶ï¼‰æ—¶åŒæ¥è¿žæŽ¥ï¼›é«˜ç‰ˆæœ¬.NET用å¯å–消的 ConnectAsync é…é¢„ç®—å–æ¶ˆæºï¼›ä½Žç‰ˆæœ¬ç”¨ BeginConnect+WaitOne 模拟超时。</para>
/// <para>超时异常类型éšç›®æ ‡æ¡†æž¶è€Œå¼‚ï¼šé«˜ç‰ˆæœ¬ä¸ºé¢„ç®—å–æ¶ˆæºè§¦å‘çš„ OperationCanceledException,低版本为本方法抛出的 TimeoutException。</para>
/// </remarks>
/// <param name="sock">套接å—</param>
/// <param name="timeout">超时时间(毫秒),å°äºŽç‰äºŽé›¶è¡¨ç¤ºä¸å¯ç”¨è¶…æ—¶</param>
/// <param name="cancellationToken">å–æ¶ˆé€šçŸ¥</param>
/// <param name="addrs">ç›®æ ‡åœ°å€æ•°ç»„(TCP),与 <paramref name="ep"/> 二选一</param>
/// <param name="port">ç›®æ ‡ç«¯å£ï¼ˆTCP)</param>
/// <param name="ep">远程终结点(Unix域套接å—),与 <paramref name="addrs"/> 二选一</param>
/// <param name="remote">远程æè¿°ï¼Œä»…用于超时异常消æ¯</param>
private static async Task ConnectCoreAsync(Socket sock, Int32 timeout, CancellationToken cancellationToken,
IPAddress[]? addrs, Int32 port, EndPoint? ep, Object? remote)
{
if (timeout <= 0)
{
if (ep != null)
sock.Connect(ep);
else
sock.Connect(addrs!, port);
return;
}
#if NET5_0_OR_GREATER
using var source = new CancellationTokenSource(timeout);
using var cts2 = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, source.Token);
using var _ = cts2.Token.Register(() => sock.Close());
if (ep != null)
await sock.ConnectAsync(ep, cts2.Token).ConfigureAwait(false);
else
await sock.ConnectAsync(addrs!, port, cts2.Token).ConfigureAwait(false);
#else
// é‡‡ç”¨å¼‚æ¥æ¥è§£å†³è¿žæŽ¥è¶…时设置问题
var ar = ep != null ? sock.BeginConnect(ep, null, null) : sock.BeginConnect(addrs!, port, null, null);
if (!ar.AsyncWaitHandle.WaitOne(timeout, true))
{
sock.Close();
throw new TimeoutException(L.T("The connection to server [{0}] timed out! [{1}ms]", remote, timeout));
}
//sock.EndConnect(ar);
await Task.Factory.FromAsync(ar, sock.EndConnect).ConfigureAwait(false);
#endif
}
#endregion
#region SSL æ¡¥
/// <summary>æœåŠ¡ç«¯SSL认è¯ï¼Œæ”¯æŒè¶…æ—¶</summary>
/// <param name="sslStream">SSLæµ</param>
/// <param name="sock">底层套接å—(旧框架超时åŽå…³é—以䏿¢æŒ‚èµ·çš„æ¡æ‰‹è¯»ï¼‰</param>
/// <param name="cert">æœåŠ¡ç«¯è¯ä¹¦</param>
/// <param name="protocols">å议版本</param>
/// <param name="timeout">超时(毫秒),å°äºŽç‰äºŽé›¶è¡¨ç¤ºä¸å¯ç”¨</param>
#if NETCOREAPP
private static async Task AuthenticateAsServerAsync(SslStream sslStream, Socket sock, X509Certificate cert, SslProtocols protocols, Int32 timeout)
{
using var source = timeout > 0 ? new CancellationTokenSource(timeout) : new CancellationTokenSource();
await sslStream.AuthenticateAsServerAsync(new SslServerAuthenticationOptions
{
ServerCertificate = cert,
EnabledSslProtocols = protocols,
CertificateRevocationCheckMode = X509RevocationMode.NoCheck,
}, source.Token).ConfigureAwait(false);
}
#else
private static async Task AuthenticateAsServerAsync(SslStream sslStream, Socket sock, X509Certificate cert, SslProtocols protocols, Int32 timeout)
{
var task = sslStream.AuthenticateAsServerAsync(cert, false, protocols, false);
if (timeout > 0)
{
// æ—§æ¡†æž¶ä¸æ”¯æŒå–消耗时认è¯ã€‚è¶…æ—¶åŽå…ˆå…³é—套接å—䏿¢æŒ‚èµ·çš„æ¡æ‰‹è¯»ï¼Œå†ç‰å¾…任务结æŸä»¥è§‚测其异常
var completed = await Task.WhenAny(task, Task.Delay(timeout)).ConfigureAwait(false);
if (completed != task)
{
sock.Close();
try { await task.ConfigureAwait(false); } catch { }
throw new TimeoutException(L.T("Server SSL authentication timed out after {0} ms.", timeout));
}
}
await task.ConfigureAwait(false);
}
#endif
/// <summary>客户端SSL认è¯ï¼ˆå¼‚æ¥ï¼‰ï¼Œè¿”回认è¯å®Œæˆçš„SSLæµ</summary>
/// <param name="sock">已连接的套接å—</param>
/// <param name="host">ç›®æ ‡ä¸»æœºï¼Œç”¨äºŽ SNI 与è¯ä¹¦æ ¡éªŒ</param>
/// <param name="certs">客户端è¯ä¹¦é›†åˆï¼ˆæœåŠ¡ç«¯è¯·æ±‚å®¢æˆ·ç«¯è¯ä¹¦æ—¶éœ€è¦ï¼‰</param>
/// <param name="sp">å议版本</param>
/// <param name="timeout">超时(毫秒),å°äºŽç‰äºŽé›¶è¡¨ç¤ºä¸å¯ç”¨</param>
/// <param name="cancellationToken">å–æ¶ˆé€šçŸ¥</param>
/// <returns>认è¯å®Œæˆçš„SSLæµ</returns>
private async Task<SslStream> AuthenticateAsClientAsync(Socket sock, String host, X509CertificateCollection certs, SslProtocols sp, Int32 timeout, CancellationToken cancellationToken)
{
var ns = new NetworkStream(sock);
var sslStream = new SslStream(ns, false, OnCertificateValidationCallback);
#if NETCOREAPP
// 超时预算 + è°ƒç”¨æ–¹å–æ¶ˆï¼šä¸¤è€…任一触å‘éƒ½ä¸æ¢æ¡æ‰‹ï¼ˆè¶…时以 OperationCanceledException 呈现,与建连å£å¾„一致)
using var source = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
if (timeout > 0) source.CancelAfter(timeout);
await sslStream.AuthenticateAsClientAsync(
new SslClientAuthenticationOptions
{
TargetHost = host,
ClientCertificates = certs,
EnabledSslProtocols = sp,
CertificateRevocationCheckMode = X509RevocationMode.NoCheck,
},
source.Token).ConfigureAwait(false);
#else
// æ—§æ¡†æž¶ä¸æ”¯æŒå–消耗时认è¯ï¼šè¶…æ—¶/å–æ¶ˆåŽå…ˆå…³é—套接å—䏿¢æŒ‚èµ·çš„æ¡æ‰‹è¯»ï¼Œå†ç‰å¾…任务结æŸä»¥è§‚测其异常
var authTask = sslStream.AuthenticateAsClientAsync(host, certs, sp, false);
if (timeout > 0 || cancellationToken.CanBeCanceled)
{
var completed = await Task.WhenAny(authTask, Task.Delay(timeout > 0 ? timeout : System.Threading.Timeout.Infinite, cancellationToken)).ConfigureAwait(false);
if (completed != authTask)
{
sock.Close();
try { await authTask.ConfigureAwait(false); } catch { }
cancellationToken.ThrowIfCancellationRequested();
throw new TimeoutException(L.T("Client SSL authentication timed out after {0} ms.", timeout));
}
}
await authTask.ConfigureAwait(false);
#endif
return sslStream;
}
#endregion
#region 写入桥
#if NETCOREAPP || NETSTANDARD2_1_OR_GREATER
/// <summary>å‘é€ä¸€æ®µæ•°æ®ï¼ŒçŸè®¡æ•°è‡ªåЍç»å‘。返回时全部å—èŠ‚å·²äº¤ç»™å†…æ ¸ï¼Œå¤±è´¥æŠ›å¼‚å¸¸</summary>
/// <param name="sock">ç›®æ ‡å¥—æŽ¥å—</param>
/// <param name="data">æ•°æ®</param>
/// <param name="deadline">ç»å‘æ€»é¢„ç®—æˆªæ¢æ—¶é—´ï¼ˆRuntime.TickCount64 毫秒),0 ä¸é™åˆ¶</param>
/// <returns>å·²å‘é€å—节数,ç‰äºŽ data.Length</returns>
private static Int32 SendAll(Socket sock, ReadOnlySpan<Byte> data, Int64 deadline)
{
var total = 0;
while (total < data.Length)
{
var sent = sock.Send(data[total..]);
if (sent <= 0) throw new IOException(L.T("Send failed (result={0}), {1}/{2} bytes sent", sent, total, data.Length));
total += sent;
if (deadline > 0 && total < data.Length && Runtime.TickCount64 > deadline)
throw new TimeoutException(L.T("Send timeout, {0}/{1} bytes sent", total, data.Length));
}
return total;
}
#endif
/// <summary>å‘é€ä¸€æ®µå†…å˜ï¼ŒçŸè®¡æ•°è‡ªåЍç»å‘ã€‚ä½Žç‰ˆæœ¬æ¡†æž¶æ— Span å‘é€é‡è½½ï¼Œé™çº§ä¸ºæ‹·è´æ•°ç»„åŽå‘é€</summary>
/// <param name="sock">ç›®æ ‡å¥—æŽ¥å—</param>
/// <param name="data">æ•°æ®</param>
/// <param name="deadline">ç»å‘æ€»é¢„ç®—æˆªæ¢æ—¶é—´ï¼ˆRuntime.TickCount64 毫秒),0 ä¸é™åˆ¶</param>
/// <returns>å·²å‘é€å—节数,ç‰äºŽ data.Length</returns>
private static Int32 SendAllMemory(Socket sock, ReadOnlySpan<Byte> data, Int64 deadline)
{
#if NETCOREAPP || NETSTANDARD2_1_OR_GREATER
return SendAll(sock, data, deadline);
#else
return SendAll(sock, data.ToArray(), 0, data.Length, deadline);
#endif
}
/// <summary>SSL æµå†™ä¸€å—内å˜ã€‚ä½Žç‰ˆæœ¬æ¡†æž¶æ— Span 写é‡è½½ï¼Œé™çº§ä¸ºæ‹·è´æ•°ç»„åŽå†™å‡º</summary>
/// <param name="stream">SSLæµ</param>
/// <param name="data">æ•°æ®</param>
private static void WriteSsl(SslStream stream, ReadOnlySpan<Byte> data)
{
#if NETCOREAPP || NETSTANDARD2_1_OR_GREATER
stream.Write(data);
#else
stream.Write(data.ToArray());
#endif
}
/// <summary>å‘é€å•个数æ®åŒ…:å•节点直写数æ®åŒ…自身缓冲(0 æ‹·è´ 0 分é…),链å¼åŒ…散列写一次系统调用æäº¤æ•´æ¡é“¾</summary>
/// <param name="sock">ç›®æ ‡å¥—æŽ¥å—</param>
/// <param name="pk">æ•°æ®åŒ…</param>
/// <param name="count">总å—节数</param>
/// <param name="deadline">ç»å‘æ€»é¢„ç®—æˆªæ¢æ—¶é—´ï¼ˆRuntime.TickCount64 毫秒),0 ä¸é™åˆ¶</param>
private static void SendPacketData(Socket sock, IPacket pk, Int32 count, Int64 deadline)
{
if (pk.Next == null && pk.TryGetArray(out var segment))
SendAll(sock, segment.Array!, segment.Offset, segment.Count, deadline);
#if NETCOREAPP || NETSTANDARD2_1_OR_GREATER
else if (pk.Next == null)
SendAllMemory(sock, pk.GetMemory().Span, deadline);
#endif
else
SendAll(sock, pk.ToSegments(), count, deadline);
}
#if NET5_0_OR_GREATER
/// <summary>异æ¥å‘é€çš„é¢„ç®—å–æ¶ˆæºã€‚å•写者å¤ç”¨ï¼›æ£å¸¸å®Œæˆæ—¶è§£é™¤è®¡æ—¶ï¼Œè§¦å‘å–æ¶ˆå³è¶…时失败</summary>
private CancellationTokenSource? _sendCts;
/// <summary>å†™æ ¸å¿ƒï¼ˆå¼‚æ¥ï¼‰ï¼šæŠŠæ•´åŒ…(å«é“¾ï¼‰å†™å®Œæ‰è¿”回,ç‰å¾…å¯å†™æœŸé—´ä¸å 线程;失败抛异常</summary>
/// <remarks>失败一律抛异常ã€ä¸åœ¨æ¤ä¸ŠæŠ¥ï¼šè°ƒç”¨æ–¹é¡»å…ˆé‡Šæ”¾å†™é”å†è°ƒ <see cref="ReportSendError"/>,å¦åˆ™ç”¨æˆ·å›žè°ƒå†…冿¬¡ Send 会在åŒä¸€æŠŠå†™é”上二次ç‰å¾…而æ»é”</remarks>
/// <param name="pk">æ•°æ®åŒ…</param>
/// <returns>å·²å‘é€å—èŠ‚æ•°ï¼›æ— å¥—æŽ¥å—返回 -1</returns>
private async ValueTask<Int32> WritePacketAsync(IPacket pk)
{
var count = pk.Total;
if (count == 0) return 0;
// 逿®µå†™å‡ºï¼ˆé€šå¸¸åªæœ‰ä¸€æ®µï¼‰ï¼Œæ¯æ®µå‘完å†å‘下一段
for (var node = pk; node != null; node = node.Next)
{
if (node.Length == 0) continue;
var rs = await WriteMemoryAsync(node.GetMemory()).ConfigureAwait(false);
if (rs < 0) return -1;
}
LastTime = DateTime.Now;
return count;
}
/// <summary>å†™æ ¸å¿ƒï¼ˆå¼‚æ¥ï¼‰ï¼šå†™å®Œä¸€å—å†…å˜æ‰è¿”回,çŸè®¡æ•°è‡ªåЍç»å‘;失败抛异常</summary>
/// <remarks>
/// <para>失败一律抛异常ã€ä¸åœ¨æ¤ä¸ŠæŠ¥ï¼ˆåŒ <see cref="WritePacketAsync"/>)。</para>
/// <para>ä¸æŽ¥æ”¶è°ƒç”¨æ–¹å–æ¶ˆä»¤ç‰Œï¼šå†™ä¾§å–消由 <see cref="SessionBase.Timeout"/> é¢„ç®—æ‰¿æ‹…ï¼ˆä¸ŽåŒæ¥å†™ä¸€è‡´ï¼‰ï¼Œ
/// è°ƒç”¨æ–¹å–æ¶ˆåœ¨ç‰å†™é”与æµå¼è¯»å—(<see cref="Stream.ReadAsync(Byte[], Int32, Int32, CancellationToken)"/>)的边界生效。</para>
/// </remarks>
/// <param name="data">æ•°æ®</param>
/// <returns>å·²å‘é€å—èŠ‚æ•°ï¼›æ— å¥—æŽ¥å—返回 -1</returns>
private async ValueTask<Int32> WriteMemoryAsync(ReadOnlyMemory<Byte> data)
{
var count = data.Length;
if (count == 0) return 0;
var sock = Client;
if (sock == null) return -1;
var total = 0;
// é¢„ç®—å–æ¶ˆæºï¼šæ£å¸¸å®Œæˆä¸å–消(å¯å¤ç”¨ï¼‰ï¼›å·²å–消说明上轮超时失败,é‡å»ºåŽæœ¬è½®ç»“æŸå³ç»ˆæ¢
var cts = _sendCts ??= new CancellationTokenSource();
if (cts.IsCancellationRequested) cts = _sendCts = new CancellationTokenSource();
var timeout = Timeout;
try
{
while (total < count)
{
// æ¯æ¬¡å‘é€å‰é‡è®¾è®¡æ—¶é¢„ç®—
if (timeout > 0) cts.CancelAfter(timeout);
Int32 sent;
if (_Stream is { } stream)
{
// SSL æµå†…部处ç†éƒ¨åˆ†å†™ï¼šæ•´æ®µå†™å®Œæ‰è¿”å›žï¼Œæ— éœ€ç»å‘
await stream.WriteAsync(data[total..], cts.Token).ConfigureAwait(false);
sent = count - total;
}
else
sent = await sock.SendAsync(data[total..], SocketFlags.None, cts.Token).ConfigureAwait(false);
if (sent <= 0) throw new IOException(L.T("Send failed (result={0}), {1}/{2} bytes sent", sent, total, count));
total += sent;
}
}
catch (OperationCanceledException) when (cts.IsCancellationRequested)
{
// é¢„ç®—è¶…æ—¶ï¼šä¸ŽåŒæ¥å‘é€è¶…时一致,按å‘é€å¤±è´¥å¤„ç†ï¼ˆç”±è°ƒç”¨æ–¹åœ¨é”外上报)
throw new TimeoutException(L.T("Send timeout, {0}/{1} bytes sent", total, count));
}
finally
{
// 解除计时预算(触å‘è¿‡å–æ¶ˆçš„å–æ¶ˆæºä¸å¤ç”¨ï¼‰
if (!cts.IsCancellationRequested) cts.CancelAfter(System.Threading.Timeout.Infinite);
}
LastTime = DateTime.Now;
return total;
}
#else
/// <summary>å†™æ ¸å¿ƒï¼ˆå¼‚æ¥ï¼‰ã€‚æœ¬æ¡†æž¶æ— å¸¦å–æ¶ˆä»¤ç‰Œçš„ Socket.SendAsync é‡è½½ï¼Œé™çº§ä¸ºåŒæ¥å†™å®Œï¼ˆä»ç„¶é›¶æ‹·è´ï¼‰ï¼›å¤±è´¥æŠ›å¼‚常</summary>
/// <param name="pk">æ•°æ®åŒ…</param>
/// <returns>å·²å‘é€å—节数</returns>
private ValueTask<Int32> WritePacketAsync(IPacket pk) => new(WritePacket(pk));
/// <summary>å†™æ ¸å¿ƒï¼ˆå¼‚æ¥ï¼Œä½Žç‰ˆæœ¬ TFM)。é™çº§ä¸ºåŒæ¥å†™å®Œï¼›å¤±è´¥æŠ›å¼‚常</summary>
/// <param name="data">æ•°æ®</param>
/// <returns>å·²å‘é€å—节数</returns>
private ValueTask<Int32> WriteMemoryAsync(ReadOnlyMemory<Byte> data) => new(WriteMemory(data.Span));
#endif
#endregion
#region 文件桥
/// <summary>å†…æ ¸é›¶æ‹·è´å‘逿–‡ä»¶ï¼ˆNET6+ ä¸”éž SSL çš„ IP 套接å—)。返回已å‘é€å—节数;ä¸å¯ç”¨æˆ–未å‘å‡ºæ•°æ®æ—¶è¿”回 -1,由调用方é™çº§</summary>
/// <remarks>
/// <para>éž SSL 会è¯ï¼šæ•´ä¸ªæ–‡ä»¶äº¤ç»™å†…æ ¸é›¶æ‹·è´æŽ¨é€ï¼Œç‰å¾…å¯å†™æœŸé—´ä¸å 线程。仅 IP 套接å—å¯ç”¨ï¼šUnix 域套接å—上 sendfile/TransmitFile
/// æ—¢ä¸ä¼šæŽ¨è¿›ä¹Ÿä¸ä¼šå®Œæˆï¼ˆå®žæµ‹æ°¸ä¹…挂起:对端ä¸è¯»æ—¶ 1KB 峿Œ‚,TCP åŒæ¡ä»¶ 0ms 完æˆï¼‰ï¼Œå¿…须排除,å¦åˆ™é™æ€æ–‡ä»¶å“应会一直挂ç€ä¸è¿”回。</para>
/// <para>åªæœ‰ã€Œæ ¹æœ¬æ²¡å‘出任何数æ®ã€çš„失败æ‰å¯å®‰å…¨é™çº§ï¼šå¹³å°/套接å—ç±»åž‹ä¸æ”¯æŒé›¶æ‹·è´ã€æˆ–路径在å˜åœ¨æ€§æ£€æŸ¥åŽè¢«åˆ 除。
/// ç½‘ç»œä¸æ–ã€å¯¹ç«¯å¤ä½ä¸€ç±»å¤±è´¥å¯èƒ½å·²é€å‡ºéƒ¨åˆ†æ–‡ä»¶ï¼Œé‡å‘会让对端收到é‡å¤å†…容,必须交由调用方按å‘é€å¤±è´¥å¤„ç†ã€‚</para>
/// </remarks>
/// <param name="filePath">文件路径</param>
/// <param name="length">文件长度</param>
/// <param name="cancellationToken">å–æ¶ˆä»¤ç‰Œ</param>
/// <returns>å·²å‘é€å—节数;ä¸å¯ç”¨æˆ–未å‘出数æ®è¿”回 -1</returns>
private async ValueTask<Int64> TrySendFileKernelAsync(String filePath, Int64 length, CancellationToken cancellationToken)
{
#if NET6_0_OR_GREATER
if (_Stream == null && Client is { AddressFamily: AddressFamily.InterNetwork or AddressFamily.InterNetworkV6 } sock)
{
try
{
// å†…æ ¸ SendFileAsync(string) æ— è¿”å›žå€¼ï¼ˆæŽ¨å®Œæ•´ä¸ªæ–‡ä»¶å³æˆåŠŸï¼‰ï¼Œä»¥æ–‡ä»¶é•¿åº¦ä½œä¸ºå‘é€é‡
await sock.SendFileAsync(filePath, cancellationToken).ConfigureAwait(false);
LastTime = DateTime.Now;
return length;
}
catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
{
// è°ƒç”¨æ–¹ä¸»åŠ¨å–æ¶ˆï¼šä¸æ˜¯å‘逿•…障,å‘上抛出
throw;
}
catch (Exception ex) when (ex is NotSupportedException or FileNotFoundException)
{
// åªæœ‰ã€Œæ ¹æœ¬æ²¡å‘出任何数æ®ã€çš„失败æ‰èƒ½å®‰å…¨é™çº§ï¼šå¹³å°/套接å—ç±»åž‹ä¸æ”¯æŒé›¶æ‹·è´ã€æˆ–路径在å˜åœ¨æ€§æ£€æŸ¥åŽè¢«åˆ 除。
// ç½‘ç»œä¸æ–ã€å¯¹ç«¯å¤ä½ä¸€ç±»å¤±è´¥å¯èƒ½å·²é€å‡ºéƒ¨åˆ†æ–‡ä»¶ï¼Œé‡å‘会让对端收到é‡å¤å†…容,必须交由外层按å‘é€å¤±è´¥å¤„ç†
WriteLog("Kernel zero-copy is unavailable; falling back to chunked send for {0}: {1}", filePath, ex.Message);
}
}
#endif
return -1;
}
#endregion
#region ç‰å¾…åŽŸè¯æ¡¥
/// <summary>创建信å·å”¤é†’æºã€‚ä½Žç‰ˆæœ¬æ¡†æž¶æ— RunContinuationsAsynchronously 选项</summary>
/// <returns>ä¿¡å·æº</returns>
private static TaskCompletionSource<Boolean> CreateSignalSource()
{
#if NET45
return new TaskCompletionSource<Boolean>();
#else
return new TaskCompletionSource<Boolean>(TaskCreationOptions.RunContinuationsAsynchronously);
#endif
}
/// <summary>ç‰å¾…ä¿¡å·ä»»åŠ¡ï¼Œæ”¯æŒå–æ¶ˆã€‚å–æ¶ˆå³æŠ› OperationCanceledException(与高版本 Task.WaitAsync å£å¾„一致)</summary>
/// <remarks>低版本框架没有 Task.WaitAsync,手工用 WhenAny é™„åŠ å–æ¶ˆï¼›è°ƒç”¨æ–¹å¯¹æœªå¯ç”¨å–消的场景先行çŸè·¯ï¼Œé¿å…多余ç‰å¾…æº</remarks>
/// <param name="task">ä¿¡å·ä»»åŠ¡</param>
/// <param name="cancellationToken">å–æ¶ˆä»¤ç‰Œ</param>
private static async Task WaitSignalAsync(Task<Boolean> task, CancellationToken cancellationToken)
{
#if NET6_0_OR_GREATER
await task.WaitAsync(cancellationToken).ConfigureAwait(false);
#else
var tcs = CreateSignalSource();
using (cancellationToken.Register(() => tcs.TrySetResult(false)))
{
await Task.WhenAny(task, tcs.Task).ConfigureAwait(false);
}
cancellationToken.ThrowIfCancellationRequested();
#endif
}
#endregion
#region æ³µæ ‡è®°æ¡¥
/// <summary>æ³µå†…æ ‡è®°ï¼ˆAsyncLocal)。net45 æ— AsyncLocal,退回线程本地(与 TokenHttpFilter åŒä¸€é™çº§å£å¾„):异æ¥ç»ä½“åˆ‡çº¿ç¨‹åŽæ ‡è®°å¯èƒ½ä¸¢å¤±ï¼Œæ¤æ—¶é€€åŒ–为ç‰åˆ°ä¸Šé™</summary>
private sealed class PumpFlag
{
#if NET45
private readonly ThreadLocal<Boolean> _flag = new();
#else
private readonly AsyncLocal<Boolean> _flag = new();
#endif
/// <summary>æ ‡è®°å€¼ã€‚æ ‡è®°è€…åº”å¤„äºŽæ¶ˆæ¯æ³µå¼‚æ¥æµå†…</summary>
public Boolean Value { get => _flag.Value; set => _flag.Value = value; }
}
#endregion
}
|