バックグラウンドでのネットワーク通信

フォアグラウンドではない間にネットワーク通信を続行するには、アプリでバックグラウンド タスクとこれら 2 つのオプションのいずれかを使用できます。

  • ソケットブローカー。 アプリが長期的な接続にソケットを使用している場合、フォアグラウンドから離れると、ソケットの所有権をシステム ソケット ブローカーに委任できます。 その後、ブローカーは、トラフィックがソケットに到着したときにアプリをアクティブにします。所有権をアプリに戻します。その後、アプリが到着トラフィックを処理します。
  • 制御チャネルのトリガー。

バックグラウンド タスクでのネットワーク操作の実行

  • SocketActivityTrigger を使用して、パケットの受信時にバックグラウンド タスクをアクティブ化し、有効期間の短いタスクを実行する必要があります。 タスクを実行した後、バックグラウンド タスクは電力を節約するために終了する必要があります。
  • ControlChannelTrigger を使用して、パケットの受信時にバックグラウンド タスクをアクティブ化し、有効期間の長いタスクを実行する必要があります。

ネットワーク関連の条件とフラグ

  • InternetAvailable 条件をバックグラウンド タスク BackgroundTaskBuilder.AddCondition に追加して、ネットワーク スタックが実行されるまでバックグラウンド タスクのトリガーを遅延させます。 バックグラウンド タスクはネットワークが稼働するまで実行されないため、この条件によって電力が節約されます。 この条件では、リアルタイムのアクティブ化は提供されません。

使用するトリガーに関係なく、バックグラウンド タスク IsNetworkRequested を設定して、バックグラウンド タスクの実行中にネットワークが稼働していることを確認します。 これにより、デバイスがコネクト スタンバイ モードになった場合でも、タスクの実行中にネットワークを稼働状態に保つようにバックグラウンド タスク インフラストラクチャに指示されます。 バックグラウンド タスクで IsNetworkRequested が使用されていない場合、バックグラウンド タスクはコネクト スタンバイ モード (電話の画面がオフになっている場合など) にネットワークにアクセスできなくなります。

ソケットブローカーと SocketActivityTrigger

アプリで DatagramSocketStreamSocket、または StreamSocketListener 接続を使用する場合は、 SocketActivityTrigger とソケット ブローカーを使用して、アプリがフォアグラウンドにいないときにトラフィックが到着したときに通知を受け取る必要があります。

アプリがアクティブでないときにソケットで受信したデータをアプリが受信して処理するには、アプリが起動時に 1 回限りのセットアップを実行し、アクティブでない状態に移行するときにソケットの所有権をソケット ブローカーに転送する必要があります。

1 回限りのセットアップ手順では、トリガーを作成し、トリガーのバックグラウンド タスクを登録し、ソケット ブローカーのソケットを有効にします。

  • SocketActivityTrigger を作成し、受信したパケットを処理するために TaskEntryPoint パラメーターをコードに設定して、トリガーのバックグラウンド タスクを登録します。
            var socketTaskBuilder = new BackgroundTaskBuilder();
            socketTaskBuilder.Name = _backgroundTaskName;
            socketTaskBuilder.TaskEntryPoint = _backgroundTaskEntryPoint;
            var trigger = new SocketActivityTrigger();
            socketTaskBuilder.SetTrigger(trigger);
            _task = socketTaskBuilder.Register();
  • ソケットをバインドする前に、ソケットで EnableTransferOwnership を呼び出します。
           _tcpListener = new StreamSocketListener();

           // Note that EnableTransferOwnership() should be called before bind,
           // so that tcpip keeps required state for the socket to enable connected
           // standby action. Background task Id is taken as a parameter to tie wake pattern
           // to a specific background task.  
           _tcpListener.EnableTransferOwnership(_task.TaskId, SocketActivityConnectedStandbyAction.Wake);
           _tcpListener.ConnectionReceived += OnConnectionReceived;
           await _tcpListener.BindServiceNameAsync("my-service-name");

ソケットが適切に設定されたら、アプリが中断しようとしているときに、ソケットで TransferOwnership を呼び出してソケット ブローカーに転送します。 ブローカーはソケットを監視し、データの受信時にバックグラウンド タスクをアクティブにします。 次の例には、StreamSocketListener ソケットの転送を実行するユーティリティ TransferOwnership 関数が含まれています。 (異なる種類のソケットにはそれぞれ独自の TransferOwnership メソッドがあるため、所有権を譲渡するソケットに適したメソッドを呼び出す必要があります。コードには、使用するソケットの種類ごとに 1 つの実装を持つオーバーロードされた TransferOwnership ヘルパーが含まれている可能性があるため、 OnSuspending コードは読みやすくなっています)。

