今天这一篇博客讲的是.net core 自带的kestrel server,当你开发微服务k8s部署在linux环境下,一般默认开启这个高性能服务,如果大家之前看过我的owin katana的博客,会发现.net core 的好多实现在之前.net standard 的版本已经实现过了,当时开发的asp.net 程序与IIS紧紧耦合在一起,后来的微软团队意识到这个问题并尝试将asp.net 解耦server,制定了owin标准并启动了一个开源项目katana,这个项目的结果并没有带动社区效应,但是此时微软已经制订了.net core的开发,并在katana文档暗示了.net vnext 版本,这个就是。net core 与owin katana 的故事。强烈建议大家有时间看看owin katana,里面有一些 dependency inject, hash map, raw http 协议等等实现。非常收益。说到这些我们开始步入正题吧。原代码在github上的asp.net core 源码。

上图大致地描述了一个asp.net core 的请求过程,但是我们会发现appication 依赖了server,所以我们需要一个Host 的去解耦server 和aplication 的实现,只要server符合host标准可以任意更换,解耦之后的代码与下图所示。

所以我们的代码都是创建一个web host然后使用usekestrel,如下所示。

  1. var host = new WebHostBuilder()
  2. .UseKestrel(options =>
  3. {
  4. options.Listen(IPAddress.Loopback, 5001);
  5. })
  6. .UseStartup<Startup>();

首先我们知道一个server 实现需要网络编程,所以我们需要socket库来快速编程,它已经帮你实现了tcp与udp协议,不需要自己重复的造轮子。首先我们需要看UseKestrel的方法做了什么。

  1. 1 public static IWebHostBuilder UseKestrel(this IWebHostBuilder hostBuilder)
  2. 2 {
  3. 3 return hostBuilder.ConfigureServices(services =>
  4. 4 {
  5. 5 // Don't override an already-configured transport
  6. 6 services.TryAddSingleton<IConnectionListenerFactory, SocketTransportFactory>();
  7. 7
  8. 8 services.AddTransient<IConfigureOptions<KestrelServerOptions>, KestrelServerOptionsSetup>();
  9. 9 services.AddSingleton<IServer, KestrelServer>();
  10. 10 });
  11. 11 }

依赖注入注册了三个对象,一个连接池,一个配置类还有一个是server,会和web host注册了IServer 的实现类,然后我们继续看一下,当你调用run的时候会将控制权从web host 转移给server,如下代码第18行所示。

  1. 1 public virtual async Task StartAsync(CancellationToken cancellationToken = default)
  2. 2 {
  3. 3 HostingEventSource.Log.HostStart(); 6
  4. 7 var application = BuildApplication();
  5. 8
  6. 12 // Fire IHostedService.Start
  7. 13 await _hostedServiceExecutor.StartAsync(cancellationToken).ConfigureAwait(false);//启动后台服务
  8. 14
  9. 15 var diagnosticSource = _applicationServices.GetRequiredService<DiagnosticListener>();
  10. 16 var httpContextFactory = _applicationServices.GetRequiredService<IHttpContextFactory>();
  11. 17 var hostingApp = new HostingApplication(application, _logger, diagnosticSource, httpContextFactory);
  12. 18 await Server.StartAsync(hostingApp, cancellationToken).ConfigureAwait(false);//socket 启动
  13. 19 _startedServer = true;
  14. 20
  15. 21 // Fire IApplicationLifetime.Started
  16. 22 _applicationLifetime?.NotifyStarted();
  17. 23
  18. 24
  19. 25 _logger.Started();
  20. 26
  21. 27 // Log the fact that we did load hosting startup assemblies.
  22. 28 if (_logger.IsEnabled(LogLevel.Debug))
  23. 29 {
  24. 30 foreach (var assembly in _options.GetFinalHostingStartupAssemblies())
  25. 31 {
  26. 32 _logger.LogDebug("Loaded hosting startup assembly {assemblyName}", assembly);
  27. 33 }
  28. 34 }
  29. 35
  30. 36 if (_hostingStartupErrors != null)
  31. 37 {
  32. 38 foreach (var exception in _hostingStartupErrors.InnerExceptions)
  33. 39 {
  34. 40 _logger.HostingStartupAssemblyError(exception);
  35. 41 }
  36. 42 }
  37. 43 }

