上篇讲到.net core web app是如何启动并接受请求的,下面接着探索kestrel server是如何完成此任务的。

1.kestrel server的入口KestrelServer.Start(Microsoft.AspNetCore.Hosting.Server.IHttpApplication)

FrameFactory创建的frame实例最终会交给libuv的loop回调接收请求。但是在这过程中还是有很多的初始化工作需要做的。后面我们就管中窥豹来看一看。

  1. public void Start<TContext>(IHttpApplication<TContext> application)
  2. {
  3. var engine = new KestrelEngine(new ServiceContext
  4. {
  5. FrameFactory = context =>
  6. {
  7. return new Frame<TContext>(application, context);
  8. },
  9. AppLifetime = _applicationLifetime,
  10. Log = trace,
  11. ThreadPool = new LoggingThreadPool(trace),
  12. DateHeaderValueManager = dateHeaderValueManager,
  13. ServerOptions = Options
  14. });
  15. //启动引擎。完成libuv的配置和启动
  16. engine.Start(threadCount);
  17. //针对绑定的多个地址创建server来接收请求。也就是针对ip:port来启动tcp监听
  18. foreach (var address in _serverAddresses.Addresses.ToArray())
  19. {
  20. engine.CreateServer(ipv4Address);
  21. }
  22. }

2.启动kestrel engine。engine.Start(threadCount);

启动绑定的端口*最大处理线程的thread。并初始化libuv组件。

每一个线程初始化libuv,注册loop回调等,并启动libuv。

  1. public void Start(int count)
  2. {
  3. for (var index = 0; index < count; index++)
  4. {
  5. Threads.Add(new KestrelThread(this));
  6. }
  7. foreach (var thread in Threads)
  8. {
  9. thread.StartAsync().Wait();
  10. }
  11. }
  12. private void ThreadStart(object parameter)
  13. {
  14. lock (_startSync)
  15. {
  16. var tcs = (TaskCompletionSource<int>) parameter;
  17. try
  18. {
  19. //初始化loop
  20. _loop.Init(_engine.Libuv);
  21. //注册loop回调
  22. //EnqueueCloseHandle:持有的资源释放后的回调方法,回调往queue内增加一个item,事件循环该queue完成资源的最终释放
  23. _post.Init(_loop, OnPost, EnqueueCloseHandle);
  24. //注册心跳定时器
  25. _heartbeatTimer.Init(_loop, EnqueueCloseHandle);
  26. //启动心跳定时器
  27. _heartbeatTimer.Start(OnHeartbeat, timeout: HeartbeatMilliseconds, repeat: HeartbeatMilliseconds);
  28. _initCompleted = true;
  29. tcs.SetResult(0);
  30. }
  31. catch (Exception ex)
  32. {
  33. tcs.SetException(ex);
  34. return;
  35. }
  36. }
  37. try
  38. {
  39. //当前线程执行到Run()这里会挂起
  40. _loop.Run();
  41. //应用程序stop,shutdown之类的情况,libuv唤醒当前线程,完成资源清理
  42. if (_stopImmediate)
  43. {
  44. // thread-abort form of exit, resources will be leaked
  45. //线程中止形式的退出,资源会被泄露。
  46. return;
  47. }
  48. // run the loop one more time to delete the open handles
  49. //再次运行循环以删除打开的句柄
  50. _post.Reference();
  51. _post.Dispose();
  52. _heartbeatTimer.Dispose();
  53. // Ensure the Dispose operations complete in the event loop.
  54. //确保事件循环中的Dispose操作完成。
  55. _loop.Run();
  56. _loop.Dispose();
  57. }
  58. catch (Exception ex)
  59. {
  60. _closeError = ExceptionDispatchInfo.Capture(ex);
  61. // Request shutdown so we can rethrow this exception
  62. // in Stop which should be observable.
  63. //请求关闭,以便我们可以重新抛出此异常在停止应该是可观察的。
  64. _appLifetime.StopApplication();
  65. }
  66. finally
  67. {
  68. _threadTcs.SetResult(null);
  69. }
  70. }

3.libuv启动完成之后,接着就是处理订阅注册tcp了。

