How to connect with a datagram socket (XAML)
This topic shows how to enable a Windows Runtime app to send and receive data with a UDP datagram socket using a DatagramSocket. This type of socket can be used to send and receive network packets with low overhead, but reception of the data is not guaranteed.
The client component of the sample demonstrates the following features:
- Use the DatagramSocket class to create a UDP socket for the client to send and receive data.
- Add a handler for a DatagramSocket.MessageReceived event that indicates that a UDP datagram was received on the DatagramSocket object.
- Set the remote endpoint for a UDP network server where packets should be sent using one of the DatagramSocket.ConnectAsync methods.
- Send data to the server using the Streams.DataWriter object which allows a programmer to write common types (integers and strings, for example) on any stream.
- Close the socket.
The server component of the sample demonstrates the following features:
- Use the DatagramSocket class to create a UDP socket to listen for and receive incoming datagram packets and for sending packets.
- Add a handler for a DatagramSocket.MessageReceived event that indicates that a UDP datagram was received on the DatagramSocket object.
- Bind the socket to a local service name to listen for incoming UDP packets using the DatagramSocket.BindServiceNameAsync method.
- Receive a DatagramSocket.MessageReceived event that indicates that a UDP datagram was received on the DatagramSocket object.
- Receive data from the client using the DatagramSocket.MessageReceived handler. The DatagramSocketMessageReceivedEventArgs object passed to the DatagramSocket.MessageReceived handler allows an app to receive data from the client and also determine the remote address and port that sent the data.
- Close the socket.
Note Use of this sample requires network access using the loopback interface.
Objective: Create a network connection to another computer or device using a DatagramSocket socket.
Prerequisites
The following examples use C# or C++ and are loosely based on the DatagramSocket sample. For general help creating a Windows Runtime app using C# or Visual Basic, see Create your first Windows Runtime app using C# or Visual Basic. For general help creating a Windows Runtime app using C++, see Create your first Windows Runtime app using C++.
To ensure your Windows Runtime app is network ready, you must set any network capabilities that are needed in the project Package.appxmanifest file. For more information, see How to set network capabilities.
Create a new project
- Open Microsoft Visual Studio 2013 and select New Project from the File menu.
- In the list of templates, choose Visual C# or Visual C++ (depending on your preferred development language).
- Under the section, choose Store apps.
- Under the section, select Universal Apps, Windows apps, or Windows Phone apps (depending on the platform you are targeting), and then select Blank Application.
- Name the application
DatagramsocketBasic
and click OK.
Set capabilities to enable network access
You need to set network capabilities for your app to enable access to the local network and the Internet. For an app using a DatagramSocket to connect to a network service on a different computer, the app would need network capabilities set. If the app needs to be able to connect as a client to remote services on the Internet, then the Internet (Client) capability is needed. If the app needs to be able to connect as a client to remote services on a home network or work network, then the Private Networks (Client & Server) capability is needed.
Note On Windows Phone, there is only one network capability (Internet (Client & Server))which enables all network access for the app.
For more information on network access, see How to configure network isolation capabilities.
The steps below show how to set network capabilities.
Use Microsoft Visual Studio to open the package.appxmanifest file.
Select the Capabilities tab.
To build the Windows version of the sample, Select the Internet (Client) and Private Networks (Client & Server) capabilities.
To build the Windows Phone version of the sample, select the Internet (Client & Server) capability.
Save and close the manifest file.
Add XAML UI
In this section, we define the app layout in XAML to specify the size and position of each object in the app. We complete the UI for the app by adding controls and content to display data.
This sample uses simple XAML UI elements that include the following:
- A horizontal StackPanel that contains a TextBlock for a label, a TextBox to input the server hostname or IP address, a TextBox to input the service name or UDP port number to connect to, and a Button used to start the asynchronous connection request.
- A horizontal StackPanel that contains a TextBlock for a label, a TextBox to input text data to send to the server, and a Button used to start the asynchronous request to send the data.
- A horizontal StackPanel that contains a TextBlock for a label and a TextBox for the current status. This is where status and error messages will be displayed.
- A Grid where any output received from the network service is displayed. In this sample, any text data received is displayed as plain text.
For a Visual C# sample, open the cs folder. For a Visual C++ sample, open the cpp folder. Open the existing blankPage.xaml file and rename the file to StartListener.xaml. Add the following UI elements to this file.
Note The XAML code below will need to be changed for a Windows Phone Store app.
<common:LayoutAwarePage x:Class="DatagramSocketBasic.StartListener" xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="using:SDKTemplate" xmlns:common="using:SDKTemplate.Common" xmlns:d="https://schemas.microsoft.com/expression/blend/2008" xmlns:mc="https://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d"> <Grid x:Name="LayoutRoot" Background="White" HorizontalAlignment="Left" VerticalAlignment="Top"> <Grid.RowDefinitions> <RowDefinition Height="*"/> <RowDefinition Height="*"/> <RowDefinition Height="*"/> </Grid.RowDefinitions> <Grid x:Name="Input" Grid.Row="0" Grid.RowSpan="2"> <Grid.RowDefinitions> <RowDefinition Height="Auto"/> <RowDefinition Height="*"/> </Grid.RowDefinitions> <TextBlock x:Name="InputTextBlock1" TextWrapping="Wrap" Grid.Row="0" Style="{StaticResource BasicTextStyle}" HorizontalAlignment="Left" > DatagramSocket is used to create the 'server' side of a connection. It listens on a 'service name' (often a port number) and each time a datagram is received on the port number it fires MessageReceived event. </TextBlock> <TextBlock Grid.Row="1" Style="{StaticResource BasicTextStyle}" HorizontalAlignment="Stretch" VerticalAlignment="Center"> Service name: </TextBlock> <TextBox x:Name="ServiceNameForListener" Grid.Row="1" Text="22112" Margin="103,0,0,0" HorizontalAlignment="Left" Width="200"/> <Button x:Name="StartListener" Content="Listen" Grid.Row="2" Margin="0,39,0,-39" Click="StartListener_Click"/> </Grid> <Grid x:Name="Output" HorizontalAlignment="Left" VerticalAlignment="Top" Grid.Row="3"> </Grid> <!-- Add Storyboards to the visual states below as necessary for supporting the various layouts --> <VisualStateManager.VisualStateGroups> <VisualStateGroup> <VisualState x:Name="FullScreenLandscape"/> <VisualState x:Name="Filled"/> <VisualState x:Name="FullScreenPortrait"/> <VisualState x:Name="Snapped"/> </VisualStateGroup> </VisualStateManager.VisualStateGroups> </Grid> </common:LayoutAwarePage>
Open a new ConnectToListener.xaml file and add the following UI elements to this file.
Note The XAML code below will need to be changed for a Windows Phone Store app.
<common:LayoutAwarePage x:Class="DatagramSocketBasic.ConnectToListener" xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="using:SDKTemplate" xmlns:common="using:SDKTemplate.Common" xmlns:d="https://schemas.microsoft.com/expression/blend/2008" xmlns:mc="https://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d"> <Grid x:Name="LayoutRoot" Background="White"> <Grid.ColumnDefinitions> <ColumnDefinition Width="6*"/> <ColumnDefinition Width="677*"/> </Grid.ColumnDefinitions> <Grid.RowDefinitions> <RowDefinition Height="Auto"/> <RowDefinition Height="*"/> </Grid.RowDefinitions> <Grid x:Name="Input" Grid.Row="0" Grid.ColumnSpan="2"> <Grid.RowDefinitions> <RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/> </Grid.RowDefinitions> <TextBlock Grid.Row="0" TextWrapping="Wrap" Style="{StaticResource BasicTextStyle}" HorizontalAlignment="Left"> Next, you need the 'other side of the connection' -- you need to connect to a listener. The host name and service name (often a port number) to connect to are the 'Host name:' and 'Service name:' entries. The service name should match what you started to listen to! </TextBlock> <TextBlock Grid.Row="1" TextWrapping="Wrap" Style="{StaticResource BasicTextStyle}" HorizontalAlignment="Left"> The connection will automatically use IPv6 as needed. It will also resolve internationalized domain names. </TextBlock> <TextBlock Grid.Row="2" TextWrapping="Wrap" Style="{StaticResource BasicTextStyle}" HorizontalAlignment="Left"> Due to the network security system, you cannot connect to other applications running on the same machine. This means that you can only use 'localhost' to connect to the same application (specifically, you can connect to a listener on the same machine running in the same app container) </TextBlock> <TextBlock Grid.Row="3" Style="{StaticResource BasicTextStyle}" HorizontalAlignment="Stretch" VerticalAlignment="Center"> Host name: </TextBlock> <TextBox x:Name="HostNameForConnect" Grid.Row="3" Text="localhost" IsEnabled="False" HorizontalAlignment="Left" Margin="103,0,0,0" Width="200"/> <TextBlock Grid.Row="4" Style="{StaticResource BasicTextStyle}" HorizontalAlignment="Stretch" VerticalAlignment="Center"> Service name: </TextBlock> <TextBox x:Name="ServiceNameForConnect" Grid.Row="4" Text="22112" HorizontalAlignment="Left" Margin="103,0,0,0" Width="200"/> <Button x:Name="ConnectSocket" Grid.Row="5" Content="Connect" Margin="0,0,10,0" Click="ConnectSocket_Click"/> </Grid> <Grid x:Name="Output" HorizontalAlignment="Left" VerticalAlignment="Top" Margin="0,180,0,0"/> <!-- Add Storyboards to the visual states below as necessary for supporting the various layouts --> <VisualStateManager.VisualStateGroups> <VisualStateGroup> <VisualState x:Name="FullScreenLandscape"/> <VisualState x:Name="Filled"/> <VisualState x:Name="FullScreenPortrait"/> <VisualState x:Name="Snapped"/> </VisualStateGroup> </VisualStateManager.VisualStateGroups> </Grid> </common:LayoutAwarePage>
Open a new SendData.xaml file and add the following UI elements to this file.
Note The XAML code below will need to be changed for a Windows Phone Store app.
<common:LayoutAwarePage x:Class="DatagramSocketBasic.SendData" xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="using:SDKTemplate" xmlns:common="using:SDKTemplate.Common" xmlns:d="https://schemas.microsoft.com/expression/blend/2008" xmlns:mc="https://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d"> <Grid x:Name="LayoutRoot" Background="White"> <Grid.RowDefinitions> <RowDefinition Height="Auto"/> <RowDefinition Height="*"/> </Grid.RowDefinitions> <Grid x:Name="Input" Grid.Row="0"> <Grid.RowDefinitions> <RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/> </Grid.RowDefinitions> <TextBlock x:Name="InputTextBlock1" Grid.Row="0" TextWrapping="Wrap" Style="{StaticResource BasicTextStyle}" HorizontalAlignment="Left"> Now you can send data to the 'server'. Sending data is often done with the DataWriter object; it will write to the socket stream. </TextBlock> <Button x:Name="SendHello" Grid.Row="1" Content="Send 'hello'" Margin="0,0,10,0" Click="SendHello_Click"/> </Grid> <Grid x:Name="Output" Grid.Row="1"> <TextBlock x:Name="SendOutput" Grid.Row="0" TextWrapping="Wrap" Style="{StaticResource BasicTextStyle}" HorizontalAlignment="Left"/> </Grid> <!-- Add Storyboards to the visual states below as necessary for supporting the various layouts --> <VisualStateManager.VisualStateGroups> <VisualStateGroup> <VisualState x:Name="FullScreenLandscape"/> <VisualState x:Name="Filled"/> <VisualState x:Name="FullScreenPortrait"/> <VisualState x:Name="Snapped"/> </VisualStateGroup> </VisualStateManager.VisualStateGroups> </Grid> </common:LayoutAwarePage>
Open a new CloseSocket.xaml file and add the following UI elements to this file.
Note The XAML code below will need to be changed for a Windows Phone Store app.
<common:LayoutAwarePage x:Class="DatagramSocketSample.Scenario4" xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="using:SDKTemplate" xmlns:common="using:SDKTemplate.Common" xmlns:d="https://schemas.microsoft.com/expression/blend/2008" xmlns:mc="https://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d"> <Grid x:Name="LayoutRoot" Background="White"> <Grid.RowDefinitions> <RowDefinition Height="Auto"/> <RowDefinition Height="*"/> </Grid.RowDefinitions> <Grid x:Name="Input" Grid.Row="0"> <Grid.RowDefinitions> <RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/> </Grid.RowDefinitions> <TextBlock x:Name="InputTextBlock1" Grid.Row="0" TextWrapping="Wrap" Style="{StaticResource BasicTextStyle}" HorizontalAlignment="Left"> Lastly, you can close all sockets. </TextBlock> <TextBlock x:Name="InputTextBlock2" Grid.Row="1" TextWrapping="Wrap" Style="{StaticResource BasicTextStyle}" HorizontalAlignment="Left"> If you don't close your socket, it will be closed for you when the application exits. </TextBlock> <Button x:Name="CloseSockets" Grid.Row="2" Content="Close" Margin="0,0,10,0" Click="CloseSockets_Click"/> </Grid> <Grid x:Name="Output" Grid.Row="1"> </Grid> <!-- Add Storyboards to the visual states below as necessary for supporting the various layouts --> <VisualStateManager.VisualStateGroups> <VisualStateGroup> <VisualState x:Name="FullScreenLandscape"/> <VisualState x:Name="Filled"/> <VisualState x:Name="FullScreenPortrait"/> <VisualState x:Name="Snapped"/> </VisualStateGroup> </VisualStateManager.VisualStateGroups> </Grid> </common:LayoutAwarePage>
Define variables for sockets and event functions
The code in this step creates a number of variables including the listener socket, the client socket, and various variable for errors and events. Variables are created to track whether the client socket is in a connected or closing state. This step also defines the hostname and service name (UDP port) to connect and send data to as well as the local service name (UDP port) on which to accept and receive data. The value of the remote hostname, remote service name, and local service name are set to a default value which can be changed in the UI.
For a Visual C# sample, open the cs folder and open a new StartListener.cs file. For a Visual C++ sample, open the cpp folder and open a new StartListener.cpp file.
using System; using Windows.ApplicationModel.Core; using Windows.Networking; using Windows.Networking.Sockets; using Windows.Storage.Streams; using Windows.UI.Core; using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; using Windows.UI.Xaml.Navigation; namespace DatagramSocketBasic { /// <summary> /// A page for first scenario. /// </summary> public sealed partial class Scenario1 : SDKTemplate.Common.LayoutAwarePage { // A pointer back to the main page. // This is needed if you want to call methods in MainPage such // as NotifyUser() MainPage rootPage = MainPage.Current; public Scenario1() { this.InitializeComponent(); } /// <summary> /// Invoked when this page is about to be displayed in a Frame. /// </summary> /// <param name="e">Event data that describes how this page was reached. The Parameter /// property is typically used to configure the page.</param> protected override void OnNavigatedTo(NavigationEventArgs e) { } /// <summary> /// This is the click handler for the 'StartListener' button. /// </summary> /// <param name="sender">Object for which the event was generated.</param> /// <param name="e">Event's parameters.</param> private async void StartListener_Click(object sender, RoutedEventArgs e) { if (String.IsNullOrEmpty(ServiceNameForListener.Text)) { rootPage.NotifyUser("Please provide a service name.", NotifyType.ErrorMessage); return; } if (CoreApplication.Properties.ContainsKey("listener")) { rootPage.NotifyUser("This step has already been executed. Please move to the next one.", NotifyType.ErrorMessage); return; } DatagramSocket listener = new DatagramSocket(); listener.MessageReceived += MessageReceived; // Save the socket, so subsequent steps can use it. CoreApplication.Properties.Add("listener", listener); // Start listen operation. try { await listener.BindServiceNameAsync(ServiceNameForListener.Text); rootPage.NotifyUser("Listening", NotifyType.StatusMessage); } catch (Exception exception) { CoreApplication.Properties.Remove("listener"); // If this is an unknown status it means that the error is fatal and retry will likely fail. if (SocketError.GetStatus(exception.HResult) == SocketErrorStatus.Unknown) { throw; } rootPage.NotifyUser("Start listening failed with error: " + exception.Message, NotifyType.ErrorMessage); } } async void MessageReceived(DatagramSocket socket, DatagramSocketMessageReceivedEventArgs eventArguments) { object outObj; if (CoreApplication.Properties.TryGetValue("remotePeer", out outObj)) { EchoMessage((RemotePeer)outObj, eventArguments); return; } // We do not have an output stream yet so create one. try { IOutputStream outputStream = await socket.GetOutputStreamAsync(eventArguments.RemoteAddress, eventArguments.RemotePort); // It might happen that the OnMessage was invoked more than once before the GetOutputStreamAsync completed. // In this case we will end up with multiple streams - make sure we have just one of it. RemotePeer peer; lock (this) { if (CoreApplication.Properties.TryGetValue("remotePeer", out outObj)) { peer = (RemotePeer)outObj; } else { peer = new RemotePeer(outputStream, eventArguments.RemoteAddress, eventArguments.RemotePort); CoreApplication.Properties.Add("remotePeer", peer); } } EchoMessage(peer, eventArguments); } catch (Exception exception) { // If this is an unknown status it means that the error is fatal and retry will likely fail. if (SocketError.GetStatus(exception.HResult) == SocketErrorStatus.Unknown) { throw; } NotifyUserFromAsyncThread("Connect failed with error: " + exception.Message, NotifyType.ErrorMessage); } } async void EchoMessage(RemotePeer peer, DatagramSocketMessageReceivedEventArgs eventArguments) { if (!peer.IsMatching(eventArguments.RemoteAddress, eventArguments.RemotePort)) { // In the sample we are communicating with just one peer. To communicate with multiple peers application // should cache output streams (i.e. by using a hash map), because creating an output stream for each // received datagram is costly. Keep in mind though, that every cache requires logic to remove old // or unused elements; otherwise cache turns into a memory leaking structure. NotifyUserFromAsyncThread(String.Format("Got datagram from {0}:{1}, but already 'connected' to {3}", eventArguments.RemoteAddress, eventArguments.RemotePort, peer), NotifyType.ErrorMessage); } try { await peer.OutputStream.WriteAsync(eventArguments.GetDataReader().DetachBuffer()); } catch (Exception exception) { // If this is an unknown status it means that the error is fatal and retry will likely fail. if (SocketError.GetStatus(exception.HResult) == SocketErrorStatus.Unknown) { throw; } NotifyUserFromAsyncThread("Send failed with error: " + exception.Message, NotifyType.ErrorMessage); } } private void NotifyUserFromAsyncThread(string strMessage, NotifyType type) { var ignore = Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => rootPage.NotifyUser(strMessage, type)); } } class RemotePeer { IOutputStream outputStream; HostName hostName; String port; public RemotePeer(IOutputStream outputStream, HostName hostName, String port) { this.outputStream = outputStream; this.hostName = hostName; this.port = port; } public bool IsMatching(HostName hostName, String port) { return (this.hostName == hostName && this.port == port); } public IOutputStream OutputStream { get { return outputStream; } } public override String ToString() { return hostName + port; } } }
Create a listener and start listening to a service name (port)
The code in this section creates a listener and starts listening. There are also functions added to handle events when the user requests that the listener bind to an IP address and UDP port, accept a connection, and read data sent from the client.
Note Although this specific example is self-contained (client and server are in the same app), you would normally have separate client and server apps.
For a Visual C# sample, open the cs folder and open a new StartListener.cs file. For a Visual C++ sample, open the cpp folder and open a new StartListener.cpp file.
using System; using Windows.ApplicationModel.Core; using Windows.Networking; using Windows.Networking.Sockets; using Windows.Storage.Streams; using Windows.UI.Core; using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; using Windows.UI.Xaml.Navigation; namespace DatagramSocketBasic { /// <summary> /// A page for first scenario. /// </summary> public sealed partial class Scenario1 : SDKTemplate.Common.LayoutAwarePage { // A pointer back to the main page. // This is needed if you want to call methods in MainPage such // as NotifyUser() MainPage rootPage = MainPage.Current; public Scenario1() { this.InitializeComponent(); } /// <summary> /// Invoked when this page is about to be displayed in a Frame. /// </summary> /// <param name="e">Event data that describes how this page was reached. The Parameter /// property is typically used to configure the page.</param> protected override void OnNavigatedTo(NavigationEventArgs e) { } /// <summary> /// This is the click handler for the 'StartListener' button. /// </summary> /// <param name="sender">Object for which the event was generated.</param> /// <param name="e">Event's parameters.</param> private async void StartListener_Click(object sender, RoutedEventArgs e) { if (String.IsNullOrEmpty(ServiceNameForListener.Text)) { rootPage.NotifyUser("Please provide a service name.", NotifyType.ErrorMessage); return; } if (CoreApplication.Properties.ContainsKey("listener")) { rootPage.NotifyUser("This step has already been executed. Please move to the next one.", NotifyType.ErrorMessage); return; } DatagramSocket listener = new DatagramSocket(); listener.MessageReceived += MessageReceived; // Save the socket, so subsequent steps can use it. CoreApplication.Properties.Add("listener", listener); // Start listen operation. try { await listener.BindServiceNameAsync(ServiceNameForListener.Text); rootPage.NotifyUser("Listening", NotifyType.StatusMessage); } catch (Exception exception) { CoreApplication.Properties.Remove("listener"); // If this is an unknown status it means that the error is fatal and retry will likely fail. if (SocketError.GetStatus(exception.HResult) == SocketErrorStatus.Unknown) { throw; } rootPage.NotifyUser("Start listening failed with error: " + exception.Message, NotifyType.ErrorMessage); } } async void MessageReceived(DatagramSocket socket, DatagramSocketMessageReceivedEventArgs eventArguments) { object outObj; if (CoreApplication.Properties.TryGetValue("remotePeer", out outObj)) { EchoMessage((RemotePeer)outObj, eventArguments); return; } // We do not have an output stream yet so create one. try { IOutputStream outputStream = await socket.GetOutputStreamAsync(eventArguments.RemoteAddress, eventArguments.RemotePort); // It might happen that the OnMessage was invoked more than once before the GetOutputStreamAsync completed. // In this case we will end up with multiple streams - make sure we have just one of it. RemotePeer peer; lock (this) { if (CoreApplication.Properties.TryGetValue("remotePeer", out outObj)) { peer = (RemotePeer)outObj; } else { peer = new RemotePeer(outputStream, eventArguments.RemoteAddress, eventArguments.RemotePort); CoreApplication.Properties.Add("remotePeer", peer); } } EchoMessage(peer, eventArguments); } catch (Exception exception) { // If this is an unknown status it means that the error is fatal and retry will likely fail. if (SocketError.GetStatus(exception.HResult) == SocketErrorStatus.Unknown) { throw; } NotifyUserFromAsyncThread("Connect failed with error: " + exception.Message, NotifyType.ErrorMessage); } } async void EchoMessage(RemotePeer peer, DatagramSocketMessageReceivedEventArgs eventArguments) { if (!peer.IsMatching(eventArguments.RemoteAddress, eventArguments.RemotePort)) { // In the sample we are communicating with just one peer. To communicate with multiple peers application // should cache output streams (i.e. by using a hash map), because creating an output stream for each // received datagram is costly. Keep in mind though, that every cache requires logic to remove old // or unused elements; otherwise cache turns into a memory leaking structure. NotifyUserFromAsyncThread(String.Format("Got datagram from {0}:{1}, but already 'connected' to {3}", eventArguments.RemoteAddress, eventArguments.RemotePort, peer), NotifyType.ErrorMessage); } try { await peer.OutputStream.WriteAsync(eventArguments.GetDataReader().DetachBuffer()); } catch (Exception exception) { // If this is an unknown status it means that the error is fatal and retry will likely fail. if (SocketError.GetStatus(exception.HResult) == SocketErrorStatus.Unknown) { throw; } NotifyUserFromAsyncThread("Send failed with error: " + exception.Message, NotifyType.ErrorMessage); } } private void NotifyUserFromAsyncThread(string strMessage, NotifyType type) { var ignore = Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => rootPage.NotifyUser(strMessage, type)); } } class RemotePeer { IOutputStream outputStream; HostName hostName; String port; public RemotePeer(IOutputStream outputStream, HostName hostName, String port) { this.outputStream = outputStream; this.hostName = hostName; this.port = port; } public bool IsMatching(HostName hostName, String port) { return (this.hostName == hostName && this.port == port); } public IOutputStream OutputStream { get { return outputStream; } } public override String ToString() { return hostName + port; } } }
Create the socket and connect to a remote endpoint
The code in this step adds a function to create the socket and connect to the remote endpoint, typically a server, using the DatagramSocket.ConnectAsync method. A function is added to handle when a message is received by the client. A function is also added to handle cases when there is an error when the client tries to make a connection.
For a Visual C# sample, open the cs folder and open a new ConnectToListener.cs file. For a Visual C++ sample, open the cpp folder and open a new ConnectToListener.cpp file.
using System; using Windows.ApplicationModel.Core; using Windows.Networking; using Windows.Networking.Sockets; using Windows.Storage.Streams; using Windows.UI.Core; using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; using Windows.UI.Xaml.Navigation; namespace DatagramSocketBasic { /// <summary> /// A page for second scenario. /// </summary> public sealed partial class Scenario2 : SDKTemplate.Common.LayoutAwarePage { // A pointer back to the main page. This is needed if you want to call methods in MainPage such // as NotifyUser() MainPage rootPage = MainPage.Current; public Scenario2() { this.InitializeComponent(); } /// <summary> /// Invoked when this page is about to be displayed in a Frame. /// </summary> /// <param name="e">Event data that describes how this page was reached. The Parameter /// property is typically used to configure the page.</param> protected override void OnNavigatedTo(NavigationEventArgs e) { } /// <summary> /// This is the click handler for the 'ConnectSocket' button. /// </summary> /// <param name="sender">Object for which the event was generated.</param> /// <param name="e">Event's parameters.</param> private async void ConnectSocket_Click(object sender, RoutedEventArgs e) { if (String.IsNullOrEmpty(ServiceNameForConnect.Text)) { rootPage.NotifyUser("Please provide a service name.", NotifyType.ErrorMessage); return; } // By default 'HostNameForConnect' is disabled and host name validation is not required. When enabling the text // box validating the host name is required since it was received from an untrusted source (user input). // The host name is validated by catching ArgumentExceptions thrown by the HostName constructor for invalid // input. // Note that when enabling the text box users may provide names for hosts on the intErnet that require the // "Internet (Client)" capability. HostName hostName; try { hostName = new HostName(HostNameForConnect.Text); } catch (ArgumentException) { rootPage.NotifyUser("Error: Invalid host name.", NotifyType.ErrorMessage); return; } if (CoreApplication.Properties.ContainsKey("clientSocket")) { rootPage.NotifyUser("This step has already been executed. Please move to the next one.", NotifyType.ErrorMessage); return; } DatagramSocket socket = new DatagramSocket(); socket.MessageReceived += MessageReceived; // Save the socket, so subsequent steps can use it. CoreApplication.Properties.Add("clientSocket", socket); rootPage.NotifyUser("Connecting to: " + HostNameForConnect.Text, NotifyType.StatusMessage); try { // Connect to the server (in our case the listener we created in previous step). await socket.ConnectAsync(hostName, ServiceNameForConnect.Text); rootPage.NotifyUser("Connected", NotifyType.StatusMessage); // Mark the socket as connected. Set the value to null, as we care only about the fact that the property is set. CoreApplication.Properties.Add("connected", null); } catch (Exception exception) { // If this is an unknown status it means that the error is fatal and retry will likely fail. if (SocketError.GetStatus(exception.HResult) == SocketErrorStatus.Unknown) { throw; } rootPage.NotifyUser("Connect failed with error: " + exception.Message, NotifyType.ErrorMessage); } } void MessageReceived(DatagramSocket socket, DatagramSocketMessageReceivedEventArgs eventArguments) { try { uint stringLength = eventArguments.GetDataReader().UnconsumedBufferLength; NotifyUserFromAsyncThread("Receive data from remote peer: \"" + eventArguments.GetDataReader().ReadString(stringLength) + "\"", NotifyType.StatusMessage); } catch (Exception exception) { SocketErrorStatus socketError = SocketError.GetStatus(exception.HResult); if (socketError == SocketErrorStatus.ConnectionResetByPeer) { // This error would indicate that a previous send operation resulted in an ICMP "Port Unreachable" message. NotifyUserFromAsyncThread("Peer does not listen on the specific port. Please make sure that you run step 1 first " + "or you have a server properly working on a remote server.", NotifyType.ErrorMessage); } else if (socketError != SocketErrorStatus.Unknown) { NotifyUserFromAsyncThread("Error happened when receiving a datagram: " + socketError.ToString(), NotifyType.ErrorMessage); } else { throw; } } } private void NotifyUserFromAsyncThread(string strMessage, NotifyType type) { var ignore = Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => rootPage.NotifyUser(strMessage, type)); } } }
Send and receive data on the client
The code in this step adds a function to send data to the remote UDP endpoint using methods on the Windows.Storage.Streams.DataWriter class.
For a Visual C# sample, open the cs folder and open a new SendData.cs file. For a Visual C++ sample, open the cpp folder and open a new SendData.cpp file.
using System; using System.Diagnostics; using Windows.ApplicationModel.Core; using Windows.Networking; using Windows.Networking.Sockets; using Windows.Storage.Streams; using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; using Windows.UI.Xaml.Navigation; namespace DatagramSocketBasic { /// <summary> /// A page for third scenario. /// </summary> public sealed partial class Scenario3 : SDKTemplate.Common.LayoutAwarePage { // A pointer back to the main page. This is needed if you want to call methods in MainPage such // as NotifyUser() MainPage rootPage = MainPage.Current; public Scenario3() { this.InitializeComponent(); } /// <summary> /// Invoked when this page is about to be displayed in a Frame. /// </summary> /// <param name="e">Event data that describes how this page was reached. The Parameter /// property is typically used to configure the page.</param> protected override void OnNavigatedTo(NavigationEventArgs e) { } /// <summary> /// This is the click handler for the 'SendHello' button. /// </summary> /// <param name="sender">Object for which the event was generated.</param> /// <param name="e">Event's parameters.</param> private async void SendHello_Click(object sender, RoutedEventArgs e) { if (!CoreApplication.Properties.ContainsKey("connected")) { rootPage.NotifyUser("Please run previous steps before doing this one.", NotifyType.ErrorMessage); return; } object outValue; DatagramSocket socket; if (!CoreApplication.Properties.TryGetValue("clientSocket", out outValue)) { rootPage.NotifyUser("Please run previous steps before doing this one.", NotifyType.ErrorMessage); return; } socket = (DatagramSocket)outValue; // Create a DataWriter if we did not create one yet. Otherwise use one that is already cached. DataWriter writer; if (!CoreApplication.Properties.TryGetValue("clientDataWriter", out outValue)) { writer = new DataWriter(socket.OutputStream); CoreApplication.Properties.Add("clientDataWriter", writer); } else { writer = (DataWriter)outValue; } // Write first the length of the string as UINT32 value followed up by the string. Writing data to the writer will just store data in memory. string stringToSend = "Hello"; writer.WriteString(stringToSend); // Write the locally buffered data to the network. try { await writer.StoreAsync(); SendOutput.Text = "\"" + stringToSend + "\" sent successfully."; } catch (Exception exception) { // If this is an unknown status it means that the error if fatal and retry will likely fail. if (SocketError.GetStatus(exception.HResult) == SocketErrorStatus.Unknown) { throw; } rootPage.NotifyUser("Send failed with error: " + exception.Message, NotifyType.ErrorMessage); } } } }
Close the sockets
The code in this step will close the sockets using the DatagramSocket.Close method. When sockets are closed, all pending operations will end and the error routines are called.
For a Visual C# sample, open the cs folder and open a new SocketClose.cs file. For a Visual C++ sample, open the cpp folder and open a new SocketClose.cpp file.
using System; using System.Diagnostics; using Windows.ApplicationModel.Core; using Windows.Networking; using Windows.Networking.Sockets; using Windows.Storage.Streams; using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; using Windows.UI.Xaml.Navigation; namespace DatagramSocketBasic{ /// <summary> /// A page for fourth scenario. /// </summary> public sealed partial class Scenario4 : SDKTemplate.Common.LayoutAwarePage { // A pointer back to the main page. This is needed if you want to call methods in MainPage such // as NotifyUser() MainPage rootPage = MainPage.Current; public Scenario4() { this.InitializeComponent(); } /// <summary> /// Invoked when this page is about to be displayed in a Frame. /// </summary> /// <param name="e">Event data that describes how this page was reached. The Parameter /// property is typically used to configure the page.</param> protected override void OnNavigatedTo(NavigationEventArgs e) { } /// <summary> /// This is the click handler for the 'CloseSockets' button. /// </summary> /// <param name="sender">Object for which the event was generated.</param> /// <param name="e">Event's parameters.</param> private void CloseSockets_Click(object sender, RoutedEventArgs e) { object outValue; if (CoreApplication.Properties.TryGetValue("clientDataWriter", out outValue)) { // Remove the data writer from the list of application properties as we are about to close it. CoreApplication.Properties.Remove("clientDataWriter"); DataWriter dataWriter = (DataWriter)outValue; // To reuse the socket with other data writer, application has to detach the stream from the writer // before disposing it. This is added for completeness, as this sample closes the socket in // very next block. dataWriter.DetachStream(); dataWriter.Dispose(); } if (CoreApplication.Properties.TryGetValue("clientSocket", out outValue)) { // Remove the socket from the list of application properties as we are about to close it. CoreApplication.Properties.Remove("clientSocket"); DatagramSocket socket = (DatagramSocket)outValue; socket.Dispose(); } if (CoreApplication.Properties.TryGetValue("listener", out outValue)) { // Remove the listener from the list of application properties as we are about to close it. CoreApplication.Properties.Remove("listener"); DatagramSocket listener = (DatagramSocket)outValue; listener.Dispose(); } if (CoreApplication.Properties.ContainsKey("remotePeer")) { // Remove the remote perr from the list of application properties. CoreApplication.Properties.Remove("remotePeer"); } if (CoreApplication.Properties.ContainsKey("connected")) { CoreApplication.Properties.Remove("connected"); } rootPage.NotifyUser("Socket and listener closed", NotifyType.StatusMessage); } } }
Run the application
- To run the app, press F5 in Microsoft Visual Studio Express 2012 for Windows 8 to run the project. Select the buttons in order to start the listener, to connect the client to the listener, to send data, and to close sockets.
Summary and next steps
In this topic, you created an app that uses a UDP datagram socket to make a network connection and send data using a DatagramSocket object. The app also showed how to listen on a UDP port and receive data.
The source code and build files for this topic are available as the DatagramSocket sample.
You can also use a stream socket to make a network connection used to send and receive data. For an example, see How to connecting with a stream socket.
Related topics
Other resources
How to configure network isolation capabilities
How to connect with a stream socket
How to set timeouts on socket operations
How to use advanced socket controls
Troubleshoot and debug network connections
Reference
Samples