HTTP.SYS desconecta por la fuerza los enlaces HTTP para los servicios auto hospedados de WCF

Este artículo le ayuda a resolver el problema que hace que Windows Server 2012 servidor basado en R2 coloque una conexión TCP subyacente.

Versión del producto original:   Windows Communication Foundation
Número KB original:   3137046

Síntomas

En un servicio Windows Communication Foundation (WCF) auto hospedado en un servidor que ejecuta Windows Server 2012 R2 y que usa enlaces basados en HTTP (por ejemplo), el servidor quita intermitentemente la conexión basicHttpBinding TCP subyacente. Cuando se produce este problema, puede verlo en los registrosHTTP.SYS que se encuentran normalmente en la C:\WINDOWS\System32\LogFiles\HTTPERR carpeta. Los archivos de registro deben tener una entrada que cite Timer_MinBytesPerSecond como motivo. Este problema no se produce en Windows Server 2008 R2.

La entrada de registro es similar a la siguiente:

#Fields: date time c-ip c-port s-ip s-port cs-version cs-method cs-uri sc-status s-siteid s-reason s-queuename
date time 10.145.136.58 41079 10.171.70.136 8888 HTTP/1.1 POST /MySelfHostedService/TestService1 - - Timer_MinBytesPerSecond -
date time 10.145.136.58 41106 10.171.70.136 8888 HTTP/1.1 POST /MySelfHostedService/TestService1 - - Timer_MinBytesPerSecond -
date time 10.145.136.58 40995 10.171.70.136 8888 HTTP/1.1 POST /MySelfHostedService/TestService1 - - Timer_MinBytesPerSecond -
date time 10.145.136.58 41022 10.171.70.136 8888 HTTP/1.1 POST /MySelfHostedService/TestService1 - - Timer_MinBytesPerSecond -

En los seguimientos wcf, el servicio produce un error durante una operación de bytes de recepción con System.Net.HttpListenerException , como en el ejemplo siguiente:

<Exception>
   <ExceptionType>System.ServiceModel.CommunicationException, System.ServiceModel, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</ExceptionType>
   <Message>The I/O operation has been aborted because of either a thread exit or an application request</Message>
   <StackTrace>
      at System.ServiceModel.Channels.HttpRequestContext.ListenerHttpContext.ListenerContextHttpInput.ListenerContextInputStream.EndRead(IAsyncResult result)
      at System.ServiceModel.Channels.HttpInput.ParseMessageAsyncResult.OnRead(IAsyncResult result)
      at System.Runtime.Fx.AsyncThunk.UnhandledExceptionFrame(IAsyncResult result)
      at System.Net.LazyAsyncResult.Complete(IntPtr userToken)
      at System.Net.HttpRequestStream.HttpRequestStreamAsyncResult.IOCompleted(HttpRequestStreamAsyncResult asyncResult, UInt32 errorCode, UInt32 numBytes)
      at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
      at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
      at System.Threading._IOCompletionCallback.PerformIOCompletionCallback(UInt32 errorCode, UInt32 numBytes, NativeOverlapped* pOVERLAP)
   </StackTrace>
   <ExceptionString>System.ServiceModel.CommunicationException: The I/O operation has been aborted because of either a thread exit or an application request ---&gt; System.Net.HttpListenerException: The I/O operation has been aborted because of either a thread exit or an application request
      at System.Net.HttpRequestStream.EndRead(IAsyncResult asyncResult)
      at System.ServiceModel.Channels.DetectEofStream.EndRead(IAsyncResult result)
      at System.ServiceModel.Channels.HttpRequestContext.ListenerHttpContext.ListenerContextHttpInput.ListenerContextInputStream.EndRead(IAsyncResult result)
      --- End of inner exception stack trace ---
   </ExceptionString>
   <InnerException>
      <ExceptionType>System.Net.HttpListenerException, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</ExceptionType>
      <Message>The I/O operation has been aborted because of either a thread exit or an application request</Message>
      <StackTrace>
         at System.Net.HttpRequestStream.EndRead(IAsyncResult asyncResult)
         at System.ServiceModel.Channels.DetectEofStream.EndRead(IAsyncResult result)
         at System.ServiceModel.Channels.HttpRequestContext.ListenerHttpContext.ListenerContextHttpInput.ListenerContextInputStream.EndRead(IAsyncResult result)
      </StackTrace>
      <ExceptionString>System.Net.HttpListenerException (0x80004005): The I/O operation has been aborted because of either a thread exit or an application request
         at System.Net.HttpRequestStream.EndRead(IAsyncResult asyncResult)
         at System.ServiceModel.Channels.DetectEofStream.EndRead(IAsyncResult result)
         at System.ServiceModel.Channels.HttpRequestContext.ListenerHttpContext.ListenerContextHttpInput.ListenerContextInputStream.EndRead(IAsyncResult result)
      </ExceptionString>
      <NativeErrorCode>3E3</NativeErrorCode>
   </InnerException>