回到1的kestrel的start中。接着执行engine.CreateServer(ipv4Address);,这里和.net 里面的tcplistener不太一样。.net里面就是listener bind,start,accept就好了。而libuv涉及到一个多路io复用的概念,这也是为什么使用他能高并发的原因。

  1. public IDisposable CreateServer(ServerAddress address)
  2. {
  3. var usingPipes = address.IsUnixPipe;
  4. var pipeName = (Libuv.IsWindows ? @"\\.\pipe\kestrel_" : "/tmp/kestrel_") + Guid.NewGuid().ToString("n");
  5. var single = Threads.Count == 1;
  6. var first = true;
  7. foreach (var thread in Threads)
  8. {
  9. if(single){}//single就不考虑,这种情况真是环境是不会这样玩的
  10. else if (first)
  11. {
  12. //根据当前平台创建tcp listener
  13. var listener = usingPipes
  14. ? (ListenerPrimary)new PipeListenerPrimary(ServiceContext)
  15. : new TcpListenerPrimary(ServiceContext);
  16. listener.StartAsync(pipeName, address, thread).Wait();
  17. }
  18. else
  19. {
  20. //如果是多次对同一个ip:port做监听
  21. var listener = usingPipes
  22. ? (ListenerSecondary)new PipeListenerSecondary(ServiceContext)
  23. : new TcpListenerSecondary(ServiceContext);
  24. listener.StartAsync(pipeName, address, thread).Wait();
  25. }
  26. first = false;
  27. }
  28. }

tcplistener启动细节,这里就只看TcpListenerPrimary了。

首先说明一下TcpListenerPrimary这个类的继承关系:TcpListenerPrimary -->ListenerPrimary -->Listener。这样才有助于后续代码的理解。

后续代码到处都能看到thread.post/postaysnc的代码。这玩意的意思是把传入的action放到libuv loop中,并激活异步完成回调。libuv另一个重要的概念各种回调。

1.接着上面的代码,我们进入TcpListenerPrimary.StartAsync()方法。方法在ListenerPrimary中。

  1. public async Task StartAsync(string pipeName, ServerAddress address, KestrelThread thread)
  2. {
  3. _pipeName = pipeName;
  4. await StartAsync(address, thread).ConfigureAwait(false);
  5. await Thread.PostAsync(state => ((ListenerPrimary)state).PostCallback(), this).ConfigureAwait(false);
  6. }

2.接着上面的代码进入StartAsync(address, thread)。他是父类Listener的方法。

  1. public Task StartAsync(ServerAddress address, KestrelThread thread)
  2. {
  3. ServerAddress = address; Thread = thread;
  4. var tcs = new TaskCompletionSource<int>(this);
  5. Thread.Post(state =>
  6. {
  7. var tcs2 = (TaskCompletionSource<int>)state;
  8. var listener = ((Listener)tcs2.Task.AsyncState);
  9. //创建socket
  10. listener.ListenSocket = listener.CreateListenSocket();
  11. ////socket监听,libu注册监听并设置回调函数,最大队列。
  12. ListenSocket.Listen(Constants.ListenBacklog, ConnectionCallback, this);
  13. tcs2.SetResult(0);
  14. }, tcs);
  15. return tcs.Task;
  16. }
  17. protected override UvStreamHandle CreateListenSocket()
  18. {
  19. //初始化socket并bind到address
  20. var socket = new UvTcpHandle(Log);
  21. socket.Init(Thread.Loop, Thread.QueueCloseHandle);
  22. //是否使用Nagle's algorithm算法。
  23. socket.NoDelay(ServerOptions.NoDelay);
  24. socket.Bind(ServerAddress);
  25. // If requested port was "0", replace with assigned dynamic port.
  26. ServerAddress.Port = socket.GetSockIPEndPoint().Port;
  27. return socket;
  28. }

在接着上面的代码ListenSocket.Listen成功之后,libuv回调ConnectionCallback函数。

进入ConnectionCallback函数,完成重要的listen Accept.

step1:listen成功libuv回调ConnectionCallback方法。

step2:初始化接收请求socket,并将之关联到监听socket

step3:适配接收请求socket,如果是第一次适配的话则创建connection

step4:创建connection并启动

step5:new connection 关联 Frame对象。

step6:启动frame

step7:由Connection类调用一次以开始RequestProcessingAsync循环。