アプリはソケットの所有権をソケット ブローカーに転送し、次のいずれかの適切な方法を使用してバックグラウンド タスクの ID を渡します。


// declare int _transferOwnershipCount as a field.

private async void TransferOwnership(StreamSocketListener tcpListener)
{
    await tcpListener.CancelIOAsync();

    var dataWriter = new DataWriter();
    ++_transferOwnershipCount;
    dataWriter.WriteInt32(_transferOwnershipCount);
    var context = new SocketActivityContext(dataWriter.DetachBuffer());
    tcpListener.TransferOwnership(_socketId, context);
}

private void OnSuspending(object sender, SuspendingEventArgs e)
{
    var deferral = e.SuspendingOperation.GetDeferral();

    TransferOwnership(_tcpListener);
    deferral.Complete();
}

バックグラウンド タスクのイベント ハンドラーで、次の手順を実行します。

  • まず、非同期メソッドを使用してイベントを処理できるように、バックグラウンド タスクの遅延を取得します。
var deferral = taskInstance.GetDeferral();
  • 次に、イベント引数から SocketActivityTriggerDetails を抽出し、イベントが発生した理由を見つけます。
var details = taskInstance.TriggerDetails as SocketActivityTriggerDetails;
    var socketInformation = details.SocketInformation;
    switch (details.Reason)
  • ソケット アクティビティが原因でイベントが発生した場合は、ソケットに DataReader を作成し、リーダーを非同期的に読み込んでから、アプリの設計に従ってデータを使用します。 ソケットの所有権をソケット ブローカーに戻して、ソケット アクティビティの通知を再度受け取る必要があることに注意してください。

次の例では、ソケットで受信したテキストがトーストに表示されます。

case SocketActivityTriggerReason.SocketActivity:
            var socket = socketInformation.StreamSocket;
            DataReader reader = new DataReader(socket.InputStream);
            reader.InputStreamOptions = InputStreamOptions.Partial;
            await reader.LoadAsync(250);
            var dataString = reader.ReadString(reader.UnconsumedBufferLength);
            ShowToast(dataString);
            socket.TransferOwnership(socketInformation.Id); /* Important! */
            break;
  • キープアライブ タイマーの有効期限が切れたためにイベントが発生した場合、コードはソケットを維持し、キープ アライブ タイマーを再起動するために、ソケット経由でデータを送信する必要があります。 ここでも、ソケットの所有権をソケット ブローカーに戻して、さらにイベント通知を受信することが重要です。
case SocketActivityTriggerReason.KeepAliveTimerExpired:
            socket = socketInformation.StreamSocket;
            DataWriter writer = new DataWriter(socket.OutputStream);
            writer.WriteBytes(Encoding.UTF8.GetBytes("Keep alive"));
            await writer.StoreAsync();
            writer.DetachStream();
            writer.Dispose();
            socket.TransferOwnership(socketInformation.Id); /* Important! */
            break;
  • ソケットが閉じられたためにイベントが発生した場合は、ソケットを再確立して、新しいソケットを作成した後、その所有権をソケット ブローカーに転送することを確認します。 このサンプルでは、ホスト名とポートをローカル設定に格納して、新しいソケット接続を確立するために使用できるようにします。
case SocketActivityTriggerReason.SocketClosed:
            socket = new StreamSocket();
            socket.EnableTransferOwnership(taskInstance.Task.TaskId, SocketActivityConnectedStandbyAction.Wake);
            if (ApplicationData.Current.LocalSettings.Values["hostname"] == null)
            {
                break;
            }
            var hostname = (String)ApplicationData.Current.LocalSettings.Values["hostname"];
            var port = (String)ApplicationData.Current.LocalSettings.Values["port"];
            await socket.ConnectAsync(new HostName(hostname), port);
            socket.TransferOwnership(socketId);
            break;
  • イベント通知の処理が完了したら、必ず遅延を完了してください。
  deferral.Complete();