</Exception>

Causa

A partir Windows Server 2012 R2, el controlador del kernel que controla las solicitudes HTTP (http.sys) se cambió en términos de cómo controla la Timer_MinBytesPerSecond propiedad. De forma predeterminada, Http.sys considera cualquier velocidad inferior a 150 bytes por segundo como un posible ataque de conexión de baja velocidad y descarta la conexión TCP para liberar el recurso. Este problema no se produce en Windows Server 2008 R2 porque el umbral de una conexión lenta en Windows Server 2012 R2 es mucho más restrictivo.

Solución alternativa

Para evitar esta característica, establezca el valor en 0xFFFFFFFF (valor entero máximo de 32 bits sin signo), que es minSendBytesPerSecond 4.294.967.295 en decimal. Este valor específico deshabilita la característica de conexión de velocidad inferior.

Use uno de los siguientes métodos para establecer el minSendBytesPerSecond valor en 0xFFFFFFFF .

  • Método 1: Usar un archivo de configuración

    <system.net>
       <settings>
          <httpListener>
             <timeouts minSendBytesPerSecond="4294967295" />
          </httpListener>
       </settings>
    </system.net>
    
  • Método 2: Establecer mediante programación

    Cambie la propiedad explícitamente en el código, como en el ejemplo siguiente:

    System.Net.HttpListenerTimeoutManager.MinSendBytesPerSecond = 4294967295
    

