How to create and use a TCP socket client app for Windows Phone 8
[ This article is for Windows Phone 8 developers. If you’re developing for Windows 10, see the latest documentation. ]
This topic introduces you to the steps necessary to create a simple TCP socket client app for Windows Phone. A typical scenario is that a client app communicates with a server over a TCP socket connection. For the sake of simplicity, and to keep this topic self-contained, we will use the built-in Simple TCP/IP Services on your computer as the server side of this communication. To create a similar client app using a UDP socket, see How to create and use a UDP socket client app for Windows Phone 8.
Important Note: |
---|
This topic requires the use of a TCP socket server for client communications. The Simple TCP/IP Services feature of Windows is used as the server component in this example. The following procedures provide instruction for enabling the Simple TCP/IP Services feature on your computer. You may prefer to use your own socket server implementation. |
This topic contains the following sections.
- Creating the TCP socket client UI
- Connecting to a TCP socket server
- Sending data to a TCP socket server
- Receiving data from a TCP socket server
- Using the SocketClient class in the app
- Enabling Simple TCP/IP Services on your computer
- Running the TCP socket client app
- Related Topics
Creating the TCP socket client UI
In this section, you create the UI for demonstrating the TCP Socket client functionality.
To create the TCP socket client UI
In Visual Studio, create a new project by selecting the File | New Project menu command.
The New Project window is displayed. Expand the Visual C# templates, and then select the Windows Phone templates.
Select the **Windows Phone App ** template. Fill in the Name with a name of your choice.
Click OK. The Windows Phone platform selection dialog box appears. Select a target version or accept the default.
Click OK. A new project is created, and MainPage.xaml is opened in the Visual Studio designer window.
On MainPage.xaml, remove the XAML code for the StackPanel named “TitlePanel” and replace it with the following code:
<!--TitlePanel contains the name of the application and page title--> <StackPanel x:Name="TitlePanel" Grid.Row="0" Margin="12,17,0,28"> <TextBlock x:Name="ApplicationTitle" Text="TCP Socket Application" Style="{StaticResource PhoneTextNormalStyle}"/> <TextBlock x:Name="PageTitle" Text="Client" Margin="9,-7,0,0" Style="{StaticResource PhoneTextTitle1Style}"/> </StackPanel>
The XAML code adds two TextBlock elements to the app, one for the app title named “ApplicationTitle” and one for the page title named “PageTitle”.
On MainPage.xaml, remove the XAML code for the Grid named “ContentPanel” and replace it with the following code:
<!--ContentPanel - place additional content here--> <Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,-8,12,8"> <Grid.ColumnDefinitions> <ColumnDefinition Width="Auto"/> <!-- Fit to content --> <ColumnDefinition Width="Auto"/> <!-- Fit to content --> <ColumnDefinition Width="Auto"/> <!-- Fit to content --> <ColumnDefinition Width="*"/> <!-- Take up remaining space --> </Grid.ColumnDefinitions> <Grid.RowDefinitions> <RowDefinition Height="Auto"/> <!-- Fit to content --> <RowDefinition Height="Auto"/> <!-- Fit to content --> <RowDefinition Height="Auto"/> <!-- Fit to content --> <RowDefinition Height="*"/> <!-- Take up remaining space --> </Grid.RowDefinitions> <!-- Grid Row 0: Remote Host Input Field >--> <TextBlock Grid.Row="0" Grid.Column="0" Text="Host Name:" VerticalAlignment="Center" HorizontalAlignment="Center" FontSize="{StaticResource PhoneFontSizeNormal}" /> <TextBox x:Name="txtRemoteHost" Grid.Row="0" Grid.Column="1" Height="70" Width="200" VerticalAlignment="Top" HorizontalAlignment="Left" FontSize="{StaticResource PhoneFontSizeNormal}" /> <!-- Grid Row 1: Echo >--> <!-- TextBlock for Echo command label--> <TextBlock Grid.Row="1" Grid.Column="0" Text="Text To Echo:" VerticalAlignment="Center" HorizontalAlignment="Center" FontSize="{StaticResource PhoneFontSizeNormal}" /> <!-- TextBox for Echo command text input--> <TextBox x:Name="txtInput" Grid.Row="1" Grid.Column="1" Height="70" Width="200" VerticalAlignment="Top" HorizontalAlignment="Left" FontSize="{StaticResource PhoneFontSizeNormal}" /> <!-- Button to the right of the input textbox for the Echo command >--> <Button x:Name="btnEcho" Grid.Row="1" Grid.Column="2" Height="70" Width="120" Content="Echo" FontSize="{StaticResource PhoneFontSizeNormal}" Click="btnEcho_Click"/> <!-- Grid Row 2: Quote of the Day--> <!-- Button for the Quote command >--> <Button x:Name="btnGetQuote" Grid.Row="2" Grid.ColumnSpan="4" Height="70" Content="Get Quote of the Day" FontSize="{StaticResource PhoneFontSizeNormal}" Click="btnGetQuote_Click"/> <!-- Grid Row 3: Output--> <!-- Output TextBox named 'txtOutput' >--> <TextBox x:Name="txtOutput" Grid.Row="3" Grid.ColumnSpan="4" Background="Black" BorderBrush="Green" AcceptsReturn="False" Foreground="LightGray" FontFamily="Courier New" IsHitTestVisible="False" FontSize="{StaticResource PhoneFontSizeSmall}" TextWrapping="Wrap" /> </Grid>
The XAML code creates a Grid element to contain all other elements. It first defines the Grid to have three rows and three columns. Next, the XAML defines a TextBox for the data input of the remote host name and a TextBlock as a label for this field. The next row of the Grid consists of another TextBox for more data input from the user, and two Button elements with their Click event handlers assigned. Finally, the XAML defines another TextBox to display the output from the app. You implement the btnEcho_Click and btnGetQuote_Click methods in the following sections. The layout in the designer should look like this.
Connecting to a TCP socket server
In this section, you create a socket and connect to the server by using the System.Net.Sockets API. The calls to the System.Net.Sockets API are encapsulated in a SocketClient class for clarity.
To connect to a TCP socket server
Create a new class by selecting the project in the Solution Explorer window, right-clicking it and selecting Add | Class … from the context menu. This brings up the Add New Item dialog with the class template selected. Change the name of the class in the Name field to “SocketClient” and click Add. A new class with the name “SocketClient” is created in the project.
Open SocketClient.cs and add the following using directives to the top of the page:
using System.Net.Sockets; using System.Threading; using System.Text;
Define the following variables at the top of the SocketClient class:
// Cached Socket object that will be used by each call for the lifetime of this class Socket _socket = null; // Signaling object used to notify when an asynchronous operation is completed static ManualResetEvent _clientDone = new ManualResetEvent(false); // Define a timeout in milliseconds for each asynchronous call. If a response is not received within this // timeout period, the call is aborted. const int TIMEOUT_MILLISECONDS = 5000; // The maximum size of the data buffer to use with the asynchronous socket methods const int MAX_BUFFER_SIZE = 2048;
The _socket variable is used to store the Socket object after it is created. The _clientDone variable is a ManualResetEvent that is used to coordinate the asynchronous calls that are invoked through the System.Net.Sockets API.
In SocketClient.cs, add the following method, which creates the TCP socket and sends an asynchronous connect request to the server. The response of this action is handled by an inline callback.
/// <summary> /// Attempt a TCP socket connection to the given host over the given port /// </summary> /// <param name="hostName">The name of the host</param> /// <param name="portNumber">The port number to connect</param> /// <returns>A string representing the result of this connection attempt</returns> public string Connect(string hostName, int portNumber) { string result = string.Empty; // Create DnsEndPoint. The hostName and port are passed in to this method. DnsEndPoint hostEntry = new DnsEndPoint(hostName, portNumber); // Create a stream-based, TCP socket using the InterNetwork Address Family. _socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); // Create a SocketAsyncEventArgs object to be used in the connection request SocketAsyncEventArgs socketEventArg = new SocketAsyncEventArgs(); socketEventArg.RemoteEndPoint = hostEntry; // Inline event handler for the Completed event. // Note: This event handler was implemented inline in order to make this method self-contained. socketEventArg.Completed += new EventHandler<SocketAsyncEventArgs>(delegate(object s, SocketAsyncEventArgs e) { // Retrieve the result of this request result = e.SocketError.ToString(); // Signal that the request is complete, unblocking the UI thread _clientDone.Set(); }); // Sets the state of the event to nonsignaled, causing threads to block _clientDone.Reset(); // Make an asynchronous Connect request over the socket _socket.ConnectAsync(socketEventArg); // Block the UI thread for a maximum of TIMEOUT_MILLISECONDS milliseconds. // If no response comes back within this time then proceed _clientDone.WaitOne(TIMEOUT_MILLISECONDS); return result; }
This method introduces the following concepts:
Create an EndPoint: The DnsEndPoint class contains a host name or an IP address and remote port information needed by an app to connect to a service on a host.
Create a Socket: The Socket object is instantiated with the AddressFamily parameter set to AddressFamily..::.InterNetwork, the SocketType parameter is set to SocketType..::.Stream, and the ProtocolType parameter is set to ProtocolType..::.Tcp. The AddressFamily parameter specifies the addressing scheme that the Socket uses to resolve an address. For example, AddressFamily..::.InterNetwork indicates that an IP version 4 address is expected when a Socket connects to an endpoint. Note that a socket connection has not yet been established. We are simply setting up our socket to be a stream-based socket using the TCP protocol. The actual connection attempt to the server takes place in the ConnectAsync call.
Important Note: |
---|
InterNetwork is the only valid value for the AddressFamily parameter in the Socket constructor in Windows Phone OS 7.1. Selecting any other member of the AddressFamily enumeration will result in an error. |
Note
When an app is reactivated from a dormant state, there is no need to create a new instance of a socket. To establish a connection again, call ConnectAsync. If any connection preferences or requirements were defined for the socket, they will be maintained during the dormant state. For details on execution model and app states, see App activation and deactivation for Windows Phone 8
Sending data to a TCP socket server
In this section, you add a Send method to the SocketClient class in order to send data to the server.
To send data to a TCP socket server
In SocketClient.cs, add the following method.
/// <summary> /// Send the given data to the server using the established connection /// </summary> /// <param name="data">The data to send to the server</param> /// <returns>The result of the Send request</returns> public string Send(string data) { string response = "Operation Timeout"; // We are re-using the _socket object initialized in the Connect method if (_socket != null) { // Create SocketAsyncEventArgs context object SocketAsyncEventArgs socketEventArg = new SocketAsyncEventArgs(); // Set properties on context object socketEventArg.RemoteEndPoint = _socket.RemoteEndPoint; socketEventArg.UserToken = null; // Inline event handler for the Completed event. // Note: This event handler was implemented inline in order // to make this method self-contained. socketEventArg.Completed += new EventHandler<SocketAsyncEventArgs>(delegate(object s, SocketAsyncEventArgs e) { response = e.SocketError.ToString(); // Unblock the UI thread _clientDone.Set(); }); // Add the data to be sent into the buffer byte[] payload = Encoding.UTF8.GetBytes(data); socketEventArg.SetBuffer(payload, 0, payload.Length); // Sets the state of the event to nonsignaled, causing threads to block _clientDone.Reset(); // Make an asynchronous Send request over the socket _socket.SendAsync(socketEventArg); // Block the UI thread for a maximum of TIMEOUT_MILLISECONDS milliseconds. // If no response comes back within this time then proceed _clientDone.WaitOne(TIMEOUT_MILLISECONDS); } else { response = "Socket is not initialized"; } return response; }
This method illustrates the following concepts:
Create a context object: This object is the context object for whatever asynchronous method is being called (in this case SendAsync). The data buffer, callback method, and various other context-specific data is set on this object and then passed to the asynchronous call. Once the call is complete, this object can be examined for completion status and operation result..
Set the buffer on the context object: The data to be sent to the server is placed in the buffer of the SocketAsyncEventArgs object as an array of bytes.
Define a callback for the Completed event: This method uses a delegate in order to handle the Completed event from the SendAsync call.
Wait until the call is complete: In this example, the WaitOne method causes the UI Thread to block until the Completed event fires or the call times out.
Receiving data from a TCP socket server
In this section, you add a Receive method to the SocketClient class in order to send data to the server.
To receive data from a TCP socket server
In SocketClient.cs, add the following code.
/// <summary> /// Receive data from the server using the established socket connection /// </summary> /// <returns>The data received from the server</returns> public string Receive() { string response = "Operation Timeout"; // We are receiving over an established socket connection if (_socket != null) { // Create SocketAsyncEventArgs context object SocketAsyncEventArgs socketEventArg = new SocketAsyncEventArgs(); socketEventArg.RemoteEndPoint = _socket.RemoteEndPoint; // Setup the buffer to receive the data socketEventArg.SetBuffer(new Byte[MAX_BUFFER_SIZE], 0, MAX_BUFFER_SIZE); // Inline event handler for the Completed event. // Note: This even handler was implemented inline in order to make // this method self-contained. socketEventArg.Completed += new EventHandler<SocketAsyncEventArgs>(delegate(object s, SocketAsyncEventArgs e) { if (e.SocketError == SocketError.Success) { // Retrieve the data from the buffer response = Encoding.UTF8.GetString(e.Buffer, e.Offset, e.BytesTransferred); response = response.Trim('\0'); } else { response = e.SocketError.ToString(); } _clientDone.Set(); }); // Sets the state of the event to nonsignaled, causing threads to block _clientDone.Reset(); // Make an asynchronous Receive request over the socket _socket.ReceiveAsync(socketEventArg); // Block the UI thread for a maximum of TIMEOUT_MILLISECONDS milliseconds. // If no response comes back within this time then proceed _clientDone.WaitOne(TIMEOUT_MILLISECONDS); } else { response = "Socket is not initialized"; } return response; } /// <summary> /// Closes the Socket connection and releases all associated resources /// </summary> public void Close() { if (_socket != null) { _socket.Close(); } }
This method illustrates the following concepts:
Create a context object: This object is the context object for whatever asynchronous method is being called (in this case, ReceiveAsync). The data buffer, callback method, and various other context-specific data is set on this object and then passed to the asynchronous call. Once the call is complete, this object can be examined for completion status and operation result.
Set the buffer on the context object: The data to be received from the server is placed in the buffer of the SocketAsyncEventArgs object as an array of bytes.
Define a callback for the Completed event: This method uses a delegate to handle the Completed event from the ReceiveAsync call.
Wait until the call is complete: In this example, the Connect method blocks until the Completed event fires or the call times out.
Close method. In order for the client app to explicitly close the Socket that was created, a method Close is added to the SocketClient class.
Using the SocketClient class in the app
In the previous sections a SocketClient class was implemented to encapsulate all the operations necessary to communicate with the server over a stream-based TCP socket. Now we return to the client app and add the functionality necessary to use this SocketClient class. This app communicates with the Echo service and the Quote of the Day (QOTD) service on your computer. The Echo service echoes back whatever data was sent to it. The Quote of the Day service returns a quotation as one or more lines of text in a message. We add the event handlers for the Click events from the btnEcho and btnGetQuote buttons. We also add some methods to perform output to the txtOutputTextBox and to validate input from the txtRemoteHost and txtInputTextBox elements.
To use the SocketClient class in the app
At the top of the MainPage.xaml.cs class, define the following constants:
// Constants const int ECHO_PORT = 7; // The Echo protocol uses port 7 in this sample const int QOTD_PORT = 17; // The Quote of the Day (QOTD) protocol uses port 17 in this sample
In MainPage.xaml.cs, add the following code.
/// <summary> /// Handle the btnEcho_Click event by sending text to the echo server /// and outputting the response /// </summary> private void btnEcho_Click(object sender, RoutedEventArgs e) { // Clear the log ClearLog(); // Make sure we can perform this action with valid data if (ValidateRemoteHost() && ValidateInput()) { // Instantiate the SocketClient SocketClient client = new SocketClient(); // Attempt to connect to the echo server Log(String.Format("Connecting to server '{0}' over port {1} (echo) ...", txtRemoteHost.Text, ECHO_PORT), true); string result = client.Connect(txtRemoteHost.Text, ECHO_PORT); Log(result, false); // Attempt to send our message to be echoed to the echo server Log(String.Format("Sending '{0}' to server ...", txtInput.Text), true); result = client.Send(txtInput.Text); Log(result, false); // Receive a response from the echo server Log("Requesting Receive ...", true); result = client.Receive(); Log(result, false); // Close the socket connection explicitly client.Close(); } } /// <summary> /// Handle the btnEcho_Click event by receiving text from the Quote of /// the Day (QOTD) server and outputting the response /// </summary> private void btnGetQuote_Click(object sender, RoutedEventArgs e) { // Clear the log ClearLog(); // Make sure we can perform this action with valid data if (ValidateRemoteHost()) { // Instantiate the SocketClient object SocketClient client = new SocketClient(); // Attempt connection to the Quote of the Day (QOTD) server Log(String.Format("Connecting to server '{0}' over port {1} (Quote of the Day) ...", txtRemoteHost.Text, QOTD_PORT), true); string result = client.Connect(txtRemoteHost.Text, QOTD_PORT); Log(result, false); // Note: The QOTD protocol is not expecting data to be sent to it. // So we omit a send call in this example. // Receive response from the QOTD server Log("Requesting Receive ...", true); result = client.Receive(); Log(result, false); // Close the socket conenction explicitly client.Close(); } } #region UI Validation /// <summary> /// Validates the txtInput TextBox /// </summary> /// <returns>True if the txtInput TextBox contains valid data, otherwise /// False. ///</returns> private bool ValidateInput() { // txtInput must contain some text if (String.IsNullOrWhiteSpace(txtInput.Text)) { MessageBox.Show("Please enter some text to echo"); return false; } return true; } /// <summary> /// Validates the txtRemoteHost TextBox /// </summary> /// <returns>True if the txtRemoteHost contains valid data, /// otherwise False /// </returns> private bool ValidateRemoteHost() { // The txtRemoteHost must contain some text if (String.IsNullOrWhiteSpace(txtRemoteHost.Text)) { MessageBox.Show("Please enter a host name"); return false; } return true; } #endregion #region Logging /// <summary> /// Log text to the txtOutput TextBox /// </summary> /// <param name="message">The message to write to the txtOutput TextBox</param> /// <param name="isOutgoing">True if the message is an outgoing (client to server) /// message, False otherwise. /// </param> /// <remarks>We differentiate between a message from the client and server /// by prepending each line with ">>" and "<<" respectively.</remarks> private void Log(string message, bool isOutgoing) { string direction = (isOutgoing) ? ">> " : "<< "; txtOutput.Text += Environment.NewLine + direction + message; } /// <summary> /// Clears the txtOutput TextBox /// </summary> private void ClearLog() { txtOutput.Text = String.Empty; } #endregion
The Echo operation is implemented in the above code, as follows:
Handle the Click event on the btnEcho button: The Echo operation is implemented in the btnEcho_Click event. The Echo operation consists of a Connect, Send, and Receive operation on the socket. This is in contrast to the GetQuote operation, which uses just Connect and Receive operations.
SocketClient object is scoped to the method: This was done for the sake of simplicity and to keep the Echo and GetQuote calls self-contained. An instance of the SocketClient class could also be stored at the class scope and then reused.
Port numbers as constants: The client connects to the Echo Protocol in this sample using the well-known port number 7. This port number is defined as ECHO_PORT in the MainPage.xaml.cs class.
Connect: This is done through a call to the Connect method of SocketClient, passing in the hostname received in txtRemoteHost and the port number defined in the ECHO_PORT constant. The input is validated and feedback from this operation is written to txtOutput by using the Log method.
Send: The text in the txtInput field is sent to the server by using the Send method on the SocketClient object. The input is validated and feedback from this operation is written to txtOutput by using the Log method.
Receive: Next, the data from the echo server is received by calling the Receive method on the SocketClient object. This is written to the txtOutput by using the Log method.
**Close:**Finally, the socket is closed by explicitly calling the Close method of the SocketClient object.
The GetQuote operation is implemented in the above code as follows:
Handle the Click event on the btnGetQuote button: The GetQuote operation is implemented in the btnGetQuote_Click event. The GetQuote operation consists of a Connect and Receive operation on the socket. This is in contrast to the Echo operation, which uses the Connect, Send, and Receive operations.
SocketClient object is scoped to the method: This was done for the sake of simplicity and to keep the Echo and GetQuote calls self-contained. An instance of the SocketClient class could also be stored at the class scope and then reused.
Port numbers as constants: The client connects to the Quote of the Day (QOTD) Protocol in this sample using the well-known port number 17. This port number is defined as QOTD_PORT in the MainPage.xaml.cs class.
Connect: This is done through a call to the Connect method of SocketClient, passing in the hostname received in txtRemoteHost and the port number defined in the QOTD_PORT constant. The input is validated, and feedback from this operation is written to the txtOutput by using the Log method.
Send: There is no send request made in the case of Quote of the Day. We simply pull the quote from the server by using a Receive request.
Receive: Next, the data from the quote of the day server is received by calling the Receive method on the SocketClient object. This is written to txtOutput by using the Log method.
Close: Finally, the socket is closed by explicitly calling the Close method of the SocketClient object.
Enabling Simple TCP/IP Services on your computer
This topic uses the Echo and Quote of the Day services available on every Windows computer. Simple TCP/IP Services is a feature available on all versions of Windows. This feature offers the following services: Character Generator, Daytime, Discard, Echo, and Quote of the Day. Each service is accessible over TCP and each is assigned a default port over which to communicate. The default service to port mapping is as follows.
Service Name |
Description |
Port |
---|---|---|
Echo |
Echoes back data from any messages it receives on this server port. Echo can be useful as a network debugging and monitoring tool. |
7 |
Quote of the Day |
Returns a quotation as one or more lines of text in a message. Quotations are taken at random from the following file: %SYSTEMROOT%\System32\Drivers\Etc\Quotes. A sample quote file is installed with Simple TCP/IP Services. If this file is missing, the quote service fails. |
17 |
Daytime |
Returns messages containing the day of the week, month, day, year, current time (in hh:mm:ss format), and time zone information. Some programs can use the output from this service for debugging or monitoring variations in system clock time or on a different host. |
13 |
Character Generator |
Sends data made up of the set of 95 printable ASCII characters. Useful as a debugging tool for testing or troubleshooting line printers. |
19 |
Discard |
Discards all messages received on this port without response or acknowledgment. Can serve as a null port for receiving and routing TCP/IP test messages during network setup and configuration or, in some cases, can be used by programs as a message discard function. |
9 |
To enable Simple TCP/IP Services on your computer
In Control Panel, open Programs and Features.
Click Turn Windows Features On or Off.
In the Windows Features dialog, select the Simple TCP/IP Services check box to enable this feature, and then click OK.
Important Note: |
---|
To perform this procedure, you must be a member of the Administrators group or the Network Configuration Operators group on the local computer. |
- In the Services list on your computer, verify that the Simple TCP/IP Services service is started. If not, start the service manually. For more information about starting a service, see Configure How a Service Is Started.
Running the TCP socket client app
This section describes how to run the app that was produced in this topic.
To run the TCP socket client app
On a device, run the app by selecting the Debug | Start Debugging menu command.
To try the Echo feature:
Add a host name in the Host Name field.
Add the text that you want to send in the Text to Echo field.
Click the Echo button.
In the output window, you should see the communication round-trips between the client on the phone and the server, including any errors that may have occurred.
To try the Quote of the Day feature:
Add a host name in the Host Name field.
Click the Get Quote button.
In the output window, you should see the communication round-trips between the client on the phone and the server, including any errors that may have occurred.
See Also
Reference
Microsoft.Phone.Net.NetworkInformation
Other Resources
How to create and use a UDP socket client app for Windows Phone 8
How to send and receive data in a multicast group for Windows Phone 8
Network and network interface information for Windows Phone 8