asp.net core 3.1 源码分析之KestrelServer
public class KestrelServer : IServer { private readonly List<(IConnectionListener, Task)> _transports = new List<(IConnectionListener, Task)>(); private readonly IServerAddressesFeature _serverAddresses; private readonly IConnectionListenerFactory _transportFactory; private bool _hasStarted; private int _stopping; private readonly TaskCompletionSource<object> _stoppedTcs = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously); public KestrelServer( IOptions<KestrelServerOptions> options, IConnectionListenerFactory transportFactory, ILoggerFactory loggerFactory) : this(transportFactory, CreateServiceContext(options, loggerFactory)) { } // For testing internal KestrelServer(IConnectionListenerFactory transportFactory, ServiceContext serviceContext) { if (transportFactory == null) { throw new ArgumentNullException(nameof(transportFactory)); } _transportFactory = transportFactory; ServiceContext = serviceContext; Features = new FeatureCollection(); _serverAddresses = new ServerAddressesFeature(); Features.Set(_serverAddresses); HttpCharacters.Initialize(); } private static ServiceContext CreateServiceContext(IOptions<KestrelServerOptions> options, ILoggerFactory loggerFactory) { if (options == null) { throw new ArgumentNullException(nameof(options)); } if (loggerFactory == null) { throw new ArgumentNullException(nameof(loggerFactory)); } var serverOptions = options.Value ?? new KestrelServerOptions(); var logger = loggerFactory.CreateLogger("Microsoft.AspNetCore.Server.Kestrel"); var trace = new KestrelTrace(logger); var connectionManager = new ConnectionManager( trace, serverOptions.Limits.MaxConcurrentUpgradedConnections); var heartbeatManager = new HeartbeatManager(connectionManager); var dateHeaderValueManager = new DateHeaderValueManager(); var heartbeat = new Heartbeat( new IHeartbeatHandler[] { dateHeaderValueManager, heartbeatManager }, new SystemClock(), DebuggerWrapper.Singleton, trace); return new ServiceContext { Log = trace, HttpParser = new HttpParser<Http1ParsingHandler>(trace.IsEnabled(LogLevel.Information)), Scheduler = PipeScheduler.ThreadPool, SystemClock = heartbeatManager, DateHeaderValueManager = dateHeaderValueManager, ConnectionManager = connectionManager, Heartbeat = heartbeat, ServerOptions = serverOptions, }; } public IFeatureCollection Features { get; } public KestrelServerOptions Options => ServiceContext.ServerOptions; private ServiceContext ServiceContext { get; } private IKestrelTrace Trace => ServiceContext.Log; private ConnectionManager ConnectionManager => ServiceContext.ConnectionManager; public async Task StartAsync<TContext>(IHttpApplication<TContext> application, CancellationToken cancellationToken) { try { if (!BitConverter.IsLittleEndian) { throw new PlatformNotSupportedException(CoreStrings.BigEndianNotSupported); } ValidateOptions(); if (_hasStarted) { // The server has already started and/or has not been cleaned up yet throw new InvalidOperationException(CoreStrings.ServerAlreadyStarted); } _hasStarted = true; ServiceContext.Heartbeat?.Start(); async Task OnBind(ListenOptions options) { // Add the HTTP middleware as the terminal connection middleware options.UseHttpServer(ServiceContext, application, options.Protocols); var connectionDelegate = options.Build(); // Add the connection limit middleware if (Options.Limits.MaxConcurrentConnections.HasValue) { connectionDelegate = new ConnectionLimitMiddleware(connectionDelegate, Options.Limits.MaxConcurrentConnections.Value, Trace).OnConnectionAsync; } var connectionDispatcher = new ConnectionDispatcher(ServiceContext, connectionDelegate); var transport = await _transportFactory.BindAsync(options.EndPoint).ConfigureAwait(false); // Update the endpoint options.EndPoint = transport.EndPoint; var acceptLoopTask = connectionDispatcher.StartAcceptingConnections(transport); _transports.Add((transport, acceptLoopTask)); } await AddressBinder.BindAsync(_serverAddresses, Options, Trace, OnBind).ConfigureAwait(false); } catch (Exception ex) { Trace.LogCritical(0, ex, "Unable to start Kestrel."); Dispose(); throw; } } // Graceful shutdown if possible public async Task StopAsync(CancellationToken cancellationToken) { if (Interlocked.Exchange(ref _stopping, 1) == 1) { await _stoppedTcs.Task.ConfigureAwait(false); return; } try { var tasks = new Task[_transports.Count]; for (int i = 0; i < _transports.Count; i++) { (IConnectionListener listener, Task acceptLoop) = _transports[i]; tasks[i] = Task.WhenAll(listener.UnbindAsync(cancellationToken).AsTask(), acceptLoop); } await Task.WhenAll(tasks).ConfigureAwait(false); if (!await ConnectionManager.CloseAllConnectionsAsync(cancellationToken).ConfigureAwait(false)) { Trace.NotAllConnectionsClosedGracefully(); if (!await ConnectionManager.AbortAllConnectionsAsync().ConfigureAwait(false)) { Trace.NotAllConnectionsAborted(); } } for (int i = 0; i < _transports.Count; i++) { (IConnectionListener listener, Task acceptLoop) = _transports[i]; tasks[i] = listener.DisposeAsync().AsTask(); } await Task.WhenAll(tasks).ConfigureAwait(false); ServiceContext.Heartbeat?.Dispose(); } catch (Exception ex) { _stoppedTcs.TrySetException(ex); throw; } _stoppedTcs.TrySetResult(null); } // Ungraceful shutdown public void Dispose() { var cancelledTokenSource = new CancellationTokenSource(); cancelledTokenSource.Cancel(); StopAsync(cancelledTokenSource.Token).GetAwaiter().GetResult(); } private void ValidateOptions() { Options.ConfigurationLoader?.Load(); if (Options.Limits.MaxRequestBufferSize.HasValue && Options.Limits.MaxRequestBufferSize < Options.Limits.MaxRequestLineSize) { throw new InvalidOperationException( CoreStrings.FormatMaxRequestBufferSmallerThanRequestLineBuffer(Options.Limits.MaxRequestBufferSize.Value, Options.Limits.MaxRequestLineSize)); } if (Options.Limits.MaxRequestBufferSize.HasValue && Options.Limits.MaxRequestBufferSize < Options.Limits.MaxRequestHeadersTotalSize) { throw new InvalidOperationException( CoreStrings.FormatMaxRequestBufferSmallerThanRequestHeaderBuffer(Options.Limits.MaxRequestBufferSize.Value, Options.Limits.MaxRequestHeadersTotalSize)); } } }
KestrelServer类本身的代码并不多
主要看下StartAsync核心方法,内部有个OnBind方法
async Task OnBind(ListenOptions options) { // Add the HTTP middleware as the terminal connection middleware options.UseHttpServer(ServiceContext, application, options.Protocols); var connectionDelegate = options.Build(); // Add the connection limit middleware if (Options.Limits.MaxConcurrentConnections.HasValue) { connectionDelegate = new ConnectionLimitMiddleware(connectionDelegate, Options.Limits.MaxConcurrentConnections.Value, Trace).OnConnectionAsync; } var connectionDispatcher = new ConnectionDispatcher(ServiceContext, connectionDelegate); var transport = await _transportFactory.BindAsync(options.EndPoint).ConfigureAwait(false); // Update the endpoint options.EndPoint = transport.EndPoint; var acceptLoopTask = connectionDispatcher.StartAcceptingConnections(transport); _transports.Add((transport, acceptLoopTask)); }
看下ListenOptions参数
internal static class HttpConnectionBuilderExtensions { public static IConnectionBuilder UseHttpServer<TContext>( this IConnectionBuilder builder, ServiceContext serviceContext, IHttpApplication<TContext> application, HttpProtocols protocols) { var middleware = new HttpConnectionMiddleware<TContext>(serviceContext, application, protocols); return builder.Use(next => { return middleware.OnConnectionAsync; }); } }
public interface IConnectionBuilder { /// <summary> /// Gets the <see cref="IServiceProvider"/> that provides access to the application's service container. /// </summary> IServiceProvider ApplicationServices { get; } /// <summary> /// Adds a middleware delegate to the application's connection pipeline. /// </summary> /// <param name="middleware">The middleware delegate.</param> /// <returns>The <see cref="IConnectionBuilder"/>.</returns> IConnectionBuilder Use(Func<ConnectionDelegate, ConnectionDelegate> middleware); /// <summary> /// Builds the delegate used by this application to process connections. /// </summary> /// <returns>The connection handling delegate.</returns> ConnectionDelegate Build(); }
public class ConnectionBuilder : IConnectionBuilder { private readonly IList<Func<ConnectionDelegate, ConnectionDelegate>> _components = new List<Func<ConnectionDelegate, ConnectionDelegate>>(); public IServiceProvider ApplicationServices { get; } public ConnectionBuilder(IServiceProvider applicationServices) { ApplicationServices = applicationServices; } public IConnectionBuilder Use(Func<ConnectionDelegate, ConnectionDelegate> middleware) { _components.Add(middleware); return this; } public ConnectionDelegate Build() { ConnectionDelegate app = features => { return Task.CompletedTask; }; foreach (var component in _components.Reverse()) { app = component(app); } return app; } }
ConnectionBuilder构建一个处理http连接的委托链
public delegate Task ConnectionDelegate(ConnectionContext connection);
HttpConnectionMiddleware建立一个HttpConnection对象,调用ProcessRequestsAsync方法处理请求
先看下服务器是如何监听http请求的
public interface IConnectionListenerFactory { /// <summary> /// Creates an <see cref="IConnectionListener"/> bound to the specified <see cref="EndPoint"/>. /// </summary> /// <param name="endpoint">The <see cref="EndPoint" /> to bind to.</param> /// <param name="cancellationToken">The token to monitor for cancellation requests.</param> /// <returns>A <see cref="ValueTask{IConnectionListener}"/> that completes when the listener has been bound, yielding a <see cref="IConnectionListener" /> representing the new listener.</returns> ValueTask<IConnectionListener> BindAsync(EndPoint endpoint, CancellationToken cancellationToken = default); }
public sealed class SocketTransportFactory : IConnectionListenerFactory { private readonly SocketTransportOptions _options; private readonly SocketsTrace _trace; public SocketTransportFactory( IOptions<SocketTransportOptions> options, ILoggerFactory loggerFactory) { if (options == null) { throw new ArgumentNullException(nameof(options)); } if (loggerFactory == null) { throw new ArgumentNullException(nameof(loggerFactory)); } _options = options.Value; var logger = loggerFactory.CreateLogger("Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets"); _trace = new SocketsTrace(logger); } public ValueTask<IConnectionListener> BindAsync(EndPoint endpoint, CancellationToken cancellationToken = default) { var transport = new SocketConnectionListener(endpoint, _options, _trace); transport.Bind(); return new ValueTask<IConnectionListener>(transport); } }
internal sealed class SocketConnectionListener : IConnectionListener { private readonly MemoryPool<byte> _memoryPool; private readonly int _numSchedulers; private readonly PipeScheduler[] _schedulers; private readonly ISocketsTrace _trace; private Socket _listenSocket; private int _schedulerIndex; private readonly SocketTransportOptions _options; public EndPoint EndPoint { get; private set; } internal SocketConnectionListener( EndPoint endpoint, SocketTransportOptions options, ISocketsTrace trace) { EndPoint = endpoint; _trace = trace; _options = options; _memoryPool = _options.MemoryPoolFactory(); var ioQueueCount = options.IOQueueCount; if (ioQueueCount > 0) { _numSchedulers = ioQueueCount; _schedulers = new IOQueue[_numSchedulers]; for (var i = 0; i < _numSchedulers; i++) { _schedulers[i] = new IOQueue(); } } else { var directScheduler = new PipeScheduler[] { PipeScheduler.ThreadPool }; _numSchedulers = directScheduler.Length; _schedulers = directScheduler; } } internal void Bind() { if (_listenSocket != null) { throw new InvalidOperationException(SocketsStrings.TransportAlreadyBound); } Socket listenSocket; // Unix domain sockets are unspecified var protocolType = EndPoint is UnixDomainSocketEndPoint ? ProtocolType.Unspecified : ProtocolType.Tcp; listenSocket = new Socket(EndPoint.AddressFamily, SocketType.Stream, protocolType); // Kestrel expects IPv6Any to bind to both IPv6 and IPv4 if (EndPoint is IPEndPoint ip && ip.Address == IPAddress.IPv6Any) { listenSocket.DualMode = true; } try { listenSocket.Bind(EndPoint); } catch (SocketException e) when (e.SocketErrorCode == SocketError.AddressAlreadyInUse) { throw new AddressInUseException(e.Message, e); } EndPoint = listenSocket.LocalEndPoint; listenSocket.Listen(512); _listenSocket = listenSocket; } public async ValueTask<ConnectionContext> AcceptAsync(CancellationToken cancellationToken = default) { while (true) { try { var acceptSocket = await _listenSocket.AcceptAsync(); // Only apply no delay to Tcp based endpoints if (acceptSocket.LocalEndPoint is IPEndPoint) { acceptSocket.NoDelay = _options.NoDelay; } var connection = new SocketConnection(acceptSocket, _memoryPool, _schedulers[_schedulerIndex], _trace, _options.MaxReadBufferSize, _options.MaxWriteBufferSize); connection.Start(); _schedulerIndex = (_schedulerIndex + 1) % _numSchedulers; return connection; } catch (ObjectDisposedException) { // A call was made to UnbindAsync/DisposeAsync just return null which signals we're done return null; } catch (SocketException e) when (e.SocketErrorCode == SocketError.OperationAborted) { // A call was made to UnbindAsync/DisposeAsync just return null which signals we're done return null; } catch (SocketException) { // The connection got reset while it was in the backlog, so we try again. _trace.ConnectionReset(connectionId: "(null)"); } } } public ValueTask UnbindAsync(CancellationToken cancellationToken = default) { _listenSocket?.Dispose(); return default; } public ValueTask DisposeAsync() { _listenSocket?.Dispose(); // Dispose the memory pool _memoryPool.Dispose(); return default; } }
SocketTransportFactory创建SocketConnectionListener,监听对应的端口
并返回SocketConnection
/// <summary> /// Encapsulates all information about an individual connection. /// </summary> public abstract class ConnectionContext : IAsyncDisposable { /// <summary> /// Gets or sets a unique identifier to represent this connection in trace logs. /// </summary> public abstract string ConnectionId { get; set; } /// <summary> /// Gets the collection of features provided by the server and middleware available on this connection. /// </summary> public abstract IFeatureCollection Features { get; } /// <summary> /// Gets or sets a key/value collection that can be used to share data within the scope of this connection. /// </summary> public abstract IDictionary<object, object> Items { get; set; } /// <summary> /// Gets or sets the <see cref="IDuplexPipe"/> that can be used to read or write data on this connection. /// </summary> public abstract IDuplexPipe Transport { get; set; } /// <summary> /// Triggered when the client connection is closed. /// </summary> public virtual CancellationToken ConnectionClosed { get; set; } /// <summary> /// Gets or sets the local endpoint for this connection. /// </summary> public virtual EndPoint LocalEndPoint { get; set; } /// <summary> /// Gets or sets the remote endpoint for this connection. /// </summary> public virtual EndPoint RemoteEndPoint { get; set; } /// <summary> /// Aborts the underlying connection. /// </summary> /// <param name="abortReason">An optional <see cref="ConnectionAbortedException"/> describing the reason the connection is being terminated.</param> public virtual void Abort(ConnectionAbortedException abortReason) { // We expect this to be overridden, but this helps maintain back compat // with implementations of ConnectionContext that predate the addition of // ConnectionContext.Abort() Features.Get<IConnectionLifetimeFeature>()?.Abort(); } /// <summary> /// Aborts the underlying connection. /// </summary> public virtual void Abort() => Abort(new ConnectionAbortedException("The connection was aborted by the application via ConnectionContext.Abort().")); /// <summary> /// Releases resources for the underlying connection. /// </summary> /// <returns>A <see cref="ValueTask"/> that completes when resources have been released.</returns> public virtual ValueTask DisposeAsync() { return default; } }
internal abstract partial class TransportConnection : ConnectionContext { private IDictionary<object, object> _items; private string _connectionId; public TransportConnection() { FastReset(); } public override EndPoint LocalEndPoint { get; set; } public override EndPoint RemoteEndPoint { get; set; } public override string ConnectionId { get { if (_connectionId == null) { _connectionId = CorrelationIdGenerator.GetNextId(); } return _connectionId; } set { _connectionId = value; } } public override IFeatureCollection Features => this; public virtual MemoryPool<byte> MemoryPool { get; } public override IDuplexPipe Transport { get; set; } public IDuplexPipe Application { get; set; } public override IDictionary<object, object> Items { get { // Lazily allocate connection metadata return _items ?? (_items = new ConnectionItems()); } set { _items = value; } } public override CancellationToken ConnectionClosed { get; set; } // DO NOT remove this override to ConnectionContext.Abort. Doing so would cause // any TransportConnection that does not override Abort or calls base.Abort // to stack overflow when IConnectionLifetimeFeature.Abort() is called. // That said, all derived types should override this method should override // this implementation of Abort because canceling pending output reads is not // sufficient to abort the connection if there is backpressure. public override void Abort(ConnectionAbortedException abortReason) { Application.Input.CancelPendingRead(); } }
internal partial class TransportConnection : IConnectionIdFeature, IConnectionTransportFeature, IConnectionItemsFeature, IMemoryPoolFeature, IConnectionLifetimeFeature { // NOTE: When feature interfaces are added to or removed from this TransportConnection class implementation, // then the list of `features` in the generated code project MUST also be updated. // See also: tools/CodeGenerator/TransportConnectionFeatureCollection.cs MemoryPool<byte> IMemoryPoolFeature.MemoryPool => MemoryPool; IDuplexPipe IConnectionTransportFeature.Transport { get => Transport; set => Transport = value; } IDictionary<object, object> IConnectionItemsFeature.Items { get => Items; set => Items = value; } CancellationToken IConnectionLifetimeFeature.ConnectionClosed { get => ConnectionClosed; set => ConnectionClosed = value; } void IConnectionLifetimeFeature.Abort() => Abort(new ConnectionAbortedException("The connection was aborted by the application via IConnectionLifetimeFeature.Abort().")); }
internal partial class TransportConnection : IFeatureCollection { private static readonly Type IConnectionIdFeatureType = typeof(IConnectionIdFeature); private static readonly Type IConnectionTransportFeatureType = typeof(IConnectionTransportFeature); private static readonly Type IConnectionItemsFeatureType = typeof(IConnectionItemsFeature); private static readonly Type IMemoryPoolFeatureType = typeof(IMemoryPoolFeature); private static readonly Type IConnectionLifetimeFeatureType = typeof(IConnectionLifetimeFeature); private object _currentIConnectionIdFeature; private object _currentIConnectionTransportFeature; private object _currentIConnectionItemsFeature; private object _currentIMemoryPoolFeature; private object _currentIConnectionLifetimeFeature; private int _featureRevision; private List<KeyValuePair<Type, object>> MaybeExtra; private void FastReset() { _currentIConnectionIdFeature = this; _currentIConnectionTransportFeature = this; _currentIConnectionItemsFeature = this; _currentIMemoryPoolFeature = this; _currentIConnectionLifetimeFeature = this; } // Internal for testing internal void ResetFeatureCollection() { FastReset(); MaybeExtra?.Clear(); _featureRevision++; } private object ExtraFeatureGet(Type key) { if (MaybeExtra == null) { return null; } for (var i = 0; i < MaybeExtra.Count; i++) { var kv = MaybeExtra[i]; if (kv.Key == key) { return kv.Value; } } return null; } private void ExtraFeatureSet(Type key, object value) { if (MaybeExtra == null) { MaybeExtra = new List<KeyValuePair<Type, object>>(2); } for (var i = 0; i < MaybeExtra.Count; i++) { if (MaybeExtra[i].Key == key) { MaybeExtra[i] = new KeyValuePair<Type, object>(key, value); return; } } MaybeExtra.Add(new KeyValuePair<Type, object>(key, value)); } bool IFeatureCollection.IsReadOnly => false; int IFeatureCollection.Revision => _featureRevision; object IFeatureCollection.this[Type key] { get { object feature = null; if (key == IConnectionIdFeatureType) { feature = _currentIConnectionIdFeature; } else if (key == IConnectionTransportFeatureType) { feature = _currentIConnectionTransportFeature; } else if (key == IConnectionItemsFeatureType) { feature = _currentIConnectionItemsFeature; } else if (key == IMemoryPoolFeatureType) { feature = _currentIMemoryPoolFeature; } else if (key == IConnectionLifetimeFeatureType) { feature = _currentIConnectionLifetimeFeature; } else if (MaybeExtra != null) { feature = ExtraFeatureGet(key); } return feature; } set { _featureRevision++; if (key == IConnectionIdFeatureType) { _currentIConnectionIdFeature = value; } else if (key == IConnectionTransportFeatureType) { _currentIConnectionTransportFeature = value; } else if (key == IConnectionItemsFeatureType) { _currentIConnectionItemsFeature = value; } else if (key == IMemoryPoolFeatureType) { _currentIMemoryPoolFeature = value; } else if (key == IConnectionLifetimeFeatureType) { _currentIConnectionLifetimeFeature = value; } else { ExtraFeatureSet(key, value); } } } TFeature IFeatureCollection.Get<TFeature>() { TFeature feature = default; if (typeof(TFeature) == typeof(IConnectionIdFeature)) { feature = (TFeature)_currentIConnectionIdFeature; } else if (typeof(TFeature) == typeof(IConnectionTransportFeature)) { feature = (TFeature)_currentIConnectionTransportFeature; } else if (typeof(TFeature) == typeof(IConnectionItemsFeature)) { feature = (TFeature)_currentIConnectionItemsFeature; } else if (typeof(TFeature) == typeof(IMemoryPoolFeature)) { feature = (TFeature)_currentIMemoryPoolFeature; } else if (typeof(TFeature) == typeof(IConnectionLifetimeFeature)) { feature = (TFeature)_currentIConnectionLifetimeFeature; } else if (MaybeExtra != null) { feature = (TFeature)(ExtraFeatureGet(typeof(TFeature))); } return feature; } void IFeatureCollection.Set<TFeature>(TFeature feature) { _featureRevision++; if (typeof(TFeature) == typeof(IConnectionIdFeature)) { _currentIConnectionIdFeature = feature; } else if (typeof(TFeature) == typeof(IConnectionTransportFeature)) { _currentIConnectionTransportFeature = feature; } else if (typeof(TFeature) == typeof(IConnectionItemsFeature)) { _currentIConnectionItemsFeature = feature; } else if (typeof(TFeature) == typeof(IMemoryPoolFeature)) { _currentIMemoryPoolFeature = feature; } else if (typeof(TFeature) == typeof(IConnectionLifetimeFeature)) { _currentIConnectionLifetimeFeature = feature; } else { ExtraFeatureSet(typeof(TFeature), feature); } } private IEnumerable<KeyValuePair<Type, object>> FastEnumerable() { if (_currentIConnectionIdFeature != null) { yield return new KeyValuePair<Type, object>(IConnectionIdFeatureType, _currentIConnectionIdFeature); } if (_currentIConnectionTransportFeature != null) { yield return new KeyValuePair<Type, object>(IConnectionTransportFeatureType, _currentIConnectionTransportFeature); } if (_currentIConnectionItemsFeature != null) { yield return new KeyValuePair<Type, object>(IConnectionItemsFeatureType, _currentIConnectionItemsFeature); } if (_currentIMemoryPoolFeature != null) { yield return new KeyValuePair<Type, object>(IMemoryPoolFeatureType, _currentIMemoryPoolFeature); } if (_currentIConnectionLifetimeFeature != null) { yield return new KeyValuePair<Type, object>(IConnectionLifetimeFeatureType, _currentIConnectionLifetimeFeature); } if (MaybeExtra != null) { foreach (var item in MaybeExtra) { yield return item; } } } IEnumerator<KeyValuePair<Type, object>> IEnumerable<KeyValuePair<Type, object>>.GetEnumerator() => FastEnumerable().GetEnumerator(); IEnumerator IEnumerable.GetEnumerator() => FastEnumerable().GetEnumerator(); }
internal sealed class SocketConnection : TransportConnection { private static readonly int MinAllocBufferSize = SlabMemoryPool.BlockSize / 2; private static readonly bool IsWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows); private static readonly bool IsMacOS = RuntimeInformation.IsOSPlatform(OSPlatform.OSX); private readonly Socket _socket; private readonly ISocketsTrace _trace; private readonly SocketReceiver _receiver; private readonly SocketSender _sender; private readonly CancellationTokenSource _connectionClosedTokenSource = new CancellationTokenSource(); private readonly object _shutdownLock = new object(); private volatile bool _socketDisposed; private volatile Exception _shutdownReason; private Task _processingTask; private readonly TaskCompletionSource<object> _waitForConnectionClosedTcs = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously); private bool _connectionClosed; internal SocketConnection(Socket socket, MemoryPool<byte> memoryPool, PipeScheduler scheduler, ISocketsTrace trace, long? maxReadBufferSize = null, long? maxWriteBufferSize = null) { Debug.Assert(socket != null); Debug.Assert(memoryPool != null); Debug.Assert(trace != null); _socket = socket; MemoryPool = memoryPool; _trace = trace; LocalEndPoint = _socket.LocalEndPoint; RemoteEndPoint = _socket.RemoteEndPoint; ConnectionClosed = _connectionClosedTokenSource.Token; // On *nix platforms, Sockets already dispatches to the ThreadPool. // Yes, the IOQueues are still used for the PipeSchedulers. This is intentional. // https://github.com/aspnet/KestrelHttpServer/issues/2573 var awaiterScheduler = IsWindows ? scheduler : PipeScheduler.Inline; _receiver = new SocketReceiver(_socket, awaiterScheduler); _sender = new SocketSender(_socket, awaiterScheduler); maxReadBufferSize ??= 0; maxWriteBufferSize ??= 0; var inputOptions = new PipeOptions(MemoryPool, PipeScheduler.ThreadPool, scheduler, maxReadBufferSize.Value, maxReadBufferSize.Value / 2, useSynchronizationContext: false); var outputOptions = new PipeOptions(MemoryPool, scheduler, PipeScheduler.ThreadPool, maxWriteBufferSize.Value, maxWriteBufferSize.Value / 2, useSynchronizationContext: false); var pair = DuplexPipe.CreateConnectionPair(inputOptions, outputOptions); // Set the transport and connection id Transport = pair.Transport; Application = pair.Application; } public PipeWriter Input => Application.Output; public PipeReader Output => Application.Input; public override MemoryPool<byte> MemoryPool { get; } public void Start() { _processingTask = StartAsync(); } private async Task StartAsync() { try { // Spawn send and receive logic var receiveTask = DoReceive(); var sendTask = DoSend(); // Now wait for both to complete await receiveTask; await sendTask; _receiver.Dispose(); _sender.Dispose(); } catch (Exception ex) { _trace.LogError(0, ex, $"Unexpected exception in {nameof(SocketConnection)}.{nameof(StartAsync)}."); } } public override void Abort(ConnectionAbortedException abortReason) { // Try to gracefully close the socket to match libuv behavior. Shutdown(abortReason); // Cancel ProcessSends loop after calling shutdown to ensure the correct _shutdownReason gets set. Output.CancelPendingRead(); } // Only called after connection middleware is complete which means the ConnectionClosed token has fired. public override async ValueTask DisposeAsync() { Transport.Input.Complete(); Transport.Output.Complete(); if (_processingTask != null) { await _processingTask; } _connectionClosedTokenSource.Dispose(); } private async Task DoReceive() { Exception error = null; try { await ProcessReceives(); } catch (SocketException ex) when (IsConnectionResetError(ex.SocketErrorCode)) { // This could be ignored if _shutdownReason is already set. error = new ConnectionResetException(ex.Message, ex); // There's still a small chance that both DoReceive() and DoSend() can log the same connection reset. // Both logs will have the same ConnectionId. I don't think it's worthwhile to lock just to avoid this. if (!_socketDisposed) { _trace.ConnectionReset(ConnectionId); } } catch (Exception ex) when ((ex is SocketException socketEx && IsConnectionAbortError(socketEx.SocketErrorCode)) || ex is ObjectDisposedException) { // This exception should always be ignored because _shutdownReason should be set. error = ex; if (!_socketDisposed) { // This is unexpected if the socket hasn't been disposed yet. _trace.ConnectionError(ConnectionId, error); } } catch (Exception ex) { // This is unexpected. error = ex; _trace.ConnectionError(ConnectionId, error); } finally { // If Shutdown() has already bee called, assume that was the reason ProcessReceives() exited. Input.Complete(_shutdownReason ?? error); FireConnectionClosed(); await _waitForConnectionClosedTcs.Task; } } private async Task ProcessReceives() { // Resolve `input` PipeWriter via the IDuplexPipe interface prior to loop start for performance. var input = Input; while (true) { // Wait for data before allocating a buffer. await _receiver.WaitForDataAsync(); // Ensure we have some reasonable amount of buffer space var buffer = input.GetMemory(MinAllocBufferSize); var bytesReceived = await _receiver.ReceiveAsync(buffer); if (bytesReceived == 0) { // FIN _trace.ConnectionReadFin(ConnectionId); break; } input.Advance(bytesReceived); var flushTask = input.FlushAsync(); var paused = !flushTask.IsCompleted; if (paused) { _trace.ConnectionPause(ConnectionId); } var result = await flushTask; if (paused) { _trace.ConnectionResume(ConnectionId); } if (result.IsCompleted || result.IsCanceled) { // Pipe consumer is shut down, do we stop writing break; } } } private async Task DoSend() { Exception shutdownReason = null; Exception unexpectedError = null; try { await ProcessSends(); } catch (SocketException ex) when (IsConnectionResetError(ex.SocketErrorCode)) { shutdownReason = new ConnectionResetException(ex.Message, ex); _trace.ConnectionReset(ConnectionId); } catch (Exception ex) when ((ex is SocketException socketEx && IsConnectionAbortError(socketEx.SocketErrorCode)) || ex is ObjectDisposedException) { // This should always be ignored since Shutdown() must have already been called by Abort(). shutdownReason = ex; } catch (Exception ex) { shutdownReason = ex; unexpectedError = ex; _trace.ConnectionError(ConnectionId, unexpectedError); } finally { Shutdown(shutdownReason); // Complete the output after disposing the socket Output.Complete(unexpectedError); // Cancel any pending flushes so that the input loop is un-paused Input.CancelPendingFlush(); } } private async Task ProcessSends() { // Resolve `output` PipeReader via the IDuplexPipe interface prior to loop start for performance. var output = Output; while (true) { var result = await output.ReadAsync(); if (result.IsCanceled) { break; } var buffer = result.Buffer; var end = buffer.End; var isCompleted = result.IsCompleted; if (!buffer.IsEmpty) { await _sender.SendAsync(buffer); } output.AdvanceTo(end); if (isCompleted) { break; } } } private void FireConnectionClosed() { // Guard against scheduling this multiple times if (_connectionClosed) { return; } _connectionClosed = true; ThreadPool.UnsafeQueueUserWorkItem(state => { state.CancelConnectionClosedToken(); state._waitForConnectionClosedTcs.TrySetResult(null); }, this, preferLocal: false); } private void Shutdown(Exception shutdownReason) { lock (_shutdownLock) { if (_socketDisposed) { return; } // Make sure to close the connection only after the _aborted flag is set. // Without this, the RequestsCanBeAbortedMidRead test will sometimes fail when // a BadHttpRequestException is thrown instead of a TaskCanceledException. _socketDisposed = true; // shutdownReason should only be null if the output was completed gracefully, so no one should ever // ever observe the nondescript ConnectionAbortedException except for connection middleware attempting // to half close the connection which is currently unsupported. _shutdownReason = shutdownReason ?? new ConnectionAbortedException("The Socket transport's send loop completed gracefully."); _trace.ConnectionWriteFin(ConnectionId, _shutdownReason.Message); try { // Try to gracefully close the socket even for aborts to match libuv behavior. _socket.Shutdown(SocketShutdown.Both); } catch { // Ignore any errors from Socket.Shutdown() since we're tearing down the connection anyway. } _socket.Dispose(); } } private void CancelConnectionClosedToken() { try { _connectionClosedTokenSource.Cancel(); } catch (Exception ex) { _trace.LogError(0, ex, $"Unexpected exception in {nameof(SocketConnection)}.{nameof(CancelConnectionClosedToken)}."); } } private static bool IsConnectionResetError(SocketError errorCode) { // A connection reset can be reported as SocketError.ConnectionAborted on Windows. // ProtocolType can be removed once https://github.com/dotnet/corefx/issues/31927 is fixed. return errorCode == SocketError.ConnectionReset || errorCode == SocketError.Shutdown || (errorCode == SocketError.ConnectionAborted && IsWindows) || (errorCode == SocketError.ProtocolType && IsMacOS); } private static bool IsConnectionAbortError(SocketError errorCode) { // Calling Dispose after ReceiveAsync can cause an "InvalidArgument" error on *nix. return errorCode == SocketError.OperationAborted || errorCode == SocketError.Interrupted || (errorCode == SocketError.InvalidArgument && !IsWindows); } }
再来看下HttpConnection如何处理请求
如果是Http1.0协议,则调用Http1Connection的ProcessRequestsAsync方法处理请求
Http1Connection继承HttpProtocol并实现了以上的所有接口
当调用IHttpApplication的CreateContext方法时,需要的IFeatureCollection参数其实就是Http1Connection对象
通过Http1Connection对象来创建HttpContext
然后就进入到了asp.net的处理管道中