.NET 7更新之网络,是你期待的吗?


 

最新的.NET 7现已发布,我们想介绍一下其在网络领域所做的一些有趣的更改和添加。这篇博文讨论了 .NET 7 在HTTP 空间新 QUIC API网络安全WebSockets方面的变化。 HTTP 改进了对连接尝试失败的处理 在 .NET 6 之前的版本中,如果连接池中没有立即可用的连接,(处理程序上的设置允许的情况下,例如HTTP /1.1中的MaxConnectionsPerServer,或 HTTP/2中的EnableMultipleHttp2Connections)新的 HTTP 请求始终会发起新的连接尝试并等待响应。这样做的缺点是,建立该连接需要一段时间,而在这段时间里如果另一个连接已经可用,该请求仍将继续等待它生成的连接,从而影响延迟。在 .NET 6.0中我们改变了这一进程,无论是新建立的连接还是与此同时准备好处理请求的另一个连接,第一个可用的连接会处理请求。这样仍会一个新连接被建立(受限制),如果发起的请求未使用这一连接,则它会被合并以供后续请求使用。 不幸的是,.NET 6.0 中的这一功能对某些用户来说是有问题的:失败的连接尝试也会使位于请求队列顶部的请求失败,这可能会在某些情况下导致意外的请求失败。此外,如果由于某些原因(例如由于服务器行为不当或网络问题)池中有一个永远未被使用的连接,与之关联的新传入的请求也将延迟并可能超时。 在 .NET 7.0 中,我们实施了以下更改来解决这些问题: 失败的连接尝试只能使其相关的发起请求失败,而不会导致无关的请求失败。如果在连接失败时原始请求已得到处理,则连接失败将被忽略 ( dotnet/runtime#62935 )。 如果一个请求发起了一个新连接,但随后被池中的另一个连接处理,则新的待使用的连接尝试将不管ConnectTimeout,在短时间后自动超时。通过此更改,延迟的连接将不会延迟不相关的请求 ( dotnet/runtime#71785 )。请注意,不被使用的连接尝试自动超时失败的这一进程只会在后台自己运行,用户不会看到此进程。观察它们的唯一方法是启用telemetry。 HttpHeaders 读取线程安全 这些HttpHeaders集合从来都不是线程安全的。访问header可能会强制延迟解析它的值,从而导致对底层数据结构的修改。 在 .NET 6 之前,同时读取集合在大多数情况下恰好是线程安全的。 从 .NET 6 开始,由于内部不再需要锁定,针对header解析执行的锁定较少。这一变化导致许多用户错误地同时访问header,例如,在 gRPC ( dotnet/runtime#55898 )、NewRelic ( newrelic/newrelic-dotnet-agent#803 ) 甚至 HttpClient 本身 ( dotnet/runtime #65379)。违反 .NET 6 中的线程安全可能会导致header值重复/格式错误或在枚举(enumeration)/header访问期间产生各种异常。 .NET 7 使header行为更加直观。该HttpHeaders集合现在符合Dictionary线程安全保证: 集合可以同时支持多个读者,只要它不被修改。极少数情况下,枚举(enumeration)与书写访问权限争用,则该集合必须在整个枚举期间被锁定。要允许多个线程访问集合以同时进行读写,您必须实现自己的同步。 这是通过以下更改实现的: 无效值的“验证读取”不会删除无效值 – dotnet/runtime#67833(感谢@heathbm)。 同时读取是线程安全的——dotnet/runtime#68115检测 HTTP/2 HTTP/3 协议错误 HTTP/2 和 HTTP/3 协议在RFC 7540 第 7 节RFC 9114 第 8.1节中定义了协议级别的错误代码,例如,HTTP/2 中的REFUSED_STREAM (0x7) 或HTTP/3 中的H3_EXCESSIVE_LOAD (0x0107) 。与 HTTP 状态代码不同,这是对大多数HttpClient用户来说不重要的低级错误信息,但它在高级 HTTP/2 或 HTTP/3 场景中有帮助,特别是 grpc-dotnet,其中区分协议错误对于实现客户端重试至关重要。 我们定义了一个新的异常HttpProtocolException来在其ErrorCode属性中保存协议级错误代码。 HttpClient直接调用时,HttpProtocolException可以是内部异常HttpRequestException: try { using var response = await httpClient.GetStringAsync(url);