经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 程序设计 » ASP.net » 查看文章
【译】.NET 8 网络改进(三)
来源:cnblogs  作者:郑子铭  时间:2024/2/18 9:04:48  对本文有异议

原文 | Máňa,Natalia Kondratyeva

翻译 | 郑子铭

简化的 SocketsHttpHandler 配置

.NET 8 添加了更方便、更流畅的方式来使用 SocketsHttpHandler 作为 HttpClientFactory 中的主处理程序 (dotnet/runtime#84075)。

您可以使用 UseSocketsHttpHandler 方法设置和配置 SocketsHttpHandler。您可以使用 IConfiguration 从配置文件设置 SocketsHttpHandler 属性,也可以从代码中配置它,或者可以结合使用这两种方法。

请注意,将 IConfiguration 应用于 SocketsHttpHandler 时,仅解析 bool、int、Enum 或 TimeSpan 类型的 SocketsHttpHandler 属性。 IConfiguration 中所有不匹配的属性都将被忽略。配置仅在注册时解析一次并且不会重新加载,因此处理程序在应用程序重新启动之前不会反映任何配置文件更改。

  1. // sets up properties on the handler directly
  2. services.AddHttpClient("foo")
  3. .UseSocketsHttpHandler((h, _) => h.UseCookies = false);
  4. // uses a builder to combine approaches
  5. services.AddHttpClient("bar")
  6. .UseSocketsHttpHandler(b =>
  7. b.Configure(config.GetSection($"HttpClient:bar")) // loads simple properties from config
  8. .Configure((h, _) => // sets up SslOptions in code
  9. {
  10. h.SslOptions.RemoteCertificateValidationCallback = delegate { return true; };
  11. });
  12. );
  1. {
  2. "HttpClient": {
  3. "bar": {
  4. "AllowAutoRedirect": true,
  5. "UseCookies": false,
  6. "ConnectTimeout": "00:00:05"
  7. }
  8. }
  9. }

QUIC

OpenSSL 3 支持

当前大多数 Linux 发行版在其最新版本中都采用了 OpenSSL 3:

.NET 8 的 QUIC 支持已为此做好准备 (dotnet/runtime#81801)。

实现这一目标的第一步是确保 System.Net.Quic 下使用的 QUIC 实现 MsQuic 可以与 OpenSSL 3+ 一起使用。这项工作发生在 MsQuic 存储库 microsoft/msquic#2039 中。下一步是确保 libmsquic 包的构建和发布具有对特定发行版和版本的默认 OpenSSL 版本的相应依赖性。例如 Debian 发行版:

最后一步是确保正在测试正确版本的 MsQuic 和 OpenSSL,并且测试覆盖了所有 .NET 支持的发行版。

例外情况

在 .NET 7 中发布 QUIC API(作为预览功能)后,我们收到了几个有关异常的问题:

在 .NET 8 中,System.Net.Quic 异常行为在 dotnet/runtime#82262 中进行了彻底修改,并解决了上述问题。

修订的主要目标之一是确保 System.Net.Quic 中的异常行为在整个命名空间中尽可能一致。总的来说,当前的行为可以总结如下:

  • QuicException:特定于 QUIC 协议或与其处理相关的所有错误。
    • 连接在本地或由对等方关闭。
    • 连接因不活动而闲置。
    • 流在本地或由对等方中止。
    • QuicError 中描述的其他错误
  • SocketException:用于网络问题,例如网络状况、名称解析或用户错误。
    • 地址已被使用。
    • 无法到达目标主机。
    • 指定的地址无效。
    • 无法解析主机名。
  • AuthenticationException:适用于所有 TLS 相关问题。目标是具有与 SslStream 类似的行为。
    • 证书相关错误。
    • ALPN 协商错误。
    • 握手期间用户取消。
  • ArgumentException:当提供的 QuicConnectionOptionsQuicListenerOptions 无效时。
  • OperationCanceledException:每当 CancellationToken 被触发取消时。
  • ObjectDisposeException:每当在已释放的对象上调用方法时。

请注意,上述示例并不详尽。

除了改变行为之外,QuicException 也发生了改变。其中一项更改是调整 QuicError 枚举值。现在 SocketException 涵盖的项目已被删除,并添加了用户回调错误的新值 (dotnet/runtime#87259)。新添加的 CallbackError 用于区分 QuicListenerOptions.ConnectionOptionsCallback 引发的异常与 System.Net.Quic 引发的异常 (dotnet/runtime#88614)。因此,如果用户代码抛出 ArgumentException,QuicListener.AcceptConnectionAsync 会将其包装在 QuicException 中,并将 QuicError 设置为 CallbackError,并且内部异常将包含原始用户抛出的异常。它可以这样使用:

  1. await using var listener = await QuicListener.ListenAsync(new QuicListenerOptions
  2. {
  3. // ...
  4. ConnectionOptionsCallback = (con, hello, token) =>
  5. {
  6. if (blockedServers.Contains(hello.ServerName))
  7. {
  8. throw new ArgumentException($"Connection attempt from forbidden server: '{hello.ServerName}'.", nameof(hello));
  9. }
  10. return ValueTask.FromResult(new QuicServerConnectionOptions
  11. {
  12. // ...
  13. });
  14. },
  15. });
  16. // ...
  17. try
  18. {
  19. await listener.AcceptConnectionAsync();
  20. }
  21. catch (QuicException ex) when (ex.QuicError == QuicError.CallbackError && ex.InnerException is ArgumentException)
  22. {
  23. Console.WriteLine($"Blocked connection attempt from forbidden server: {ex.InnerException.Message}");
  24. }

异常空间的最后一个更改是将传输错误代码添加到 QuicException 中 (dotnet/runtime#88550)。传输错误代码由 RFC 9000 传输错误代码定义,并且 MsQuic 的 System.Net.Quic 已经可以使用它们,只是没有公开公开。因此,QuicException 中添加了一个新的可为 null 的属性:TransportErrorCode。我们要感谢社区贡献者 AlexRach,他在 dotnet/runtime#88614 中实现了这一更改。

Sockets

套接字空间中最有影响力的更改是显着减少无连接 (UDP) 套接字的分配 (dotnet/runtime#30797)。使用 UDP 套接字时,分配的最大贡献者之一是在每次调用 Socket.ReceiveFrom 时分配一个新的 EndPoint 对象(并支持 IPAddress 等分配)。为了缓解这个问题,引入了一组使用 SocketAddress 的新 API (dotnet/runtime#87397)。 SocketAddress 在内部将 IP 地址保存为平台相关形式的字节数组,以便可以将其直接传递给操作系统调用。因此,在调用本机套接字函数之前不需要复制 IP 地址数据。

此外,新添加的 ReceiveFromReceiveFromAsync 重载不会实例化每次调用时都会有一个新的 IPEndPoint,而是在适当的位置改变提供的 receiveAddress 参数。所有这些一起可以用来提高 UDP 套接字代码的效率:

  1. // Same initialization code as before, no change here.
  2. Socket server = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
  3. Socket client = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
  4. byte[] message = Encoding.UTF8.GetBytes("Hello world!");
  5. byte[] buffer = new byte[1024];
  6. IPEndPoint endpoint = new IPEndPoint(IPAddress.Loopback, 12345);
  7. server.Bind(endpoint);
  8. // --------
  9. // Original code that would allocate IPEndPoint for each ReceiveFromAsync:
  10. Task<SocketReceiveFromResult> receiveTaskOrig = server.ReceiveFromAsync(buffer, SocketFlags.None, endpoint);
  11. await client.SendToAsync(message, SocketFlags.None, endpoint);
  12. SocketReceiveFromResult resultOrig = await receiveTaskOrig;
  13. Console.WriteLine(Encoding.UTF8.GetString(buffer, 0, result.ReceivedBytes) + " from " + result.RemoteEndPoint);
  14. // Prints:
  15. // Hello world! from 127.0.0.1:59769
  16. // --------
  17. // New variables that can be re-used for subsequent calls:
  18. SocketAddress receivedAddress = endpoint.Serialize();
  19. SocketAddress targetAddress = endpoint.Serialize();
  20. // New code that will mutate provided SocketAddress for each ReceiveFromAsync:
  21. ValueTask<int> receiveTaskNew = server.ReceiveFromAsync(buffer, SocketFlags.None, receivedAddress);
  22. await client.SendToAsync(message, SocketFlags.None, targetAddress);
  23. var length = await receiveTaskNew;
  24. Console.WriteLine(Encoding.UTF8.GetString(buffer, 0, length) + " from " + receivedAddress);
  25. // Prints:
  26. // Hello world! from InterNetwork:16:{233,121,127,0,0,1,0,0,0,0,0,0,0,0}

最重要的是,在 dotnet/runtime#86872 中改进了 SocketAddress 的使用。 SocketAddress 现在有几个额外的成员,使其本身更有用:

  • getter Buffer:访问整个底层地址缓冲区。
  • setter Size:能够调整上述缓冲区大小(只能调整到较小的大小)。
  • static GetMaximumAddressSize:根据地址类型获取必要的缓冲区大小。
  • 接口 IEquatable:SocketAddress 可用于区分套接字与之通信的对等点,例如作为字典中的键(这不是新功能,它只是使其可通过接口调用)。

最后,删除了一些内部制作的 IP 地址数据副本,以提高性能。

网络原语

MIME 类型

添加缺失的 MIME 类型是网络空间中投票最多的问题之一 (dotnet/runtime#1489)。这是一个主要由社区驱动的更改,导致了 dotnet/runtime#85807 API 提案。由于此添加需要经过 API 审核流程,因此有必要确保添加的类型是相关的并遵循规范(IANA 媒体类型)。对于这项准备工作,我们要感谢社区贡献者 Bilal-iommarinchenko

IP网络

.NET 8 中添加的另一个新 API 是新类型 IPNetwork (dotnet/runtime#79946)。该结构允许指定 RFC 4632 中定义的无类 IP 子网。例如:

  • 127.0.0.0/8 用于对应于 A 类子网的无类定义。
  • 42.42.128.0/17 用于 215 个地址的无类别子网。
  • 2a01:110:8012::/100 用于 228 个地址的 IPv6 子网。

新的 API 可以使用构造函数从 IP 地址和前缀长度进行构造,也可以通过 TryParseParse 从字符串进行解析。最重要的是,它允许使用 Contains 方法检查 IP 地址是否属于子网。示例用法如下:

  1. // IPv4 with manual construction.
  2. IPNetwork ipNet = new IPNetwork(new IPAddress(new byte[] { 127, 0, 0, 0 }), 8);
  3. IPAddress ip1 = new IPAddress(new byte[] { 255, 0, 0, 1 });
  4. IPAddress ip2 = new IPAddress(new byte[] { 127, 0, 0, 10 });
  5. Console.WriteLine($"{ip1} {(ipNet.Contains(ip1) ? "belongs" : "doesn't belong")} to {ipNet}");
  6. Console.WriteLine($"{ip2} {(ipNet.Contains(ip2) ? "belongs" : "doesn't belong")} to {ipNet}");
  7. // Prints:
  8. // 255.0.0.1 doesn't belong to 127.0.0.0/8
  9. // 127.0.0.10 belongs to 127.0.0.0/8
  10. // IPv6 with parsing.
  11. IPNetwork ipNet = IPNetwork.Parse("2a01:110:8012::/96");
  12. IPAddress ip1 = IPAddress.Parse("2a01:110:8012::1742:4244");
  13. IPAddress ip2 = IPAddress.Parse("2a01:110:8012:1010:914e:2451:16ff:ffff");
  14. Console.WriteLine($"{ip1} {(ipNet.Contains(ip1) ? "belongs" : "doesn't belong")} to {ipNet}");
  15. Console.WriteLine($"{ip2} {(ipNet.Contains(ip2) ? "belongs" : "doesn't belong")} to {ipNet}");
  16. // Prints:
  17. // 2a01:110:8012::1742:4244 belongs to 2a01:110:8012::/96
  18. // 2a01:110:8012:1010:914e:2451:16ff:ffff doesn't belong to 2a01:110:8012::/96

请注意,不应将此类型与自 1.0 以来 ASP.NET Core 中存在的 Microsoft.AspNetCore.HttpOverrides.IPNetwork 类混淆。我们预计 ASP.NET API 最终将迁移到新的 System.Net.IPNetwork 类型 (dotnet/aspnetcore#46157)。

最后的注释

本博文选择的主题并不是 .NET 8 中所做的所有更改的详尽列表,只是我们认为可能最有趣的主题。如果您对性能改进更感兴趣,您应该查看 Stephen 的大型性能博客文章中的网络部分。如果您有任何疑问或发现任何错误,您可以在 dotnet/runtime 存储库中与我们联系。

最后,我要感谢我的合著者:

原文链接

.NET 8 Networking Improvements

知识共享许可协议

本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。

欢迎转载、使用、重新发布,但务必保留文章署名 郑子铭 (包含链接: http://www.cnblogs.com/MingsonZheng/ ),不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。

如有任何疑问,请与我联系 (MingsonZheng@outlook.com)

原文链接:https://www.cnblogs.com/MingsonZheng/p/18013688

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号