asp.net core mvc剖析:KestrelServer

KestrelServer是基于Libuv开发的高性能web服务器,那我们现在就来看一下它是如何工作的。在上一篇文章中提到了Program的Main方法,在这个方法里Build了一个WebHost,我们再来看一下代码:


1

2

3

4

5

6

7

8

9

10

11


public static void Main(string[] args)

   {

       var host = new WebHostBuilder()

           .UseKestrel()

           .UseContentRoot(Directory.GetCurrentDirectory())

           .UseIISIntegration()

           .UseStartup<Startup>()

           .Build();

 

       host.Run();

   }

  里面有一个UseKestrel方法调用,这个方法的作用就是使用KestrelServer作为web server来提供web服务。在WebHost启动的时候,调用了IServer的Start方法启动服务,由于我们使用KestrelServer作为web server,自然这里调用的就是KestrelServer.Start方法,那我们来看下KestrelServer的Start方法里主要代码:

首先,我们发现在Start方法里创建了一个KestrelEngine对象,具体代码如下:


1

2

3

4

5

6

7

8

9

10

11

12


var engine = new KestrelEngine(new ServiceContext

{

       FrameFactory = context =>

       {

           return new Frame<TContext>(application, context);

       },

       AppLifetime = _applicationLifetime,

       Log = trace,

       ThreadPool = new LoggingThreadPool(trace),

       DateHeaderValueManager = dateHeaderValueManager,

       ServerOptions = Options

 });

  KestrelEngine构造方法接受一个ServiceContext对象参数,ServiceContext里包含一个FrameFactory,从名称上很好理解,就是Frame得工厂,Frame是什么?Frame是http请求处理对象,每个请求过来后,都会交给一个Frame对象进行受理,我们这里先记住它的作用,后面还会看到它是怎么实例化的。除了这个外,还有一个是AppLiftTime,它是一个IApplicationLifetime对象,它是整个应用生命周期的管理对象,前面没有说到,这里补充上。


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26


public interface IApplicationLifetime

    {

        /// <summary>

        /// Triggered when the application host has fully started and is about to wait

        /// for a graceful shutdown.

        /// </summary>

        CancellationToken ApplicationStarted { get; }

        /// <summary>

        /// Triggered when the application host is performing a graceful shutdown.

        /// Requests may still be in flight. Shutdown will block until this event completes.

        /// </summary>

        CancellationToken ApplicationStopping { get; }

        /// <summary>

        /// Triggered when the application host is performing a graceful shutdown.

        /// All requests should be complete at this point. Shutdown will block

        /// until this event completes.

        /// </summary>

        CancellationToken ApplicationStopped { get; }

        /// <summary>

        /// Requests termination the current application.

        /// </summary>

        void StopApplication();

    }

  IApplicationLifetime中提供了三个时间点,

  1,ApplicationStarted:应用程序已启动  2,ApplicationStopping:应用程序正在停止  3,ApplicationStopped:应用程序已停止

  我们可以通过CancellationToken.Register方法注册回调方法,在上面说到的三个时间点,执行我们特定的业务逻辑。IApplicationLifetime是在WebHost的Start方法里创建的,如果想在我们自己的应用程序获取这个对象,我们可以直接通过依赖注入的方式获取即可。

我们继续回到ServiceContext对象,这里面还包含了Log对象,用于跟踪日志,一般我们是用来看程序执行的过程,并可以通过它发现程序执行出现问题的地方。还包含一个ServerOptions,它是一个KestrelServerOptions,里面包含跟服务相关的配置参数:

1,ThreadCount:服务线程数,表示服务启动后,要开启多少个服务线程,因为每个请求都会使用一个线程来进行处理,多线程会提高吞吐量,但是并不一定线程数越多越好,在系统里默认值是跟CPU内核数相等。

2,ShutdownTimeout:The amount of time after the server begins shutting down before connections will be forcefully closed(在应用程序开始停止到强制关闭当前请求连接所等待的时间,在这个时间段内,应用程序会等待请求处理完,如果还没处理完,将强制关闭)

3,Limits:KestrelServerLimits对象,里面包含了服务限制参数,比如MaxRequestBufferSize,MaxResponseBufferSize

其他参数就不再一个一个说明了。

