If you read my first post, you know that I just recently started developing for Windows 8. Well, for the past week I have been building a simple IRC client that is helping me get my feet wet with Windows 8 App development and the Windows 8 SDK. Building an IRC client is a perfect starting point on any platform, and specifically on Windows 8 since it allows me to learn and implement many of the new features in Windows 8 (such as the Search and Share Charm, Live Tiles, Toast Notifications and many more) and take advantage of the brand new APIs. I have been having a tremendous amount of fun developing the app and seeing the progress throughout the week, and I’m very excited to share my experience in a series of posts.
Before I get started on the first topic, I want to share some photos of the IRC client. Here are a couple screenshots from the day I started to where I am now:
Even though there are many (many, many) improvements and features waiting to be done, I’m trilled about what I have accomplished so far.
There are a huge number of cool and exciting new features that Windows 8 offers, bit if you've made the obvious decision to build a Windows 8 App, chances are your app will first need to connect to a remote service in some way before implementing anything else. That is why I’m starting off writing about (what I believe any Windows 8 developer should know) networking with the Windows 8 API. Of course, depending on the service and network protocol, there are different ways to accomplish the task, and the Windows 8 API makes it very easy to get your app connected in minutes, but I will specifically talk about communicating over a TCP connection.
To get started, I used a lot of readily available online resources that I think you should at least open in a new tab:
The Windows 8 API defines a very useful object called StreamSocket that handles most of the TCP communication we need. StreamSocket and related classes belong to the Windows.Networking and Windows.Networking.Streams namespaces which also offer many other capabilities and classes such as connecting to a Web Service, reacting to Network Status changes, Background Data Transfer and more. If you want to learn more about other networking features with the Networking Namespace, I encourage you to visit http://msdn.microsoft.com/en-us/library/windows/apps/xaml/BR229573(v=win.10).aspx .
Let’s get started. To initiate a TCP connection to a host, we will first need to know the Hostname or IP address of the machine and the remote port the service is attached to. Once we know the hostname and the port, we only need few lines of code to initiate a connection:
1: StreamSocket socket = new StreamSocket();
2: HostName hostname = new HostName("myremoteserver.net");
3: await socket.ConnectAsync(hostname, "80");
Congratulations, you are now connected to the target host. But what if the server is unreachable, or can’t receive any connections? To account for those scenarios, we use the wonderful try-catch statement. When a connection is unsuccessful, an exception is thrown which needs to be translated to a SocketErrorStatus that indicates the SocketError. Here is an example with the HostNotFound SocketErrorStatus:
3: StreamSocket socket = new StreamSocket();
4: HostName hostname = new HostName("myremoteserver.net");
5: await socket.ConnectAsync(hostname, "80");
7: catch (Exception exception)
9: switch (SocketError.GetStatus(exception.HResult))
11: case SocketErrorStatus.HostNotFound:
12: // Handle HostNotFound Error
15: // Handle Unknown Error
All possible SocketErrorStatus Members can be used in the same way. Their definitions can be found here.
Great, now we can detect when a host is unreachable (or Connection Times out, etc) and we can react appropriately. Let’s move on to talking to the host service when the connection is successful.
The StreamSocket class is equipped with the InputStream and OutputStream Objects that are used to receive and send data accordingly. To make our lives easier (and shorten the code we have to write) we will use a DataReader and DataWriter to read and write to the data streams. The DataReader and DataWriter can be attached to a stream to simplify reading and writing, and to use them we need to instantiate them with the stream as input.
1: // the Windows.Storage.Streams Namespace contains
2: // the DataReader and DataWriter Objects
3: using Windows.Storage.Streams;
5: // add after calling ConnectAsync on the StreamSocket Object
6: DataReader reader = new DataReader(socket.InputStream);
7: DataWriter writer = new DataWriter(socket.OutputStream);
To send data to the host, first we need to write the data to the DataWriter by calling one of the DataWriter.Write* methods, and finally committing the data by calling the StoreAsync method. The DataWriter handles the rest.
1: // write a string to the OutputStream
2: writer.WriteString("some data to send to host");
4: // commit and send the data in the OutputStream
5: await writer.StoreAsync();
And the data has been sent.
Receiving data is very similar to sending data, but with few differences. To receive data, we need to tell the DataReader how much data to receive and we need to wait for the data to be available. However, most of the times we don’t know how much data will be sent. Luckily, we can tell the DataReader to receive only the available data (up to a certain count) by setting the InputStreamOptions to Partial. And after the raw data has been received, we can call one of the DataReader.Read* methods to retrieve it:
1: // container for the received Data
2: // in this case a string
3: string receivedData;
5: // set the DataReader to only wait for available data
6: reader.InputStreamOptions = InputStreamOptions.Partial;
8: // wait for the available data up to 512 bytes
9: // count is the number of actually received bytes
10: var count = await reader.LoadAsync(512);
12: // read the data as a string and store it in our container
13: if (count > 0)
14: receivedData = reader.ReadString(count);
Now you can process the received data and start receiving or sending data again.
Most of the work is already done and we only need to process the data we receive. Because of the IRC protocol, the TCP client is required to listen for incoming data constantly, so I created a new Event that would be called each time a new string line was available and go back to listening for data. I created a new IRCClient class that would handle the IRC implementation and I created a method that I attached to the TCP Client event to handle each line as it is received. This way, all the IRC work is done by the IRCClient class, and the TCP client can go back to start listening for data again without delay.