HTTP.SYS WCF セルフホステッド サービスの HTTP バインドを強制的に切断する

この記事は、Windows Server 2012 R2 ベースのサーバーが基になる TCP 接続を削除する原因となる問題を解決するのに役立ちます。

元の製品バージョン: Windows Communication Foundation
元の KB 番号: 3137046

現象

R2 Windows Server 2012実行され、HTTP ベースのバインド (などbasicHttpBinding) を使用するサーバー上のセルフホステッド Windows Communication Foundation (WCF) サービスでは、サーバーは基になる TCP 接続を断続的に切断します。 この問題が発生すると、通常はフォルダーにある HTTP.SYS ログに C:\WINDOWS\System32\LogFiles\HTTPERR 表示されます。 ログ ファイルには、理由としてを引用 Timer_MinBytesPerSecond するエントリが必要です。 この問題は、Windows Server 2008 R2 では発生しません。

ログ エントリは次のようになります。

#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 -

WCF トレースでは、次の例のように、 を使用 System.Net.HttpListenerExceptionしたバイト受信操作中にサービスが失敗します。

<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>

原因

Windows Server 2012 R2 以降では、HTTP 要求 (http.sys) を処理するカーネル ドライバーが、プロパティの処理方法Timer_MinBytesPerSecondに関して変更されました。 既定では、Http.sys は 1 秒あたり 150 バイト未満の速度レートを低速接続攻撃の可能性があると見なし、TCP 接続を切断してリソースを解放します。 この問題は、Windows Server 2008 R2 では発生しません。これは、Windows Server 2012 R2 での低速接続のしきい値の方がはるかに制限されているためです。

回避策

この機能を回避するには、 を 0xFFFFFFFF の値 (最大 32 ビット符号なし整数値) に設定minSendBytesPerSecondします。これは 10 進数で 4,294,967,295 です。 この特定の値により、低速速度の接続機能が無効になります。

次のいずれかの方法を使用して、 を minSendBytesPerSecond0xFFFFFFFF の値に設定します。

  • 方法 1: 構成ファイルを使用する

    <system.net>
       <settings>
          <httpListener>
             <timeouts minSendBytesPerSecond="4294967295" />
          </httpListener>
       </settings>
    </system.net>
    
  • 方法 2: プログラムで設定する

    次の例のように、コードでプロパティを明示的に変更します。

    System.Net.HttpListenerTimeoutManager.MinSendBytesPerSecond = 4294967295
    

サービスのコード変更がオプションでない場合は、プログラミング オプションをカスタム serviceBehavior にできます。 つまり、次の例のように、DLL を削除して構成を変更することで、動作を既存のサービスと統合できます。

  1. Visual Studio でソリューションを開き、新しいクラス ライブラリ プロジェクトを追加します。 BehaviorProject と名前を付けます。

  2. という名前 HttpListenerBehaviorのクラスを作成します。

  3. 次のソース コードで更新します。

    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. ライブラリ アプリケーションをビルドします。

  5. 生成された DLL をアプリケーション フォルダーにコピーします。

  6. アプリケーション構成ファイルを開き、タグを <system.serviceModel> 見つけて、次のカスタム動作を追加します。

    <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>
    

詳細

変更が有効かどうかを判断するには、次のいずれかの方法を使用します。

  • 方法 1

    1. serviceHost を開いた後、メモリ ダンプ ファイルをキャプチャします。

    2. System.Net.HttpListenerTimeoutManagerのオブジェクトをダンプし、 プロパティを minSendBytesPerSecond 読み取ります。

      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 <-----------------
      

      注:

      minSendBytesPerSecond 値は 4294967295です。

  • 方法 2

    1. 管理者モードで cmd.exe 開き、コマンド プロンプトから次のコマンドを実行します (serviceHost を開いた後)。

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

    2. 次のコマンドを実行して、 netshOutput.txt ファイルを開きます。 メモ帳で開きます。

      start %temp%\netshOutput.txt

    3. サービス アプリケーションのポート番号 (8888 など) を検索し、セッションを表示します。 これは、最小送信レート (バイト/秒) が 4294967295 値でオーバーライドされていることを確認する手順です。

    4. 次のようなエントリが表示されます。

      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/
      

詳細については、 HttpListenerTimeoutManager.MinSendBytesPerSecond プロパティを参照してください。