step8:循环接收请求,接收请求到之后交给上层程序处理

  1. private static void ConnectionCallback(UvStreamHandle stream, int status, Exception error, object state)
  2. {
  3. var listener = (Listener)state;
  4. listener.OnConnection(stream, status);//step 1
  5. }
  6. protected override void OnConnection(UvStreamHandle listenSocket, int status)//step 2
  7. {
  8. var acceptSocket = new UvTcpHandle(Log);
  9. acceptSocket.Init(Thread.Loop, Thread.QueueCloseHandle);
  10. acceptSocket.NoDelay(ServerOptions.NoDelay);
  11. listenSocket.Accept(acceptSocket);
  12. DispatchConnection(acceptSocket);
  13. }
  14. protected override void DispatchConnection(UvStreamHandle socket)// step 3
  15. {
  16. var index = _dispatchIndex++ % (_dispatchPipes.Count + 1);
  17. if (index == _dispatchPipes.Count)
  18. {
  19. base.DispatchConnection(socket);
  20. }
  21. else
  22. {
  23. DetachFromIOCP(socket);
  24. var dispatchPipe = _dispatchPipes[index];
  25. var write = new UvWriteReq(Log);
  26. write.Init(Thread.Loop);
  27. write.Write2(dispatchPipe, _dummyMessage, socket,
  28. (write2, status, error, state) =>
  29. {
  30. write2.Dispose();
  31. ((UvStreamHandle)state).Dispose();
  32. },
  33. socket);
  34. }
  35. }
  36. protected virtual void DispatchConnection(UvStreamHandle socket)//step 4
  37. {
  38. var connection = new Connection(this, socket);
  39. connection.Start();
  40. }
  41. private Func<ConnectionContext, Frame> FrameFactory => ListenerContext.ServiceContext.FrameFactory;
  42. public Connection(ListenerContext context, UvStreamHandle socket) : base(context)//step 5
  43. {
  44. SocketInput = new SocketInput(Thread.Memory, ThreadPool, _bufferSizeControl);
  45. SocketOutput = new SocketOutput(Thread, _socket, this, ConnectionId, Log, ThreadPool);
  46. //重点代码在这里,FrameFactory是一个委托,是KestrelServer.Start中注册的action
  47. _frame = FrameFactory(this);
  48. }
  49. public void Start()//step 6
  50. {
  51. Log.ConnectionStart(ConnectionId);
  52. // Start socket prior to applying the ConnectionFilter
  53. _socket.ReadStart(_allocCallback, _readCallback, this);
  54. _frame.Start();
  55. }
  56. /// <summary>
  57. /// Called once by Connection class to begin the RequestProcessingAsync loop.
  58. /// </summary>
  59. public void Start()//step 7
  60. {
  61. Reset();
  62. _requestProcessingTask =
  63. Task.Factory.StartNew(
  64. (o) => ((Frame)o).RequestProcessingAsync(),
  65. this,
  66. default(CancellationToken),
  67. TaskCreationOptions.DenyChildAttach,
  68. TaskScheduler.Default).Unwrap();
  69. }
  70. /// <summary>
  71. /// 主循环消耗套接字输入,将其解析为协议帧,并调用应用程序委托,只要套接字打算保持打开。
  72. /// 从此循环得到的任务将保留在服务器需要时使用的字段中以排除和关闭所有当前活动的连接。
  73. /// </summary>
  74. public override async Task RequestProcessingAsync()
  75. {
  76. while (!_requestProcessingStopping)
  77. {
  78. InitializeHeaders();
  79. var context = _application.CreateContext(this);
  80. await _application.ProcessRequestAsync(context).ConfigureAwait(false);
  81. }
  82. }

