ASP.NET Core之详解服务器-Kestrel、IIS、http.sys
一、前言
上一章节,我们介绍了服务器相关的基础知识,本章我们继续介绍关于ASP.NET Core中服务器的内容,主要内容包括①实现跨平台的Kestrel服务器,②默认使用Kestrel作为Web服务器,③集成IIS服务器实现进程与进程外的模式,④使用Http.sys的服务器。通过这些内容比较全面的认识在ASP.NET Core中服务器的使用,多样的选择,性能情况。
二、关于Kestrel服务器
1、Kestrel服务器的定义?
Kestrel 是一个跨平台的 ASP.NET Core Web 服务器,它默认包含在 ASP.NET Core 项目模板中。作为 ASP.NET Core 应用程序的默认 Web 服务器和托管组件,Kestrel 提供了处理 HTTP 请求和响应的功能,默认处于启用状态。是一个程序内置Kestrel HTTP通信组件。
2、Kestrel服务器的关键点?
①跨平台,Kestrel可以在 Windows、macOS 和 Linux 上运行,这使得它成为构建跨平台 ASP.NET Core 应用程序的理想选择,与.NET Framework的ASP.NET只能部署在Windows的IIS环境不同,提供多个平台的支持,真正的跨平台。
②轻量级,指Kestrel服务器与其他IIS、Apache、Ngnix、Tomcat等服务器相比,其主要专注于处理 HTTP 请求和响应,不包含上述服务器的相同的完整 Web 服务器功能集,但是对于ASP.NET Core应用程序来说足够满足需求。而且可以通过代理服务器的方式来弥补其不足的部分,所以Kestrel相对于其它服务器来属于轻量级服务器。
③可扩展,指Kestrel是轻量服务器,但是通过使用反向代理服务器的方式扩展其功能,包括额外的安全性、性能等。
④可配置,指Kestrel服务器是内置在ASP.NET Core应用,所以在Program.cs
或 Startup.cs
文件中进行,或者通过环境变量和命令行参数进行,提供入口对服务器的参数配置调优等需求。可以配置 Kestrel来监听特定的 IP 地址和端口,设置 TLS/SSL 选项,以及调整其他网络相关的设置。
⑤ASP.NET Core的紧密集成,Kestrel 与 ASP.NET Core 框架紧密集成,因此你可以使用 ASP.NET Core 的所有功能,如 MVC、Razor Pages、Web API 等。
⑥性能,被设计为高性能的服务器,能够处理大量的并发请求。它使用异步 I/O 操作来减少线程使用并提高吞吐量。(是一个基于libuv的跨平台HTTP服务器,libuv是一个跨平台的异步I/O库)。
⑦安全性,Kestrel提供了许多安全性功能,如 TLS/SSL 支持、HTTP/2 支持和请求大小限制。还可以配置 Kestrel 以使用 HTTP 严格传输安全(HSTS)和其他安全相关的 HTTP 头。
3、Kestrel服务器的应用场景?
①开发环境,在IDE中提供开发 ASP.NET Core 应用程序时,Kestrel 可以作为内置的 Web 服务器使用,在本地机器上启动和运行应用程序,无需额外的Web服务器软件。
②自托管的应用程序,对于需要自托管的应用程序(即应用程序本身负责提供 Web 服务),Kestrel 是一个理想的选择。它轻量级、易于配置,并且与 ASP.NET Core 紧密集成。
③容器化部署,在ASP.NET Core使用Docker或其他容器部署时候,可以作为容器内的Web服务器,基于轻量级与跨平台非常合适在容器环境内运行。
④云原生应用,在构建云原生应用,作为应用的内置服务器,结合 Kubernetes、Service Fabric 等容器编排工具,可以轻松扩展和管理多个 Kestrel 实例。
⑤小型或者中型的生产环境,在一些小型到中型生产环境中,可以将Kestrel直接作为Web服务器使用,不必使用反向代理或负载均衡器,其高性能完全满足需求,但是对于大型的生产环境,则必须使用更强大的 Web 服务器或负载均衡器来应对更高的并发请求和更复杂的网络需求。
⑥内部应用,对于不需要直接暴露给外部世界的内部应用程序(如内部 API、后台任务等),Kestrel 可以提供足够的功能和安全性,这些应用通过内网或者VPN提供服务。
⑦物联网或嵌入式应用,在物联网(IoT)和嵌入式系统领域,可以在资源受限的设备上运行,并与 ASP.NET Core 应用程序一起提供 Web 服务。
通过上述的定义、关键点、应用场景基本对Kestrel比较清晰的认识了,具体这个组件的设计方式,实现方式可以通过查看微软提供开源代码阅读了解,当然在.NET Core的发展过程中,这个组件一样不断完善提供更好性能与功能,不仅仅作为应用服务器或者说Web服务器,我们要关注的是集成在ASP.NET Core中,提供高性能,便捷的能力。
历史:据说微软的灵感来自Node.js,因为Node.js有一个名为libuv的HTTP服务器。在ASP.NET Core的第一个版本中,微软也使用了libuv,然后在其顶部添加了一个名为Kestrel的层。此时,Node.js 和ASP.NET Core 共享相同的HTTP服务器。随着.NET Core 框架的不断发展和新的.NET Socket的实现,Microsoft基于.NET Socket构建了自己的HTTP服务器,同时删除了libuv,因为libuv不可控也不属于微软,这种重新造轮子的现象在大厂司空见惯。现在,Kestrel已经发展成熟,是一个能运行ASP.NET Core应用的微软系的HTTP服务器。
三、ASP.NET Core中默认Kestrel服务器
Kestrel服务器紧密的集成到ASP.NET Core中,并且是默认的服务器,那么在ASP.NET Core是如何集成的?是如何设置成默认服务器?是如何对服务器进行配置?我们通过阅读ASP.NET Core的源码来探索相关的内在本质与答案,通过实践Demo来验证答案,最终理解Kestrel运行原理。而且.NET Core从1.0、2.0、3.0到.NET 5.0/.NET 6.0~8.0的版本迭代中基于默认服务器没有变化,但是语法、扩展功能都进行调整,所以在探索的时候会基于版本来区分学习。
1、如何集成在ASP.NET Core?
在学习ASP.NET Core的program中要构建一个宿主(WebHost),具体内容参考前一章节关于.NET Core的宿主,在入口函数构建WebHost主要目的是①负责应用程序启动和生存期管理;②主机也是封装应用程序资源的对象;具体表现在①依赖注入(DI),使用.NET Core框架自身提供的依赖注入或者第三方;②Logging,配置,初始化日志对象;③Configuration,加载系统配置项(json/xml文件);④IHostedService 实现,是启动HTTP服务器实现Web功能,这里默认Kestrel服务器,代码如下所示
namespace DemoNetCore2 { public class Program { public static void Main(string[] args) { CreateWebHostBuilder(args).Build().Run(); } public static IWebHostBuilder CreateWebHostBuilder(string[] args) => WebHost.CreateDefaultBuilder(args) .UseStartup<Startup>(); } } namespace DemoNetCore2 { public class Startup { public Startup(IConfiguration configuration) { Configuration = configuration; } public IConfiguration Configuration { get; } // This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) { services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IHostingEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.Run(async (context) => { context.Response.ContentType = "text/plain; charset=utf-8"; await context.Response.WriteAsync("Hello World!我是基于WebHostBuilder构建的宿主环境!!"); }); app.UseMvc(); } } }
上述代码在program中通过WebHost对象,使用静态的构建方法CreateDefaultBuilder,创建一个WebHost对象,并且加载启动类Startup。在类Startup中通过ConfigureServices方法依赖注入服务,在Configure中主要启用重要的中间件;基本实现上面提到的构建WebHost的目的。但是可以发现代码并没有对Kestrel服务器进行默认,所以必须阅读相关源码寻找答案。在ASP.NET Core源码的aspnet-core\src\DefaultBuilder文件夹的WebHost类中,截取如下主要代码,一个初始化方法CreateDefaultBuilder,在program中调用,一个ConfigureWebDefaults方法默认配置。
/// <returns>The initialized <see cref="IWebHostBuilder"/>.</returns> public static IWebHostBuilder CreateDefaultBuilder(string[] args) { var builder = new WebHostBuilder(); if (string.IsNullOrEmpty(builder.GetSetting(WebHostDefaults.ContentRootKey))) { builder.UseContentRoot(Directory.GetCurrentDirectory()); } if (args != null) { builder.UseConfiguration(new ConfigurationBuilder().AddCommandLine(args).Build()); } builder.ConfigureAppConfiguration((hostingContext, config) => { var env = hostingContext.HostingEnvironment; config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true); if (env.IsDevelopment()) { if (!string.IsNullOrEmpty(env.ApplicationName)) { var appAssembly = Assembly.Load(new AssemblyName(env.ApplicationName)); if (appAssembly != null) { config.AddUserSecrets(appAssembly, optional: true); } } } config.AddEnvironmentVariables(); if (args != null) { config.AddCommandLine(args); } }) .ConfigureLogging((hostingContext, loggingBuilder) => { loggingBuilder.Configure(options => { options.ActivityTrackingOptions = ActivityTrackingOptions.SpanId | ActivityTrackingOptions.TraceId | ActivityTrackingOptions.ParentId; }); loggingBuilder.AddConfiguration(hostingContext.Configuration.GetSection("Logging")); loggingBuilder.AddConsole(); loggingBuilder.AddDebug(); loggingBuilder.AddEventSourceLogger(); }). UseDefaultServiceProvider((context, options) => { options.ValidateScopes = context.HostingEnvironment.IsDevelopment(); }); ConfigureWebDefaults(builder); return builder; } internal static void ConfigureWebDefaults(IWebHostBuilder builder) { builder.ConfigureAppConfiguration((ctx, cb) => { if (ctx.HostingEnvironment.IsDevelopment()) { StaticWebAssetsLoader.UseStaticWebAssets(ctx.HostingEnvironment, ctx.Configuration); } }); ConfigureWebDefaultsWorker( builder.UseKestrel(ConfigureKestrel), services => { services.AddRouting(); }); builder .UseIIS() .UseIISIntegration(); } internal static void ConfigureWebDefaultsSlim(IWebHostBuilder builder) { ConfigureWebDefaultsWorker(builder.UseKestrelCore().ConfigureKestrel(ConfigureKestrel), configureRouting: null); } private static void ConfigureKestrel(WebHostBuilderContext builderContext, KestrelServerOptions options) { options.Configure(builderContext.Configuration.GetSection("Kestrel"), reloadOnChange: true); } private static void ConfigureWebDefaultsWorker(IWebHostBuilder builder, Action<IServiceCollection>? configureRouting) { builder.ConfigureServices((hostingContext, services) => { // Fallback services.PostConfigure<HostFilteringOptions>(options => { if (options.AllowedHosts == null || options.AllowedHosts.Count == 0) { // "AllowedHosts": "localhost;127.0.0.1;[::1]" var hosts = hostingContext.Configuration["AllowedHosts"]?.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries); // Fall back to "*" to disable. options.AllowedHosts = (hosts?.Length > 0 ? hosts : new[] { "*" }); } }); // Change notification services.AddSingleton<IOptionsChangeTokenSource<HostFilteringOptions>>( new ConfigurationChangeTokenSource<HostFilteringOptions>(hostingContext.Configuration)); services.AddTransient<IStartupFilter, HostFilteringStartupFilter>(); services.AddTransient<IStartupFilter, ForwardedHeadersStartupFilter>(); services.AddTransient<IConfigureOptions<ForwardedHeadersOptions>, ForwardedHeadersOptionsSetup>(); // Provide a way for the default host builder to configure routing. This probably means calling AddRouting. // A lambda is used here because we don't want to reference AddRouting directly because of trimming. // This avoids the overhead of calling AddRoutingCore multiple times on app startup. if (configureRouting == null) { services.AddRoutingCore(); } else { configureRouting(services); } }); }
在源码中build对象通过ConfigureAppConfiguration委托构建系统配置项(appsettings.json),ConfigureLogging委托构建系统日志对象;ConfigureWebDefaultsWorker设置了默认服务器builder.UseKestrel(ConfigureKestrel),包括服务器配置项builderContext.Configuration.GetSection("Kestrel"),所以在构造方法已经将服务器进行默认,在使用时默认启用该服务器。
我们在开发环境或者生产环境,选择使用IIS Express启动调试,或者使用IIS部署,会发现在program中没有指定IIS作为服务器,应用程序一样运行,不会抛出异常,与我们上述确定的Kestrel为默认服务器不一致;原因是在源代码中已经集成了IIS服务器,在netcoreapp2.1版本前并未集成,在netcoreapp3.1版本中已经集成,可以使用IIS作为服务器,主要代码builder.UseIIS().UseIISIntegration();
2、ASP.NET Core使用Kestrel默认服务如何运行?
在VS创建一个应用程序,不修改启动项,然后编译发布,可以在cmd使用CLI的dotnet build,dotnet run按照默认参数启动应用程序。
在发布文件publish中运行程序,在资源管理器可以看到.NET Host进程,没有使用IIS或者其他Web服务器,然后在浏览器访问http://localhost:5000,成功返回请求结果。
四、ASP.NET Core中集成使用IIS服务器
使用默认服务器实现Web服务器的请求,ASP.NET Core已经集成IIS,可以使用IIS作为Web服务器。如上源代码中builder.UseIIS().UseIISIntegration(),其中UseIIS()是使用IIS服务器,UseIISIntegration()使用IIS为代理服务器,所以IIS服务器提供两种部署模式,使用IIS我们都知道是按照管道的方式来处理请求,那么IIS是如何整合IIS?
1、关于IIS的模块、处理映射程序?
使用IIS提供的原生Module,ASP.NET CORE Core Module来实现,它利用注册的事件将请求从IIS管道中拦截下来,并转发给ASP.NET CORE管道进行处理。如下是IIS已经安装两个ASP.NET Core Module,一个是基于.NET Core3.1版本,一个.NET 5.0~8.0。而且.NET Framework在IIS中运行一样要是IsapiMoudle模块,所以IIS是基于模块的方式将请求转给具体的应用程序中处理,返回具体的请求结果。
这个是ASP.NET Core在IIS运行所需的模块。
这个是ASP.NET在IIS运行所需的模块。
这个Cgi模块使PHP程序部署在IIS中运行。
2、关于IIS部署ASP.NET的经典托管模式。
如上图在经典ASP.NET应用程序中,所有一切都托管在IIS工作进程中(w3wp.exe),这也被称为IIS应用程序池。ASP.NET程序被托管在应用程序池中,并且被按照IIS内建的ASP.NET托管特性所实例化。当请求从http.sys传入到ASP.NET应用程序管道时,本地运行时管理器会实例化一个代表应用程序的.NET运行时,同时引入HttpRuntime对象用来处理这个请求。来自http.sys的请求被派送到对应的应用程序池和HttpRuntime实例的托管站点。
3、关于IIS中部署使用In-Process模式?
如果ASP.NET Core应用程序是内部进程模式,则无限修改任何代码,将应用程序发布,部署到IIS中。In-Process模式下的ASP.NET CORE应用运行在IIS的工作进程w3wp.exe中(开发环境如果采用IIS Express,工作进程为iisexpress.exe),ASP.NET CORE应用在这种模式下使用的服务器类型是IISHttpServer,上述的ASP.NET CORE Core Module会将原始的请求转发给这个服务器,并将后者生成响应转交给IIS服务器进行回复。
如上图,在IIS新建一个网站test,应用程序池使用无托管集成模式,然后在浏览器执行接口请求,应用程序执行首次请求会在资源管理器中新建一个进程名为IIS Worker Process(WAS(Windows Activation Service)的会接收请求,然后按照网站配置项信息,激活一个进程),然后接收请求返回结果,在程序中打印进程名称会返回w3wp的信息,表明是使用了工作进程w3wp.exe。并且通过查看发布生成的webconfig文件可以找到处理请求的模块与模式信息。
<?xml version="1.0" encoding="utf-8"?> <configuration> <location path="." inheritInChildApplications="false"> <system.webServer> <handlers> <add name="aspNetCore" path="*" verb="*" modules="AspNetCoreModuleV2" resourceType="Unspecified" /> </handlers> <aspNetCore processPath="dotnet" arguments=".\serverDemo.dll" stdoutLogEnabled="false" stdoutLogFile=".\logs\stdout" hostingModel="InProcess" /> </system.webServer> </location> </configuration> <!--ProjectGuid: BD9F5E0F-0149-4846-87DE-25FD91F61C88-->
4、关于IIS中部署使用out-Process模式?
如果ASP.NET Core应用程序是外部进程模式,采用KestrelServer的ASP.NET CORE应用运行在独立的dotnet.exe进程中。当IIS接受到针对目标应用的请求时,如果目标应用所在的进程并未启动,ASP.NET CORE Core Module还负责执行dotnet命令激活此进程,相当于充当了WAS(Windows Activation Service)的作用。实际上IIS充当反向代理的作用,可以将IIS换成nginx或者apache来反向代理服务器。
我们将上面的webconfig的hostingModel模式修改成outofProcess模式,然后重新启动网站。如下,后台进程会创建一个.NET Host的程序即图上的dotnet.exe进程和IIS的w3wp.exe,并且浏览器返回的进程是dotnet而不是w3wp。
<?xml version="1.0" encoding="utf-8"?> <configuration> <location path="." inheritInChildApplications="false"> <system.webServer> <handlers> <add name="aspNetCore" path="*" verb="*" modules="AspNetCoreModuleV2" resourceType="Unspecified" /> </handlers> <aspNetCore processPath="dotnet" arguments=".\serverDemo.dll" stdoutLogEnabled="false" stdoutLogFile=".\logs\stdout" hostingModel="OutOfProcess" /> </system.webServer> </location> </configuration> <!--ProjectGuid: BD9F5E0F-0149-4846-87DE-25FD91F61C88-->
上述就是两种模式在IIS中部署的效果,不论是采用何种部署模式,相关的配置都定义在部署目录下的web.config配置文件,它提供的针对ASP.NET CORE Core Module的映射使我们能够将ASP.NET CORE应用部署在IIS中。在web.config中,与ASP.NET CORE应用部署相关的配置定义在<aspNetCore>配置节中。
五、ASP.NET Core中使用Http.sys服务器
http.sys服务器是应用程序选择Windows环境部署,不支持跨平台,选择这个服务器是Windows环境下任何针对HTTP的网络监听器/服务器在性能上都无法与HTTP.SYS比肩,在Windows环境中系统针对HTTP的监听、接收、转发和响应大都依赖它
如上图HTTP.SYS是运行在内核态,而IIS或者其他Http的监听则是运行在用户态,所以在性能方面具备先天优势。由于HTTP.SYS是一个底层共享的网络驱动,它有效地解决了端口共享的问题。用户态进程会使用地址前缀(含端口号)“接入”HTTP.SYS,后者利用提供的地址前缀来转发请求,多个用户态进程只要保证提供的地址前缀不同就可以了,所以它们可以使用相同的端口号。端口共享使每个用户进程都可以使用标准的80/443端口。
1、在ASP.NET Core中如何使用HTTP.SYS?
我们先创建一个应用程序,然后指定HTTP.SYS为服务器,代替默认服务器,执行请求的监听、响应、返回结果。
namespace DemoNetCore8 { public class Program { public static void Main(string[] args) { // 获取宿主创建对象 var builder = WebApplication.CreateBuilder(args); builder.WebHost.UseHttpSys(); // 在宿主加入后台任务服务(应用程序) builder.Services.AddHostedService<Worker>(); // 在宿主加入自定义的应用程序 builder.Services.AddHostedService<UserHostService>(); // Add services to the container. builder.Services.AddAuthorization(); var app = builder.Build(); // Configure the HTTP request pipeline. app.UseAuthorization();
string name = Process.GetCurrentProcess().ProcessName;
string SessionId = Process.GetCurrentProcess().SessionId.ToString();
string MachineName = Process.GetCurrentProcess().MachineName;
string osDescription = RuntimeInformation.OSDescription;
app.Run(context => context.Response.WriteAsync(string.Format("ProcessName:{0}", Process.GetCurrentProcess().ProcessName)));
app.Run(context => context.Response.WriteAsync(string.Format("SessionId:{0}", Process.GetCurrentProcess().SessionId.ToString())));
app.Run(context => context.Response.WriteAsync(string.Format("MachineName:{0}", Process.GetCurrentProcess().MachineName.ToString())));
app.Run(context => context.Response.WriteAsync(string.Format("osDescription:{0}", osDescription)));
app.Run(async (context) => { context.Response.ContentType = "text/plain; charset=utf-8"; await context.Response.WriteAsync("Hello World!我是基于WebApplicationBuilder构建的宿主环境!!"); }); var summaries = new[] { "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" }; app.MapGet("/weatherforecast", (HttpContext httpContext) => { var forecast = Enumerable.Range(1, 5).Select(index => new WeatherForecast { Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)), TemperatureC = Random.Shared.Next(-20, 55), Summary = summaries[Random.Shared.Next(summaries.Length)] }) .ToArray(); return forecast; }); app.Run(); } } }
使用builder.WebHost.UseHttpSys(),因为ASP.NET Core中集成了HTTP.SYS服务器,然后在cmd中运行这个应用程序,如下图所示
如果在Windows环境下部署,使用HTTP.SYS是一个不错的选择,提供很高的性能体验。
六、总结
ASP.NET Core应用程序,在默认服务器、IIS服务器、HTTP.SYS服务器的发布部署提供了多种选择,具体使用按照实际的场景来决定,合理配置带来满足需求的体验。所以在.NET Core中对于这一块的设计还是很优秀的,上述只是Demo的实际,在生产环境还是要配置各自参数来适应生产环境的要求。
参考https://www.cnblogs.com/artech/p/inside-asp-net-core-6-33.html、https://cloud.tencent.com/developer/article/1614524?areaId=106001、https://zhuanlan.zhihu.com/p/535207607、https://cloud.tencent.com/developer/article/1023250?areaId=106001
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek “源神”启动!「GitHub 热点速览」
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 我与微信审核的“相爱相杀”看个人小程序副业
· C# 集成 DeepSeek 模型实现 AI 私有化(本地部署与 API 调用教程)
· DeepSeek R1 简明指南:架构、训练、本地部署及硬件要求