如果我们只需要将ASP.NET CORE应用部署到Windows环境下,并且希望获得更好的性能,那么我们选择的服务器类型应该是HTTP.SYS。Windows环境下任何针对HTTP的网络监听器/服务器在性能上都无法与HTTP.SYS比肩。

一、HTTP.SYS简介

二、MessagePump & UseHttpSys

三、HttpSysOptions

一、HTTP.SYS简介

HTTP.SYS本质上就是一个HTTP/HTTPS监听器,它是Windows网络子系统的一部分,是一个在内核模式下运行的网络驱动。HTTP.SYS对应的驱动文件为“%WinDir\System32\drivers\http.sys”,不要小看这个只有1M多的文件,Windows系统针对HTTP的监听、接收、转发和响应大都依赖它。如图1所示,HTTP.SYS建立在Windows网络子系统针对TCPIP协议栈的驱动(TCPIP.SYS)之上,并为用户态运行的IIS提供基础的HTTP通信服务。前面我们使用的HttpListener也建立在HTTP.SYS上面。

图1 HTTP.SYS

由于HTTP.SYS是在操作系统内核态运行,所以它提供的性能优势是其他在用户态运行的同类产品无法比拟的。由于它自身提供响应缓存,所以在缓存命中的情况下根本不需要与用户态进程进行交互。它还提供了请求队列(Request Queue),如果请求的目标进程(比如IIS的工作进程)处于活动状态,它可以直接将请求分它给它,否则请求会暂存于队列中等待目标进程来提取,这样的工作模式既减少了内核态与用户态之间的上下文切换,也确保请求不会丢失。HTTP.SYS还提供连接管理,流量限制,诊断日志等功能,并提供针对Kerberos的Windows认证。

由于HTTP.SYS是一个底层共享的网络驱动,它有效地解决了端口共享的问题。用户态进程会使用地址前缀(含端口号)“接入”HTTP.SYS,后者利用提供的地址前缀来转发请求,多个用户态进程只要保证提供的地址前缀不同就可以了,所以它们可以使用相同的端口号。端口共享使每个用户进程都可以使用标准的80/443端口。

二、MessagePump & UseHttpSys

基于HTTP.SYS的服务器体现为如下这个MessagePump类型,它内部使用一个HttpSysListener对象采用注册的监听地址接入HTTP.SYS。MessagePump提供针对HTTP 1.X、HTTP 2以及HTTPS的支持。对于Windows Server 2022和Windows 11,还支持HTTP 3。IWebHostBuilder接口如下这两个UseHttpSys扩展方法用来完成针对MessagePump的注册。

internal class MessagePump : IServer, IDisposable
{
internal HttpSysListener Listener { get; }
public IFeatureCollection Features { get; }
public MessagePump(IOptions<HttpSysOptions> options, ILoggerFactory loggerFactory,IAuthenticationSchemeProvider authentication);
public Task StartAsync<TContext>(IHttpApplication<TContext> application,CancellationToken cancellationToken);
public Task StopAsync(CancellationToken cancellationToken);
public void Dispose();
} public static class WebHostBuilderHttpSysExtensions
{
[SupportedOSPlatform("windows")]
public static IWebHostBuilder UseHttpSys(this IWebHostBuilder hostBuilder); [SupportedOSPlatform("windows")]
public static IWebHostBuilder UseHttpSys(this IWebHostBuilder hostBuilder,Action<HttpSysOptions> options);
}

如下所示的是在Minimal API下调用UseHttpSys注册MessagePump 服务器的例子。

var builder = WebApplication.CreateBuilder(args);
builder.WebHost.UseHttpSys();
var app = builder.Build();
app.MapGet("/", () => "Hello World");
app.Run();

三、HttpSysOptions

