asp.net core mvc剖析:KestrelServer
oca ica 说明 ride 注册 content virt memory
KestrelServer 是基于 Libuv 开发的高性能 web 服务器,那我们现在就来看一下它是如何工作的。在上一篇文章中提到了 Program 的 Main 方法,在这个方法里 Build 了一个 WebHost,我们再来看一下代码:
1
2
3
4
5
6
7
8
9
10
11
|
- 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 对象,具体代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
|
- 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 对象,它是整个应用生命周期的管理对象,前面没有说到,这里补充上。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
- 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 = 0; index < count; index++)
- {
- Threads.Add(new KestrelThread(this));
- }
- foreach (var thread in Threads)
- {
- thread.StartAsync().Wait();
- }
- }
上面的代码会创建指定数量的 Thread 对象,然后开始等待任务处理。KestrelThread 是对 libuv 线程处理的封装。
这些工作都准备好后,就开始启动监听服务了
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
- 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());
-
- }
|
上面红色字体代码,就是创建监听服务的方法,我们再详细看下里面的详细情况:
这个时候服务就开始接受 http 请求了,我们前面说到了,监听 socket 在 listener 类中创建(ListenerPrimary 也是一个 Listener),下面是 listener 的 start 方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
-
- 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;
-
- }
- </
- int
- ></
- int
- >
|
ConnectionCallback:当连接请求过来后被触发,在回调方法里,进行连接处理分发,连接分发代码如下:
1
2
3
4
5
|
- protected
- virtual
- void
- DispatchConnection(UvStreamHandle socket)
-
- {
-
- var
- connection =
- new
- Connection(
- this
- , socket);
-
- connection.Start();
-
- }
|
这个是 listener 类中的实现,我们前面看到,只有在线程数为 1 的情况下,才创建 Listener 对象进行监听,否则创建 ListenerPrimary 监听,ListenerPrimay 里重写了方法,它的实现如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
- 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 对象的创建
1
2
|
- var
- connection =
- new
- Connection(
- this
- , socket);
- connection.Start();
|
Connection 表示的就是当前连接,下面是它的构造方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
- 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 方法实现:
1
2
3
4
5
6
7
8
9
10
11
12
|
- public
- void
- Start()
-
- {
-
- Reset();
- //启动了异步处理任务开始进行处理
-
- _requestProcessingTask =
-
- Task.Factory.StartNew(
-
- (o) => ((Frame)o).RequestProcessingAsync(),
- //具体的处理方法
-
- this
- ,
-
- default
- (CancellationToken),
-
- TaskCreationOptions.DenyChildAttach,
-
- TaskScheduler.Default).Unwrap();
-
- _frameStartedTcs.SetResult(
- null
- );
-
- }
|
1 |
- RequestProcessingAsync方法里不再详细介绍了,把主要的代码拿出来看一下:
|
1
2
3
4
5
|
- 。。。。。
- //_application就是上一篇文章提到的HostApplication,首先调用CreateContext创建HttpContext对象
- var
- context = _application.CreateContext(
- this
- );
- 。。。。。。
- //进入处理管道
- await _application.ProcessRequestAsync(context).ConfigureAwait(
- false
- );
- 。。。。。。
|
1 |
- ProcessRequestAsync完成处理后,把结果输出给客户端,好到此介绍完毕。如果有问题,欢迎大家指点。
|
asp.net core mvc 剖析:KestrelServer
来源: http://www.bubuko.com/infodetail-2052553.html