KestrelEngine对象创建好后,通过调用 engine.Start(threadCount),根据配置的threadcount进行服务线程KestrelThread实例化,代码如下:

     public void Start(int count)
        {
            for (var index = 0; index < count; index++)
            {
                Threads.Add(new KestrelThread(this));
            }

            foreach (var thread in Threads)
            {
                thread.StartAsync().Wait();
            }
        }

上面的代码会创建指定数量的Thread对象,然后开始等待任务处理。KestrelThread是对libuv线程处理的封装。

这些工作都准备好后,就开始启动监听服务了


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19


foreach (var endPoint in listenOptions)

               {

                   try

                   {

                       _disposables.Push(engine.CreateServer(endPoint));

                   }

                   catch (AggregateException ex)

                   {

                       if ((ex.InnerException as UvException)?.StatusCode == Constants.EADDRINUSE)

                       {

                           throw new IOException($"Failed to bind to address {endPoint}: address already in use.", ex);

                       }

                       throw;

                   }

                   // If requested port was "0", replace with assigned dynamic port.

                   _serverAddresses.Addresses.Add(endPoint.ToString());

               }

  上面红色字体代码,就是创建监听服务的方法,我们再详细看下里面的详细情况:


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41


      public IDisposable CreateServer(ListenOptions listenOptions)

        {

            var listeners = new List<iasyncdisposable>();

            try

            {                //如果前面创建的线程数量为1,直接创建listener对象,启动监听

                if (Threads.Count == 1)

                {

                    var listener = new Listener(ServiceContext);

                    listeners.Add(listener);

                    listener.StartAsync(listenOptions, Threads[0]).Wait();

                }

                else

                {            //如果线程数不为1的时候

                    var pipeName = (Libuv.IsWindows ? @"\\.\pipe\kestrel_" "/tmp/kestrel_") + Guid.NewGuid().ToString("n");

                    var pipeMessage = Guid.NewGuid().ToByteArray();

             //先创建一个主监听对象,这个Listenerprimary就是一个Listener,监听socket就是在这里面创建的

                    var listenerPrimary = new ListenerPrimary(ServiceContext);

                    listeners.Add(listenerPrimary);           //启动监听

                    listenerPrimary.StartAsync(pipeName, pipeMessage, listenOptions, Threads[0]).Wait();

                    //为剩余的每个服务线程关联一个ListenerSecondary对象,这个对象使用命名Pipe与主监听对象通信,在主监听对象接收到请求后,通过pipe把接受的socket对象发送给特定的线程处理

                    foreach (var thread in Threads.Skip(1))

                    {

                        var listenerSecondary = new ListenerSecondary(ServiceContext);

                        listeners.Add(listenerSecondary);

                        listenerSecondary.StartAsync(pipeName, pipeMessage, listenOptions, thread).Wait();

                    }

                }

                return new Disposable(() =>

                {

                    DisposeListeners(listeners);

                });

            }

            catch

            {

                DisposeListeners(listeners);

                throw;

            }

        }

</iasyncdisposable>

  这个时候服务就开始接受http请求了,我们前面说到了,监听socket在listener类中创建(ListenerPrimary也是一个Listener),下面是listener的start方法


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28


      public Task StartAsync(

            ListenOptions listenOptions,

            KestrelThread thread)

        {

            ListenOptions = listenOptions;

            Thread = thread;

            var tcs = new TaskCompletionSource<int>(this);

            Thread.Post(state =>

            {

                var tcs2 = (TaskCompletionSource<int>) state;

                try

                {

                    var listener = ((Listener) tcs2.Task.AsyncState);                    //创建监听socket

                    listener.ListenSocket = listener.CreateListenSocket();                    //开始监听,当有连接请求过来后,触发ConnectionCallback方法

                    ListenSocket.Listen(Constants.ListenBacklog, ConnectionCallback, this);

                    tcs2.SetResult(0);

                }

                catch (Exception ex)

                {

                    tcs2.SetException(ex);

                }

            }, tcs);

            return tcs.Task;

        }

</int></int>

  ConnectionCallback:当连接请求过来后被触发,在回调方法里,进行连接处理分发,连接分发代码如下:


1

2

3

4

5