.net core 源码解析-web app是如何启动并接收处理请求(二) kestrel的启动的更多相关文章

  1. .net core 源码解析-web app是如何启动并接收处理请求

    最近.net core 1.1也发布了,蹒跚学步的小孩又长高了一些,园子里大家也都非常积极的在学习,闲来无事,扒拔源码,涨涨见识. 先来见识一下web站点是如何启动的,如何接受请求,.net core ...

  2. .net core 源码解析-mvc route的注册,激活,调用流程(三)

    .net core mvc route的注册,激活,调用流程 mvc的入口是route,当前请求的url匹配到合适的route之后,mvc根据route所指定的controller和action激活c ...

  3. Spring源码解析-Web容器启动过程

    Web容器启动过程,主要讲解Servlet和Spring容器结合的内容. 流程图如下: Web容器启动的Root Context是有ContextLoaderListener,一般使用spring,都 ...

  4. [源码解析] 并行分布式任务队列 Celery 之 消费动态流程

    [源码解析] 并行分布式任务队列 Celery 之 消费动态流程 目录 [源码解析] 并行分布式任务队列 Celery 之 消费动态流程 0x00 摘要 0x01 来由 0x02 逻辑 in komb ...

  5. 04、NetCore2.0下Web应用之Startup源码解析

    04.NetCore2.0Web应用之Startup源码解析   通过分析Asp.Net Core 2.0的Startup部分源码,来理解插件框架的运行机制,以及掌握Startup注册的最优姿势. - ...

  6. .Net Core 认证系统之Cookie认证源码解析

    接着上文.Net Core 认证系统源码解析,Cookie认证算是常用的认证模式,但是目前主流都是前后端分离,有点鸡肋但是,不考虑移动端的站点或者纯管理后台网站可以使用这种认证方式.注意:基于浏览器且 ...

  7. .NET Core实战项目之CMS 第三章 入门篇-源码解析配置文件及依赖注入

    作者:依乐祝 原文链接:https://www.cnblogs.com/yilezhu/p/9998021.html 写在前面 上篇文章我给大家讲解了ASP.NET Core的概念及为什么使用它,接着 ...

  8. .Net Core中的配置文件源码解析

    一.配置简述 之前在.Net Framework平台开发时,一般配置文件都是xml格式的Web.config,而需要配置其他格式的文件就需要自己去读取内容,加载配置了..而Net Core支持从命令行 ...

  9. 【源码解析】凭什么?spring boot 一个 jar 就能开发 web 项目

    问题 为什么开发web项目,spring-boot-starter-web 一个jar就搞定了?这个jar做了什么? 通过 spring-boot 工程可以看到所有开箱即用的的引导模块 spring- ...

随机推荐

  1. iOS 原生HTTP POST请求上传图片

    今天项目里做一个上传图片等个人信息的时候,使用了第三方AFNetworking - (AFHTTPRequestOperation *)POST:(NSString *)URLString param ...

  2. 从ASP.NET 升级到ASP.NET5(RC1) - 翻译

    前言 ASP.NET 5 是一次令人惊叹的对于ASP.NET的创新革命. 他将构建目标瞄准了 .NET Core CLR, 同时ASP.NET又是对于云服务进行优化,并且是跨平台的框架.很多文章已经称 ...

  3. tee(打印并保存文件)

     tee从标准设备读取数据,输出到标准输出设备,同时保存成文件-a 附加到既有文件后面,而非覆盖他.例如: pwd |  tee who.out

  4. spring源码:web容器启动(li)

    web项目中可以集成spring的ApplicationContext进行bean的管理,这样使用起来bean更加便捷,能够利用到很多spring的特性.我们比较常用的web容器有jetty,tomc ...

  5. js 压缩工具总结

    随便百度一个 “js 压缩”,然后就会有各种代码压缩工具可供选择, 向来我就爱那种绚丽新颖的玩意,比如这家显示压缩比呀,或者那家可以查错呀什么的, 今天还为国民浏览器拥有鼠标手势(按住右键画个图形就有 ...

  6. 移动站适配rel=alternate PC页和H5页适配标注

    鉴于移动化大潮的汹涌和H5页的炫丽普及,百度针对PC页与H5页的跳转适配方式推出了最优方案:1.在pc版网页上,添加指向对应移动版网址的特殊链接rel="alternate"标记, ...

  7. Atitit ftp原理与解决方案

    Atitit ftp原理与解决方案 Deodeo sh shmayama ..search ftp.. 1. http和ftp都只是通信协议,就是只管传输那一块的,那为什么不能使用ftp来显示网页?? ...

  8. ArcGIS Engine开发前基础知识(2)

    ArcGIS基本控件简介 ArcGIS Engine控件是一组可视化的开发组件,每个ArcGIS Engine控件都是一个COM组件.这些组件包括MapControl,PageLayoutContro ...

  9. 在View and Data API中更改指定元素的颜色

    大家在使用View and Data API开发过程中,经常会用到的就是改变某些元素的颜色已区别显示.比如根据某些属性做不同颜色的专题显示,或者用不同颜色表示施工进度,或者只是简单的以颜色变化来提醒用 ...

  10. IOS-小项目(饿了么 网络部分 简单实现)

    在介绍小项目之前,在此说明一下此代码并非本人所写,我只是随笔的整理者. 在介绍之前先展现一下效果图. 看过效果图大家应该很熟悉了,就是饿了么的一个界面而已,值得注意的是,实现时并没有采用本地连接,而是 ...