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
-
Enables network communications using sockets and WebSockets and supports the ControlChannelTrigger object.
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:
- An outstanding socket receive must be kept posted at all times. This is required to allow the push notification tasks to occur.
- Setting StreamSocketControl.KeepAlive property will tear down your socket connection, because TCP keep-alive is not supported using the ControlChannelTrigger object.
- Only the TCP StreamSocket is supported. Creating a UDP socket and passing it to a DatagramSocket as the transport connection to ControlChannelTrigger object will fail.
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:
- Call one of the ConnectAsync methods with a plain socket not specifying SSL. The app then initiates and completes whatever proprietary authentication exchange is required. Then upgrade the connection to use SSL/TLS for all further communications by calling the UpgradeToSslAsync. Then call the ControlChannelTrigger. WaitForPushEnabled method.
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.
Related topics
Other resources
Staying connected in the background
Supporting your app with background tasks
Tile and tile notification overview
Troubleshooting and debugging network connections
Reference
Windows.ApplicationModel.Background
Samples