In this tutorial, I will show you how can you develop a simple but effective communications protocol between a raspberry which serves as a server (or computer, the code is universal) and one or more raspberry’s. In this tutorial, we will be using Windows IoT on the Raspberry PI. Our programming language will be c# and will be using StreamSockets. If you have never worked with streamsockets before, then I suggest that you read about streamsockets on MSDN first.
Requirements
- One or more raspberry. You can simulate the server/client on your computer if you wish.
- We will be using Visual Studio for our development. A free community version is available on Microsoft.com
- I assume that you have basic programming knowledge before trying this tutorial.
What will we make?
- A simple client program which sends “Hello WindowsInstructed” to the Raspberry server. The Raspberry server will then reply Hello Client & his hostname.
Once you we did that you will have the required knowledge to advance the hello world messages and make your own chat room using Raspberry’s!
Step 1: Adding the right Dependencies.
We skip creating the project. If you haven’t made it yet, choose New project, Windows Universal, Blank App. I’ve made a new class file called StreamSocketClass, they choice is up to you if you want to do this.
The StreamSocket class uses the Windows.Networking namespace. I recommend you include all these namespaces into your project. We will be using them all.
using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Text; using System.Threading.Tasks; using Windows.Networking; using Windows.Networking.Sockets; using Windows.Storage.Streams;
Step 2: Setting the right capabilities for your App.
Because you’re using networking capabilities you will need to set the correct capabilities for your project. Double-click Package.appxmanifest and click on Capabilities.
Make sure to choose the following capabilities. As shown below.
- Internet (Client & Server)
- Internet (Client)
- Private Networks (Client & Server)
Step 3: Creating the Listener.
The listener will be responsible for listening for connections on either the client or server. It’s quite a simple class, but it’s very important. Let’s begin by creating the empty method called DataListener_ConnectionReceived.
private async void DataListener_ConnectionReceived(StreamSocketListener sender, StreamSocketListenerConnectionReceivedEventArgs args) { }
This method will be responsible for listening and handling all requests. So it’s very important that we make this work as best as we can. For our simple demo, it doesn’t have to be very complex. But I do want to show you how this works. The input is a stream so we will need a string builder to transform it into a string. We will therefore first create a StringBuilder in the DataListener_ConnectionReceived method. Our DataReader will keep on reading till the buffer (unconsumed buffer) is less than 0. Once that’s done it will detach the stream and the StringBuilder will make it into readable text and place it into the DataReceived string. There isn’t much you can change here so just copy and paste it.
private async void DataListener_ConnectionReceived(StreamSocketListener sender, StreamSocketListenerConnectionReceivedEventArgs args) { DataReader DataListener_Reader; StringBuilder DataListener_StrBuilder; string DataReceived; using (DataListener_Reader = new DataReader(args.Socket.InputStream)) { DataListener_StrBuilder = new StringBuilder(); DataListener_Reader.InputStreamOptions = InputStreamOptions.Partial; DataListener_Reader.UnicodeEncoding = Windows.Storage.Streams.UnicodeEncoding.Utf8; DataListener_Reader.ByteOrder = ByteOrder.LittleEndian; await DataListener_Reader.LoadAsync(256); while (DataListener_Reader.UnconsumedBufferLength > 0) { DataListener_StrBuilder.Append(DataListener_Reader.ReadString(DataListener_Reader.UnconsumedBufferLength)); await DataListener_Reader.LoadAsync(256); } DataListener_Reader.DetachStream(); DataReceived = DataListener_StrBuilder.ToString(); } }
Once that has been created we can start on making the logic for the listener. The listener will have two tasks. 1) Decide who is listening, is it the server or client. 2) Sent a response if he is the server, otherwise print the received data. Let’s first begin by creating some variables which we will use to change the state of the program, in your final product you might want to change this but for the demo, it’s just fine.
I’ve added the following line right under the class declaration. That way it’s accessible for all methods in the class and outside the class which might be useful if you wish to create a UI for your application which depends on the state.
public static bool IsServer { get; set; }
Now we can begin with creating the logic for your server and client. Which is really simply just make an IF statement which uses the IsServer property and then switch what the program has to do. Since we do not have a send functionality yet we will have to come back to the server if at a later stage. My final code looks like this for the listener (at this time).
private async void DataListener_ConnectionReceived(StreamSocketListener sender, StreamSocketListenerConnectionReceivedEventArgs args) { DataReader DataListener_Reader; StringBuilder DataListener_StrBuilder; string DataReceived; using (DataListener_Reader = new DataReader(args.Socket.InputStream)) { DataListener_StrBuilder = new StringBuilder(); DataListener_Reader.InputStreamOptions = InputStreamOptions.Partial; DataListener_Reader.UnicodeEncoding = Windows.Storage.Streams.UnicodeEncoding.Utf8; DataListener_Reader.ByteOrder = ByteOrder.LittleEndian; await DataListener_Reader.LoadAsync(256); while (DataListener_Reader.UnconsumedBufferLength > 0) { DataListener_StrBuilder.Append(DataListener_Reader.ReadString(DataListener_Reader.UnconsumedBufferLength)); await DataListener_Reader.LoadAsync(256); } DataListener_Reader.DetachStream(); DataReceived = DataListener_StrBuilder.ToString(); } if(DataReceived != null) { // Server if(IsServer) { Debug.WriteLine("[SERVER] I've received " + DataReceived + " from " + args.Socket.Information.RemoteHostName); // TODO: SentResponse(); } // Client else { Debug.WriteLine("[CLIENT] I've received " + DataReceived + " from " + args.Socket.Information.RemoteHostName); } } else { Debug.WriteLine("Received data was empty. Check if you sent data."); } }
As you can see I’ve used args.Socket.Information.RemoteHostName. This gives you back the HostName of the sender. So for example Raspberry2 if that’s what you raspberry is called. We will use args.Socket.Information again at a later stage in this tutorial.
Step 4: Creating the Listening Method
Now that we’ve made the listening handler we will need to create a small class that opens the ports and tell the program what it has to do. This method only has to be called a single time and could also be placed in the initialization of the app in the MainPage file. However, I recommend you don’t. It’s also important to add a new variable in your class called ServerPort. Make sure to make this method Public since we will be needing to call this outside this class file.
private string ServerPort = "12345"; public void DataListener_OpenListenPorts() { StreamSocketListener DataListener = new StreamSocketListener(); DataListener.ConnectionReceived += DataListener_ConnectionReceived; DataListener.BindServiceNameAsync(ServerPort).AsTask().Wait(); }
Step 5: Opening the Listening ports.
This is a very important step in your program. Once the program starts it has to open its ports otherwise, the server and or client cannot connect to each other. The following code should, therefore, be placed in your App Startup. I placed it in the MainPage where the project initializes.
We begin of course with declaring and creating the class in the MainPage, otherwise, we can’t call it’s methods. I assume that you know how to do this and won’t go into any details. We will be calling the public method we created in step 4 in this step, so go ahead and add that to your MainPage file in the MainPage method. Once you did that your mainpage should look like this:
namespace WindowsInstructed_Demo { public sealed partial class MainPage : Page { StreamSocketClass SocketManager = new StreamSocketClass(); public MainPage() { this.InitializeComponent(); // Open Listening ports and start listening. SocketManager.DataListener_OpenListenPorts(); } } }
Congratulations! You can now listen for connections and read the data you receive. You can also decide if it’s a client who sent it or a server! Next step is sending data!
Step 6: Creating the Data Sender
We will first be creating the DataSender. This part is the most complex one and I suggest you read on MSDN what each function does and not blindly copy and paste it. Firstly we need to declare some more variables in the class itself. You will need to add this one:
private StreamSocket ConnectionSocket;
I’ve commented some parts to make it easier to understand. However, this is just how it works and there isn’t much that you can change.
public async void SentResponse(HostName Adress, string MessageToSent) { try { // Try connect Debug.WriteLine("Attempting to connect. " + Environment.NewLine); ConnectionSocket = new StreamSocket(); // Wait on connection await ConnectionSocket.ConnectAsync(Adress, ServerPort); // Create a DataWriter DataWriter SentResponse_Writer = new DataWriter(ConnectionSocket.OutputStream); string content = MessageToSent; byte[] data = Encoding.UTF8.GetBytes(content); // Write the bytes SentResponse_Writer.WriteBytes(data); // Store the written data await SentResponse_Writer.StoreAsync(); SentResponse_Writer.DetachStream(); // Dispose the data SentResponse_Writer.Dispose(); Debug.WriteLine("Connection has been made and your message " + MessageToSent + " has been sent." + Environment.NewLine); // Dispose the connection. ConnectionSocket.Dispose(); ConnectionSocket = new StreamSocket(); } catch (Exception exception) { Debug.WriteLine("Failed to connect " + exception.Message); ConnectionSocket.Dispose(); ConnectionSocket = null; } }
Step 7: Back to the MainPage
We now have most of our code done. We will now need to change the startup behavior of the application. Go back to your MainPage file and add code for checking if it’s a server or client and then use the SentResponse method to sent data to the server if it’s the client starting the app. Make sure that you declare the IsServer variable first! And that you create the Hostname variable and set it for the server (Add using Windows.Networking; to your mainpage ;)) Your code and the mainpage file should look something like this.
using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Runtime.InteropServices.WindowsRuntime; using Windows.Foundation; using Windows.Foundation.Collections; using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; using Windows.UI.Xaml.Controls.Primitives; using Windows.UI.Xaml.Data; using Windows.UI.Xaml.Input; using Windows.UI.Xaml.Media; using Windows.UI.Xaml.Navigation; using Windows.Networking; using System.Diagnostics; namespace WindowsInstructed_Demo { public sealed partial class MainPage : Page { StreamSocketClass SocketManager = new StreamSocketClass(); public MainPage() { this.InitializeComponent(); // Declaring IsServer (True = server, False = client) StreamSocketClass.IsServer = true; // Declaring HostName of Server HostName ServerAdress = new HostName("DESKTOP-A1SAQ5U"); // Open Listening ports and start listening. SocketManager.DataListener_OpenListenPorts(); // Server if(StreamSocketClass.IsServer) { Debug.WriteLine("[SERVER] Ready to receive"); } // Client else { SocketManager.SentResponse(ServerAdress, "Hello WindowsInstructed"); } } } }
Step 8: Making the server respond!
You’ve reached the last step of this tutorial! Good job! Now we need to make the server respond to the client and sent back Hello Client! We can do this by going back to the DataListener_ConnectionReceived method and adding a single line! The SentResponse method and instead of ServerAdress we will be using the Connection Adress which you can get by using args.Socket.Information.RemoteAddress. You should really be able to program this yourself by now, and once done it should look like something like this.
// Server if(IsServer) { Debug.WriteLine("[SERVER] I've received " + DataReceived + " from " + args.Socket.Information.RemoteHostName); // Sending reply SentResponse(args.Socket.Information.RemoteAddress, "Hello Client!"); } // Client else { Debug.WriteLine("[CLIENT] I've received " + DataReceived + " from " + args.Socket.Information.RemoteHostName); }
Final Code for StreamSocketClass
// Code for WindowsInstructed.com using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Text; using System.Threading.Tasks; using Windows.Networking; using Windows.Networking.Sockets; using Windows.Storage.Streams; namespace WindowsInstructed_Demo { class StreamSocketClass { public static bool IsServer { get; set; } // Change this. True = server, false = client private string ServerPort = "12345"; private StreamSocket ConnectionSocket; public void DataListener_OpenListenPorts() { StreamSocketListener DataListener = new StreamSocketListener(); DataListener.ConnectionReceived += DataListener_ConnectionReceived; DataListener.BindServiceNameAsync(ServerPort).AsTask().Wait(); } private async void DataListener_ConnectionReceived(StreamSocketListener sender, StreamSocketListenerConnectionReceivedEventArgs args) { DataReader DataListener_Reader; StringBuilder DataListener_StrBuilder; string DataReceived; using (DataListener_Reader = new DataReader(args.Socket.InputStream)) { DataListener_StrBuilder = new StringBuilder(); DataListener_Reader.InputStreamOptions = InputStreamOptions.Partial; DataListener_Reader.UnicodeEncoding = Windows.Storage.Streams.UnicodeEncoding.Utf8; DataListener_Reader.ByteOrder = ByteOrder.LittleEndian; await DataListener_Reader.LoadAsync(256); while (DataListener_Reader.UnconsumedBufferLength > 0) { DataListener_StrBuilder.Append(DataListener_Reader.ReadString(DataListener_Reader.UnconsumedBufferLength)); await DataListener_Reader.LoadAsync(256); } DataListener_Reader.DetachStream(); DataReceived = DataListener_StrBuilder.ToString(); } if(DataReceived != null) { // Server if(IsServer) { Debug.WriteLine("[SERVER] I've received " + DataReceived + " from " + args.Socket.Information.RemoteHostName); // Sending reply SentResponse(args.Socket.Information.RemoteAddress, "Hello Client!"); } // Client else { Debug.WriteLine("[CLIENT] I've received " + DataReceived + " from " + args.Socket.Information.RemoteHostName); } } else { Debug.WriteLine("Received data was empty. Check if you sent data."); } } public async void SentResponse(HostName Adress, string MessageToSent) { try { // Try connect Debug.WriteLine("Attempting to connect. " + Environment.NewLine); ConnectionSocket = new StreamSocket(); // Wait on connection await ConnectionSocket.ConnectAsync(Adress, ServerPort); // Create a DataWriter DataWriter SentResponse_Writer = new DataWriter(ConnectionSocket.OutputStream); string content = MessageToSent; byte[] data = Encoding.UTF8.GetBytes(content); // Write the bytes SentResponse_Writer.WriteBytes(data); // Store the written data await SentResponse_Writer.StoreAsync(); SentResponse_Writer.DetachStream(); // Dispose the data SentResponse_Writer.Dispose(); Debug.WriteLine("Connection has been made and your message " + MessageToSent + " has been sent." + Environment.NewLine); // Dispose the connection. ConnectionSocket.Dispose(); ConnectionSocket = new StreamSocket(); } catch (Exception exception) { Debug.WriteLine("Failed to connect " + exception.Message); ConnectionSocket.Dispose(); ConnectionSocket = null; } } } }
Final code for MainPage
using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Runtime.InteropServices.WindowsRuntime; using Windows.Foundation; using Windows.Foundation.Collections; using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; using Windows.UI.Xaml.Controls.Primitives; using Windows.UI.Xaml.Data; using Windows.UI.Xaml.Input; using Windows.UI.Xaml.Media; using Windows.UI.Xaml.Navigation; using Windows.Networking; using System.Diagnostics; namespace WindowsInstructed_Demo { public sealed partial class MainPage : Page { StreamSocketClass SocketManager = new StreamSocketClass(); public MainPage() { this.InitializeComponent(); // Declaring IsServer (True = server, False = client) StreamSocketClass.IsServer = true; // Declaring HostName of Server HostName ServerAdress = new HostName("DESKTOP-A1SAQ5U"); // Open Listening ports and start listening. SocketManager.DataListener_OpenListenPorts(); // Server if(StreamSocketClass.IsServer) { Debug.WriteLine("[SERVER] Ready to receive"); } // Client else { SocketManager.SentResponse(ServerAdress, "Hello WindowsInstructed"); } } } }
Good to know!
- The code has no protection for making sure there is a server. So always start the server before the client.
- The code has ServerAdress set to my system name make sure to change this to the correct device name
- The code might cause your Security software on Windows to go on alert. Make sure to allow the program to communicate in your Firewall settings on port 12345 or change the port number.
You can download the entire project here: WindowsInstructed Demo
Example output
Server (Got Hello WindowsInstructed and sent Reply)
[SERVER] I've received Hello WindowsInstructed from fe80::a563:facd:be42:2ea6%25 Attempting to connect. Connection has been made and your message Hello Client has been sent.
Client (Sent Hello WindowsInstrucred and got Reply)
Attempting to connect. Connection has been made and your message Hello WindowsInstructed has been sent. [CLIENT] I've received Hello Client from fe80::a563:facd:be42:2ea6%25
2 Comments