From d2ef2e941d32ddd13fc8e84e38cad799a848a259 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8B=A5=E6=B1=9D=E6=A3=8B=E8=8C=97?= <505554090@qq.com> Date: Sat, 6 Dec 2025 15:25:46 +0800 Subject: [PATCH] =?UTF-8?q?=E5=8F=91=E5=B8=83=EF=BC=9A4.0.2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- TouchSocketVersion.props | 2 +- src/TouchSocket.Core/IO/ConsoleAction.cs | 2 +- src/TouchSocket.Core/Text/StringPool.cs | 128 +++++ src/TouchSocket.Core/Text/TextValues.cs | 111 +++-- src/TouchSocket.Http/Common/HttpBase.cs | 200 ++++++-- src/TouchSocket.Http/Common/HttpHeaders.cs | 465 ++++++------------ src/TouchSocket.Http/Common/HttpRequest.cs | 55 ++- src/TouchSocket.Http/Common/HttpResponse.cs | 27 +- .../Common/Internal/InternalHttpCollection.cs | 220 +++++++-- .../Common/Internal/InternalHttpHeader.cs | 2 +- .../Common/TouchSocketHttpUtility.cs | 155 +++--- .../Components/HttpClientBase.cs | 1 - .../Components/HttpSessionClient.cs | 32 +- .../HttpServerDataHandlingAdapter.cs | 9 +- .../HttpContent/HttpContent.cs | 16 - .../HttpContent/ReadonlyMemoryHttpContent.cs | 11 +- .../HttpContent/StreamHttpContent.cs | 7 +- src/TouchSocket.Mqtt/Actors/MqttActor.cs | 5 +- .../Components/Tcp/MqttTcpClient.cs | 7 +- .../ReconnectionOptionsExtension.cs | 101 ++++ .../MqttMessages/V5/MqttConnectMessage_v5.cs | 55 ++- .../V5/MqttSubscribeMessage_v5.cs | 18 +- .../Components/Tcp/TcpClientBase.cs | 2 + .../Components/Tcp/TcpServiceBaseT.cs | 2 +- .../Extensions/TouchSocketConfigExtension.cs | 2 +- src/TouchSocket/Options/ReconnectionOption.cs | 8 +- src/TouchSocket/Options/TcpListenOption.cs | 14 +- src/TouchSocket/Transports/TcpTransport.cs | 1 + 28 files changed, 1007 insertions(+), 651 deletions(-) create mode 100644 src/TouchSocket.Core/Text/StringPool.cs create mode 100644 src/TouchSocket.Mqtt/Extensions/ReconnectionOptionsExtension.cs diff --git a/TouchSocketVersion.props b/TouchSocketVersion.props index 57f3adb47..afe83fca2 100644 --- a/TouchSocketVersion.props +++ b/TouchSocketVersion.props @@ -1,7 +1,7 @@ - 4.0.1 + 4.0.2 diff --git a/src/TouchSocket.Core/IO/ConsoleAction.cs b/src/TouchSocket.Core/IO/ConsoleAction.cs index fc3499a6e..bbb0bb836 100644 --- a/src/TouchSocket.Core/IO/ConsoleAction.cs +++ b/src/TouchSocket.Core/IO/ConsoleAction.cs @@ -137,7 +137,7 @@ public sealed class ConsoleAction var separator = new string('─', maxOrderLength + 4); var distinctActions = new List(); - foreach (var item in this.m_actions.OrderBy(a => a.Value.FullOrder)) + foreach (var item in this.m_actions) { if (!distinctActions.Contains(item.Value.FullOrder.ToLower())) { diff --git a/src/TouchSocket.Core/Text/StringPool.cs b/src/TouchSocket.Core/Text/StringPool.cs new file mode 100644 index 000000000..3c2d051cd --- /dev/null +++ b/src/TouchSocket.Core/Text/StringPool.cs @@ -0,0 +1,128 @@ +// ------------------------------------------------------------------------------ +// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 +// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 +// CSDN博客:https://blog.csdn.net/qq_40374647 +// 哔哩哔哩视频:https://space.bilibili.com/94253567 +// Gitee源代码仓库:https://gitee.com/RRQM_Home +// Github源代码仓库:https://github.com/RRQM +// API首页:https://touchsocket.net/ +// 交流QQ群:234762506 +// 感谢您的下载和使用 +// ------------------------------------------------------------------------------ + +namespace TouchSocket.Core; + +/// +/// 表示一个字符串池,用于高效管理和复用字符串。 +/// +public sealed class StringPool +{ + private readonly Encoding m_encoding; + private readonly int m_maxCapacity; + + private struct Entry + { + public byte[] Bytes; + public string Value; + public int Length; + public byte First; + } + + private readonly Dictionary m_buckets; + + /// + /// 初始化 类的新实例。 + /// + /// 用于字符串编码的 。 + /// 最大容量,默认为 -1 表示不限制。 + /// 时抛出。 + public StringPool(Encoding encoding, int maxCapacity = -1) + { + ThrowHelper.ThrowIfNull(encoding, nameof(encoding)); + this.m_encoding = encoding; + this.m_maxCapacity = maxCapacity; + this.m_buckets = new Dictionary(); + } + + /// + /// 获取与指定字节序列对应的字符串。 + /// + /// 字节序列。 + /// 与字节序列对应的字符串。 + public string Get(ReadOnlySpan bytes) + { + if (bytes.Length == 0) + { + return string.Empty; + } + + var hash = ComputeHash(bytes); + + if (this.m_buckets.TryGetValue(hash, out var entries)) + { + for (var i = 0; i < entries.Length; i++) + { + var e = entries[i]; + if (e.Length == bytes.Length && e.First == bytes[0] && bytes.SequenceEqual(e.Bytes)) + { + return e.Value; + } + } + } + + var str = bytes.ToString(this.m_encoding); + var keyBytes = bytes.ToArray(); + var newEntry = new Entry + { + Bytes = keyBytes, + Value = str, + Length = keyBytes.Length, + First = keyBytes[0] + }; + + if (this.m_buckets.TryGetValue(hash, out var existingEntries)) + { + for (var i = 0; i < existingEntries.Length; i++) + { + var cur = existingEntries[i]; + if (cur.Length == newEntry.Length && cur.First == newEntry.First && new ReadOnlySpan(newEntry.Bytes).SequenceEqual(cur.Bytes)) + { + return cur.Value; + } + } + + if (this.m_maxCapacity == -1 || this.m_buckets.Count < this.m_maxCapacity) + { + var arr = new Entry[existingEntries.Length + 1]; + Array.Copy(existingEntries, arr, existingEntries.Length); + arr[existingEntries.Length] = newEntry; + this.m_buckets[hash] = arr; + } + } + else + { + if (this.m_maxCapacity == -1 || this.m_buckets.Count < this.m_maxCapacity) + { + this.m_buckets[hash] = new[] { newEntry }; + } + } + + return str; + } + + private static int ComputeHash(ReadOnlySpan data) + { + unchecked + { + const uint offset = 2166136261u; + const uint prime = 16777619u; + var hash = offset; + for (var i = 0; i < data.Length; i++) + { + hash ^= data[i]; + hash *= prime; + } + return (int)hash; + } + } +} diff --git a/src/TouchSocket.Core/Text/TextValues.cs b/src/TouchSocket.Core/Text/TextValues.cs index bbe35e748..5a4c0e320 100644 --- a/src/TouchSocket.Core/Text/TextValues.cs +++ b/src/TouchSocket.Core/Text/TextValues.cs @@ -34,7 +34,7 @@ public readonly struct TextValues : IEnumerable, IEquatable /// public TextValues(string value) { - m_values = value; + this.m_values = value; } /// @@ -42,7 +42,7 @@ public readonly struct TextValues : IEnumerable, IEquatable /// public TextValues(string[] values) { - m_values = values; + this.m_values = values; } /// @@ -53,7 +53,7 @@ public readonly struct TextValues : IEnumerable, IEquatable [MethodImpl(MethodImplOptions.AggressiveInlining)] get { - var value = m_values; + var value = this.m_values; if (value is null) { return 0; @@ -74,9 +74,17 @@ public readonly struct TextValues : IEnumerable, IEquatable [MethodImpl(MethodImplOptions.AggressiveInlining)] get { - object value = m_values; - if (value is null) return null; - if (value is string s) return s; + var value = this.m_values; + if (value is null) + { + return null; + } + + if (value is string s) + { + return s; + } + var arr = Unsafe.As(value); return arr.Length > 0 ? arr[0] : null; } @@ -85,7 +93,7 @@ public readonly struct TextValues : IEnumerable, IEquatable /// /// 指示是否为空集合。 /// - public bool IsEmpty => Count == 0; + public bool IsEmpty => this.m_values is null; /// /// 通过索引获取指定值。 @@ -95,15 +103,10 @@ public readonly struct TextValues : IEnumerable, IEquatable [MethodImpl(MethodImplOptions.AggressiveInlining)] get { - object value = m_values; - if (value is null) - { - throw new IndexOutOfRangeException(); - } + var value = this.m_values ?? throw new IndexOutOfRangeException(); if (value is string s) { - if (index == 0) return s; - throw new IndexOutOfRangeException(); + return index == 0 ? s : throw new IndexOutOfRangeException(); } return Unsafe.As(value)[index]; } @@ -153,7 +156,7 @@ public readonly struct TextValues : IEnumerable, IEquatable { return this; } - var current = m_values; + var current = this.m_values; if (current is null) { return new TextValues(value); @@ -174,7 +177,7 @@ public readonly struct TextValues : IEnumerable, IEquatable /// public bool Equals(string other, StringComparison comparison) { - return Equals(new TextValues(other)); + return this.Equals(new TextValues(other)); } /// @@ -182,17 +185,17 @@ public readonly struct TextValues : IEnumerable, IEquatable /// public bool Equals(TextValues other) { - if (ReferenceEquals(m_values, other.m_values)) return true; - int count = Count; + if (ReferenceEquals(this.m_values, other.m_values)) return true; + var count = this.Count; if (count != other.Count) return false; if (count == 0) return true; if (count == 1) { - return string.Equals(First, other.First, StringComparison.Ordinal); + return string.Equals(this.First, other.First, StringComparison.Ordinal); } - var a = ToArray(); + var a = this.ToArray(); var b = other.ToArray(); - for (int i = 0; i < a.Length; i++) + for (var i = 0; i < a.Length; i++) { if (!string.Equals(a[i], b[i], StringComparison.Ordinal)) return false; } @@ -202,31 +205,31 @@ public readonly struct TextValues : IEnumerable, IEquatable /// /// 判断相等。 /// - public override bool Equals(object obj) => obj is TextValues v && Equals(v); + public override bool Equals(object obj) => obj is TextValues v && this.Equals(v); /// /// 获取枚举器。 /// - public Enumerator GetEnumerator() => new Enumerator(m_values); + public Enumerator GetEnumerator() => new Enumerator(this.m_values); - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator(); - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator(); /// /// 获取哈希码。 /// public override int GetHashCode() { - object value = m_values; + var value = this.m_values; if (value is null) return 0; if (value is string s) { return s?.GetHashCode() ?? 0; } var arr = Unsafe.As(value); - int hash = 17; - for (int i = 0; i < arr.Length; i++) + var hash = 17; + for (var i = 0; i < arr.Length; i++) { hash = hash * 31 + (arr[i]?.GetHashCode() ?? 0); } @@ -238,10 +241,10 @@ public readonly struct TextValues : IEnumerable, IEquatable /// public string[] ToArray() { - object value = m_values; + var value = this.m_values; if (value is null) { - return Array.Empty(); + return []; } if (value is string s) { @@ -255,7 +258,7 @@ public readonly struct TextValues : IEnumerable, IEquatable /// public override string ToString() { - object value = m_values; + var value = this.m_values; if (value is null) { return string.Empty; @@ -265,8 +268,16 @@ public readonly struct TextValues : IEnumerable, IEquatable return s ?? string.Empty; } var arr = Unsafe.As(value); - if (arr.Length == 0) return string.Empty; - if (arr.Length == 1) return arr[0] ?? string.Empty; + if (arr.Length == 0) + { + return string.Empty; + } + + if (arr.Length == 1) + { + return arr[0] ?? string.Empty; + } + return string.Join(",", arr); } @@ -281,22 +292,22 @@ public readonly struct TextValues : IEnumerable, IEquatable internal Enumerator(object values) { - m_values = values; - m_index = -1; - m_current = null; + this.m_values = values; + this.m_index = -1; + this.m_current = null; } /// /// 当前值。 /// - public string Current => m_current; + public readonly string Current => this.m_current; - object IEnumerator.Current => Current; + readonly object IEnumerator.Current => this.Current; /// /// 释放资源。 /// - public void Dispose() + public readonly void Dispose() { } @@ -305,26 +316,26 @@ public readonly struct TextValues : IEnumerable, IEquatable /// public bool MoveNext() { - if (m_values is null) + if (this.m_values is null) { return false; } - if (m_values is string s) + if (this.m_values is string s) { - if (m_index < 0) + if (this.m_index < 0) { - m_index = 0; - m_current = s; + this.m_index = 0; + this.m_current = s; return true; } return false; } - var arr = Unsafe.As(m_values); - int next = m_index + 1; + var arr = Unsafe.As(this.m_values); + var next = this.m_index + 1; if ((uint)next < (uint)arr.Length) { - m_index = next; - m_current = arr[next]; + this.m_index = next; + this.m_current = arr[next]; return true; } return false; @@ -335,8 +346,8 @@ public readonly struct TextValues : IEnumerable, IEquatable /// public void Reset() { - m_index = -1; - m_current = null; + this.m_index = -1; + this.m_current = null; } } diff --git a/src/TouchSocket.Http/Common/HttpBase.cs b/src/TouchSocket.Http/Common/HttpBase.cs index 1c64dc2ed..cde072f14 100644 --- a/src/TouchSocket.Http/Common/HttpBase.cs +++ b/src/TouchSocket.Http/Common/HttpBase.cs @@ -10,6 +10,7 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ +using System.Buffers; using System.Diagnostics; using System.Reflection; using System.Runtime.CompilerServices; @@ -37,6 +38,11 @@ public abstract class HttpBase : IRequestInfo private readonly InternalHttpHeader m_headers = new InternalHttpHeader(); + private readonly StringPool m_stringPool = new StringPool(Encoding.UTF8,32); + + private bool m_isChunk; + private long m_contentLength; + /// /// 可接受MIME类型 /// @@ -70,13 +76,10 @@ public abstract class HttpBase : IRequestInfo /// public bool IsChunk { - get - { - var transferEncoding = this.Headers.Get(HttpHeaders.TransferEncoding); - return "chunked".Equals(transferEncoding, StringComparison.OrdinalIgnoreCase); - } + get => this.m_isChunk; set { + this.m_isChunk = value; if (value) { this.Headers.Add(HttpHeaders.TransferEncoding, "chunked"); @@ -93,12 +96,12 @@ public abstract class HttpBase : IRequestInfo /// public long ContentLength { - get + get => this.m_contentLength; + set { - var contentLength = this.m_headers.Get(HttpHeaders.ContentLength); - return contentLength.IsEmpty ? 0 : long.TryParse(contentLength, out var value) ? value : 0; + this.m_contentLength = value; + this.m_headers.Add(HttpHeaders.ContentLength, value.ToString()); } - set => this.m_headers.Add(HttpHeaders.ContentLength, value.ToString()); } /// @@ -133,7 +136,8 @@ public abstract class HttpBase : IRequestInfo [MethodImpl(MethodImplOptions.AggressiveInlining)] internal bool ParsingHeader(ref TReader reader) where TReader : IBytesReader { - var index = ReaderExtension.IndexOf(ref reader, TouchSocketHttpUtility.CRLFCRLF); + var sequence = reader.Sequence; + var index = sequence.IndexOf(TouchSocketHttpUtility.CRLFCRLF); if (index < 0) { @@ -150,10 +154,21 @@ public abstract class HttpBase : IRequestInfo } // 一次性获取头部数据,减少多次调用GetSpan的开销 - var headerSpan = reader.GetSpan(headerLength); + if (headerLength < 1024) + { + Span headerSpan = stackalloc byte[headerLength]; - // 调用优化后的头部解析方法 - this.ReadHeadersOptimized(headerSpan); + sequence.Slice(0, headerLength).CopyTo(headerSpan); + + // 调用优化后的头部解析方法 + this.ReadHeadersOptimized(headerSpan); + } + else + { + var headerSpan = reader.GetSpan(headerLength); + // 调用优化后的头部解析方法 + this.ReadHeadersOptimized(headerSpan); + } // 跳过头部数据和 \r\n\r\n 分隔符 reader.Advance(totalRequired); @@ -164,6 +179,8 @@ public abstract class HttpBase : IRequestInfo { this.m_headers.Clear(); this.ContentStatus = ContentCompletionStatus.Unknown; + this.m_isChunk = false; + this.m_contentLength = 0; } /// @@ -178,10 +195,10 @@ public abstract class HttpBase : IRequestInfo var colonIndex = line.IndexOf(TouchSocketHttpUtility.COLON); if (colonIndex <= 0) { - return; // 无效格式,冒号必须存在且不能在第一位 + return; } - var keySpan = TouchSocketHttpUtility.TrimWhitespace(line.Slice(0, colonIndex)); + var keySpan = line.Slice(0, colonIndex); var valueSpan = TouchSocketHttpUtility.TrimWhitespace(line.Slice(colonIndex + 1)); if (keySpan.IsEmpty || valueSpan.IsEmpty) @@ -189,47 +206,164 @@ public abstract class HttpBase : IRequestInfo return; } - var key = keySpan.ToString(Encoding.UTF8).ToLowerInvariant(); - - // 检测是否包含英文逗号,按需分割 - if (valueSpan.IndexOf((byte)',') < 0) + if (keySpan.Length == 14 && EqualsIgnoreCaseAscii(keySpan, "Content-Length"u8)) { - var value = valueSpan.ToString(Encoding.UTF8); - this.m_headers.Add(key, value); + if (TryParseLong(valueSpan, out var length)) + { + this.m_contentLength = length; + } + var value = this.m_stringPool.Get(valueSpan); + this.m_headers.AddInternal(HttpHeaders.ContentLength, value); return; } - // 多值处理 - var values = new List(); - int start = 0; + if (keySpan.Length == 17 && EqualsIgnoreCaseAscii(keySpan, "Transfer-Encoding"u8)) + { + this.m_isChunk = EqualsIgnoreCaseAscii(valueSpan, "chunked"u8); + var value = this.m_stringPool.Get(valueSpan); + this.m_headers.AddInternal(HttpHeaders.TransferEncoding, value); + return; + } + + var key = this.m_stringPool.Get(keySpan); + + var commaCount = 0; + for (var i = 0; i < valueSpan.Length; i++) + { + if (valueSpan[i] == (byte)',') + { + commaCount++; + } + } + + if (commaCount == 0) + { + var value = this.m_stringPool.Get(valueSpan); + this.m_headers.AddInternal(key, value); + return; + } + + var values = new string[commaCount + 1]; + var valueIndex = 0; + var start = 0; + while (start < valueSpan.Length) { - int commaIndex = valueSpan.Slice(start).IndexOf((byte)','); + var nextComma = valueSpan.Slice(start).IndexOf((byte)','); ReadOnlySpan segment; - if (commaIndex >= 0) + + if (nextComma >= 0) { - segment = valueSpan.Slice(start, commaIndex); - start += commaIndex + 1; + segment = valueSpan.Slice(start, nextComma); + start += nextComma + 1; } else { segment = valueSpan.Slice(start); start = valueSpan.Length; } + segment = TouchSocketHttpUtility.TrimWhitespace(segment); if (!segment.IsEmpty) { - values.Add(segment.ToString(Encoding.UTF8)); + values[valueIndex++] = this.m_stringPool.Get(segment); } } - if (values.Count == 1) + + if (valueIndex == 0) { - this.m_headers.Add(key, values[0]); + return; } - else if (values.Count > 1) + + if (valueIndex == 1) { - this.m_headers.Add(key, values.ToArray()); + this.m_headers.AddInternal(key, values[0]); } + else if (valueIndex < values.Length) + { + var trimmedValues = new string[valueIndex]; + Array.Copy(values, trimmedValues, valueIndex); + this.m_headers.AddInternal(key, trimmedValues); + } + else + { + this.m_headers.AddInternal(key, values); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool EqualsIgnoreCaseAscii(ReadOnlySpan span1, ReadOnlySpan span2) + { + if (span1.Length != span2.Length) + { + return false; + } + + for (var i = 0; i < span1.Length; i++) + { + var c1 = span1[i]; + var c2 = span2[i]; + + if (c1 == c2) + { + continue; + } + + if ((uint)(c1 - 'A') <= 'Z' - 'A') + { + c1 = (byte)(c1 | 0x20); + } + + if ((uint)(c2 - 'A') <= 'Z' - 'A') + { + c2 = (byte)(c2 | 0x20); + } + + if (c1 != c2) + { + return false; + } + } + + return true; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool TryParseLong(ReadOnlySpan span, out long result) + { + result = 0; + if (span.IsEmpty) + { + return false; + } + + var sign = 1; + var index = 0; + + if (span[0] == (byte)'-' || span[0] == (byte)'+') + { + sign = span[0] == (byte)'-' ? -1 : 1; + index++; + if (index >= span.Length) + { + return false; + } + } + + for (var i = index; i < span.Length; i++) + { + var c = span[i]; + if (c < (byte)'0' || c > (byte)'9') + { + return false; + } + + var digit = c - '0'; + result = result * 10 + digit; + } + + result *= sign; + return true; } [MethodImpl(MethodImplOptions.AggressiveInlining)] diff --git a/src/TouchSocket.Http/Common/HttpHeaders.cs b/src/TouchSocket.Http/Common/HttpHeaders.cs index aa04523f0..fb5caf290 100644 --- a/src/TouchSocket.Http/Common/HttpHeaders.cs +++ b/src/TouchSocket.Http/Common/HttpHeaders.cs @@ -10,6 +10,8 @@ // 感谢您的下载和使用 //------------------------------------------------------------------------------ +using System.Runtime.CompilerServices; + namespace TouchSocket.Http; /// /// 请求头静态类 @@ -280,328 +282,167 @@ public static class HttpHeaders /// Content-Disposition /// public const string ContentDisposition = "Content-Disposition"; -} -///// -///// 请求头枚举 -///// -//public enum HttpHeaders : byte -//{ -// /// -// /// Cache-Control 标头,指定请求/响应链上所有缓存控制机制必须服从的指令。 -// /// -// [Description("Cache-Control")] -// CacheControl = 0, + internal static bool IsPredefinedHeader(string key) + { + var keyLength = key.Length; + if (keyLength == 0) + { + return false; + } -// /// -// /// Connection 标头,指定特定连接需要的选项。 -// /// -// [Description("Connection")] -// Connection = 1, + var firstChar = char.ToLowerInvariant(key[0]); -// /// -// /// Date 标头,指定开始创建请求的日期和时间。 -// /// -// [Description("Date")] -// Date = 2, + return firstChar switch + { + 'a' => IsAGroupHeader(key, keyLength), + 'c' => IsCGroupHeader(key, keyLength), + 'd' => keyLength == 4 && ReferenceEquals(key, HttpHeaders.Date), + 'e' => IsEGroupHeader(key, keyLength), + 'f' => keyLength == 4 && ReferenceEquals(key, HttpHeaders.From), + 'h' => keyLength == 4 && ReferenceEquals(key, HttpHeaders.Host), + 'i' => IsIGroupHeader(key, keyLength), + 'k' => keyLength == 9 && ReferenceEquals(key, HttpHeaders.KeepAlive), + 'l' => keyLength == 8 && ReferenceEquals(key, HttpHeaders.Location), + 'm' => keyLength == 11 && ReferenceEquals(key, HttpHeaders.MaxForwards), + 'o' => keyLength == 6 && ReferenceEquals(key, HttpHeaders.Origin), + 'p' => IsPGroupHeader(key, keyLength), + 'r' => IsRGroupHeader(key, keyLength), + 's' => IsSGroupHeader(key, keyLength), + 't' => IsTGroupHeader(key, keyLength), + 'u' => keyLength == 10 && ReferenceEquals(key, HttpHeaders.UserAgent), + 'v' => IsVGroupHeader(key, keyLength), + 'w' => keyLength == 13 && ReferenceEquals(key, HttpHeaders.WwwAuthenticate), + _ => false + }; + } -// /// -// /// Keep-Alive 标头,指定用以维护持久性连接的参数。 -// /// -// [Description("Keep-Alive")] -// KeepAlive = 3, + #region 各分组Header判断(模块化,易维护) + [MethodImpl(MethodImplOptions.AggressiveInlining | (MethodImplOptions)512)] + private static bool IsAGroupHeader(string key, int keyLength) + { + // 预定义长度:Age(3)、Allow(5)、Accept(6)、Accept-Ranges(12)、Accept-Charset/Authorization(13)、Accept-Encoding(14)、Accept-Language(15) + return keyLength switch + { + 3 => ReferenceEquals(key, HttpHeaders.Age), + 5 => ReferenceEquals(key, HttpHeaders.Allow), + 6 => ReferenceEquals(key, HttpHeaders.Accept), + 12 => ReferenceEquals(key, HttpHeaders.AcceptRanges), + 13 => ReferenceEquals(key, HttpHeaders.AcceptCharset) || ReferenceEquals(key, HttpHeaders.Authorization), + 14 => ReferenceEquals(key, HttpHeaders.AcceptEncoding), + 15 => ReferenceEquals(key, HttpHeaders.AcceptLanguage), + _ => false // 长度不匹配直接返回false + }; + } -// /// -// /// Pragma 标头,指定可应用于请求/响应链上的任何代理的特定于实现的指令。 -// /// -// [Description("Pragma")] -// Pragma = 4, + [MethodImpl(MethodImplOptions.AggressiveInlining | (MethodImplOptions)512)] + private static bool IsCGroupHeader(string key, int keyLength) + { + // 预定义长度:Cookie(6)、Connection/Content-MD5(10)、Content-Type(12)、Cache-Control/Content-Range(13)、Content-Length(14)、Content-Encoding(15)、Content-Language/Content-Location(16)、Content-Disposition(19) + return keyLength switch + { + 6 => ReferenceEquals(key, HttpHeaders.Cookie), + 10 => ReferenceEquals(key, HttpHeaders.Connection) || ReferenceEquals(key, HttpHeaders.ContentMd5), + 12 => ReferenceEquals(key, HttpHeaders.ContentType), + 13 => ReferenceEquals(key, HttpHeaders.CacheControl) || ReferenceEquals(key, HttpHeaders.ContentRange), + 14 => ReferenceEquals(key, HttpHeaders.ContentLength), + 15 => ReferenceEquals(key, HttpHeaders.ContentEncoding), + 16 => ReferenceEquals(key, HttpHeaders.ContentLanguage) || ReferenceEquals(key, HttpHeaders.ContentLocation), + 19 => ReferenceEquals(key, HttpHeaders.ContentDisposition), + _ => false + }; + } -// /// -// /// Trailer 标头,指定标头字段显示在以 chunked 传输编码方式编码的消息的尾部。 -// /// -// [Description("Trailer")] -// Trailer = 5, + [MethodImpl(MethodImplOptions.AggressiveInlining | (MethodImplOptions)512)] + private static bool IsEGroupHeader(string key, int keyLength) + { + // 预定义长度:ETag(4)、Expect(6)、Expires(7) + return keyLength switch + { + 4 => ReferenceEquals(key, HttpHeaders.ETag), + 6 => ReferenceEquals(key, HttpHeaders.Expect), + 7 => ReferenceEquals(key, HttpHeaders.Expires), + _ => false + }; + } -// /// -// /// Transfer-Encoding 标头,指定对消息正文应用的转换的类型(如果有)。 -// /// -// [Description("Transfer-Encoding")] -// TransferEncoding = 6, + [MethodImpl(MethodImplOptions.AggressiveInlining | (MethodImplOptions)512)] + private static bool IsIGroupHeader(string key, int keyLength) + { + // 预定义长度:If-Match/If-Range(7)、If-None-Match(11)、If-Modified-Since(15)、If-Unmodified-Since(18) + return keyLength switch + { + 7 => ReferenceEquals(key, HttpHeaders.IfMatch) || ReferenceEquals(key, HttpHeaders.IfRange), + 11 => ReferenceEquals(key, HttpHeaders.IfNoneMatch), + 15 => ReferenceEquals(key, HttpHeaders.IfModifiedSince), + 18 => ReferenceEquals(key, HttpHeaders.IfUnmodifiedSince), + _ => false + }; + } -// /// -// /// Upgrade 标头,指定客户端支持的附加通信协议。 -// /// -// [Description("Upgrade")] -// Upgrade = 7, + [MethodImpl(MethodImplOptions.AggressiveInlining | (MethodImplOptions)512)] + private static bool IsPGroupHeader(string key, int keyLength) + { + // 预定义长度:Pragma(6)、Proxy-Authorization/Proxy-Authenticate(16) + return keyLength switch + { + 6 => ReferenceEquals(key, HttpHeaders.Pragma), + 16 => ReferenceEquals(key, HttpHeaders.ProxyAuthorization) || ReferenceEquals(key, HttpHeaders.ProxyAuthenticate), + _ => false + }; + } -// /// -// /// Via 标头,指定网关和代理程序要使用的中间协议。 -// /// -// [Description("Via")] -// Via = 8, + [MethodImpl(MethodImplOptions.AggressiveInlining | (MethodImplOptions)512)] + private static bool IsRGroupHeader(string key, int keyLength) + { + // 预定义长度:Range(5)、Referer(6)、Retry-After(10) + return keyLength switch + { + 5 => ReferenceEquals(key, HttpHeaders.Range), + 6 => ReferenceEquals(key, HttpHeaders.Referer), + 10 => ReferenceEquals(key, HttpHeaders.RetryAfter), + _ => false + }; + } -// /// -// /// Warning 标头,指定关于可能未在消息中反映的消息的状态或转换的附加信息。 -// /// -// [Description("Warning")] -// Warning = 9, + [MethodImpl(MethodImplOptions.AggressiveInlining | (MethodImplOptions)512)] + private static bool IsSGroupHeader(string key, int keyLength) + { + // 预定义长度:Server(6)、Set-Cookie(8) + return keyLength switch + { + 6 => ReferenceEquals(key, HttpHeaders.Server), + 8 => ReferenceEquals(key, HttpHeaders.SetCookie), + _ => false + }; + } -// /// -// /// Allow 标头,指定支持的 HTTP 方法集。 -// /// -// [Description("Allow")] -// Allow = 10, + [MethodImpl(MethodImplOptions.AggressiveInlining | (MethodImplOptions)512)] + private static bool IsTGroupHeader(string key, int keyLength) + { + // 预定义长度:TE(2)、Trailer(7)、Translate(8)、Transfer-Encoding(16) + return keyLength switch + { + 2 => ReferenceEquals(key, HttpHeaders.Te), + 7 => ReferenceEquals(key, HttpHeaders.Trailer), + 8 => ReferenceEquals(key, HttpHeaders.Translate), + 16 => ReferenceEquals(key, HttpHeaders.TransferEncoding), + _ => false + }; + } -// /// -// /// Content-Length 标头,指定伴随正文数据的长度(以字节为单位)。 -// /// -// [Description("Content-Length")] -// ContentLength = 11, + [MethodImpl(MethodImplOptions.AggressiveInlining | (MethodImplOptions)512)] + private static bool IsVGroupHeader(string key, int keyLength) + { + // 预定义长度:Via(3)、Vary(4)、Warning(7) + return keyLength switch + { + 3 => ReferenceEquals(key, HttpHeaders.Via), + 4 => ReferenceEquals(key, HttpHeaders.Vary), + 7 => ReferenceEquals(key, HttpHeaders.Warning), + _ => false + }; + } + #endregion -// /// -// /// Content-Type 标头,指定伴随正文数据的 MIME 类型。 -// /// -// [Description("Content-Type")] -// ContentType = 12, - -// /// -// /// Content-Encoding 标头,指定已应用于伴随正文数据的编码。 -// /// -// [Description("Content-Encoding")] -// ContentEncoding = 13, - -// /// -// /// Content-Langauge 标头,指定伴随正文数据的自然语言。 -// /// -// [Description("Content-Langauge")] -// ContentLanguage = 14, - -// /// -// /// Content-Location 标头,指定可从其中获得伴随正文的 URI。 -// /// -// [Description("Content-Location")] -// ContentLocation = 15, - -// /// -// /// Content-MD5 标头,指定伴随正文数据的 MD5 摘要,用于提供端到端消息完整性检查。 -// /// -// [Description("Content-MD5")] -// ContentMd5 = 16, - -// /// -// /// Content-Range 标头,指定在完整正文中应用伴随部分正文数据的位置。 -// /// -// [Description("Content-Range")] -// ContentRange = 17, - -// /// -// /// Expires 标头,指定日期和时间,在此之后伴随的正文数据应视为陈旧的。 -// /// -// [Description("Expires")] -// Expires = 18, - -// /// -// /// Last-Modified 标头,指定上次修改伴随的正文数据的日期和时间。 -// /// -// [Description("Last-Modified")] -// LastModified = 19, - -// /// -// /// Accept 标头,指定响应可接受的 MIME 类型。 -// /// -// [Description("Accept")] -// Accept = 20, - -// /// -// /// Accept-Charset 标头,指定响应可接受的字符集。 -// /// -// [Description("Accept-Charset")] -// AcceptCharset = 21, - -// /// -// /// Accept-Encoding 标头,指定响应可接受的内容编码。 -// /// -// [Description("Accept-Encoding")] -// AcceptEncoding = 22, - -// /// -// /// Accept-Langauge 标头,指定响应首选的自然语言。 -// /// -// [Description("Accept-Langauge")] -// AcceptLanguage = 23, - -// /// -// /// Authorization 标头,指定客户端为向服务器验证自身身份而出示的凭据。 -// /// -// [Description("Authorization")] -// Authorization = 24, - -// /// -// /// Cookie 标头,指定向服务器提供的 Cookie 数据。 -// /// -// [Description("Cookie")] -// Cookie = 25, - -// /// -// /// Expect 标头,指定客户端要求的特定服务器行为。 -// /// -// [Description("Expect")] -// Expect = 26, - -// /// -// /// From 标头,指定控制请求用户代理的用户的 Internet 电子邮件地址。 -// /// -// [Description("From")] -// From = 27, - -// /// -// /// Host 标头,指定所请求资源的主机名和端口号。 -// /// -// [Description("Host")] -// Host = 28, - -// /// -// /// If-Match 标头,指定仅当客户端的指示资源的缓存副本是最新的时,才执行请求的操作。 -// /// -// [Description("If-Match")] -// IfMatch = 29, - -// /// -// /// If-Modified-Since 标头,指定仅当自指示的数据和时间之后修改了请求的资源时,才执行请求的操作。 -// /// -// [Description("If-Modified-Since")] -// IfModifiedSince = 30, - -// /// -// /// If-None-Match 标头,指定仅当客户端的指示资源的缓存副本都不是最新的时,才执行请求的操作。 -// /// -// [Description("If-None-Match")] -// IfNoneMatch = 31, - -// /// -// /// If-Range 标头,指定如果客户端的缓存副本是最新的,仅发送指定范围的请求资源。 -// /// -// [Description("If-Range")] -// IfRange = 32, - -// /// -// /// If-Unmodified-Since 标头,指定仅当自指示的日期和时间之后修改了请求的资源时,才执行请求的操作。 -// /// -// [Description("If-Unmodified-Since")] -// IfUnmodifiedSince = 33, - -// /// -// /// Max-Forwards 标头,指定一个整数,表示此请求还可转发的次数。 -// /// -// [Description("Max-Forwards")] -// MaxForwards = 34, - -// /// -// /// Proxy-Authorization 标头,指定客户端为向代理验证自身身份而出示的凭据。 -// /// -// [Description("Proxy-Authorization")] -// ProxyAuthorization = 35, - -// /// -// /// Referer 标头,指定从中获得请求 URI 的资源的 URI。 -// /// -// [Description("Referer")] -// Referer = 36, - -// /// -// /// Range 标头,指定代替整个响应返回的客户端请求的响应的子范围。 -// /// -// [Description("Range")] -// Range = 37, - -// /// -// /// TE 标头,指定响应可接受的传输编码方式。 -// /// -// [Description("TE")] -// Te = 38, - -// /// -// /// Translate 标头,与 WebDAV 功能一起使用的 HTTP 规范的 Microsoft 扩展。 -// /// -// [Description("Translate")] -// Translate = 39, - -// /// -// /// User-Agent 标头,指定有关客户端代理的信息。 -// /// -// [Description("User-Agent")] -// UserAgent = 40, - -// /// -// /// Accept-Ranges 标头,指定服务器接受的范围。 -// /// -// [Description("Accept-Ranges")] -// AcceptRanges = 41, - -// /// -// /// Age 标头,指定自起始服务器生成响应以来的时间长度(以秒为单位)。 -// /// -// [Description("Age")] -// Age = 42, - -// /// -// /// Etag 标头,指定请求的变量的当前值。 -// /// -// [Description("Etag")] -// ETag = 43, - -// /// -// /// Location 标头,指定为获取请求的资源而将客户端重定向到的 URI。 -// /// -// [Description("Location")] -// Location = 44, - -// /// -// /// Proxy-Authenticate 标头,指定客户端必须对代理验证其自身。 -// /// -// [Description("Proxy-Authenticate")] -// ProxyAuthenticate = 45, - -// /// -// /// Retry-After 标头,指定某个时间(以秒为单位)或日期和时间,在此时间之后客户端可以重试其请求。 -// /// -// [Description("Retry-After")] -// RetryAfter = 46, - -// /// -// /// Server 标头,指定关于起始服务器代理的信息。 -// /// -// [Description("Server")] -// Server = 47, - -// /// -// /// Set-Cookie 标头,指定提供给客户端的 Cookie 数据。 -// /// -// [Description("Set-Cookie")] -// SetCookie = 48, - -// /// -// /// Vary 标头,指定用于确定缓存的响应是否为新响应的请求标头。 -// /// -// [Description("Vary")] -// Vary = 49, - -// /// -// /// WWW-Authenticate 标头,指定客户端必须对服务器验证其自身。 -// /// -// [Description("WWW-Authenticate")] -// WwwAuthenticate = 50, - -// /// -// /// Origin。 -// /// -// [Description("Origin")] -// Origin = 51, - -// /// -// /// Content-Disposition -// /// -// [Description("Content-Disposition")] -// ContentDisposition = 52 -//} \ No newline at end of file +} \ No newline at end of file diff --git a/src/TouchSocket.Http/Common/HttpRequest.cs b/src/TouchSocket.Http/Common/HttpRequest.cs index 818790425..7a284c258 100644 --- a/src/TouchSocket.Http/Common/HttpRequest.cs +++ b/src/TouchSocket.Http/Common/HttpRequest.cs @@ -193,28 +193,26 @@ public class HttpRequest : HttpBase { var start = 0; - // 解析 HTTP Method (GET/POST) var methodEnd = TouchSocketHttpUtility.FindNextWhitespace(requestLineSpan, start); if (methodEnd == -1) { throw new Exception("Invalid HTTP request line: " + requestLineSpan.ToString(Encoding.UTF8)); } - this.Method = new HttpMethod(requestLineSpan.Slice(start, methodEnd - start).ToString(Encoding.UTF8)); + var methodSpan = requestLineSpan.Slice(start, methodEnd - start); + this.Method = ParseHttpMethodFast(methodSpan); start = TouchSocketHttpUtility.SkipSpaces(requestLineSpan, methodEnd + 1); - // 解析 URL var urlEnd = TouchSocketHttpUtility.FindNextWhitespace(requestLineSpan, start); if (urlEnd == -1) { this.URL = TouchSocketHttpUtility.UnescapeDataString(requestLineSpan.Slice(start)); - return; // No protocol version + return; } this.URL = TouchSocketHttpUtility.UnescapeDataString(requestLineSpan.Slice(start, urlEnd - start)); start = TouchSocketHttpUtility.SkipSpaces(requestLineSpan, urlEnd + 1); - // 解析 Protocol (HTTP/1.1) var protocolSpan = requestLineSpan.Slice(start); var slashIndex = protocolSpan.IndexOf((byte)'/'); if (slashIndex > 0 && slashIndex < protocolSpan.Length - 1) @@ -224,6 +222,53 @@ public class HttpRequest : HttpBase } } + private static HttpMethod ParseHttpMethodFast(ReadOnlySpan span) + { + switch (span.Length) + { + case 3: + if (EqualsIgnoreCaseAscii(span, "GET")) return HttpMethod.Get; + if (EqualsIgnoreCaseAscii(span, "PUT")) return HttpMethod.Put; + break; + case 4: + if (EqualsIgnoreCaseAscii(span, "POST")) return HttpMethod.Post; + break; + case 6: + if (EqualsIgnoreCaseAscii(span, "DELETE")) return HttpMethod.Delete; + break; + case 7: + if (EqualsIgnoreCaseAscii(span, "CONNECT")) return HttpMethod.Connect; + break; + } + return new HttpMethod(span.ToString(Encoding.UTF8)); + } + + private static bool EqualsIgnoreCaseAscii(ReadOnlySpan span, string token) + { + if (span.Length != token.Length) + { + return false; + } + for (var i = 0; i < span.Length; i++) + { + var b = span[i]; + if (b >= (byte)'a' && b <= (byte)'z') + { + b = (byte)(b - 32); + } + var c = (byte)token[i]; + if (c >= (byte)'a' && c <= (byte)'z') + { + c = (byte)(c - 32); + } + if (b != c) + { + return false; + } + } + return true; + } + private static void GetParameters(ReadOnlySpan querySpan, InternalHttpParams parameters) { while (!querySpan.IsEmpty) diff --git a/src/TouchSocket.Http/Common/HttpResponse.cs b/src/TouchSocket.Http/Common/HttpResponse.cs index 39910560f..4cb19da6f 100644 --- a/src/TouchSocket.Http/Common/HttpResponse.cs +++ b/src/TouchSocket.Http/Common/HttpResponse.cs @@ -101,9 +101,9 @@ public abstract class HttpResponse : HttpBase } else { - content.InternalTryComputeLength(out var contentLength); - var writer = new PipeBytesWriter(transport.Writer); content.InternalBuildingHeader(this.Headers); + + var writer = new PipeBytesWriter(transport.Writer); this.BuildHeader(ref writer); var result = content.InternalBuildingContent(ref writer); @@ -213,7 +213,6 @@ public abstract class HttpResponse : HttpBase this.m_sentHeader = false; this.m_sentLength = 0; this.Responsed = false; - this.IsChunk = false; this.StatusCode = 200; this.StatusMessage = "Success"; this.Content = default; @@ -306,11 +305,23 @@ public abstract class HttpResponse : HttpBase private void BuildHeader(ref TWriter writer) where TWriter : IBytesWriter { - TouchSocketHttpUtility.AppendHTTP(ref writer); - TouchSocketHttpUtility.AppendSlash(ref writer); - TouchSocketHttpUtility.AppendUtf8String(ref writer, this.ProtocolVersion); + var versionBytes = TouchSocketHttpUtility.GetHttpVersionBytes(this.ProtocolVersion); + if (!versionBytes.IsEmpty) + { + writer.Write(versionBytes); + } + else + { + TouchSocketHttpUtility.AppendHTTP(ref writer); + TouchSocketHttpUtility.AppendSlash(ref writer); + TouchSocketHttpUtility.AppendUtf8String(ref writer, this.ProtocolVersion); + } + TouchSocketHttpUtility.AppendSpace(ref writer); - TouchSocketHttpUtility.AppendUtf8String(ref writer, this.StatusCode.ToString()); + + var statusCodeBytes = TouchSocketHttpUtility.GetStatusCodeBytes(this.StatusCode); + writer.Write(statusCodeBytes); + TouchSocketHttpUtility.AppendSpace(ref writer); TouchSocketHttpUtility.AppendUtf8String(ref writer, this.StatusMessage); TouchSocketHttpUtility.AppendRn(ref writer); @@ -322,11 +333,9 @@ public abstract class HttpResponse : HttpBase TouchSocketHttpUtility.AppendSpace(ref writer); TouchSocketHttpUtility.AppendUtf8String(ref writer, header.Value); TouchSocketHttpUtility.AppendRn(ref writer); - } TouchSocketHttpUtility.AppendRn(ref writer); - } private ITransport GetITransport() diff --git a/src/TouchSocket.Http/Common/Internal/InternalHttpCollection.cs b/src/TouchSocket.Http/Common/Internal/InternalHttpCollection.cs index 8dd9a0811..6a5a326dd 100644 --- a/src/TouchSocket.Http/Common/Internal/InternalHttpCollection.cs +++ b/src/TouchSocket.Http/Common/Internal/InternalHttpCollection.cs @@ -14,11 +14,46 @@ namespace TouchSocket.Http; internal abstract class InternalHttpCollection : IDictionary { + private readonly IEqualityComparer m_comparer; private readonly Dictionary m_dictionary; + private readonly List> m_pendingItems = new List>(); + private bool m_hasDuplicateKeys; + private bool m_hasNonPredefinedKeys; + private bool m_isDictionaryBuilt; protected InternalHttpCollection(IEqualityComparer comparer = null) { - m_dictionary = new Dictionary(comparer ?? StringComparer.OrdinalIgnoreCase); + this.m_comparer = comparer ?? StringComparer.OrdinalIgnoreCase; + this.m_dictionary = new Dictionary(this.m_comparer); + } + + public int Count + { + get + { + this.EnsureDictionaryBuilt(); + return this.m_dictionary.Count; + } + } + + public bool IsReadOnly => false; + + public ICollection Keys + { + get + { + this.EnsureDictionaryBuilt(); + return this.m_dictionary.Keys; + } + } + + public ICollection Values + { + get + { + this.EnsureDictionaryBuilt(); + return this.m_dictionary.Values; + } } public TextValues this[string key] @@ -26,85 +61,186 @@ internal abstract class InternalHttpCollection : IDictionary get { ThrowHelper.ThrowIfNull(key, nameof(key)); - return m_dictionary.TryGetValue(key, out var value) ? value : TextValues.Empty; - } - set - { - ThrowHelper.ThrowIfNull(key, nameof(key)); - m_dictionary[key] = value; + this.EnsureDictionaryBuilt(); + return this.m_dictionary.TryGetValue(key, out var value) ? value : TextValues.Empty; } + + set => this.Add(key, value); } - public ICollection Keys => m_dictionary.Keys; - - public ICollection Values => m_dictionary.Values; - - public int Count => m_dictionary.Count; - - public bool IsReadOnly => false; - public void Add(string key, TextValues value) { ThrowHelper.ThrowIfNull(key, nameof(key)); - if (m_dictionary.TryGetValue(key, out var old)) + + if (this.m_isDictionaryBuilt) { - m_dictionary[key] = Merge(old, value); + if (this.m_dictionary.TryGetValue(key, out var old)) + { + this.m_dictionary[key] = Merge(old, value); + } + else + { + this.m_dictionary.Add(key, value); + } } else { - m_dictionary.Add(key, value); + if (!this.m_hasNonPredefinedKeys && !HttpHeaders.IsPredefinedHeader(key)) + { + this.m_hasNonPredefinedKeys = true; + } + + if (!this.m_hasDuplicateKeys && this.m_pendingItems.Count > 0) + { + for (var i = 0; i < this.m_pendingItems.Count; i++) + { + if (this.m_comparer.Equals(this.m_pendingItems[i].Key, key)) + { + this.m_hasDuplicateKeys = true; + break; + } + } + } + + this.m_pendingItems.Add(new KeyValuePair(key, value)); } } + public void Add(KeyValuePair item) => this.Add(item.Key, item.Value); + + public void Clear() + { + this.m_dictionary.Clear(); + this.m_pendingItems.Clear(); + this.m_hasDuplicateKeys = false; + this.m_hasNonPredefinedKeys = false; + this.m_isDictionaryBuilt = false; + } + + public bool Contains(KeyValuePair item) + { + this.EnsureDictionaryBuilt(); + return ((IDictionary)this.m_dictionary).Contains(item); + } + public bool ContainsKey(string key) { ThrowHelper.ThrowIfNull(key, nameof(key)); - return m_dictionary.ContainsKey(key); + this.EnsureDictionaryBuilt(); + return this.m_dictionary.ContainsKey(key); } + public void CopyTo(KeyValuePair[] array, int arrayIndex) + { + this.EnsureDictionaryBuilt(); + ((IDictionary)this.m_dictionary).CopyTo(array, arrayIndex); + } + + public TextValues Get(string key) + { + ThrowHelper.ThrowIfNull(key, nameof(key)); + this.EnsureDictionaryBuilt(); + return this.m_dictionary.TryGetValue(key, out var value) ? value : TextValues.Empty; + } + + public IEnumerator> GetEnumerator() + { + if (this.m_isDictionaryBuilt) + { + return this.m_dictionary.GetEnumerator(); + } + else if (this.m_hasDuplicateKeys || this.m_hasNonPredefinedKeys) + { + this.EnsureDictionaryBuilt(); + return this.m_dictionary.GetEnumerator(); + } + else + { + return this.m_pendingItems.GetEnumerator(); + } + } + + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() => this.GetEnumerator(); + public bool Remove(string key) { ThrowHelper.ThrowIfNull(key, nameof(key)); - return m_dictionary.Remove(key); + this.EnsureDictionaryBuilt(); + return this.m_dictionary.Remove(key); + } + + public bool Remove(KeyValuePair item) + { + this.EnsureDictionaryBuilt(); + return ((IDictionary)this.m_dictionary).Remove(item); } public bool TryGetValue(string key, out TextValues value) { ThrowHelper.ThrowIfNull(key, nameof(key)); - return m_dictionary.TryGetValue(key, out value); + this.EnsureDictionaryBuilt(); + return this.m_dictionary.TryGetValue(key, out value); } - public void Add(KeyValuePair item) => Add(item.Key, item.Value); - - public void Clear() => m_dictionary.Clear(); - - public bool Contains(KeyValuePair item) => ((IDictionary)m_dictionary).Contains(item); - - public void CopyTo(KeyValuePair[] array, int arrayIndex) => ((IDictionary)m_dictionary).CopyTo(array, arrayIndex); - - public bool Remove(KeyValuePair item) => ((IDictionary)m_dictionary).Remove(item); - - public IEnumerator> GetEnumerator() => m_dictionary.GetEnumerator(); - - System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() => GetEnumerator(); + internal void AddInternal(string key, TextValues value) + { + this.m_pendingItems.Add(new KeyValuePair(key, value)); + } protected static TextValues Merge(TextValues a, TextValues b) { - if (a.IsEmpty) return b; - if (b.IsEmpty) return a; - var ac = a.Count; - var bc = b.Count; + if (a.IsEmpty) + { + return b; + } + + if (b.IsEmpty) + { + return a; + } + var arrA = a.ToArray(); var arrB = b.ToArray(); + var ac = arrA.Length; + var bc = arrB.Length; + var newArr = new string[ac + bc]; Array.Copy(arrA, 0, newArr, 0, ac); Array.Copy(arrB, 0, newArr, ac, bc); return new TextValues(newArr); } - public TextValues Get(string key) + private void EnsureDictionaryBuilt() { - ThrowHelper.ThrowIfNull(key, nameof(key)); - return m_dictionary.TryGetValue(key, out var value) ? value : TextValues.Empty; + if (this.m_isDictionaryBuilt) + { + return; + } + if (!this.m_hasDuplicateKeys && !this.m_hasNonPredefinedKeys && this.m_dictionary.Count == 0) + { + foreach (var item in this.m_pendingItems) + { + this.m_dictionary.Add(item.Key, item.Value); + } + } + else + { + foreach (var item in this.m_pendingItems) + { + if (this.m_dictionary.TryGetValue(item.Key, out var old)) + { + this.m_dictionary[item.Key] = Merge(old, item.Value); + } + else + { + this.m_dictionary.Add(item.Key, item.Value); + } + } + } + + this.m_pendingItems.Clear(); + this.m_hasDuplicateKeys = false; + this.m_hasNonPredefinedKeys = false; + this.m_isDictionaryBuilt = true; } -} +} \ No newline at end of file diff --git a/src/TouchSocket.Http/Common/Internal/InternalHttpHeader.cs b/src/TouchSocket.Http/Common/Internal/InternalHttpHeader.cs index 666cf3576..7b973aa82 100644 --- a/src/TouchSocket.Http/Common/Internal/InternalHttpHeader.cs +++ b/src/TouchSocket.Http/Common/Internal/InternalHttpHeader.cs @@ -22,7 +22,7 @@ internal sealed class InternalHttpHeader : InternalHttpCollection, IHttpHeader { ThrowHelper.ThrowIfNull(key, nameof(key)); - if (!TryGetValue(key, out var headerValue)) + if (!this.TryGetValue(key, out var headerValue)) { return false; } diff --git a/src/TouchSocket.Http/Common/TouchSocketHttpUtility.cs b/src/TouchSocket.Http/Common/TouchSocketHttpUtility.cs index a39b16a42..18ec561a7 100644 --- a/src/TouchSocket.Http/Common/TouchSocketHttpUtility.cs +++ b/src/TouchSocket.Http/Common/TouchSocketHttpUtility.cs @@ -14,160 +14,119 @@ using System.Runtime.CompilerServices; namespace TouchSocket.Http; -/// -/// TouchSocketHttp辅助工具类 -/// -public static class TouchSocketHttpUtility +static class TouchSocketHttpUtility { public const int MaxReadSize = 1024 * 1024; - // HTTP头部解析相关常量 - /// - /// 冒号字节值 - /// public const byte COLON = (byte)':'; - - /// - /// 空格字节值 - /// public const byte SPACE = (byte)' '; - - /// - /// 制表符字节值 - /// public const byte TAB = (byte)'\t'; - /// - /// 获取一个只读的字节序列,表示回车换行(CRLF)。 - /// - /// - /// 一个包含回车和换行字节的只读字节序列。 - /// public static ReadOnlySpan CRLF => "\r\n"u8; - - /// - /// 获取一个只读的字节序列,表示双回车换行(CRLFCRLF)。 - /// - /// - /// 一个包含双回车和换行字节的只读字节序列。 - /// public static ReadOnlySpan CRLFCRLF => "\r\n\r\n"u8; - /// - /// 在 中追加 "&" 符号。 - /// - /// 实现了 的类型。 - /// 字节块实例。 + private static readonly byte[] s_http11Response = "HTTP/1.1 "u8.ToArray(); + private static readonly byte[] s_http10Response = "HTTP/1.0 "u8.ToArray(); + + private static readonly string[] s_statusCodeCache = new string[600]; + private static readonly byte[][] s_statusCodeBytesCache = new byte[600][]; + + static TouchSocketHttpUtility() + { + for (var i = 0; i < 600; i++) + { + s_statusCodeCache[i] = i.ToString(); + s_statusCodeBytesCache[i] = Encoding.UTF8.GetBytes(s_statusCodeCache[i]); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static ReadOnlySpan GetStatusCodeBytes(int statusCode) + { + if (statusCode >= 0 && statusCode < 600) + { + return s_statusCodeBytesCache[statusCode]; + } + return Encoding.UTF8.GetBytes(statusCode.ToString()); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static ReadOnlySpan GetHttpVersionBytes(string version) + { + if (version == "1.1") + { + return s_http11Response.AsSpan(0, 8); + } + if (version == "1.0") + { + return s_http10Response.AsSpan(0, 8); + } + return default; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void AppendAnd(ref TWriter writer) where TWriter : IBytesWriter { writer.Write("&"u8); } - /// - /// 在 中追加 ":" 符号。 - /// - /// 实现了 的类型。 - /// 字节块实例。 + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void AppendColon(ref TWriter writer) where TWriter : IBytesWriter { writer.Write(":"u8); } - /// - /// 在 中追加 "=" 符号。 - /// - /// 实现了 的类型。 - /// 字节块实例。 + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void AppendEqual(ref TWriter writer) where TWriter : IBytesWriter { writer.Write("="u8); } - /// - /// 在 中追加 "HTTP" 字符串。 - /// - /// 实现了 的类型。 - /// 字节块实例。 + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void AppendHTTP(ref TWriter writer) where TWriter : IBytesWriter { writer.Write("HTTP"u8); } - /// - /// 在 中追加 "?" 符号。 - /// - /// 实现了 的类型。 - /// 字节块实例。 + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void AppendQuestionMark(ref TWriter writer) where TWriter : IBytesWriter { writer.Write("?"u8); } - /// - /// 在 中追加回车换行符 "\r\n"。 - /// - /// 实现了 的类型。 - /// 字节块实例。 + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void AppendRn(ref TWriter writer) where TWriter : IBytesWriter { writer.Write(CRLF); } - /// - /// 在 中追加 "/" 符号。 - /// - /// 实现了 的类型。 - /// 字节块实例。 + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void AppendSlash(ref TWriter writer) where TWriter : IBytesWriter { writer.Write("/"u8); } - /// - /// 在 中追加空格符。 - /// - /// 实现了 的类型。 - /// 字节块实例。 + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void AppendSpace(ref TWriter writer) where TWriter : IBytesWriter { writer.Write(StringExtension.DefaultSpaceUtf8Span); } - /// - /// 在 中追加指定的 UTF-8 编码字符串。 - /// - /// 实现了 的类型。 - /// 字节块实例。 - /// 要追加的字符串。 + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void AppendUtf8String(ref TWriter writer, string value) where TWriter : IBytesWriter { WriterExtension.WriteNormalString(ref writer, value, Encoding.UTF8); } - /// - /// 在 中追加指定整数的十六进制表示。 - /// - /// 实现了 的类型。 - /// 字节块实例。 - /// 要追加的整数值。 + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void AppendHex(ref TWriter writer, int value) where TWriter : IBytesWriter { AppendUtf8String(ref writer, $"{value:X}"); } - /// - /// 检查字节是否为HTTP规范允许的空白字符(空格或制表符) - /// - /// 要检查的字节 - /// 如果是空白字符返回true,否则返回false [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static bool IsWhitespace(byte b) => b == SPACE || b == TAB; - /// - /// 高效的空白字符去除,避免使用通用的Trim方法 - /// - /// 要处理的字节跨度 - /// 去除前后空白字符后的字节跨度 [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static ReadOnlySpan TrimWhitespace(ReadOnlySpan span) { @@ -175,35 +134,35 @@ public static class TouchSocketHttpUtility var end = span.Length - 1; while (start <= end && IsWhitespace(span[start])) + { start++; + } while (end >= start && IsWhitespace(span[end])) + { end--; - - return start > end ? ReadOnlySpan.Empty : span.Slice(start, end - start + 1); + } + return start > end ? [] : span[start..(end + 1)]; } internal static string UnescapeDataString(ReadOnlySpan urlSpan) { #if NET9_0_OR_GREATER - // 直接处理字节的URL解码 Span charBuffer = stackalloc char[urlSpan.Length]; var charCount = Encoding.UTF8.GetChars(urlSpan, charBuffer); return Uri.UnescapeDataString(charBuffer.Slice(0, charCount)); #else return Uri.UnescapeDataString(urlSpan.ToString(Encoding.UTF8)); #endif - } internal static string UnescapeDataString(ReadOnlySpan urlSpan) { #if NET9_0_OR_GREATER - return Uri.UnescapeDataString(urlSpan); + return Uri.UnescapeDataString(urlSpan); #else return Uri.UnescapeDataString(urlSpan.ToString()); #endif - } internal static int FindNextWhitespace(ReadOnlySpan span, int start) @@ -224,7 +183,6 @@ public static class TouchSocketHttpUtility { start++; } - return start; } @@ -240,7 +198,6 @@ public static class TouchSocketHttpUtility } else { - // 处理没有值的键 keySpan = kvSpan; valueSpan = []; } diff --git a/src/TouchSocket.Http/Components/HttpClientBase.cs b/src/TouchSocket.Http/Components/HttpClientBase.cs index 615e1b5fc..2b676fdd2 100644 --- a/src/TouchSocket.Http/Components/HttpClientBase.cs +++ b/src/TouchSocket.Http/Components/HttpClientBase.cs @@ -210,7 +210,6 @@ public abstract class HttpClientBase : TcpClientBase, IHttpSession return; } - content.InternalTryComputeLength(out var contentLength); content.InternalBuildingHeader(request.Headers); request.BuildHeader(ref writer); diff --git a/src/TouchSocket.Http/Components/HttpSessionClient.cs b/src/TouchSocket.Http/Components/HttpSessionClient.cs index 38e1f4e00..301e00b12 100644 --- a/src/TouchSocket.Http/Components/HttpSessionClient.cs +++ b/src/TouchSocket.Http/Components/HttpSessionClient.cs @@ -20,22 +20,24 @@ namespace TouchSocket.Http; /// public abstract partial class HttpSessionClient : TcpSessionClientBase, IHttpSessionClient { - private HttpContext m_httpContext; - - private ServerHttpResponse m_serverHttpResponse; private readonly HttpServerDataHandlingAdapter m_httpAdapter; - private WebSocketDataHandlingAdapter m_webSocketAdapter; + private readonly WebSocketDataHandlingAdapter m_webSocketAdapter; + private readonly HttpContext m_httpContext; + private readonly ServerHttpResponse m_serverHttpResponse; + /// /// 构造函数 /// protected HttpSessionClient() { this.Protocol = Protocol.Http; - this.m_httpAdapter = new HttpServerDataHandlingAdapter(this.OnReceivingHttpRequest); + var serverHttpRequest = new ServerHttpRequest(this); + this.m_serverHttpResponse = new ServerHttpResponse(serverHttpRequest, this); + this.m_httpContext = new HttpContext(serverHttpRequest, this.m_serverHttpResponse); + this.m_httpAdapter = new HttpServerDataHandlingAdapter(serverHttpRequest,this.OnReceivingHttpRequest); this.m_webSocketAdapter = new WebSocketDataHandlingAdapter(); } - internal ITransport InternalTransport => this.Transport; /// @@ -65,18 +67,6 @@ public abstract partial class HttpSessionClient : TcpSessionClientBase, IHttpSes return base.OnTcpConnecting(e); } - private async Task OnReceivingHttpRequest(ServerHttpRequest request) - { - if (this.m_httpContext == null) - { - this.m_serverHttpResponse = new ServerHttpResponse(request, this); - this.m_httpContext = new HttpContext(request, this.m_serverHttpResponse); - } - - await this.OnReceivedHttpRequest(this.m_httpContext).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - this.m_serverHttpResponse.Reset(); - } - /// protected override async Task OnTcpReceived(ReceivedDataEventArgs e) { @@ -103,4 +93,10 @@ public abstract partial class HttpSessionClient : TcpSessionClientBase, IHttpSes base.SafetyDispose(disposing); } + + private async Task OnReceivingHttpRequest(ServerHttpRequest request) + { + await this.OnReceivedHttpRequest(this.m_httpContext).ConfigureAwait(EasyTask.ContinueOnCapturedContext); + this.m_serverHttpResponse.Reset(); + } } \ No newline at end of file diff --git a/src/TouchSocket.Http/DataAdapter/HttpServerDataHandlingAdapter.cs b/src/TouchSocket.Http/DataAdapter/HttpServerDataHandlingAdapter.cs index d939653b9..b8eb0bc6f 100644 --- a/src/TouchSocket.Http/DataAdapter/HttpServerDataHandlingAdapter.cs +++ b/src/TouchSocket.Http/DataAdapter/HttpServerDataHandlingAdapter.cs @@ -14,12 +14,13 @@ namespace TouchSocket.Http; internal sealed class HttpServerDataHandlingAdapter : SingleStreamDataHandlingAdapter { - public HttpServerDataHandlingAdapter(Func func) + public HttpServerDataHandlingAdapter(ServerHttpRequest request, Func func) { this.m_func = func; + this.m_requestRoot = request; } private ServerHttpRequest m_currentRequest; - private ServerHttpRequest m_requestRoot; + private readonly ServerHttpRequest m_requestRoot; private long m_surLen; private Task m_task; @@ -61,8 +62,6 @@ internal sealed class HttpServerDataHandlingAdapter : SingleStreamDataHandlingAd { throw new Exception($"此适配器必须适用于{nameof(IHttpService)}"); } - - this.m_requestRoot = new ServerHttpRequest(httpSessionClient); base.OnLoaded(owner); } @@ -119,7 +118,7 @@ internal sealed class HttpServerDataHandlingAdapter : SingleStreamDataHandlingAd { this.m_currentRequest.InternalSetContent(ReadOnlyMemory.Empty); } - await this.GoReceivedAsync(this.m_currentRequest); + await this.GoReceivedAsync(this.m_currentRequest).ConfigureAwait(EasyTask.ContinueOnCapturedContext); this.m_currentRequest = null; } } diff --git a/src/TouchSocket.Http/HttpContent/HttpContent.cs b/src/TouchSocket.Http/HttpContent/HttpContent.cs index 43a51bf61..84218b864 100644 --- a/src/TouchSocket.Http/HttpContent/HttpContent.cs +++ b/src/TouchSocket.Http/HttpContent/HttpContent.cs @@ -36,18 +36,9 @@ public abstract class HttpContent /// HTTP头的接口实现 internal void InternalBuildingHeader(IHttpHeader header) { - if (this.TryComputeLength(out var length)) - { - header.TryAdd(HttpHeaders.ContentLength, length.ToString()); - } this.OnBuildingHeader(header); } - internal bool InternalTryComputeLength(out long length) - { - return this.TryComputeLength(out length); - } - internal Task InternalWriteContent(PipeWriter writer, CancellationToken cancellationToken) { return this.WriteContent(writer, cancellationToken); @@ -67,13 +58,6 @@ public abstract class HttpContent /// HTTP头的接口实现 protected abstract void OnBuildingHeader(IHttpHeader header); - /// - /// 尝试计算内容的长度。 - /// - /// 输出参数,表示内容的长度。 - /// 如果成功计算长度,则返回 true;否则返回 false。 - protected abstract bool TryComputeLength(out long length); - protected abstract Task WriteContent(PipeWriter writer, CancellationToken cancellationToken); #region implicit diff --git a/src/TouchSocket.Http/HttpContent/ReadonlyMemoryHttpContent.cs b/src/TouchSocket.Http/HttpContent/ReadonlyMemoryHttpContent.cs index 89277f9c7..18022926a 100644 --- a/src/TouchSocket.Http/HttpContent/ReadonlyMemoryHttpContent.cs +++ b/src/TouchSocket.Http/HttpContent/ReadonlyMemoryHttpContent.cs @@ -30,9 +30,11 @@ public class ReadonlyMemoryHttpContent : HttpContent { this.m_memory = memory; this.m_contentType = contentType; + this.m_lengthString= this.m_memory.Length.ToString(); } private readonly string m_contentType; + private readonly string m_lengthString; /// /// 获取封装的只读内存。 @@ -60,7 +62,7 @@ public class ReadonlyMemoryHttpContent : HttpContent /// protected override void OnBuildingHeader(IHttpHeader header) { - header.Add(HttpHeaders.ContentLength, this.m_memory.Length.ToString()); + header.Add(HttpHeaders.ContentLength, this.m_lengthString); if (this.m_contentType != null) { header.Add(HttpHeaders.ContentType, this.m_contentType); @@ -76,11 +78,4 @@ public class ReadonlyMemoryHttpContent : HttpContent { await writer.WriteAsync(this.m_memory, cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } - - /// - protected override bool TryComputeLength(out long length) - { - length = this.m_memory.Length; - return true; - } } \ No newline at end of file diff --git a/src/TouchSocket.Http/HttpContent/StreamHttpContent.cs b/src/TouchSocket.Http/HttpContent/StreamHttpContent.cs index 03edefdc3..e227b7b5d 100644 --- a/src/TouchSocket.Http/HttpContent/StreamHttpContent.cs +++ b/src/TouchSocket.Http/HttpContent/StreamHttpContent.cs @@ -75,10 +75,13 @@ public class StreamHttpContent : HttpContent { header.Add(HttpHeaders.TransferEncoding, "chunked"); } + else if (TryComputeLength(out var length)) + { + header.Add(HttpHeaders.ContentLength,length.ToString()); + } } - /// - protected override bool TryComputeLength(out long length) + private bool TryComputeLength(out long length) { try { diff --git a/src/TouchSocket.Mqtt/Actors/MqttActor.cs b/src/TouchSocket.Mqtt/Actors/MqttActor.cs index c9b328a9f..15401fbae 100644 --- a/src/TouchSocket.Mqtt/Actors/MqttActor.cs +++ b/src/TouchSocket.Mqtt/Actors/MqttActor.cs @@ -36,8 +36,9 @@ public abstract class MqttActor : DisposableObject, IOnlineClient if (disposing) { - this.m_tokenSource.Cancel(); - this.m_tokenSource.Dispose(); + this.m_tokenSource.SafeCancel(); + this.m_tokenSource.SafeDispose(); + this.m_waitHandlePool.CancelAll(); } } diff --git a/src/TouchSocket.Mqtt/Components/Tcp/MqttTcpClient.cs b/src/TouchSocket.Mqtt/Components/Tcp/MqttTcpClient.cs index 9166cab47..4bfb3c74f 100644 --- a/src/TouchSocket.Mqtt/Components/Tcp/MqttTcpClient.cs +++ b/src/TouchSocket.Mqtt/Components/Tcp/MqttTcpClient.cs @@ -117,7 +117,10 @@ public class MqttTcpClient : TcpClientBase, IMqttTcpClient await base.TcpConnectAsync(cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); var connAckMessage = await this.m_mqttActor.ConnectAsync(connectMessage, cancellationToken).ConfigureAwait(EasyTask.ContinueOnCapturedContext); - + if (connAckMessage.ReturnCode!= MqttReasonCode.ConnectionAccepted) + { + ThrowHelper.ThrowException($"Connection failed with reason: {connAckMessage.ReturnCode},reasonString: {connAckMessage.ReasonString}"); + } await this.PluginManager.RaiseAsync(typeof(IMqttConnectedPlugin), this.Resolver, this, new MqttConnectedEventArgs(connectMessage, connAckMessage)).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } @@ -149,6 +152,8 @@ public class MqttTcpClient : TcpClientBase, IMqttTcpClient /// protected override async Task OnTcpClosed(ClosedEventArgs e) { + this.m_mqttActor.WaitHandlePool.CancelAll(); + await this.PluginManager.RaiseAsync(typeof(IMqttClosedPlugin), this.Resolver, this, new MqttClosedEventArgs(e.Manual, e.Message)).ConfigureAwait(EasyTask.ContinueOnCapturedContext); await base.OnTcpClosed(e).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } diff --git a/src/TouchSocket.Mqtt/Extensions/ReconnectionOptionsExtension.cs b/src/TouchSocket.Mqtt/Extensions/ReconnectionOptionsExtension.cs new file mode 100644 index 000000000..7e9bed6aa --- /dev/null +++ b/src/TouchSocket.Mqtt/Extensions/ReconnectionOptionsExtension.cs @@ -0,0 +1,101 @@ +// ------------------------------------------------------------------------------ +// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 +// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 +// CSDN博客:https://blog.csdn.net/qq_40374647 +// 哔哩哔哩视频:https://space.bilibili.com/94253567 +// Gitee源代码仓库:https://gitee.com/RRQM_Home +// Github源代码仓库:https://github.com/RRQM +// API首页:https://touchsocket.net/ +// 交流QQ群:234762506 +// 感谢您的下载和使用 +// ------------------------------------------------------------------------------ + +using TouchSocket.Sockets; + +namespace TouchSocket.Mqtt; + +/// +/// 提供扩展方法以支持MQTT检查操作。 +/// +public static class ReconnectionOptionsExtension +{ + /// + /// 配置MQTT检查操作。 + /// + /// 客户端类型,必须实现 + /// 重连选项。 + /// 活动时间间隔,默认为3秒。 + /// Ping超时时间,默认为5秒。 + /// 当时间参数小于或等于零时抛出。 + /// 时抛出。 + public static void UseMqttCheckAction( + this ReconnectionOption reconnectionOption, + TimeSpan? activeTimeSpan = null, + TimeSpan? pingTimeout = null) + where TClient : IConnectableClient, IOnlineClient, IDependencyClient, IMqttClient + { + ThrowHelper.ThrowIfNull(reconnectionOption, nameof(reconnectionOption)); + var span = activeTimeSpan ?? TimeSpan.FromSeconds(3); + var timeout = pingTimeout ?? TimeSpan.FromSeconds(5); + + // 验证时间参数的有效性 + if (span <= TimeSpan.Zero) + { + throw new ArgumentOutOfRangeException(nameof(activeTimeSpan), "活动时间间隔必须大于零"); + } + + if (timeout <= TimeSpan.Zero) + { + throw new ArgumentOutOfRangeException(nameof(pingTimeout), "Ping超时时间必须大于零"); + } + + reconnectionOption.CheckAction = async (client) => + { + // 第1步:快速在线状态检查 + // 如果客户端已经离线,无需进一步检查,直接返回Dead状态 + if (!client.Online) + { + return ConnectionCheckResult.Dead; + } + + // 第2步:活动时间检查 + // 如果客户端在指定时间内有活动,说明连接正常,跳过本次心跳检查 + var lastActiveTime = client.GetLastActiveTime(); + var timeSinceLastActivity = DateTimeOffset.UtcNow - lastActiveTime; + + if (timeSinceLastActivity < span) + { + return ConnectionCheckResult.Skip; + } + + // 第3步:主动心跳检查 + // 通过Ping操作验证连接的实际可用性 + try + { + using var pingCts = new CancellationTokenSource(timeout); + var pingResult = await client.PingAsync(pingCts.Token).ConfigureAwait(false); + + if (pingResult.IsSuccess) + { + return ConnectionCheckResult.Alive; + } + + using var closeCts = new CancellationTokenSource(timeout); + + var closeResult = await client.CloseAsync("心跳插件ping失败主动断开连接", closeCts.Token).ConfigureAwait(false); + + return ConnectionCheckResult.Dead; + } + catch (OperationCanceledException) + { + // Ping超时,认为连接已死 + return ConnectionCheckResult.Dead; + } + catch + { + // 其他异常也认为连接不可用 + return ConnectionCheckResult.Dead; + } + }; + } +} diff --git a/src/TouchSocket.Mqtt/MqttMessages/V5/MqttConnectMessage_v5.cs b/src/TouchSocket.Mqtt/MqttMessages/V5/MqttConnectMessage_v5.cs index 4d98df770..50aae1abe 100644 --- a/src/TouchSocket.Mqtt/MqttMessages/V5/MqttConnectMessage_v5.cs +++ b/src/TouchSocket.Mqtt/MqttMessages/V5/MqttConnectMessage_v5.cs @@ -162,30 +162,43 @@ public partial class MqttConnectMessage #region Properties - MqttExtension.WriteSessionExpiryInterval(ref writer, this.SessionExpiryInterval); - MqttExtension.WriteAuthenticationMethod(ref writer, this.AuthenticationMethod); - MqttExtension.WriteAuthenticationData(ref writer, this.AuthenticationData.Span); - MqttExtension.WriteReceiveMaximum(ref writer, this.ReceiveMaximum); - MqttExtension.WriteTopicAliasMaximum(ref writer, this.TopicAliasMaximum); - MqttExtension.WriteMaximumPacketSize(ref writer, this.MaximumPacketSize); - MqttExtension.WriteRequestResponseInformation(ref writer, this.RequestResponseInformation); - MqttExtension.WriteRequestProblemInformation(ref writer, this.RequestProblemInformation); - MqttExtension.WriteUserProperties(ref writer, this.UserProperties); + // 写入属性长度字段与属性内容 + var variableByteIntegerRecorder = new VariableByteIntegerRecorder(); + var propertiesWriter = this.CreateVariableWriter(ref writer); + variableByteIntegerRecorder.CheckOut(ref propertiesWriter); + MqttExtension.WriteSessionExpiryInterval(ref propertiesWriter, this.SessionExpiryInterval); + MqttExtension.WriteAuthenticationMethod(ref propertiesWriter, this.AuthenticationMethod); + MqttExtension.WriteAuthenticationData(ref propertiesWriter, this.AuthenticationData.Span); + MqttExtension.WriteReceiveMaximum(ref propertiesWriter, this.ReceiveMaximum); + MqttExtension.WriteTopicAliasMaximum(ref propertiesWriter, this.TopicAliasMaximum); + MqttExtension.WriteMaximumPacketSize(ref propertiesWriter, this.MaximumPacketSize); + MqttExtension.WriteRequestResponseInformation(ref propertiesWriter, this.RequestResponseInformation); + MqttExtension.WriteRequestProblemInformation(ref propertiesWriter, this.RequestProblemInformation); + MqttExtension.WriteUserProperties(ref propertiesWriter, this.UserProperties); + variableByteIntegerRecorder.CheckIn(ref propertiesWriter); + writer.Advance(propertiesWriter.Position); #endregion Properties MqttExtension.WriteMqttInt16String(ref writer, this.ClientId); if (this.WillFlag) { - MqttExtension.WritePayloadFormatIndicator(ref writer, this.WillPayloadFormatIndicator); - MqttExtension.WriteMessageExpiryInterval(ref writer, this.WillMessageExpiryInterval); - MqttExtension.WriteResponseTopic(ref writer, this.WillResponseTopic); - MqttExtension.WriteCorrelationData(ref writer, this.WillCorrelationData.Span); - MqttExtension.WriteContentType(ref writer, this.WillContentType); - MqttExtension.WriteWillDelayInterval(ref writer, this.WillDelayInterval); + // 写入遗嘱属性长度字段与属性内容 + var willVariableByteIntegerRecorder = new VariableByteIntegerRecorder(); + var willPropertiesWriter = this.CreateVariableWriter(ref writer); + willVariableByteIntegerRecorder.CheckOut(ref willPropertiesWriter); + MqttExtension.WritePayloadFormatIndicator(ref willPropertiesWriter, this.WillPayloadFormatIndicator); + MqttExtension.WriteMessageExpiryInterval(ref willPropertiesWriter, this.WillMessageExpiryInterval); + MqttExtension.WriteResponseTopic(ref willPropertiesWriter, this.WillResponseTopic); + MqttExtension.WriteCorrelationData(ref willPropertiesWriter, this.WillCorrelationData.Span); + MqttExtension.WriteContentType(ref willPropertiesWriter, this.WillContentType); + MqttExtension.WriteWillDelayInterval(ref willPropertiesWriter, this.WillDelayInterval); + MqttExtension.WriteUserProperties(ref willPropertiesWriter, this.WillUserProperties); + willVariableByteIntegerRecorder.CheckIn(ref willPropertiesWriter); + writer.Advance(willPropertiesWriter.Position); + MqttExtension.WriteMqttInt16String(ref writer, this.WillTopic); MqttExtension.WriteMqttInt16Memory(ref writer, this.WillPayload); - MqttExtension.WriteUserProperties(ref writer, this.WillUserProperties); } if (this.UserNameFlag) @@ -260,16 +273,6 @@ public partial class MqttConnectMessage } } - //this.SessionExpiryInterval = propertiesReader.SessionExpiryInterval; - //this.AuthenticationMethod = propertiesReader.AuthenticationMethod; - //this.AuthenticationData = propertiesReader.AuthenticationData; - //this.ReceiveMaximum = propertiesReader.ReceiveMaximum; - //this.TopicAliasMaximum = propertiesReader.TopicAliasMaximum; - //this.MaximumPacketSize = propertiesReader.MaximumPacketSize; - //this.RequestResponseInformation = propertiesReader.RequestResponseInformation; - //this.RequestProblemInformation = propertiesReader.RequestProblemInformation; - //this.UserProperties = propertiesReader.UserProperties; - #endregion Properties this.ClientId = MqttExtension.ReadMqttInt16String(ref reader); diff --git a/src/TouchSocket.Mqtt/MqttMessages/V5/MqttSubscribeMessage_v5.cs b/src/TouchSocket.Mqtt/MqttMessages/V5/MqttSubscribeMessage_v5.cs index c1c633b5f..4b4a32a59 100644 --- a/src/TouchSocket.Mqtt/MqttMessages/V5/MqttSubscribeMessage_v5.cs +++ b/src/TouchSocket.Mqtt/MqttMessages/V5/MqttSubscribeMessage_v5.cs @@ -24,7 +24,10 @@ public partial class MqttSubscribeMessage var variableByteIntegerRecorder = new VariableByteIntegerRecorder(); var byteBlockWriter = this.CreateVariableWriter(ref writer); variableByteIntegerRecorder.CheckOut(ref byteBlockWriter); - MqttExtension.WriteSubscriptionIdentifier(ref byteBlockWriter, this.SubscriptionIdentifier); + if (this.SubscriptionIdentifier > 0) + { + MqttExtension.WriteSubscriptionIdentifier(ref byteBlockWriter, this.SubscriptionIdentifier); + } MqttExtension.WriteUserProperties(ref byteBlockWriter, this.UserProperties); variableByteIntegerRecorder.CheckIn(ref byteBlockWriter); writer.Advance(byteBlockWriter.Position); @@ -32,7 +35,13 @@ public partial class MqttSubscribeMessage foreach (var item in this.m_subscribeRequests) { MqttExtension.WriteMqttInt16String(ref writer, item.Topic); - WriterExtension.WriteValue(ref writer, (byte)item.QosLevel); + // MQTT v5 订阅选项包含: QoS(bit 0-1), NL(bit 2), RAP(bit 3), Retain Handling(bit 4-5) + byte options = 0; + options = options.SetQosLevel(0, item.QosLevel); + options = options.SetBit(2, item.NoLocal); + options = options.SetBit(3, item.RetainAsPublished); + options = options.SetRetainHandling(4, item.RetainHandling); + WriterExtension.WriteValue(ref writer, options); } } @@ -40,7 +49,7 @@ public partial class MqttSubscribeMessage protected override void UnpackWithMqtt5(ref TReader reader) { this.MessageId = ReaderExtension.ReadValue(ref reader, EndianType.Big); - this.MessageId = ReaderExtension.ReadValue(ref reader, EndianType.Big); + var propertiesReader = new MqttV5PropertiesReader(ref reader); while (propertiesReader.TryRead(ref reader, out var mqttPropertyId)) @@ -58,8 +67,7 @@ public partial class MqttSubscribeMessage break; } } - //this.SubscriptionIdentifier = propertiesReader.SubscriptionIdentifier; - //this.UserProperties = propertiesReader.UserProperties; + while (!this.EndOfByteBlock(reader)) { var topic = MqttExtension.ReadMqttInt16String(ref reader); diff --git a/src/TouchSocket/Components/Tcp/TcpClientBase.cs b/src/TouchSocket/Components/Tcp/TcpClientBase.cs index ba30699b4..44701b2fe 100644 --- a/src/TouchSocket/Components/Tcp/TcpClientBase.cs +++ b/src/TouchSocket/Components/Tcp/TcpClientBase.cs @@ -429,6 +429,8 @@ public abstract partial class TcpClientBase : SetupConfigObject, ITcpSession TargetHost = iPHost.Host }; } + + sslOption.TargetHost ??= iPHost.Host; await this.AuthenticateAsync(sslOption).ConfigureAwait(EasyTask.ContinueOnCapturedContext); } diff --git a/src/TouchSocket/Components/Tcp/TcpServiceBaseT.cs b/src/TouchSocket/Components/Tcp/TcpServiceBaseT.cs index d30c42e2a..2f757c727 100644 --- a/src/TouchSocket/Components/Tcp/TcpServiceBaseT.cs +++ b/src/TouchSocket/Components/Tcp/TcpServiceBaseT.cs @@ -163,8 +163,8 @@ public abstract class TcpServiceBase : ConnectableService, ITc ReuseAddress = this.Config.GetValue(TouchSocketConfigExtension.ReuseAddressProperty), NoDelay = this.Config.GetValue(TouchSocketConfigExtension.NoDelayProperty), Adapter = this.Config.GetValue(TouchSocketConfigExtension.TcpDataHandlingAdapterProperty), + Backlog= this.Config.GetValue(TouchSocketConfigExtension.BacklogProperty) }; - option.Backlog = this.Config.GetValue(TouchSocketConfigExtension.BacklogProperty) ?? option.Backlog; optionList.Add(option); } } diff --git a/src/TouchSocket/Extensions/TouchSocketConfigExtension.cs b/src/TouchSocket/Extensions/TouchSocketConfigExtension.cs index 40e16bd65..0bcd0abcf 100644 --- a/src/TouchSocket/Extensions/TouchSocketConfigExtension.cs +++ b/src/TouchSocket/Extensions/TouchSocketConfigExtension.cs @@ -113,7 +113,7 @@ public static class TouchSocketConfigExtension /// 挂起连接队列的最大长度,所需类型 /// [GeneratorProperty(TargetType = typeof(TouchSocketConfig))] - public static readonly DependencyProperty BacklogProperty = new("Backlog", null); + public static readonly DependencyProperty BacklogProperty = new("Backlog", 100); /// /// 设置默认Id的获取方式,所需类型 diff --git a/src/TouchSocket/Options/ReconnectionOption.cs b/src/TouchSocket/Options/ReconnectionOption.cs index 3b55d4f8d..3291a84ac 100644 --- a/src/TouchSocket/Options/ReconnectionOption.cs +++ b/src/TouchSocket/Options/ReconnectionOption.cs @@ -117,9 +117,10 @@ public class ReconnectionOption while (this.MaxRetryCount < 0 || attempts < this.MaxRetryCount) { - if (cancellationToken.IsCancellationRequested) + if (client.DisposedValue||cancellationToken.IsCancellationRequested) + { return; - + } if (client.PauseReconnection) { if (this.LogReconnection) @@ -150,9 +151,6 @@ public class ReconnectionOption } catch (Exception ex) { - if (cancellationToken.IsCancellationRequested) - return; - this.OnFailed?.Invoke(client, attempts, ex); if (this.LogReconnection) diff --git a/src/TouchSocket/Options/TcpListenOption.cs b/src/TouchSocket/Options/TcpListenOption.cs index a6dccfc9a..e02c604a2 100644 --- a/src/TouchSocket/Options/TcpListenOption.cs +++ b/src/TouchSocket/Options/TcpListenOption.cs @@ -20,27 +20,27 @@ public class TcpListenOption /// /// 名称 /// - public string Name { get; set; } + public string Name { get; init; } /// /// 监听地址 /// - public IPHost IpHost { get; set; } + public IPHost IpHost { get; init; } /// /// 是否使用地址复用 /// - public bool ReuseAddress { get; set; } + public bool ReuseAddress { get; init; } /// /// Tcp处理并发连接时最大半连接队列 /// - public int Backlog { get; set; } = 100; + public int Backlog { get; init; } = 100; /// /// 禁用延迟发送 /// - public bool NoDelay { get; set; } = true; + public bool NoDelay { get; init; } = true; /// /// 是否使用ssl加密 @@ -50,10 +50,10 @@ public class TcpListenOption /// /// 用于Ssl加密的证书 /// - public ServiceSslOption ServiceSslOption { get; set; } + public ServiceSslOption ServiceSslOption { get; init; } /// /// 配置Tcp适配器 /// - public Func Adapter { get; set; } + public Func Adapter { get; init; } } \ No newline at end of file diff --git a/src/TouchSocket/Transports/TcpTransport.cs b/src/TouchSocket/Transports/TcpTransport.cs index 62b422ff4..976ecafa2 100644 --- a/src/TouchSocket/Transports/TcpTransport.cs +++ b/src/TouchSocket/Transports/TcpTransport.cs @@ -107,6 +107,7 @@ internal sealed class TcpTransport : BaseTransport this.UseSsl = true; } + /// public override async Task CloseAsync(string msg, CancellationToken cancellationToken = default) {