SocketActivityTrigger とソケット ブローカーの使用を示す完全なサンプルについては、SocketActivityStreamSocket サンプルを参照してください。 ソケットの初期化はScenario1_Connect.xaml.csで実行され、バックグラウンド タスクの実装はSocketActivityTask.cs。

このトピックで説明しているように OnSuspending イベント ハンドラーでそうするのではなく、サンプルでは、新しいソケットを作成したり既存のソケットを取得したりするとすぐに TransferOwnership を呼び出していることに、おそらく気付くでしょう。 これは、サンプルでは SocketActivityTrigger のデモンストレーションに重点を置き、実行中に他のアクティビティにソケットを使用しないためです。 アプリはおそらくより複雑になり、 OnSuspending を使用して TransferOwnership を呼び出すタイミングを決定する必要があります。

制御チャネルのトリガー

まず、コントロール チャネル トリガー (CCT) を適切に使用していることを確認します。 DatagramSocketStreamSocket、または StreamSocketListener 接続を使用している場合は、SocketActivityTrigger を使用することをお勧めします。 StreamSocket には CCT を使用できますが、より多くのリソースを使用するため、コネクト スタンバイ モードでは機能しない可能性があります。

IXMLHTTPRequest2System.Net.Http.HttpClient、または Windows.Web.Http.HttpClient、あるいは WebSockets を使用している場合は、ControlChannelTrigger を使用する必要があります。

WebSocket を使用した ControlChannelTrigger

Important

このセクションで説明する機能 (WebSocket を使用した ControlChannelTrigger) は、Windows SDK バージョン 10.0.15063.0 以降でサポートされています。

ControlChannelTriggerMessageWebSocket または StreamWebSocket を使用する場合は、いくつかの特別な考慮事項が適用されます。 ControlChannelTriggerMessageWebSocket または StreamWebSocket を使用する場合は、トランスポート固有の使用パターンとベスト プラクティスに従う必要があります。 さらに、これらの考慮事項は、 StreamWebSocket でパケットを受信する要求の処理方法にも影響します。 MessageWebSocket でパケットを受信する要求は影響を受けません。

ControlChannelTriggerMessageWebSocket または StreamWebSocket を使用する場合は、次の使用パターンとベスト プラクティスに従う必要があります。

  • 未処理のソケット受信は常にポストされる必要があります。 これは、プッシュ通知タスクを実行できるようにするために必要です。
  • WebSocket プロトコルは、キープアライブ メッセージの標準モデルを定義します。 WebSocketKeepAlive クラスは、クライアントが開始した WebSocket プロトコルのキープアライブ メッセージをサーバーに送信できます。 WebSocketKeepAlive クラスは、アプリによって KeepAliveTrigger の TaskEntryPoint として登録する必要があります。

いくつかの特別な考慮事項は、 StreamWebSocket でパケットを受信する要求の処理方法に影響します。 特に、ControlChannelTriggerStreamWebSocket を使用する場合、アプリでは、C# の await モデルと VB.NET または C++ のタスクではなく、読み取りを処理するために生の非同期パターンを使用する必要があります。 生の非同期パターンについては、このセクションの後半のコード サンプルで説明します。

生の非同期パターンを使用すると、Windowsは ControlChannelTrigger のバックグラウンド タスクの IBackgroundTask.Run メソッドを受信完了コールバックの戻り値と同期できます。 Run メソッドは、完了コールバックが返された後に呼び出されます。 これにより、 Run メソッドが呼び出される前に、アプリでデータ/エラーが確実に受信されます。

完了コールバックから制御が戻る前に、アプリケーションは新たな読み取り要求を発行する必要があることに注意してください。 また、DataReaderMessageWebSocket または StreamWebSocket トランスポートと直接使用することはできません。これは、上記の同期を中断するためです。 トランスポート上で DataReader.LoadAsync メソッドを直接使用することはサポートされていません。 代わりに、StreamWebSocket.InputStream プロパティの IInputStream.ReadAsync メソッドによって返されるIBuffer を後で DataReader.FromBuffer メソッドに渡して、さらに処理することができます。

次のサンプルは、 StreamWebSocket で読み取りを処理するために生の非同期パターンを使用する方法を示しています。

