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

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

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

public void Start<TContext>(IHttpApplication<TContext> application)
{
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
});
//启动引擎。完成libuv的配置和启动
engine.Start(threadCount);
//针对绑定的多个地址创建server来接收请求。也就是针对ip:port来启动tcp监听
foreach (var address in _serverAddresses.Addresses.ToArray())
{
engine.CreateServer(ipv4Address);
}
}

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

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

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

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();
}
}
private void ThreadStart(object parameter)
{
lock (_startSync)
{
var tcs = (TaskCompletionSource<int>) parameter;
try
{
//初始化loop
_loop.Init(_engine.Libuv);
//注册loop回调
//EnqueueCloseHandle:持有的资源释放后的回调方法,回调往queue内增加一个item,事件循环该queue完成资源的最终释放
_post.Init(_loop, OnPost, EnqueueCloseHandle);
//注册心跳定时器
_heartbeatTimer.Init(_loop, EnqueueCloseHandle);
//启动心跳定时器
_heartbeatTimer.Start(OnHeartbeat, timeout: HeartbeatMilliseconds, repeat: HeartbeatMilliseconds);
_initCompleted = true;
tcs.SetResult(0);
}
catch (Exception ex)
{
tcs.SetException(ex);
return;
}
}
try
{
//当前线程执行到Run()这里会挂起
_loop.Run();
//应用程序stop,shutdown之类的情况,libuv唤醒当前线程,完成资源清理
if (_stopImmediate)
{
// thread-abort form of exit, resources will be leaked
//线程中止形式的退出,资源会被泄露。
return;
} // run the loop one more time to delete the open handles
//再次运行循环以删除打开的句柄
_post.Reference();
_post.Dispose();
_heartbeatTimer.Dispose(); // Ensure the Dispose operations complete in the event loop.
//确保事件循环中的Dispose操作完成。
_loop.Run(); _loop.Dispose();
}
catch (Exception ex)
{
_closeError = ExceptionDispatchInfo.Capture(ex);
// Request shutdown so we can rethrow this exception
// in Stop which should be observable.
//请求关闭,以便我们可以重新抛出此异常在停止应该是可观察的。
_appLifetime.StopApplication();
}
finally
{
_threadTcs.SetResult(null);
}
}

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

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

public IDisposable CreateServer(ServerAddress address)
{
var usingPipes = address.IsUnixPipe;
var pipeName = (Libuv.IsWindows ? @"\\.\pipe\kestrel_" : "/tmp/kestrel_") + Guid.NewGuid().ToString("n");
var single = Threads.Count == 1;
var first = true; foreach (var thread in Threads)
{
if(single){}//single就不考虑,这种情况真是环境是不会这样玩的
else if (first)
{
//根据当前平台创建tcp listener
var listener = usingPipes
? (ListenerPrimary)new PipeListenerPrimary(ServiceContext)
: new TcpListenerPrimary(ServiceContext);
listener.StartAsync(pipeName, address, thread).Wait();
}
else
{
//如果是多次对同一个ip:port做监听
var listener = usingPipes
? (ListenerSecondary)new PipeListenerSecondary(ServiceContext)
: new TcpListenerSecondary(ServiceContext);
listener.StartAsync(pipeName, address, thread).Wait();
}
first = false;
}
}

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

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

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

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

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

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

public Task StartAsync(ServerAddress address, KestrelThread thread)
{
ServerAddress = address; Thread = thread;
var tcs = new TaskCompletionSource<int>(this);
Thread.Post(state =>
{
var tcs2 = (TaskCompletionSource<int>)state;
var listener = ((Listener)tcs2.Task.AsyncState);
//创建socket
listener.ListenSocket = listener.CreateListenSocket();
////socket监听,libu注册监听并设置回调函数,最大队列。
ListenSocket.Listen(Constants.ListenBacklog, ConnectionCallback, this);
tcs2.SetResult(0);
}, tcs);
return tcs.Task;
}
protected override UvStreamHandle CreateListenSocket()
{
//初始化socket并bind到address
var socket = new UvTcpHandle(Log);
socket.Init(Thread.Loop, Thread.QueueCloseHandle);
//是否使用Nagle's algorithm算法。
socket.NoDelay(ServerOptions.NoDelay);
socket.Bind(ServerAddress);
// If requested port was "0", replace with assigned dynamic port.
ServerAddress.Port = socket.GetSockIPEndPoint().Port;
return socket;
}

在接着上面的代码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:循环接收请求,接收请求到之后交给上层程序处理