protected virtual void DispatchConnection(UvStreamHandle socket)

   {

       var connection = new Connection(this, socket);

       connection.Start();

   }

  这个是listener类中的实现,我们前面看到,只有在线程数为1的情况下,才创建Listener对象进行监听,否则创建ListenerPrimary监听,ListenerPrimay里重写了方法,它的实现如下:


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25


protected override void DispatchConnection(UvStreamHandle socket)

   {            //这里采用轮询的方式,把连接请求依次分发给不同的线程进行处理

       var index = _dispatchIndex++ % (_dispatchPipes.Count + 1);

       if (index == _dispatchPipes.Count)

       {              //

           base.DispatchConnection(socket);

       }

       else

       {

           DetachFromIOCP(socket);

           var dispatchPipe = _dispatchPipes[index];                //这里就是通过命名pipe,传递socket给特定的线程

           var write = new UvWriteReq(Log);

           write.Init(Thread.Loop);

           write.Write2(

               dispatchPipe,

               _dummyMessage,

               socket,

               (write2, status, error, state) =>

               {

                   write2.Dispose();

                   ((UvStreamHandle)state).Dispose();

               },

               socket);

       }

   }

  好了,连接请求找到处理线程后,后面就可以开始处理工作了。ListenerSecondary里的代码比较复杂,其实最终都会调用下面的代码完成Connection对象的创建


1

2


var connection = new Connection(this, socket);

connection.Start();

  Connection表示的就是当前连接,下面是它的构造方法


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27


public Connection(ListenerContext context, UvStreamHandle socket) : base(context)

        {

            _socket = socket;

            _connectionAdapters = context.ListenOptions.ConnectionAdapters;

            socket.Connection = this;

            ConnectionControl = this;

            ConnectionId = GenerateConnectionId(Interlocked.Increment(ref _lastConnectionId));

            if (ServerOptions.Limits.MaxRequestBufferSize.HasValue)

            {

                _bufferSizeControl = new BufferSizeControl(ServerOptions.Limits.MaxRequestBufferSize.Value,this);

            }

        //创建输入输出socket流

            Input = new SocketInput(Thread.Memory, ThreadPool, _bufferSizeControl);

            Output = new SocketOutput(Thread, _socket, this, ConnectionId, Log, ThreadPool);

            var tcpHandle = _socket as UvTcpHandle;

            if (tcpHandle != null)

            {

                RemoteEndPoint = tcpHandle.GetPeerIPEndPoint();

                LocalEndPoint = tcpHandle.GetSockIPEndPoint();

            }

        //创建处理frame,这里的framefactory就是前面创建KestrelEngine时创建的工厂

            _frame = FrameFactory(this);

            _lastTimestamp = Thread.Loop.Now();

        }

  然后调用Connection的Start方法开始进行处理,这里面直接把处理任务交给Frame处理,Start方法实现:


1

2

3

4

5

6

7

8

9

10

11

12


public void Start()

        {

            Reset();       //启动了异步处理任务开始进行处理

            _requestProcessingTask =

                Task.Factory.StartNew(

                    (o) => ((Frame)o).RequestProcessingAsync(),//具体的处理方法

                    this,

                    default(CancellationToken),

                    TaskCreationOptions.DenyChildAttach,

                    TaskScheduler.Default).Unwrap();

            _frameStartedTcs.SetResult(null);

        }

  

1 RequestProcessingAsync方法里不再详细介绍了,把主要的代码拿出来看一下:

1

2

3

4

5


。。。。。//_application就是上一篇文章提到的HostApplication,首先调用CreateContext创建HttpContext对象

var context = _application.CreateContext(this);

。。。。。。//进入处理管道

await _application.ProcessRequestAsync(context).ConfigureAwait(false);

。。。。。。

  

1 ProcessRequestAsync完成处理后,把结果输出给客户端,好到此介绍完毕。如果有问题,欢迎大家指点。
时间: 2024-10-29 19:08:27

asp.net core mvc剖析:KestrelServer的相关文章

asp.net core mvc剖析:路由

