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

  1. public static void Main(string[] args)
  2. {
  3. var host = new WebHostBuilder()
  4. .UseKestrel()
  5. .UseContentRoot(Directory.GetCurrentDirectory())
  6. .UseIISIntegration()
  7. .UseStartup<Startup>()
  8. .Build();
  9.  
  10. host.Run();
  11. }

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

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

  1. var engine = new KestrelEngine(new ServiceContext
  2. {
  3. FrameFactory = context =>
  4. {
  5. return new Frame<TContext>(application, context);
  6. },
  7. AppLifetime = _applicationLifetime,
  8. Log = trace,
  9. ThreadPool = new LoggingThreadPool(trace),
  10. DateHeaderValueManager = dateHeaderValueManager,
  11. ServerOptions = Options
  12. });

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

  1. public interface IApplicationLifetime
  2. {
  3. /// <summary>
  4. /// Triggered when the application host has fully started and is about to wait
  5. /// for a graceful shutdown.
  6. /// </summary>
  7. CancellationToken ApplicationStarted { get; }
  8.  
  9. /// <summary>
  10. /// Triggered when the application host is performing a graceful shutdown.
  11. /// Requests may still be in flight. Shutdown will block until this event completes.
  12. /// </summary>
  13. CancellationToken ApplicationStopping { get; }
  14.  
  15. /// <summary>
  16. /// Triggered when the application host is performing a graceful shutdown.
  17. /// All requests should be complete at this point. Shutdown will block
  18. /// until this event completes.
  19. /// </summary>
  20. CancellationToken ApplicationStopped { get; }
  21.  
  22. /// <summary>
  23. /// Requests termination the current application.
  24. /// </summary>
  25. void StopApplication();
  26. }

  IApplicationLifetime中提供了三个时间点,

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

  我们可以通过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

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

  1. KestrelEngine对象创建好后,通过调用 engine.Start(threadCount),根据配置的threadcount进行服务线程KestrelThread实例化,代码如下:
  1.     public void Start(int count)
  2. {
  3. for (var index = ; index < count; index++)
  4. {
  5. Threads.Add(new KestrelThread(this));
  6. }
  7.  
  8. foreach (var thread in Threads)
  9. {
  10. thread.StartAsync().Wait();
  11. }
  12. }

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

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

  1. foreach (var endPoint in listenOptions)
  2. {
  3. try
  4. {
  5. _disposables.Push(engine.CreateServer(endPoint));
  6. }
  7. catch (AggregateException ex)
  8. {
  9. if ((ex.InnerException as UvException)?.StatusCode == Constants.EADDRINUSE)
  10. {
  11. throw new IOException($"Failed to bind to address {endPoint}: address already in use.", ex);
  12. }
  13.  
  14. throw;
  15. }
  16.  
  17. // If requested port was "0", replace with assigned dynamic port.
  18. _serverAddresses.Addresses.Add(endPoint.ToString());
  19. }

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

  1.      public IDisposable CreateServer(ListenOptions listenOptions)
  2. {
  3. var listeners = new List<IAsyncDisposable>();
  4.  
  5. try
  6. {
    //如果前面创建的线程数量为1,直接创建listener对象,启动监听
  7. if (Threads.Count == 1)
  8. {
  9. var listener = new Listener(ServiceContext);
  10. listeners.Add(listener);
  11. listener.StartAsync(listenOptions, Threads[0]).Wait();
  12. }
  13. else
  14. {
                //如果线程数不为1的时候
  15. var pipeName = (Libuv.IsWindows ? @"\\.\pipe\kestrel_" : "/tmp/kestrel_") + Guid.NewGuid().ToString("n");
  16. var pipeMessage = Guid.NewGuid().ToByteArray();
  17.            //先创建一个主监听对象,这个Listenerprimary就是一个Listener,监听socket就是在这里面创建的
  18. var listenerPrimary = new ListenerPrimary(ServiceContext);
  19. listeners.Add(listenerPrimary);
                //启动监听
  20. listenerPrimary.StartAsync(pipeName, pipeMessage, listenOptions, Threads[0]).Wait();
  21. //为剩余的每个服务线程关联一个ListenerSecondary对象,这个对象使用命名Pipe与主监听对象通信,在主监听对象接收到请求后,通过pipe把接受的socket对象发送给特定的线程处理
  22. foreach (var thread in Threads.Skip(1))
  23. {
  24. var listenerSecondary = new ListenerSecondary(ServiceContext);
  25. listeners.Add(listenerSecondary);
  26. listenerSecondary.StartAsync(pipeName, pipeMessage, listenOptions, thread).Wait();
  27. }
  28. }
  29.  
  30. return new Disposable(() =>
  31. {
  32. DisposeListeners(listeners);
  33. });
  34. }
  35. catch
  36. {
  37. DisposeListeners(listeners);
  38. throw;
  39. }
  40. }

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

  1.      public Task StartAsync(
  2. ListenOptions listenOptions,
  3. KestrelThread thread)
  4. {
  5. ListenOptions = listenOptions;
  6. Thread = thread;
  7.  
  8. var tcs = new TaskCompletionSource<int>(this);
  9.  
  10. Thread.Post(state =>
  11. {
  12. var tcs2 = (TaskCompletionSource<int>) state;
  13. try
  14. {
  15. var listener = ((Listener) tcs2.Task.AsyncState);
    //创建监听socket
  16. listener.ListenSocket = listener.CreateListenSocket();
    //开始监听,当有连接请求过来后,触发ConnectionCallback方法
  17. ListenSocket.Listen(Constants.ListenBacklog, ConnectionCallback, this);
  18. tcs2.SetResult(0);
  19. }
  20. catch (Exception ex)
  21. {
  22. tcs2.SetException(ex);
  23. }
  24. }, tcs);
  25.  
  26. return tcs.Task;
  27. }

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

  1.      protected virtual void DispatchConnection(UvStreamHandle socket)
  2. {
  3. var connection = new Connection(this, socket);
  4. connection.Start();
  5. }

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

  1.      protected override void DispatchConnection(UvStreamHandle socket)
  2. {
    //这里采用轮询的方式,把连接请求依次分发给不同的线程进行处理
  3. var index = _dispatchIndex++ % (_dispatchPipes.Count + 1);
  4. if (index == _dispatchPipes.Count)
  5. {
       //
  6. base.DispatchConnection(socket);
  7. }
  8. else
  9. {
  10. DetachFromIOCP(socket);
  11. var dispatchPipe = _dispatchPipes[index];
    //这里就是通过命名pipe,传递socket给特定的线程
  12. var write = new UvWriteReq(Log);
  13. write.Init(Thread.Loop);
  14. write.Write2(
  15. dispatchPipe,
  16. _dummyMessage,
  17. socket,
  18. (write2, status, error, state) =>
  19. {
  20. write2.Dispose();
  21. ((UvStreamHandle)state).Dispose();
  22. },
  23. socket);
  24. }
  25. }

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

  1. var connection = new Connection(this, socket);
  2. connection.Start();

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

  1. public Connection(ListenerContext context, UvStreamHandle socket) : base(context)
  2. {
  3. _socket = socket;
  4. _connectionAdapters = context.ListenOptions.ConnectionAdapters;
  5. socket.Connection = this;
  6. ConnectionControl = this;
  7.  
  8. ConnectionId = GenerateConnectionId(Interlocked.Increment(ref _lastConnectionId));
  9.  
  10. if (ServerOptions.Limits.MaxRequestBufferSize.HasValue)
  11. {
  12. _bufferSizeControl = new BufferSizeControl(ServerOptions.Limits.MaxRequestBufferSize.Value, this);
  13. }
  14.        //创建输入输出socket流
  15. Input = new SocketInput(Thread.Memory, ThreadPool, _bufferSizeControl);
  16. Output = new SocketOutput(Thread, _socket, this, ConnectionId, Log, ThreadPool);
  17.  
  18. var tcpHandle = _socket as UvTcpHandle;
  19. if (tcpHandle != null)
  20. {
  21. RemoteEndPoint = tcpHandle.GetPeerIPEndPoint();
  22. LocalEndPoint = tcpHandle.GetSockIPEndPoint();
  23. }
  24.        //创建处理frame,这里的framefactory就是前面创建KestrelEngine时创建的工厂
  25. _frame = FrameFactory(this);
  26. _lastTimestamp = Thread.Loop.Now();
  27. }

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

  1. public void Start()
  2. {
  3. Reset();
           //启动了异步处理任务开始进行处理
  4. _requestProcessingTask =
  5. Task.Factory.StartNew(
  6. (o) => ((Frame)o).RequestProcessingAsync(),//具体的处理方法
  7. this,
  8. default(CancellationToken),
  9. TaskCreationOptions.DenyChildAttach,
  10. TaskScheduler.Default).Unwrap();
  11. _frameStartedTcs.SetResult(null);
  12. }

  

  1. RequestProcessingAsync方法里不再详细介绍了,把主要的代码拿出来看一下:
  1. 。。。。。
    //_application就是上一篇文章提到的HostApplication,首先调用CreateContext创建HttpContext对象
  2. var context = _application.CreateContext(this);
  3. 。。。。。。
    //进入处理管道
  4. await _application.ProcessRequestAsync(context).ConfigureAwait(false);
  5. 。。。。。。

  

  1. ProcessRequestAsync完成处理后,把结果输出给客户端,好到此介绍完毕。如果有问题,欢迎大家指点。

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

  1. asp.net core mvc剖析:启动流程

    asp.net core mvc是微软开源的跨平台的mvc框架,首先它跟原有的MVC相比,最大的不同就是跨平台,然后又增加了一些非常实用的新功能,比如taghelper,viewcomponent,D ...

  2. asp.net core mvc剖析:路由

    在mvc框架中,任何一个动作请求都会被映射到具体控制器中的方法上,那框架是如何完成这样一个过程的,现在我们就来简单分析下流程. 我们紧跟上面的主题,任何一个请求都会交给处理管道进行处理,那mvc处理的 ...

  3. asp.net core mvc剖析:mvc执行过程(一)

    前面介绍了路由的过程,我们再来看下MvcRouteHandler的代码: public Task RouteAsync(RouteContext context) { ...... //根据路由信息查 ...

  4. asp.net core mvc剖析:mvc动作选择

    一个http请求过来后,首先经过路由规则的匹配,找到最符合条件的的IRouter,然后调用IRouter.RouteAsync来设置RouteContext.Handler,最后把请求交给RouteC ...

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

    在启动流程文章中提到,在WebHost类中,通过BuildApplication完成http请求处理管道的构建.在来看一下代码: ...... //这个调用的就是Startup.cs类中的Config ...

  6. asp.net core mvc剖析:动作执行

    紧跟上一篇文章.通过路由和动作匹配后,最终会得到跟当前请求最匹配的一个ActionDescriptor,然后通过IActionInvoker执行动作. 我们先来看一下IActionInvoker如何得 ...

  7. net core mvc剖析:启动流程

    net core mvc剖析:启动流程 asp.net core mvc是微软开源的跨平台的mvc框架,首先它跟原有的MVC相比,最大的不同就是跨平台,然后又增加了一些非常实用的新功能,比如taghe ...

  8. 剖析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 ...

  9. ASP.NET Core MVC内置服务的使用

    ASP.NET Core中的依赖注入可以说是无处不在,其通过创建一个ServiceCollection对象并将服务注册信息以ServiceDescriptor对象的形式添加在其中,其次针对Servic ...

随机推荐

  1. curl讲解第一篇---入门和基本使用

    概念 它支持很多协议:FTP, FTPS, HTTP, HTTPS, GOPHER, TELNET, DICT, FILE 以及 LDAP. curl同样支持HTTPS认证,HTTP POST方法, ...

  2. FZU 1056 扫雷游戏

    水题.统计一下周围有几个雷. #include<cstdio> #include<cstring> #include<cmath> #include<algo ...

  3. Golang测试技术

    本篇文章内容来源于Golang核心开发组成员Andrew Gerrand在Google I/O 2014的一次主题分享“Testing Techniques”,即介绍使用Golang开发 时会使用到的 ...

  4. 用于ARM上的FFT与IFFT源代码(C语言,不依赖特定平台)(转)

    源:用于ARM上的FFT与IFFT源代码(C语言,不依赖特定平台) 代码在2011年全国电子大赛结束后(2011年9月3日)发布,多个版本,注释详细. /*********************** ...

  5. 微信小程序实操-image height:auto问题,url地址报错,“不在以下合法域名列表中”问题等

    1.修改app顶部title 使用API: wx.setNavigationBarTitle({ title: 'titleName'}); 2.ajax请求 wx.request({ url: 'h ...

  6. 如何在Eclipse下安装myeclipse插件

    来自http://www.blogjava.net/show911/archive/2008/04/27/86284.html 下载myeclipse插件 支持eclipse3.1.x, 具体安装步骤 ...

  7. PHP导出MYSQL数据库并压缩

    PHP可以一键导出MYSQL备份文件,并压缩存放,尽管phpMyAdmin有这功能,不过若你自己开发网站或者是为别人写CMS,你不应该要求别人用你程序的时候再去另外用phpMyAdmin备份MYSQL ...

  8. iOS 开发 之 编程知识点

    iOS 创建和设置pch iOS 之 时间格式与字符串转换 iOS 之 二维码生成与扫描(LBXScan) iOS 之 定时器 iOS 之 通知 iOS 之 NSString 去除前后空格和回车键 i ...

  9. Android中服务的生命周期回调方法

  10. ie6和ie7下z-index bug的解决办法

    一.匆匆带过的概念 关于CSS中层级z-index的定义啊什么的不是本文的重点,不会花费过多篇幅详细讲述.这里就简单带过,z-index伴随着层的概念产生的.网页 中,层的概念与photoshop或是 ...