private static void ConnectionCallback(UvStreamHandle stream, int status, Exception error, object state)
{
var listener = (Listener)state;
listener.OnConnection(stream, status);//step 1
}
protected override void OnConnection(UvStreamHandle listenSocket, int status)//step 2
{
var acceptSocket = new UvTcpHandle(Log);
acceptSocket.Init(Thread.Loop, Thread.QueueCloseHandle);
acceptSocket.NoDelay(ServerOptions.NoDelay);
listenSocket.Accept(acceptSocket);
DispatchConnection(acceptSocket);
}
protected override void DispatchConnection(UvStreamHandle socket)// step 3
{
var index = _dispatchIndex++ % (_dispatchPipes.Count + 1);
if (index == _dispatchPipes.Count)
{
base.DispatchConnection(socket);
}
else
{
DetachFromIOCP(socket);
var dispatchPipe = _dispatchPipes[index];
var write = new UvWriteReq(Log);
write.Init(Thread.Loop);
write.Write2(dispatchPipe, _dummyMessage, socket,
(write2, status, error, state) =>
{
write2.Dispose();
((UvStreamHandle)state).Dispose();
},
socket);
}
}
protected virtual void DispatchConnection(UvStreamHandle socket)//step 4
{
var connection = new Connection(this, socket);
connection.Start();
} private Func<ConnectionContext, Frame> FrameFactory => ListenerContext.ServiceContext.FrameFactory;
public Connection(ListenerContext context, UvStreamHandle socket) : base(context)//step 5
{
SocketInput = new SocketInput(Thread.Memory, ThreadPool, _bufferSizeControl);
SocketOutput = new SocketOutput(Thread, _socket, this, ConnectionId, Log, ThreadPool);
//重点代码在这里,FrameFactory是一个委托,是KestrelServer.Start中注册的action
_frame = FrameFactory(this);
}
public void Start()//step 6
{
Log.ConnectionStart(ConnectionId);
// Start socket prior to applying the ConnectionFilter
_socket.ReadStart(_allocCallback, _readCallback, this);
_frame.Start();
}
/// <summary>
/// Called once by Connection class to begin the RequestProcessingAsync loop.
/// </summary>
public void Start()//step 7
{
Reset();
_requestProcessingTask =
Task.Factory.StartNew(
(o) => ((Frame)o).RequestProcessingAsync(),
this,
default(CancellationToken),
TaskCreationOptions.DenyChildAttach,
TaskScheduler.Default).Unwrap();
}
/// <summary>
/// 主循环消耗套接字输入,将其解析为协议帧,并调用应用程序委托,只要套接字打算保持打开。
/// 从此循环得到的任务将保留在服务器需要时使用的字段中以排除和关闭所有当前活动的连接。
/// </summary>
public override async Task RequestProcessingAsync()
{
while (!_requestProcessingStopping)
{
InitializeHeaders();
var context = _application.CreateContext(this);
await _application.ProcessRequestAsync(context).ConfigureAwait(false);
}
}

.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. Boost条件变量condition_variable_any

    Boost条件变量可以用来实现线程同步,它必须与互斥量配合使用.使用条件变量实现生产者消费者的简单例子如下,需要注意的是cond_put.wait(lock)是在等待条件满足.如果条件不满足,则释放锁 ...

  2. 手游聚合SDK开发之远程开关---渠道登入白名单

    白名单有啥好说的呢?无非就是筛选登入,大家第一眼看到就是这个印象,白名单也是有文章的,弄的时机不同会给你带来很不错的收益,注意是收益.还是举例来说,游戏上线前渠道都会做一个预下载,一般提前1-2天,这 ...

  3. gRPC源码分析0-导读

    gRPC是Google开源的新一代RPC框架,官网是http://www.grpc.io.正式发布于2016年8月,技术栈非常的新,基于HTTP/2,netty4.1,proto3.虽然目前在工程化方 ...

  4. spider RPC高级特性

    多租户 spider原生支持多租户部署,spider报文头对外开放了机构号.系统号两个属性用于支持多租户场景下的路由. 多租户场景下的路由可以支持下述几种模式: n  系统号: n  系统号+服务号( ...

  5. javascript浏览器检测

    <script type="text/javascript">   /**  * 获取浏览器类型以及版本号  * 支持国产浏览器:猎豹浏览器.搜狗浏览器.傲游浏览器.3 ...

  6. 我的第一篇博客----LCS学习笔记

    LCS引论 在这篇博文中,博主要给大家讲一个算法----最长公共子序列(LCS)算法.我最初接触这个算法是在高中学信息学竞赛的时候.那时候花了好长时间理解这个算法.老师经常说,这种算法是母算法,即从这 ...

  7. Atitit.http代理的实现 代码java php c# python

    Atitit.http代理的实现 代码java php c# python 1. 代理服务器用途 代理服务器看成是一种扩展浏览器功能的途径.例如,在把数据发送给浏览器之前,可以用代理服务器压缩数据 调 ...

  8. T-SQL 去除特定字段的前导0

    在工作过程中遇到一个需求,要从特定字段中删除前导零,这是一个简单的VARCHAR(10)字段. 例如,如果字段包含"00001A",则SELECT语句需要将数据返回为"1 ...

  9. favicon.ico 404

    favicon.ico是一个图标文件.就是浏览网站时显示在地址栏的那个图标. 类似是百度的 显示在网站地址最前面的一张图片 可以在网站根目录(TOMCAT_HOME/webapps/ROOT/favi ...

  10. 跳入linux的第一个坑-因为安装Ubuntu导致的硬盘被误格的恢复.(记TestDisk使用记录)

    不看废话,直接跳到操作说明 前几日心血来潮想把家中的旧笔记本换成Linux操作系统,算是在业余生活中正式投入Linux的怀抱.说干就干,发行版选择了Ubuntu,下载了Ubuntu16.04的ISO, ...