在调用UseHttpSys扩展方法注册基于HTTP.SYS的MessagePump服务器的时候,我们可以利用提供的Action<HttpSysOptions>委托对相关的配置选项进行设置。HttpSysOptions的UrlPrefixes属性返回注册的监听地址前缀,但是最终是否这种直接注册到服务器上的监听器地址,取决于IServerAddressesFeature特性的PreferHostingUrls属性,这一点与KestrelServer是一致的。

public class HttpSysOptions
{
public UrlPrefixCollection UrlPrefixes { get; }
public RequestQueueMode RequestQueueMode { get; set; }
public string? RequestQueueName { get; set; }
public long RequestQueueLimit { get; set; }
public AuthenticationManager Authentication { get; }
public ClientCertificateMethod ClientCertificateMethod { get; set; }
public long? MaxConnections { get; set; }
public long? MaxRequestBodySize { get; set; }
public int MaxAccepts { get; set; }
public Http503VerbosityLevel Http503Verbosity { get; set; }
public TimeoutManager Timeouts { get; }
public bool AllowSynchronousIO { get; set; }
public bool EnableResponseCaching { get; set; }
public bool ThrowWriteExceptions { get; set; }
public bool UnsafePreferInlineScheduling { get; set; }
public bool UseLatin1RequestHeaders { get; set; }
}

HTTP.SYS利用请求队列来存放待处理的请求,我们可以利用RequestQueueMode属性决定创建一个新的队列或者使用现有的队列。该属性类型为如下这个RequestQueueMode枚举,枚举项Create表示创建新的队列,Attach表示使用现有的以RequestQueueName属性命名的对象,如果该队列不存在会抛出异常。CreateOrAttach提供了一个折中方案,如果指定名称的队列不存在就创建一个以此命名的新队列。该属性的默认值为Create,RequestQueueName属性默认值为Null(代表匿名队列),RequestQueueLimit属性表示队列的容量,默认值为1000。HttpSysOptions承载的很多配置选项只会应用到新创建的请求队列上。

public enum RequestQueueMode
{
Create,
Attach,
CreateOrAttach
}

HttpSysOptions的Authentication属性返回一个AuthenticationManager对象,我们利用它完成针对认证的设置。我们可以利用Schemes属性设置认证方案,该属性默认为None。如果不允许匿名访问,可以将AllowAnonymous属性设为False。如果将AutomaticAuthentication属性返回True(默认值),认证用户将自动赋值给HttpContext上下文的User属性。AuthenticationDisplayName属性用来为认证方案提供一个显示名称。

public sealed class AuthenticationManager
{
public AuthenticationSchemes Schemes { get; set; }
public bool AllowAnonymous {get; set; }
public bool AutomaticAuthentication { get; set; }
public string? AuthenticationDisplayName { get; set; }
} [Flags]
public enum AuthenticationSchemes
{
None = 0x0,
Digest = 0x1,
Negotiate = 0x2,
Ntlm = 0x4,
Basic = 0x8,
Anonymous = 0x8000,
IntegratedWindowsAuthentication = 0x6
}

HTTPS站点可以要求提供证书来对其实施认证,HttpSysOptions的ClientCertificateMethod属性用于设置请求客户端证书的方式,该属性返回如下这个ClientCertificateMethod枚举。在.NET 5之前,客户端证书采用Renegotation的方式来提取的,Renegotiation是在已经建立的SSL/TLS连接上再次发起的一轮“协商握手”,这种方式对应AllowRenegotation枚举项。由于可能带来一些性能和死锁的问题,这种方式在.NET 5之后已经默认禁止了,目前默认的方式是创建SSL/TLS连接的初始阶段就提取该证书,这种方式对应AllowRenegotation枚举项,这也是ClientCertificateMethod属性的默认值。

public enum ClientCertificateMethod
{
NoCertificate,
AllowCertificate,
AllowRenegotation
}