void PostSocketRead(int length)
{
    try
    {
        var readBuf = new Windows.Storage.Streams.Buffer((uint)length);
        var readOp = socket.InputStream.ReadAsync(readBuf, (uint)length, InputStreamOptions.Partial);
        readOp.Completed = (IAsyncOperationWithProgress<IBuffer, uint>
            asyncAction, AsyncStatus asyncStatus) =>
        {
            switch (asyncStatus)
            {
                case AsyncStatus.Completed:
                case AsyncStatus.Error:
                    try
                    {
                        // GetResults in AsyncStatus::Error is called as it throws a user friendly error string.
                        IBuffer localBuf = asyncAction.GetResults();
                        uint bytesRead = localBuf.Length;
                        readPacket = DataReader.FromBuffer(localBuf);
                        OnDataReadCompletion(bytesRead, readPacket);
                    }
                    catch (Exception exp)
                    {
                        Diag.DebugPrint("Read operation failed:  " + exp.Message);
                    }
                    break;
                case AsyncStatus.Canceled:

                    // Read is not cancelled in this sample.
                    break;
           }
       };
   }
   catch (Exception exp)
   {
       Diag.DebugPrint("failed to post a read failed with error:  " + exp.Message);
   }
}

読み取り完了ハンドラーは、ControlChannelTrigger のバックグラウンド タスクの IBackgroundTask.Run メソッドが呼び出される前に発生することが保証されます。 Windowsには、アプリが読み取り完了コールバックから戻るのを待機する内部同期があります。 アプリは通常、読み取り完了コールバックで MessageWebSocket または StreamWebSocket からのデータまたはエラーをすばやく処理します。 メッセージ自体は、 IBackgroundTask.Run メソッドのコンテキスト内で処理されます。 次のサンプルでは、読み取り完了ハンドラーがメッセージを挿入し、バックグラウンド タスクを後で処理するメッセージ キューを使用して、このポイントを示します。

次の例は、 StreamWebSocket で読み取りを処理するために生の非同期パターンで使用する読み取り完了ハンドラーを示しています。

public void OnDataReadCompletion(uint bytesRead, DataReader readPacket)
{
    if (readPacket == null)
    {
        Diag.DebugPrint("DataReader is null");

        // Ideally when read completion returns error,
        // apps should be resilient and try to
        // recover if there is an error by posting another recv
        // after creating a new transport, if required.
        return;
    }
    uint buffLen = readPacket.UnconsumedBufferLength;
    Diag.DebugPrint("bytesRead: " + bytesRead + ", unconsumedbufflength: " + buffLen);

    // check if buffLen is 0 and treat that as fatal error.
    if (buffLen == 0)
    {
        Diag.DebugPrint("Received zero bytes from the socket. Server must have closed the connection.");
        Diag.DebugPrint("Try disconnecting and reconnecting to the server");
        return;
    }

    // Perform minimal processing in the completion
    string message = readPacket.ReadString(buffLen);
    Diag.DebugPrint("Received Buffer : " + message);

    // Enqueue the message received to a queue that the push notify
    // task will pick up.
    AppContext.messageQueue.Enqueue(message);

    // Post another receive to ensure future push notifications.
    PostSocketRead(MAX_BUFFER_LENGTH);
}

Websocket の追加の詳細は、キープアライブ ハンドラーです。 WebSocket プロトコルは、キープアライブ メッセージの標準モデルを定義します。

MessageWebSocket または StreamWebSocket を使用する場合は、KeepAliveTrigger の TaskEntryPoint として WebSocketKeepAlive クラス インスタンスを登録して、アプリをアンサスペンドし、キープアライブ メッセージをサーバー (リモート エンドポイント) に定期的に送信できるようにします。 これは、バックグラウンド登録アプリ コードの一部として、およびパッケージ マニフェストで行う必要があります。

Windowsのこのタスク エントリ ポイント。Sockets.WebSocketKeepAlive は、次の 2 つの場所で指定する必要があります。

  • ソース コードで KeepAliveTrigger トリガーを作成する場合 (以下の例を参照)。
  • キープアライブ バックグラウンド タスクの宣言用のアプリ パッケージ マニフェスト内

次の例では、アプリ マニフェストの <Application> 要素の下に、ネットワーク トリガー通知とキープアライブ トリガーを追加します。

  <Extensions>
    <Extension Category="windows.backgroundTasks"
         Executable="$targetnametoken$.exe"
         EntryPoint="Background.PushNotifyTask">
      <BackgroundTasks>
        <Task Type="controlChannel" />
      </BackgroundTasks>
    </Extension>
    <Extension Category="windows.backgroundTasks"
         Executable="$targetnametoken$.exe"
         EntryPoint="Windows.Networking.Sockets.WebSocketKeepAlive">
      <BackgroundTasks>
        <Task Type="controlChannel" />
      </BackgroundTasks>
    </Extension>
  </Extensions>

