asp.net core mvc剖析:KestrelServer
KestrelServer是基于Libuv开发的高性能web服务器,那我们现在就来看一下它是如何工作的。在上一篇文章中提到了Program的Main方法,在这个方法里Build了一个WebHost,我们再来看一下代码:
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对象,具体代码如下:
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对象,它是整个应用生命周期的管理对象,前面没有说到,这里补充上。
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 = ; index < count; index++)
{
Threads.Add(new KestrelThread(this));
} foreach (var thread in Threads)
{
thread.StartAsync().Wait();
}
}
上面的代码会创建指定数量的Thread对象,然后开始等待任务处理。KestrelThread是对libuv线程处理的封装。
这些工作都准备好后,就开始启动监听服务了
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());
}
上面红色字体代码,就是创建监听服务的方法,我们再详细看下里面的详细情况:
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;
}
}
这个时候服务就开始接受http请求了,我们前面说到了,监听socket在listener类中创建(ListenerPrimary也是一个Listener),下面是listener的start方法
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;
}
ConnectionCallback:当连接请求过来后被触发,在回调方法里,进行连接处理分发,连接分发代码如下:
protected virtual void DispatchConnection(UvStreamHandle socket)
{
var connection = new Connection(this, socket);
connection.Start();
}
这个是listener类中的实现,我们前面看到,只有在线程数为1的情况下,才创建Listener对象进行监听,否则创建ListenerPrimary监听,ListenerPrimay里重写了方法,它的实现如下:
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对象的创建
var connection = new Connection(this, socket);
connection.Start();
Connection表示的就是当前连接,下面是它的构造方法
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方法实现:
public void Start()
{
Reset();
//启动了异步处理任务开始进行处理
_requestProcessingTask =
Task.Factory.StartNew(
(o) => ((Frame)o).RequestProcessingAsync(),//具体的处理方法
this,
default(CancellationToken),
TaskCreationOptions.DenyChildAttach,
TaskScheduler.Default).Unwrap();
_frameStartedTcs.SetResult(null);
}
RequestProcessingAsync方法里不再详细介绍了,把主要的代码拿出来看一下:
。。。。。
//_application就是上一篇文章提到的HostApplication,首先调用CreateContext创建HttpContext对象
var context = _application.CreateContext(this);
。。。。。。
//进入处理管道
await _application.ProcessRequestAsync(context).ConfigureAwait(false);
。。。。。。
ProcessRequestAsync完成处理后,把结果输出给客户端,好到此介绍完毕。如果有问题,欢迎大家指点。
asp.net core mvc剖析:KestrelServer的更多相关文章
- asp.net core mvc剖析:启动流程
asp.net core mvc是微软开源的跨平台的mvc框架,首先它跟原有的MVC相比,最大的不同就是跨平台,然后又增加了一些非常实用的新功能,比如taghelper,viewcomponent,D ...
- asp.net core mvc剖析:路由
在mvc框架中,任何一个动作请求都会被映射到具体控制器中的方法上,那框架是如何完成这样一个过程的,现在我们就来简单分析下流程. 我们紧跟上面的主题,任何一个请求都会交给处理管道进行处理,那mvc处理的 ...
- asp.net core mvc剖析:mvc执行过程(一)
前面介绍了路由的过程,我们再来看下MvcRouteHandler的代码: public Task RouteAsync(RouteContext context) { ...... //根据路由信息查 ...
- asp.net core mvc剖析:mvc动作选择
一个http请求过来后,首先经过路由规则的匹配,找到最符合条件的的IRouter,然后调用IRouter.RouteAsync来设置RouteContext.Handler,最后把请求交给RouteC ...
- asp.net core mvc剖析:处理管道构建
在启动流程文章中提到,在WebHost类中,通过BuildApplication完成http请求处理管道的构建.在来看一下代码: ...... //这个调用的就是Startup.cs类中的Config ...
- asp.net core mvc剖析:动作执行
紧跟上一篇文章.通过路由和动作匹配后,最终会得到跟当前请求最匹配的一个ActionDescriptor,然后通过IActionInvoker执行动作. 我们先来看一下IActionInvoker如何得 ...
- net core mvc剖析:启动流程
net core mvc剖析:启动流程 asp.net core mvc是微软开源的跨平台的mvc框架,首先它跟原有的MVC相比,最大的不同就是跨平台,然后又增加了一些非常实用的新功能,比如taghe ...
- 剖析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 ...
- ASP.NET Core MVC内置服务的使用
ASP.NET Core中的依赖注入可以说是无处不在,其通过创建一个ServiceCollection对象并将服务注册信息以ServiceDescriptor对象的形式添加在其中,其次针对Servic ...
随机推荐
- MYSQL管理----数据库删除恢复
(1) 如果备份了,就好解决了.略. (2)如果日志打开,使用mysqlbinlog来恢复. mysqlbinlog工具的使用,大家可以看MySQL的帮助手册.里面有详细的用, 在这个例子中,重点是- ...
- Xcode6之后创建Pch预编译文件
在Xcode6之前,创建一个新工程xcode会在Supporting files文件夹下面自动创建一个“工程名-Prefix.pch”文件,也是一个头文件,pch头文件的内容能被项目中的其他所有源文件 ...
- CentOS6.x升级MySQL版本5.1到5.6
CentOS6.x升级MySQL版本5.1到5.6 分类: Web MySQL 2014-08-04 11:22 2813人阅读 评论(1) 收藏 举报 mysql云服务器升级centos6 有一些虚 ...
- YUM更换源(1)--yum找不到安装包
公司提供的CentOS VM中,/etc/yum.repos.d 下 只有一个build.repo,其中提供的yum 源只有公司内部的几个源,很多包在这些源中都找不到.于是要添加别的源,下面的帖子中介 ...
- Set笔记
Set 继承自Collection的一个接口,特点是:无序,不可重复.注意啊!!只有Collection实现了迭代器!也就是说Map是没有实现迭代器的,需要keySet,values,entrySet ...
- jquery弹窗插件
.zhuti { position:absolute; z-index:; font-size:14px; border-radius:5px; box-shadow: 5px white; over ...
- 关于浏览器和HTTP协议
关于浏览器 浏览器的主要功能就是向服务器发出请求,在浏览器窗口中展示想要访问的网络资源.这里资源一般是指 HTML 文档,图片等其他的类型.资源的位置由用户使用 URL(统一资源标示符)指定. 而浏览 ...
- html css基础(一)
1.HTML:做静态网页,是一种标签语言, HTML结构: 一个HTML文档由4个基本部分组成: ① 一个文档声明:<!DOCTYPE HTML> ② 一个html标签对:<html ...
- Android px、dp和sp单位区别
[转]http://www.cnblogs.com/bjzhanghao/archive/2012/11/06/2757300.html 在调整布局的时候,某些控件的大小需要设置单位: px: 即像素 ...
- 笔记-Python基础教程(第二版)第一章
第一章 快速改造:基础知识 01:整除.乘方 (Python3.0之前 如2.7版本) >>> 1/2 ==>0 1/2整除,普通除法: 解决办法1: 1.0/2.0 ==& ...