当我们转进到StartAsync方法时会看到如下代码

  1. 1 public async Task StartAsync<TContext>(IHttpApplication<TContext> application, CancellationToken cancellationToken)
  2. 2 {
  3. 3 try
  4. 4 {
  5. 19 // ServiceContext.Heartbeat?.Start();//一个是连接池一个是日期时间
  6. 20
  7. 21 async Task OnBind(ListenOptions options)
  8. 22 {
  9. 23 // Add the HTTP middleware as the terminal connection middleware
  10. 24 options.UseHttpServer(ServiceContext, application, options.Protocols);//注册中间件
  11. 25
  12. 26 var connectionDelegate = options.Build();
  13. 27
  14. 28 // Add the connection limit middleware
  15. 29 if (Options.Limits.MaxConcurrentConnections.HasValue)
  16. 30 {
  17. 31 connectionDelegate = new ConnectionLimitMiddleware(connectionDelegate, Options.Limits.MaxConcurrentConnections.Value, Trace).OnConnectionAsync;
  18. 32 }
  19. 33
  20. 34 var connectionDispatcher = new ConnectionDispatcher(ServiceContext, connectionDelegate);
  21. 35 var transport = await _transportFactory.BindAsync(options.EndPoint).ConfigureAwait(false);
  22. 36
  23. 37 // Update the endpoint
  24. 38 options.EndPoint = transport.EndPoint;
  25. 39 var acceptLoopTask = connectionDispatcher.StartAcceptingConnections(transport);
  26. 40
  27. 41 _transports.Add((transport, acceptLoopTask));
  28. 42 }
  29. 43
  30. 44 await AddressBinder.BindAsync(_serverAddresses, Options, Trace, OnBind).ConfigureAwait(false);
  31. 45 }
  32. 46 catch (Exception ex)
  33. 47 {51 }
  34. 52 }
  1.  

AddressBinder就是server绑定的ip地址,这个可以在StartUp方法或者环境变量里面配置,里面传了一个回调方法OnBind, 在第24行的UseHttpServer会注册server 内部的中间件去处理这个请求,在第35行socet会绑定地址,用tcp协议,默认使用512个最大pending队列,在接受socket会有多处异步编程和开启线程,建议大家在调试的时候可以修改代码用尽可能少的线程来进行调试。accept 的代码如下图所示

  1. 1 private void StartAcceptingConnectionsCore(IConnectionListener listener)
  2. 2 {
  3. 3 // REVIEW: Multiple accept loops in parallel?
  4. 4 _ = AcceptConnectionsAsync();
  5. 5
  6. 6 async Task AcceptConnectionsAsync()
  7. 7 {
  8. 8 try
  9. 9 {
  10. 10 while (true)
  11. 11 {
  12. 12 var connection = await listener.AcceptAsync();
  13. 13 19
  14. 20 // Add the connection to the connection manager before we queue it for execution
  15. 21 var id = Interlocked.Increment(ref _lastConnectionId);
  16. 22 var kestrelConnection = new KestrelConnection(id, _serviceContext, _connectionDelegate, connection, Log);
  17. 23
  18. 24 _serviceContext.ConnectionManager.AddConnection(id, kestrelConnection);27
  19. 28 ThreadPool.UnsafeQueueUserWorkItem(kestrelConnection, preferLocal: false);
  20. 29 }
  21. 30 }
  22. 31 catch (Exception ex)
  23. 32 {
  24. 33 // REVIEW: If the accept loop ends should this trigger a server shutdown? It will manifest as a hang
  25. 34 Log.LogCritical(0, ex, "The connection listener failed to accept any new connections.");
  26. 35 }
  27. 36 finally
  28. 37 {
  29. 38 _acceptLoopTcs.TrySetResult(null);
  30. 39 }
  31. 40 }
  32. 41 }