ControlChannelTrigger のコンテキストで await ステートメントを使用し、StreamWebSocket、MessageWebSocket、または StreamSocket で非同期操作を使用する場合、アプリは非常に注意する必要があります。 Task<bool> オブジェクトを使用して、プッシュ通知用の ControlChannelTriggerStreamWebSocket 上の WebSocket キープアライブを登録し、トランスポートを接続できます。 登録の一環として、 StreamWebSocket トランスポートが ControlChannelTrigger のトランスポートとして設定され、読み取りがポストされます。 Task.Result は、タスクのすべてのステップが実行され、メッセージ本文でステートメントが返されるまで、現在のスレッドをブロックします。 メソッドが true または false を返すまで、タスクは解決されません。 これにより、メソッド全体が実行されます。 タスクには、タスクによって保護されている複数のawait ステートメントを含めることができます。 このパターンは、StreamWebSocket または MessageWebSocket がトランスポートとして使用される場合に、ControlChannelTrigger オブジェクトと共に使用する必要があります。 完了に長い時間がかかる可能性がある操作 (一般的な非同期読み取り操作など) の場合、アプリは前に説明した生の非同期パターンを使用する必要があります。

次の例では、プッシュ通知用の ControlChannelTriggerStreamWebSocket 上の WebSocket キープアライブを登録します。

private bool RegisterWithControlChannelTrigger(string serverUri)
{
    // Make sure the objects are created in a system thread
    // Demonstrate the core registration path
    // Wait for the entire operation to complete before returning from this method.
    // The transport setup routine can be triggered by user control, by network state change
    // or by keepalive task
    Task<bool> registerTask = RegisterWithCCTHelper(serverUri);
    return registerTask.Result;
}

async Task<bool> RegisterWithCCTHelper(string serverUri)
{
    bool result = false;
    socket = new StreamWebSocket();

    // Specify the keepalive interval expected by the server for this app
    // in order of minutes.
    const int serverKeepAliveInterval = 30;

    // Specify the channelId string to differentiate this
    // channel instance from any other channel instance.
    // When background task fires, the channel object is provided
    // as context and the channel id can be used to adapt the behavior
    // of the app as required.
    const string channelId = "channelOne";

    // For websockets, the system does the keepalive on behalf of the app
    // But the app still needs to specify this well known keepalive task.
    // This should be done here in the background registration as well
    // as in the package manifest.
    const string WebSocketKeepAliveTask = "Windows.Networking.Sockets.WebSocketKeepAlive";

    // Try creating the controlchanneltrigger if this has not been already
    // created and stored in the property bag.
    ControlChannelTriggerStatus status;

    // Create the ControlChannelTrigger object and request a hardware slot for this app.
    // If the app is not on LockScreen, then the ControlChannelTrigger constructor will
    // fail right away.
    try
    {
        channel = new ControlChannelTrigger(channelId, serverKeepAliveInterval,
                                   ControlChannelTriggerResourceType.RequestHardwareSlot);
    }
    catch (UnauthorizedAccessException exp)
    {
        Diag.DebugPrint("Is the app on lockscreen? " + exp.Message);
        return result;
    }

    Uri serverUriInstance;
    try
    {
        serverUriInstance = new Uri(serverUri);
    }
    catch (Exception exp)
    {
        Diag.DebugPrint("Error creating URI: " + exp.Message);
        return result;
    }

    // Register the apps background task with the trigger for keepalive.
    var keepAliveBuilder = new BackgroundTaskBuilder();
    keepAliveBuilder.Name = "KeepaliveTaskForChannelOne";
    keepAliveBuilder.TaskEntryPoint = WebSocketKeepAliveTask;
    keepAliveBuilder.SetTrigger(channel.KeepAliveTrigger);
    keepAliveBuilder.Register();

    // Register the apps background task with the trigger for push notification task.
    var pushNotifyBuilder = new BackgroundTaskBuilder();
    pushNotifyBuilder.Name = "PushNotificationTaskForChannelOne";
    pushNotifyBuilder.TaskEntryPoint = "Background.PushNotifyTask";
    pushNotifyBuilder.SetTrigger(channel.PushNotificationTrigger);
    pushNotifyBuilder.Register();

    // Tie the transport method to the ControlChannelTrigger object to push enable it.
    // Note that if the transport' s TCP connection is broken at a later point of time,
    // the ControlChannelTrigger object can be reused to plug in a new transport by
    // calling UsingTransport API again.
    try
    {
        channel.UsingTransport(socket);

        // Connect the socket
        //
        // If connect fails or times out it will throw exception.
        // ConnectAsync can also fail if hardware slot was requested
        // but none are available
        await socket.ConnectAsync(serverUriInstance);

        // Call WaitForPushEnabled API to make sure the TCP connection has
        // been established, which will mean that the OS will have allocated
        // any hardware slot for this TCP connection.
        //
        // In this sample, the ControlChannelTrigger object was created by
        // explicitly requesting a hardware slot.
        //
        // On systems that without connected standby, if app requests hardware slot as above,
        // the system will fallback to a software slot automatically.
        //
        // On systems that support connected standby,, if no hardware slot is available, then app
        // can request a software slot by re-creating the ControlChannelTrigger object.
        status = channel.WaitForPushEnabled();
        if (status != ControlChannelTriggerStatus.HardwareSlotAllocated
            && status != ControlChannelTriggerStatus.SoftwareSlotAllocated)
        {
            throw new Exception(string.Format("Neither hardware nor software slot could be allocated. ChannelStatus is {0}", status.ToString()));
        }

        // Store the objects created in the property bag for later use.
        CoreApplication.Properties.Remove(channel.ControlChannelTriggerId);

        var appContext = new AppContext(this, socket, channel, channel.ControlChannelTriggerId);
        ((IDictionary<string, object>)CoreApplication.Properties).Add(channel.ControlChannelTriggerId, appContext);
        result = true;

        // Almost done. Post a read since we are using streamwebsocket
        // to allow push notifications to be received.
        PostSocketRead(MAX_BUFFER_LENGTH);
    }
    catch (Exception exp)
    {
         Diag.DebugPrint("RegisterWithCCTHelper Task failed with: " + exp.Message);

         // Exceptions may be thrown for example if the application has not
         // registered the background task class id for using real time communications
         // broker in the package manifest.
    }
    return result;
}

