ASP.Net Core解读服务器

一、服务器的作用

ASP.NET Core 应用与进程内 HTTP 服务器实现一起运行。 该服务器实现侦听 HTTP 请求,并以组成 HttpContext 的请求功能集形式,将它们呈现给应用,即服务器负责HTTP请求的监听、接收和最终的响应。具体来说,启动后的服务器会绑定到指定的端口进行请求监听,一旦有请求抵达,服务器会根据该请求创建代表请求上下文的HttpContext对象,并将该上下文分发给注册的中间件进行处理,当中间件管道完成了针对请求的处理之后,服务器会将最终生成的响应回复给客户端。由于服务器是整个请求处理管道的“龙头”,所以启动和关闭应用的最终目的是启动和关闭服务器。

二、服务器的原理

ASP.NET Core框架中的服务器通过IServer接口来表示,该接口具有如下所示的3个成员: 

namespace Microsoft.AspNetCore.Hosting.Server
{
    /// <summary>
    /// Represents a server.
    /// </summary>
    public interface IServer : IDisposable
    {
        /// <summary>
        /// A collection of HTTP features of the server.
        /// </summary>
        IFeatureCollection Features { get; }

        /// <summary>
        /// Start the server with an application.
        /// </summary>
        /// <param name="application">An instance of <see cref="IHttpApplication{TContext}"/>.</param>
        /// <typeparam name="TContext">The context associated with the application.</typeparam>
        /// <param name="cancellationToken">Indicates if the server startup should be aborted.</param>
        Task StartAsync<TContext>(IHttpApplication<TContext> application, CancellationToken cancellationToken) where TContext : notnull;

        /// <summary>
        /// Stop processing requests and shut down the server, gracefully if possible.
        /// </summary>
        /// <param name="cancellationToken">Indicates if the graceful shutdown should be aborted.</param>
        Task StopAsync(CancellationToken cancellationToken);
    }
}
  • Features:保存服务器提供的特性,即服务器提供的特性保存在Features属性表示的IFeatureCollection集合中

  • StartAsync<TContext>:启动服务器

  • StopAsync:关闭服务器

服务器在开始监听请求之前总是绑定一个或者多个监听地址,这个地址是应用程序从外部指定的。具体来说,应用程序指定的监听地址会封装成一个特性,并且在服务器启动之前被添加到它的特性集合中。IServerAddressesFeature接口负责承载监听地址列表的特性。

namespace Microsoft.AspNetCore.Hosting.Server.Features
{
    /// <summary>
    /// Specifies the address used by the server.
    /// </summary>
    public interface IServerAddressesFeature
    {
        /// <summary>
        /// An <see cref="ICollection{T}" /> of addresses used by the server.
        /// </summary>
        ICollection<string> Addresses { get; }

        /// <summary>
        /// <see langword="true" /> to prefer URLs configured by the host rather than the server.
        /// </summary>
        bool PreferHostingUrls { get; set; }
    }
}
  • Addresses:地址列表

  • PreferHostingUrls:表示如果监听地址同时设置到承载系统配置和服务器上,是否优先考虑使用前者。

正如前面所说,服务器将用来处理IHttpApplication<TContext>接口表示的应用,所以可以将ASP.NET Core的请求处理管道视为IServer对象和IHttpApplication<TContext>对象的组合。当调用IServer对象的StartAsync<TContext>方法启动服务器时,我们需要提供这个用来处理请求的IHttpApplication<TContext>对象。IHttpApplication<TContext>采用基于上下文的请求处理方式,泛型参数TContext代表的就是上下文的类型。在IHttpApplication<TContext>处理请求之前,它需要先创建一个上下文对象,该上下文会在请求处理结束之后被释放。

三、服务器器的分类

1、Kestrel服务器

Kestrel 服务器是默认跨平台 HTTP 服务器实现。 Kestrel 提供了最佳性能和内存利用率。.NET Core 支持的所有平台和版本均支持 Kestrel。默认情况下,ASP.NET Core 项目模板使用 Kestrel。 在“Program.cs”中,ConfigureWebHostDefaults 方法调用 UseKestrel:

使用 Kestrel:

public static void Main(string[] args)
{
    CreateHostBuilder(args).Build().Run();
}

public static IHostBuilder CreateHostBuilder(string[] args) =>
    Host.CreateDefaultBuilder(args)
        .ConfigureWebHostDefaults(webBuilder =>
        { 
       webBuilder.UserKestrel(); webBuilder.UseStartup
<Startup>(); });

(1)Kestrel的原理

通过上面我们知道, Core定义了两个基本的接口IServer,及IHttpApplication<TContext>IServer接口定义了Web Server的基本功能,IHttpApplication<TContext>则定义了处理HTTP协议的应用程序的基本功能。Kestrel作为ASP.NET Core服务器的一种,先来看下KestrelServer的部分源码:

namespace Microsoft.AspNetCore.Server.Kestrel.Core
{
    internal class KestrelServerImpl : IServer
    {       
     。。。。。。
        public async Task StartAsync<TContext>(IHttpApplication<TContext> application, CancellationToken cancellationToken) where TContext : notnull
        {
            try
            {
                if (!BitConverter.IsLittleEndian)
                {
                    throw new PlatformNotSupportedException(CoreStrings.BigEndianNotSupported);
                }

                ValidateOptions();

                if (_hasStarted)
                {
                    // The server has already started and/or has not been cleaned up yet
                    throw new InvalidOperationException(CoreStrings.ServerAlreadyStarted);
                }
                _hasStarted = true;

                ServiceContext.Heartbeat?.Start();

                async Task OnBind(ListenOptions options)
                {
                    // INVESTIGATE: For some reason, MsQuic needs to bind before
                    // sockets for it to successfully listen. It also seems racy.
                    if ((options.Protocols & HttpProtocols.Http3) == HttpProtocols.Http3)
                    {
                        if (_multiplexedTransportFactory is null)
                        {
                            throw new InvalidOperationException($"Cannot start HTTP/3 server if no {nameof(IMultiplexedConnectionListenerFactory)} is registered.");
                        }

                        options.UseHttp3Server(ServiceContext, application, options.Protocols);
                        var multiplexedConnectionDelegate = ((IMultiplexedConnectionBuilder)options).Build();

                        // Add the connection limit middleware
                        multiplexedConnectionDelegate = EnforceConnectionLimit(multiplexedConnectionDelegate, Options.Limits.MaxConcurrentConnections, Trace);

                        options.EndPoint = await _transportManager.BindAsync(options.EndPoint, multiplexedConnectionDelegate, options).ConfigureAwait(false);
                    }

                    // Add the HTTP middleware as the terminal connection middleware
                    if ((options.Protocols & HttpProtocols.Http1) == HttpProtocols.Http1
                        || (options.Protocols & HttpProtocols.Http2) == HttpProtocols.Http2
                        || options.Protocols == HttpProtocols.None) // TODO a test fails because it doesn't throw an exception in the right place
                                                                    // when there is no HttpProtocols in KestrelServer, can we remove/change the test?
                    {
                        if (_transportFactory is null)
                        {
                            throw new InvalidOperationException($"Cannot start HTTP/1.x or HTTP/2 server if no {nameof(IConnectionListenerFactory)} is registered.");
                        }

                        options.UseHttpServer(ServiceContext, application, options.Protocols);
                        var connectionDelegate = options.Build();

                        // Add the connection limit middleware
                        connectionDelegate = EnforceConnectionLimit(connectionDelegate, Options.Limits.MaxConcurrentConnections, Trace);

                        options.EndPoint = await _transportManager.BindAsync(options.EndPoint, connectionDelegate, options.EndpointConfig).ConfigureAwait(false);
                    }
                }

                AddressBindContext = new AddressBindContext(_serverAddresses, Options, Trace, OnBind);

                await BindAsync(cancellationToken).ConfigureAwait(false);
            }
            catch
            {
                // Don't log the error https://github.com/dotnet/aspnetcore/issues/29801
                Dispose();
                throw;
            }
        }

        // Graceful shutdown if possible
        public async Task StopAsync(CancellationToken cancellationToken)
        {
            if (Interlocked.Exchange(ref _stopping, 1) == 1)
            {
                await _stoppedTcs.Task.ConfigureAwait(false);
                return;
            }

            _stopCts.Cancel();

            // Don't use cancellationToken when acquiring the semaphore. Dispose calls this with a pre-canceled token.
            await _bindSemaphore.WaitAsync().ConfigureAwait(false);

            try
            {
                await _transportManager.StopAsync(cancellationToken).ConfigureAwait(false);
            }
            catch (Exception ex)
            {
                _stoppedTcs.TrySetException(ex);
                throw;
            }
            finally
            {
                ServiceContext.Heartbeat?.Dispose();
                _configChangedRegistration?.Dispose();
                _stopCts.Dispose();
                _bindSemaphore.Release();
            }

            _stoppedTcs.TrySetResult();
        }  
       。。。。。。
    }
}

可以看到KestrelServerImpl实现了接口IServer。Kestrel服务的代码量并不下,其中主要是辅助接受用户请求和解析HTTP协议的代码。Kestrel服务在接受和处理请求时,都用到了线程池,可以极大的提高服务器的吞吐量。kestrel服务中具体的流程:

  • Kestrel首先会检查服务器的字节序,目前是不支持大端序的。