接收到accept socket的时候,会创建一个kestrelconnection 对象,这个对象实现线程方法,然后它会重新去等待一个请求的到来,而用户代码的执行则交给线程池执行。在第14行就是之前kerstrel server 内部的中间件build生成的方法,他的主要功能就是解析socket的携带http信息。

  1. 1 internal async Task ExecuteAsync()
  2. 2 {
  3. 3 var connectionContext = TransportConnection;
  4. 4
  5. 5 try
  6. 6 {
  7. 10 using (BeginConnectionScope(connectionContext))
  8. 11 {
  9. 12 try
  10. 13 {
  11. 14 await _connectionDelegate(connectionContext);
  12. 15 }
  13. 16 catch (Exception ex)
  14. 17 {
  15. 18 Logger.LogError(0, ex, "Unhandled exception while processing {ConnectionId}.", connectionContext.ConnectionId);
  16. 19 }
  17. 20 }
  18. 21 }
  19. 22 finally
  20. 23 {
  21. 34 _serviceContext.ConnectionManager.RemoveConnection(_id);
  22. 35 }
  23. 36 }

由于http协议版本的不一致导致解析方式的不同,如果有兴趣的小伙伴可以具体查看这一块的逻辑。

  1. 1 switch (SelectProtocol())
  2. 2 {
  3. 3 case HttpProtocols.Http1:
  4. 4 // _http1Connection must be initialized before adding the connection to the connection manager
  5. 5 requestProcessor = _http1Connection = new Http1Connection<TContext>(_context);
  6. 6 _protocolSelectionState = ProtocolSelectionState.Selected;
  7. 7 break;
  8. 8 case HttpProtocols.Http2:
  9. 9 // _http2Connection must be initialized before yielding control to the transport thread,
  10. 10 // to prevent a race condition where _http2Connection.Abort() is called just as
  11. 11 // _http2Connection is about to be initialized.
  12. 12 requestProcessor = new Http2Connection(_context);
  13. 13 _protocolSelectionState = ProtocolSelectionState.Selected;
  14. 14 break;
  15. 15 case HttpProtocols.None:
  16. 16 // An error was already logged in SelectProtocol(), but we should close the connection.
  17. 17 break;
  18. 18 default:
  19. 19 // SelectProtocol() only returns Http1, Http2 or None.
  20. 20 throw new NotSupportedException($"{nameof(SelectProtocol)} returned something other than Http1, Http2 or None.");
  21. 21 }

然后server解析完请求之后所做的重要的一步就是创建httpContext,然后server在第40行将控制权转给web host,web host 会自动调用application code 也就是用户代码。

  1. 1 private async Task ProcessRequests<TContext>(IHttpApplication<TContext> application)
  2. 2 {
  3. 3 while (_keepAlive)
  4. 4 {
  5. 33 var context = application.CreateContext(this);
  6. 34
  7. 35 try
  8. 36 {
  9. 37 KestrelEventSource.Log.RequestStart(this);
  10. 38
  11. 39 // Run the application code for this request
  12. 40 await application.ProcessRequestAsync(context);
  13. 41 55 }
  14. 56 catch (BadHttpRequestException ex)
  15. 57 {
  16. 58 // Capture BadHttpRequestException for further processing
  17. 59 // This has to be caught here so StatusCode is set properly before disposing the HttpContext
  18. 60 // (DisposeContext logs StatusCode).
  19. 61 SetBadRequestState(ex);
  20. 62 ReportApplicationError(ex);
  21. 63 }
  22. 64 catch (Exception ex)
  23. 65 {
  24. 66 ReportApplicationError(ex);
  25. 67 }
  26. 68
  27. 69 KestrelEventSource.Log.RequestStop(this);129 }
  28. 130 }

到这里server 的工作大部分都结束了,在之前的描述中我们看到web host 怎么将控制权给到server 的, server 创建好httpContext规则后又是如何将控制权给到web host , web host 又如何去调用application code的, web host 实际上build 的时候将用户的中间件定义为链表结构暴露一个入口供web host调用,其他的有时间我会再写博客描述这一块。谢谢大家今天的阅读了。欢迎大家能够留言一起讨论。最后谢谢大家的阅读,如果有任何不懂的地方可以私信我。