ControlChannelTriggerMessageWebSocket または StreamWebSocket を使用する方法の詳細については、ControlChannelTrigger StreamWebSocket サンプルを参照してください。

HttpClient を使用した ControlChannelTrigger

ControlChannelTriggerHttpClient を使用する場合は、いくつかの特別な考慮事項が適用されます。 ControlChannelTriggerHttpClient を使用する場合は、トランスポート固有の使用パターンとベスト プラクティスに従う必要があります。 さらに、これらの考慮事項は、 HttpClient でパケットを受信する要求の処理方法にも影響します。

注: 現在、SSL を使用する HttpClient は、ネットワーク トリガー機能と ControlChannelTrigger を使用してサポートされていません。   ControlChannelTriggerHttpClient を使用する場合は、次の使用パターンとベスト プラクティスに従う必要があります。

  • アプリでは、特定の URI に要求を送信する前に、System.Net.Http 名前空間の HttpClient または HttpClientHandler オブジェクトにさまざまなプロパティとヘッダーを設定する必要がある場合があります。
  • アプリでは、ControlChannelTrigger で使用する HttpClient トランスポートを作成する前に、トランスポートを適切にテストしてセットアップするための最初の要求が必要になる場合があります。 トランスポートを適切にセットアップできることをアプリが判断したら、ControlChannelTrigger オブジェクトで使用されるトランスポート オブジェクトとして HttpClient オブジェクトを構成できます。 このプロセスは、一部のシナリオでトランスポート経由で確立された接続が切断されないように設計されています。 証明書と共に SSL を使用すると、アプリで PIN エントリのダイアログを表示する必要がある場合や、複数の証明書を選択する必要がある場合があります。 プロキシ認証とサーバー認証が必要な場合があります。 プロキシまたはサーバー認証の有効期限が切れると、接続が閉じられる可能性があります。 アプリでこれらの認証の有効期限の問題に対処する 1 つの方法は、タイマーを設定することです。 HTTP リダイレクトが必要な場合、2 つ目の接続を確実に確立できるとは限りません。 最初のテスト要求では、 HttpClient オブジェクトを ControlChannelTrigger オブジェクトとのトランスポートとして使用する前に、アプリで最も up-to-date リダイレクト URL を使用できることを確認します。

