How to use a stream socket with a network trigger (XAML)

[This article is for Windows 8.x and Windows Phone 8.x developers writing Windows Runtime apps. If you’re developing for Windows 10, see the latest documentation]

This topic shows how to maintain a socket network connection using a network trigger in a Windows Store app when an app is in the background.

What you need to know

Technologies

Prerequisites

  • The following information applies to any connected or network-aware Windows Store app that depends on network connections using TCP stream sockets to always be connected. This topic applies to apps written in C++/XAML and apps using the .NET Framework 4.5 in C#, VB.NET, or managed C++ on Windows 8 and Windows Server 2012.

    This topic does not apply to apps written in JavaScript or a foreground app in JavaScript with an in-process C# or C++ binary. Background network connectivity using a network trigger with ControlChannelTrigger is not supported by a JavaScript app. For information on background tasks that apply to JavaScript apps, see Supporting your app with background tasks. For information on background network connectivity supported by a JavaScript app, see Staying connected in the background (HTML).

Instructions

Step 1: Using a TCP stream socket with ControlChannelTrigger

Network triggers using ControlChannelTrigger can be used with different transports. The choice of the transport affects how your app must be written.

Some special considerations apply when using StreamSocket with ControlChannelTrigger. There are some transport-specific usage patterns and best practices that should be followed when using a StreamSocket with ControlChannelTrigger. In addition, these considerations affect the way that requests to receive packets on the StreamSocket are handled.

The following usage patterns and best practices should be followed when using StreamSocket with ControlChannelTrigger:

Some special considerations affect the way that requests to receive packets on the StreamSocket are handled. In particular, when using a StreamSocket with the ControlChannelTrigger, your app must use a raw async pattern for handling reads instead of the await model in C# and VB.NET or Tasks in C++.

Using the raw async pattern allows Windows to synchronize the IBackgroundTask.Run method on the background task for the ControlChannelTrigger with the return of the receive completion callback. The Run method is invoked after all completion callbacks return. This ensures that the app has received the data/errors before the Run method is invoked.

It is important to note that the app has to post another read before it returns control from the completion callback. It is also important to note that the DataReader cannot be directly used with the StreamSocket transport since that breaks the synchronization described above. It is not supported to use the DataReader.LoadAsync method directly on top of the transport. Instead, the IBuffer returned by the IInputStream.ReadAsync method on the StreamSocket.InputStream property can be later passed to DataReader.FromBuffer method for further processing.

The following sample shows how to use a raw async pattern for handling reads on the StreamSocket.

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);
   }
}

If there was data received, the read completion handler is guaranteed to fire and complete before the IBackgroundTask.Run method on the background task for the ControlChannelTrigger is invoked. Windows has internal synchronization to wait for an app to return from the read completion callback. The app typically quickly processes the data or the error from the StreamSocket in the read completion callback. The message itself is processed within the context of the IBackgroundTask.Run method. In this sample below, this point is illustrated by using a message queue that the read completion handler inserts the message into and the background task later processes.

The app must be prepared to handle spurious invocations where no data has been received, but the IBackgroundTask.Run method on the background task for the ControlChannelTrigger is invoked.

The following sample shows the read completion handler to use with a raw async pattern for handling reads on the StreamSocket.

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);
}

When using StreamSocket and SSL with ControlChannelTrigger, there are two usage models that are supported.

  • StreamSocket.ConnectAsync with SSL - Call the one of the ConnectAsync methods that support passing a protectionLevel parameter so that SSL can be specified. Then call the ControlChannelTrigger. WaitForPushEnabled method to notify the system that a connection has been established and the system should complete the internal configuration of the control channel trigger.
  • StreamSocket.ConnectAsync with a plain socket - Call the one of the ConnectAsync methods with a plain socket not specifying SSL. This establishes an initial connection to a network service without encryption. Then call the ControlChannelTrigger. WaitForPushEnabled method to notify the system that a connection has been established and the system should complete the internal configuration of the control channel trigger. The app then initiates and completes whatever proprietary authentication exchange is required. After authentication has been completed, upgrade the connection to use SSL/TLS for all further communications by calling the UpgradeToSslAsync inline and wait for completion.

The following usage model is not supported when using StreamSocket and SSL with ControlChannelTrigger:

For more information on using StreamSocket with SSL, see How to secure socket connections with TLS/SSL (XAML).

For more information on using StreamSocket with ControlChannelTrigger, see the ControlChannelTrigger StreamSocket sample.

Step 2: Previous steps

For more information on how to create a lock screen app to receive background network notifications that use network triggers, see Quickstart: Create a lock screen app that uses background network triggers.

For more information on how to use network triggers to deliver notifications to a lock screen app, see How to use network triggers to deliver notifications to a lock screen app.

For more information on how to write a background task to receive background network notifications that use network triggers, see How to write a background task for a network trigger.

For more information on how to re-establish a network trigger and a transport connection, see How to re-establish a network trigger and transport connection.

Step 3: Further steps

For more information on guidelines and checklists for using network triggers, see Guidelines and checklist for using network triggers.

Other resources

Adding support for networking

Background Networking

Badge overview

Connecting with sockets(XAML)

Lock screen overview

Staying connected in the background

Supporting your app with background tasks

Tile and tile notification overview

Toast notification overview

Troubleshooting and debugging network connections

Reference

ControlChannelTrigger

StreamSocket

Windows.ApplicationModel.Background

Windows.Networking.Sockets

Samples

Background task sample

ControlChannelTrigger TCP socket sample

Lock screen apps sample