HttpSysOptions的MaxConnections和MaxRequestBodySize属性分别表示最大连接数和请求主体内容的最大字节数,如果它们被设置为Null,意味着忽略对应的限制。这两个属性的默认值分别Null和30,000,000。MaxAccepts属性表示接受的最大并发请求,默认值为当前处理器数量的5倍。如果并发请求数量超过限流设置,后续请求会拒绝处理,此时服务器会直接回复一个状态码为503的响应,与此同时还会根据Http503Verbosity属性设置的等级作相应的处理。如果该属性值为Basic(默认值),当前TCP连接会重置,Full和Limitmed选项会影响响应的状态描述,前者返回详细的Reason Phrase,后者采用标准的“Service Unavailable”。

public enum Http503VerbosityLevel
{
Basic,
Limited,
Full
}

HttpSysOptions的Timeouts属性返回如下这个TimeoutManager对象,我们利用它完成各种超时设置,包括请求主体内容抵达时间(EntityBody)、读取请求主体内容时间(DrainEntityBody),请求在队列中存放的时间(RequestQueue)、连接闲置时间(IdleConnection)和解析请求报头时间(HeaderWait),这些超时时间默认都是两分钟。MinSendBytesPerSecond属性表示响应数据的最小发送率,默认为每秒150字节。

public sealed class TimeoutManager
{
public TimeSpan EntityBody { get; set; }
public TimeSpan DrainEntityBody { get; set; }
public TimeSpan RequestQueue { get; set; }
public TimeSpan IdleConnection { get; set; }
public TimeSpan HeaderWait { get; set; }
public long MinSendBytesPerSecond { get; set; }
}

HttpSysOptions还定义了其他一系列属性。AllowSynchronousIO属性(默认为False)表示是否运行以同步IO的方式完成针对请求和响应主体内容的读写。EnableResponseCaching属性(默认为True)表示允许响应缓存。ThrowWriteExceptions属性(默认为False)表示因断开连接导致写入响应主体内容失败是否需要抛出异常。如果将UnsafePreferInlineScheduling(默认为False)设置为True,意味着会直接在读取请求的IO线程中执行后续的应用代码,否则我们编写的应用代码会分发到线程池中进行处理。这样可以通过避免线程切换减少单个请求的处理耗时,但是会对整体的吞吐量带来负面影响。UseLatin1RequestHeaders属性(默认为False)表示是否采用Latin1字符集(ISO-8859-1)对请求报头进行编码。

最新文章

  1. C#动态webservice调用接口 (JAVA,C#)
  2. Face++ – 提供给你实时的脸部识别 API
  3. 文件上传小技巧/后端处理【以php示例】
  4. 在Linux中怎么把用户添加到组中
  5. 4种kill某个用户所有进程的方法
  6. MySQL常用查询
  7. asp.net下载的方法
  8. 从零開始开发Android版2048 (五) 撤销的实现
  9. MVC校验特性
  10. Linux学习——shell编程之变量
  11. Net Core中数据库事务隔离详解——以Dapper和Mysql为例
  12. JXLS 2.4.0系列教程(五)——更进一步的应用和页面边距bug修复
  13. 吴裕雄 python 机器学习——Lasso回归
  14. 24小时学通Linux内核之向内核添加代码
  15. skywalking学习之路---skywalking环境从零搭建部署
  16. mysql主从复制报错(一主一从):从库报错Last_SQL_Errno: 1008
  17. 2018-2019-2 网络对抗技术 20165320 Exp5 MSF基础应用
  18. auto类型-现代C++新特性
  19. 解决 cmake_symlink_library: System Error: Operation not supported
  20. Win10桌面图标无法拖动

热门文章

  1. 不带头结点的单链表(基于c语言)
  2. spring boot使用注解进行模糊查询
  3. Blazor 002 : 一种开历史倒车的UI描述语言 -- Razor
  4. phpcms手工注入教程
  5. ctf之计算器
  6. GitHub还能这样玩,这次我真是开了眼了
  7. 【论文阅读】CVPR2021: MP3: A Unified Model to Map, Perceive, Predict and Plan
  8. JavaScript this的理解
  9. Maven项目报错:“No goals have been specified for this build”解决办法
  10. python 字典dict 增删改查操作