他のネットワーク トランスポートとは異なり、HttpClient オブジェクトを ControlChannelTrigger オブジェクトの UsingTransport メソッドに直接渡すことはできません。 代わりに、HttpClient オブジェクトと ControlChannelTrigger で使用するために、HttpRequestMessage オブジェクトを特別に構築する必要があります。 HttpRequestMessage オブジェクトは、RtcRequestFactory.Create メソッドを使用して作成されます。 作成された HttpRequestMessage オブジェクトは、 UsingTransport メソッドに渡されます。

次の例では、HttpClient オブジェクトと ControlChannelTrigger で使用する HttpRequestMessage オブジェクトを構築する方法を示します。

using System;
using System.Net;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using Windows.Networking.Sockets;

public HttpRequestMessage httpRequest;
public HttpClient httpClient;
public HttpRequestMessage httpRequest;
public ControlChannelTrigger channel;
public Uri serverUri;

private void SetupHttpRequestAndSendToHttpServer()
{
    try
    {
        // For HTTP based transports that use the RTC broker, whenever we send next request, we will abort the earlier
        // outstanding http request and start new one.
        // For example in case when http server is taking longer to reply, and keep alive trigger is fired in-between
        // then keep alive task will abort outstanding http request and start a new request which should be finished
        // before next keep alive task is triggered.
        if (httpRequest != null)
        {
            httpRequest.Dispose();
        }

        httpRequest = RtcRequestFactory.Create(HttpMethod.Get, serverUri);

        SendHttpRequest();
    }
        catch (Exception e)
    {
        Diag.DebugPrint("Connect failed with: " + e.ToString());
        throw;
    }
}

いくつかの特別な考慮事項は、 HttpClient で HTTP 要求を送信して応答の受信を開始する要求が処理される方法に影響します。 特に、ControlChannelTriggerHttpClient を使用する場合、アプリは await モデルではなく送信を処理するためにタスクを使用する必要があります。

HttpClient を使用すると、ControlChannelTrigger のバックグラウンド タスクの IBackgroundTask.Run メソッドとの同期と、受信完了コールバックの戻り値は同期されません。 このため、アプリは Run メソッドでブロック HttpResponseMessage 手法のみを使用し、応答全体が受信されるまで待機できます。

ControlChannelTrigger での HttpClient の使用は、StreamSocketMessageWebSocketまたは StreamWebSocket トランスポートとは大いに異なります。 HttpClient 受信コールバックは、HttpClient コード以降、タスクを介してアプリに配信されます。 つまり、 ControlChannelTrigger プッシュ通知タスクは、データまたはエラーがアプリにディスパッチされるとすぐに起動します。 次のサンプルでは、 HttpClient.SendAsync メソッドによって返される responseTask を、プッシュ通知タスクが取得してインラインで処理するグローバル ストレージに格納します。

次の例は、ControlChannelTrigger で使用した場合に HttpClient で送信要求を処理する方法を示しています。

using System;
using System.Net;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using Windows.Networking.Sockets;

private void SendHttpRequest()
{
    if (httpRequest == null)
    {
        throw new Exception("HttpRequest object is null");
    }

    // Tie the transport method to the controlchanneltrigger object to push enable it.
    // Note that if the transport' s TCP connection is broken at a later point of time,
    // the controlchanneltrigger object can be reused to plugin a new transport by
    // calling UsingTransport API again.
    channel.UsingTransport(httpRequest);

    // Call the SendAsync function to kick start the TCP connection establishment
    // process for this http request.
    Task<HttpResponseMessage> httpResponseTask = httpClient.SendAsync(httpRequest);

    // Call WaitForPushEnabled API to make sure the TCP connection has been established,
    // which will mean that the OS will have allocated any hardware slot for this TCP connection.
    ControlChannelTriggerStatus status = channel.WaitForPushEnabled();
    Diag.DebugPrint("WaitForPushEnabled() completed with status: " + status);
    if (status != ControlChannelTriggerStatus.HardwareSlotAllocated
        && status != ControlChannelTriggerStatus.SoftwareSlotAllocated)
    {
        throw new Exception("Hardware/Software slot not allocated");
    }

    // The HttpClient receive callback is delivered via a Task to the app.
    // The notification task will fire as soon as the data or error is dispatched
    // Enqueue the responseTask returned by httpClient.sendAsync
    // into a queue that the push notify task will pick up and process inline.
    AppContext.messageQueue.Enqueue(httpResponseTask);
}