kestrel Server的源码分析的更多相关文章

  1. Go Revel - server.go 源码分析

    之前介绍了 Go Revel - main函数分析 http://www.cnblogs.com/hangxin1940/p/3263775.html 最后会调用 `revel.Run(*port)` ...

  2. udhcp server端源码分析1--文件组织结构

    1:dhcpd.c udhcpd_main函数是整个程序的入口,依次完成的主要工作有读取配置文件信息至全局结构体.记录程序pid number.初始化lease链表.将程序作为daemon运行.死循环 ...

  3. dubbo源码分析4-基于netty的dubbo协议的server

    dubbo源码分析1-reference bean创建 dubbo源码分析2-reference bean发起服务方法调用 dubbo源码分析3-service bean的创建与发布 dubbo源码分 ...

  4. Eureka 源码分析之 Eureka Server

    文章首发于公众号<程序员果果> 地址 : https://mp.weixin.qq.com/s/FfJrAGQuHyVrsedtbr0Ihw 简介 上一篇文章<Eureka 源码分析 ...

  5. dubbo源码分析6-telnet方式的管理实现

    dubbo源码分析1-reference bean创建 dubbo源码分析2-reference bean发起服务方法调用 dubbo源码分析3-service bean的创建与发布 dubbo源码分 ...

  6. dubbo源码分析1-reference bean创建

    dubbo源码分析1-reference bean创建 dubbo源码分析2-reference bean发起服务方法调用 dubbo源码分析3-service bean的创建与发布 dubbo源码分 ...

  7. dubbo源码分析2-reference bean发起服务方法调用

    dubbo源码分析1-reference bean创建 dubbo源码分析2-reference bean发起服务方法调用 dubbo源码分析3-service bean的创建与发布 dubbo源码分 ...

  8. dubbo源码分析3-service bean的创建与发布

    dubbo源码分析1-reference bean创建 dubbo源码分析2-reference bean发起服务方法调用 dubbo源码分析3-service bean的创建与发布 dubbo源码分 ...

  9. dubbo源码分析5-dubbo的扩展点机制

    dubbo源码分析1-reference bean创建 dubbo源码分析2-reference bean发起服务方法调用 dubbo源码分析3-service bean的创建与发布 dubbo源码分 ...

随机推荐

  1. vue常用方法封装-一键安装使用(赠送免费工具)

    相信大家在使用vue开发过程中一定遇到了各种方法的整理收集,每次遇到新的问题都需要找到合适的方法 这里我给大家封装了一些vue项目中常用到的方法合集,免费提供费大家 因此,jsoften横空出世,不为 ...

  2. 「NGK每日快讯」12.11日NGK公链第38期官方快讯!

  3. Word带数学公式发布博客

    Word公式编辑器无法直接上传博客,一个一个的转换LaTeX还要加$,十分麻烦. 下面是我昨天摸索出来的办法.作为博客新人,这个问题困扰我一晚上,能解决我也是非常高兴的. 如果各位前辈有好方法的话,请 ...

  4. 对Innodb中MVCC的理解

    一.什么是MVCC MVCC (Multiversion Concurrency Control) 中文全程叫多版本并发控制,是现代数据库(如MySql)引擎实现中常用的处理读写冲突的手段,目的在于提 ...

  5. Python安装教程

    1.下载好Python安装包后,双击打开(第一个是32位,第二个是64位,根据自己电脑位数进行选择): 2.打开后如下,先将下方的Python添加到系统环境变量勾选上,再点击第一个默认安装即可: 3. ...

  6. 【OI向】快速傅里叶变换(Fast Fourier Transform)

    [OI向]快速傅里叶变换(Fast Fourier Transform) FFT的作用 ​ 在学习一项算法之前,我们总该关心这个算法究竟是为了干什么. ​ (以下应用只针对OI) ​ 一句话:求多项式 ...

  7. Svelte 极简入门

    ​弹指之间即可完成.   注意:原文发表于 2017-8-7,随着框架不断演进,部分内容可能已不适用.     Svelte 是一种新型框架.   以往我们要引入一个框架或者类库,可以通过在页面上放置 ...

  8. 第30天学习打卡(异常概述 IO流概述)

    异常概述 即非正常情况,通俗的说,异常就是程序出现的错误 异常的分类(Throwable) 异常(Exception) 合理的应用程序可能需要捕获的问题 举例:NullPointerException ...

  9. nacos--配置中心之客户端

    nacos提供com.alibaba.nacos.api.config.ConfigService作为客户端的API用于发布,订阅,获取配置信息: ConfigService获取配置信息流程: 优先使 ...

  10. 在Linux中安装MariaDB并添加远程访问

    在Linux中安装MariaDB并添加远程访问 最近学习到了数据库部分,因为有一台台式机一台笔记本换着用,就没有把数据库安装在本机,本来打算用之前买的虚拟空间的数据库的,结果速度太慢用起来太难受了,就 ...