if (!BitConverter.IsLittleEndian)
{
    throw new PlatformNotSupportedException(CoreStrings.BigEndianNotSupported);
}
  • 然后检查最大请求长度限制的设置项,以及服务器是否已经启动。
ValidateOptions();

if (_hasStarted)
{
    // The server has already started and/or has not been cleaned up yet
    throw new InvalidOperationException(CoreStrings.ServerAlreadyStarted);
}
_hasStarted = true;
  • 最后,通过BindAsync方法中的AddressBinder对预先配置的IP地址或终结点(EndPoint)名称进行监听,开始接受客户端的请求。
 await BindAsync(cancellationToken).ConfigureAwait(false);

当每有一个新的HTTP请求通过TCP协议或其他协议和服务器成功建立连接后,AddressBinder使用ThreadPool.UnsafeQueueUserWorkItem()方法将OnBind()方法添加到线程池中,等待线程池的调度。如果此时进程有可用的线程,就会调用OnBind()方法,处理用户的HTTP请求。OnBind()方法默认使用HttpConnectionMiddleware<ServiceContext>中间件,处理新接入的用户请求,当设置了MaxConcurrentConnections值为True时,则会默认使用ConnectionLimitMiddleware中间件,限制最大可用连接数,如果当前请求数已经达到最大可接受连接数,则拒绝用户的请求并断开连接,否则调用HttpConnectionMiddleware<ServiceContext>中间件,继续处理用户的请求。具体的可以参考源码

(2)Kestrel的应用场景

  • 本身作为边缘服务器,处理直接来自网络(包括 Internet)的请求。

Kestrel 直接与 Internet 通信,不使用反向代理服务器

  • 与反向代理服务器(如 Internet Information Services (IIS)、Nginx 或 Apache)结合使用。 反向代理服务器接收来自 Internet 的 HTTP 请求,并将这些请求转发到 Kestrel。

    Kestrel 通过反向代理服务器(如 IIS、Nginx 或 Apache)间接与 Internet 进行通信

2、IIS Http服务器

IIS HTTP 服务器是 IIS 的进程内服务器。使用IIS进程内托管,ASP.NET Core 在与其 IIS 工作进程相同的进程中运行。 进程内承载相较进程外承载提供更优的性能,因为请求并不通过环回适配器进行代理,环回适配器是一个网络接口,用于将传出的网络流量返回给同一计算机。 IIS 使用 Windows 进程激活服务 (WAS) 处理进程管理。通过进程外托管,ASP.NET Core 应用在独立于 IIS 工作进程的进程中运行,而由模块来处理进程管理。 该模块在第一个请求到达时启动 ASP.NET Core 应用的进程,并在应用关闭或崩溃时重新启动该应用。这基本上与在 Windows 进程激活服务 (WAS) 托管的进程内运行的应用中出现的行为相同。

3、HTTP.sys服务器

HTTP.sys 服务器是仅用于 Windows 的 HTTP 服务器,它基于 HTTP.sys 核心驱动程序和 HTTP 服务器 API。下面对kestrel服务器和HTTP.sys服务器进行个对比:

与 HTTP.sys 相比,Kestrel 具有以下优势:

  • 更好的性能和内存利用率。
  • 跨平台
  • 灵活性,它是独立于操作系统进行开发和修补的。
  • 编程端口和 TLS 配置
  • 扩展性,允许 PPv2 等协议和备用传输。

Http.Sys 作为共享内核模式组件运行,具有 kestrel 不具备的以下功能:

  • 端口共享
  • 内核模式 Windows 身份验证。 Kestrel 仅支持用户模式的身份验证。
  • 通过队列传输的快速代理
  • 直接文件传输
  • 响应缓存

总的来说,如果 ASP.NET Core 应用在 Windows 上运行,则 HTTP.sys 是 Kestrel 的替代选项。 与 HTTP.sys 相比,建议使用 Kestrel,除非应用需要 Kestrel 未提供的功能。HTTP.sys服务器的应用场景:

HTTP.sys 直接与 Internet 进行通信

对于仅向内部网络公开的应用,HTTP.sys 同样适用。

HTTP.sys 直接与内部网络进行通信

HTTP.sys服务器同样实现了IServer接口,具体可以查看源码,这里不过多介绍。

4、自定义服务器

如果内置服务器无法满足应用需求,可以创建一个自定义服务器实现。 .NET 的开放 Web 接口 (OWIN) 指南 演示了如何编写基于 Nowin 的 IServer 实现。 只有应用使用的功能接口需要实现,但至少必须支持 IHttpRequestFeature 和 IHttpResponseFeature。

 

原文地址:https://www.cnblogs.com/qtiger/p/15342647.html