次の例は、ControlChannelTrigger で使用されたときに HttpClient で受信した応答を読み取る方法を示しています。

using System.Net;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;

public string ReadResponse(Task<HttpResponseMessage> httpResponseTask)
{
    string message = null;
    try
    {
        if (httpResponseTask.IsCanceled || httpResponseTask.IsFaulted)
        {
            Diag.DebugPrint("Task is cancelled or has failed");
            return message;
        }
        // We' ll wait until we got the whole response.
        // This is the only supported scenario for HttpClient for ControlChannelTrigger.
        HttpResponseMessage httpResponse = httpResponseTask.Result;
        if (httpResponse == null || httpResponse.Content == null)
        {
            Diag.DebugPrint("Cannot read from httpresponse, as either httpResponse or its content is null. try to reset connection.");
        }
        else
        {
            // This is likely being processed in the context of a background task and so
            // synchronously read the Content' s results inline so that the Toast can be shown.
            // before we exit the Run method.
            message = httpResponse.Content.ReadAsStringAsync().Result;
        }
    }
    catch (Exception exp)
    {
        Diag.DebugPrint("Failed to read from httpresponse with error:  " + exp.ToString());
    }
    return message;
}

ControlChannelTriggerHttpClient を使用する方法の詳細については、ControlChannelTrigger HttpClient サンプルを参照してください。

IXMLHttpRequest2 での ControlChannelTrigger

ControlChannelTriggerIXMLHTTPRequest2 を使用する場合は、いくつかの特別な考慮事項が適用されます。 ControlChannelTriggerIXMLHTTPRequest2 を使用する場合は、トランスポート固有の使用パターンとベスト プラクティスに従う必要があります。 ControlChannelTrigger を使用しても、IXMLHTTPRequest2 で HTTP 要求を送受信する要求の処理方法には影響しません。

ControlChannelTriggerIXMLHTTPRequest2 を使用する場合の使用パターンとベスト プラクティス

  • トランスポートとして使用される 場合の IXMLHTTPRequest2 オブジェクトの有効期間は、要求/応答が 1 つだけです。 ControlChannelTrigger オブジェクトと共に使用する場合は、ControlChannelTrigger オブジェクトを 1 回作成して設定してから、新しい IXMLHTTPRequest2 オブジェクトを関連付けるたびに UsingTransport メソッドを繰り返し呼び出すと便利です。 アプリは、新しい IXMLHTTPRequest2 オブジェクトを指定する前に、以前の IXMLHTTPRequest2 オブジェクトを削除して、アプリが割り当てられたリソース制限を超えないようにする必要があります。
  • アプリでは、Send メソッドを呼び出す前に、SetProperty メソッドと SetRequestHeader メソッドを呼び出して HTTP トランスポートを設定する必要がある場合があります。
  • アプリでは、ControlChannelTrigger で使用するトランスポートを作成する前に、トランスポートを適切にテストしてセットアップするために、最初の送信要求が必要になる場合があります。 トランスポートが適切にセットアップされていることがアプリで判断されると、 IXMLHTTPRequest2 オブジェクトを ControlChannelTrigger で使用されるトランスポート オブジェクトとして構成できます。 このプロセスは、一部のシナリオでトランスポート経由で確立された接続が切断されないように設計されています。 証明書と共に SSL を使用すると、アプリで PIN エントリのダイアログを表示する必要がある場合や、複数の証明書を選択する必要がある場合があります。 プロキシ認証とサーバー認証が必要な場合があります。 プロキシまたはサーバー認証の有効期限が切れると、接続が閉じられる可能性があります。 アプリでこれらの認証の有効期限の問題に対処する 1 つの方法は、タイマーを設定することです。 HTTP リダイレクトが必要な場合、2 つ目の接続を確実に確立できるとは限りません。 最初のテスト要求では、 IXMLHTTPRequest2 オブジェクトを ControlChannelTrigger オブジェクトとのトランスポートとして使用する前に、アプリで最も up-to-date リダイレクト URL を使用できることを確認します。

ControlChannelTriggerIXMLHTTPRequest2 を使用する方法の詳細については、IXMLHTTPRequest2 での ControlChannelTrigger のサンプルを参照してください。

重要な API