La opción de programación se puede convertir en una opción personalizada si un cambio serviceBehavior de código en el servicio no es una opción. Es decir, un comportamiento se puede integrar con un servicio existente quitando un DLL y cambiando la configuración, como en el ejemplo siguiente:

  1. Abra la solución en Visual Studio y agregue un nuevo proyecto de biblioteca de clases. Así mismo, asigne BehaviorProject el nombre .

  2. Cree una clase denominada HttpListenerBehavior .

  3. Actualíctelo con el siguiente código fuente:

    namespace BehaviorProject
    {
        public class HttpListenerBehavior : BehaviorExtensionElement, IServiceBehavior
        {
            public override Type BehaviorType
            {
                get { return this.GetType(); }
            }
            protected override object CreateBehavior()
            {
                return new HttpListenerBehavior();
            }
            public void AddBindingParameters(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase, System.Collections.ObjectModel.Collection<ServiceEndpoint> endpoints, BindingParameterCollection bindingParameters)
            {
                return;
            }
            public void ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
            {
                UpdateSystemNetConfiguration();
            }
            private void UpdateSystemNetConfiguration()
            {
                ConfigurationProperty minSendBytesPerSecond;
                minSendBytesPerSecond = new ConfigurationProperty("minSendBytesPerSecond",
                typeof(long), (long)uint.MaxValue, null, null, ConfigurationPropertyOptions.None);
                ConfigurationPropertyCollection properties;
                HttpListenerTimeoutsElement timeOuts = new HttpListenerTimeoutsElement();
                properties = GetMember(timeOuts, "properties") as ConfigurationPropertyCollection;
                if (properties != null)
                {
                    properties.Remove("minSendBytesPerSecond");
                    SetMember(timeOuts, "minSendBytesPerSecond", minSendBytesPerSecond);
                    properties.Add(minSendBytesPerSecond);
                }
            }
            public void Validate(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
            {
                return;
            }
            public static object GetMember(object Source, string Field)
            {
                string[] fields = Field.Split('.');
                object curr = Source;
                BindingFlags bindingFlags = BindingFlags.Static | BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public;
    
                bool succeeded = false;
    
                foreach (string field in fields)
                {
                    Type t = curr.GetType();
                    succeeded = false;
                    FieldInfo fInfo = t.GetField(field, bindingFlags);
                    if (fInfo != null)
                    {
                        curr = fInfo.GetValue(curr);
                        succeeded = true;
                        continue;
                    }
                    PropertyInfo pInfo = t.GetProperty(field, bindingFlags);
                    if (pInfo != null)
                    {
                        curr = pInfo.GetValue(curr, null);
                        succeeded = true;
                        continue;
                    }
                    throw new System.IndexOutOfRangeException();
                }
                if (succeeded) return curr;
                throw new System.ArgumentNullException();
            }
    
            public static void SetMember(object Source, string Field, object Value)
            {
                string[] fields = Field.Split('.');
                object curr = Source;
                BindingFlags bindingFlags = BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public;
    
                bool succeeded = false;
                int i = 0;
                foreach (string field in fields)
                {
                    i++;
                    Type t = curr.GetType();
                    succeeded = false;
                    FieldInfo fInfo = t.GetField(field, bindingFlags);
                    if (fInfo != null)
                    {
                        if (i == fields.Length)
                        fInfo.SetValue(curr, Value);
                        curr = fInfo.GetValue(curr);
                        succeeded = true;
                        continue;
                    }
                    PropertyInfo pInfo = t.GetProperty(field, bindingFlags);
                    if (pInfo != null)
                    {
                        if (i == fields.Length)
                        fInfo.SetValue(curr, Value);
                        curr = pInfo.GetValue(curr, null);
                        succeeded = true;
                        continue;
                    }
                    throw new System.IndexOutOfRangeException();
                }
                if (succeeded) return;
                throw new System.ArgumentNullException();
            }
        }
    }
    
  4. Cree la aplicación de biblioteca.

  5. Copie la DLL generada en la carpeta de la aplicación.

  6. Abra el archivo de configuración de la aplicación, busque la <system.serviceModel> etiqueta y agregue el siguiente comportamiento personalizado:

    <extensions>
       <behaviorExtensions>
          <add name="httpListenerBehavior" type="BehaviorProject.HttpListenerBehavior, BehaviorProject, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"/>
       </behaviorExtensions>
    </extensions>
    <behaviors>
       <serviceBehaviors>
          <!-- if the serviceBehavior used by the service is named, add to the appropriate named behavior --> 
          <behavior name="customBehavior">
             <!-- The behavior is referenced by the following in line. Visual Studio will mark this line with a red underline because it is not in the config schema. It can be ignored. Notice that the other behaviors (like serviceMetadata) does not need to be added if they are not currently present --> 
             <httpListenerBehavior />
             <serviceMetadata httpGetEnabled="true"/>
             <serviceDebug includeExceptionDetailInFaults="true"/>
          </behavior>
       </serviceBehaviors>
    </behaviors>
    

Más información

Para determinar si los cambios fueron efectivos, use uno de los métodos siguientes.

  • Método 1

    1. Capture un archivo de volcado de memoria después de abrir el serviceHost.

    2. Vuelque el objeto de System.Net.HttpListenerTimeoutManager tipo y lea la minSendBytesPerSecond propiedad.

      0:000> !DumpObj /d 02694a64
      Name: System.Net.HttpListenerTimeoutManager
      MethodTable: 7308b070
      EEClass: 72ec5238
      Size: 20(0x14) bytes
      File: C:\Windows\Microsoft.Net\assembly\GAC_MSIL\System\v4.0_4.0.0.0__b77a5c561934e089\System.dll
      Fields:
      MT Field Offset Type VT Attr Value Name
      73092254 4001605 4 ....Net.HttpListener 0 instance 026932f0 listener
      73c755d4 4001606 8 System.Int32[] 0 instance 02694a78 timeouts
      73c7ef20 4001607 c System.UInt32 1 instance 4294967295 minSendBytesPerSecond <-----------------
      

      Nota

      El valor de minSendBytesPerSecond es 4294967295.

  • Método 2

    1. En modo administrador, abra cmd.exe y ejecute el siguiente comando desde un símbolo del sistema (después de abrir serviceHost):

      netsh http show servicestate view="session" >%temp%\netshOutput.txt

    2. Ejecute el siguiente comando para abrir el netshOutput.txt archivo. Se abrirá en Bloc de notas.

      start %temp%\netshOutput.txt

    3. Busque el número de puerto de la aplicación de servicio (como 8888) y, a continuación, vea la sesión. Este es el paso para comprobar que la velocidad de envío mínima (bytes/s) se invalida con el valor 4294967295 segundo.

    4. Debería ver una entrada similar a la siguiente:

      Server session ID: FE00000320000021
      Version: 2.0
      State: Active
      Properties:
      Max bandwidth: 4294967295
      Timeouts:
      Entity body timeout (secs): 120
      Drain entity body timeout (secs): 120
      Request queue timeout (secs): 120
      Idle connection timeout (secs): 120
      Header wait timeout (secs): 120
      Minimum send rate (bytes/sec): 150
      URL groups:
      URL group ID: FD00000340000001
      State: Active
      Request queue name: Request queue is unnamed.
      Properties:
      Max bandwidth: inherited
      Max connections: inherited
      Timeouts:
      Entity body timeout (secs): 0
      Drain entity body timeout (secs): 0
      Request queue timeout (secs): 0
      Idle connection timeout (secs): 0
      Header wait timeout (secs): 0
      Minimum send rate (bytes/sec): 4294967295 <-------------------
      Number of registered URLs: 1
      Registered URLs:
      HTTP://+:8888/TESTSERVICE1/
      

Para obtener más información, vea la propiedad HttpListenerTimeoutManager.MinSendBytesPerSecond.