在mvc框架中,任何一个动作请求都会被映射到具体控制器中的方法上,那框架是如何完成这样一个过程的,现在我们就来简单分析下流程. 我们紧跟上面的主题,任何一个请求都会交给处理管道进行处理,那mvc处理的流程自然也应该处于这个管道中,在startup.cs文件的Configure方法中,我们会看到这样的代码 1 2 3 4 5 6 7 app.UseMvc(routes =>             {                 routes.MapRoute(                

asp.net core mvc剖析:处理管道构建

在启动流程文章中提到,在WebHost类中,通过BuildApplication完成http请求处理管道的构建.在来看一下代码: ...... //这个调用的就是Startup.cs类中的Configure方法 configure(builder); //生成中间件链式结构 return builder.Build(); 在框架中,一个中间件处理逻辑是使用一个RequestDelegate委托类型来表示的,定义:delegate Task RequestDelegate(HttpContext

剖析ASP.NET Core MVC(Part 1)- AddMvcCore(译)

原文:https://www.stevejgordon.co.uk/asp-net-core-mvc-anatomy-addmvccore发布于:2017年3月环境:ASP.NET Core 1.1 欢迎阅读新系列的第一部分,我将剖析MVC源代码,给大家展示隐藏在表面之下的工作机制.此系列将分析MVC的内部,如果觉得枯燥,可以停止阅读.但就我个人而言,也是经过反复阅读.调试甚至抓狂,直到最后理解ASP.NET MVC源代码(或者自认为理解),从中获益匪浅.通过了解框架的运作机制,我们可以更好的使

asp.net core MVC 全局过滤器之ExceptionFilter过滤器(一)

本系类将会讲解asp.net core MVC中的内置全局过滤器的使用,将分为以下章节 asp.net core MVC 过滤器之ExceptionFilter过滤器(一) asp.net core MVC 过滤器之ActionFilter过滤器(二) asp.net core MVC 过滤器之ResultFilter过滤器(三) asp.net core MVC 过滤器之ResourceFilter过滤器(四) asp.net core MVC 过滤器之AuthorizationFilter过

ASP.NET Core MVC 在linux上的创建及发布

前言 ASP.NET core转眼都发布半月多了,社区最近也是非常活跃,虽然最近从事python工作,但也一直对.NET念念不忘,看过了园区大神们搭建的Asp.net core项目之后,自己也是跃跃欲试,准备搞一下ASP.NET Core mvc的创建和部署,于是便有了这篇文章,希望能够帮助到你. 环境准备 这是我的开发环境,使用的nginx是nginx 1.6.3 直接yum install,然后需要安装dotnet环境,可以参照官网教程https://www.microsoft.com/ne

ASP.NET Core MVC之Serilog日志处理,你了解多少?

前言 本节我们来看看ASP.NET Core MVC中比较常用的功能,对于导入和导出目前仍在探索中,项目需要自定义列合并,所以事先探索了如何在ASP.NET Core MVC进行导入.导出,更高级的内容还需等我学习再给出. EntityFramework Core 在学习ASP.NET Core MVC之前我们来看看在EF Core中如何更新对象指定属性,这个问题之前我们已经探讨过,但是还是存在一点问题,请往下看. public void Update(T entity, params Expr

创建ASP.NET Core MVC应用程序(3)-基于Entity Framework Core(Code First)创建MySQL数据库表

创建ASP.NET Core MVC应用程序(3)-基于Entity Framework Core(Code First)创建MySQL数据库表 创建数据模型类(POCO类) 在Models文件夹下添加一个User类: namespace MyFirstApp.Models { public class User { public int ID { get; set; } public string Name { get; set; } public string Email { get; se

【目录】开始使用ASP.NET Core MVC和Visual Studio

参照微软教程:Building your first ASP.NET Core MVC app with Visual Studio This series of tutorials will teach you the basics of building an ASP.NET Core MVC web app using Visual Studio. Getting started Adding a controller Adding a view Adding a model Workin

ASP.NET Core MVC/WebAPi 模型绑定探索

前言 相信一直关注我的园友都知道,我写的博文都没有特别枯燥理论性的东西,主要是当每开启一门新的技术之旅时,刚开始就直接去看底层实现原理,第一会感觉索然无味,第二也不明白到底为何要这样做,所以只有当你用到了,你再去看理论性的文章时才会豁然开朗,这是我一直以来学习技术的方法.本文我们来讲解.NET Core中的模型绑定. 话题 在ASP.NET Core之前MVC和Web APi被分开,也就说其请求管道是独立的,而在ASP.NET Core中,WebAPi和MVC的请求管道被合并在一起,当我们建立控