A TCP Server using .NET Gadgeteer

By Marco Minerva, translated by Mike Dodaro from the original version in Italian.  .NET Gadgeteer offers to a very simple method to create a web server that will respond to GET requests, using the SetupWebEvent function supplied by Ethernet module as implemented by GHI Electronics. Examples are available on this blog, as in the post: .NET Gadgeteer Web Services; Picture, Audio, Application.

If we work with more complex scenarios, or need more versatility, we can create a multi threaded TCP server that will accept requests, process them, and respond to the client. These operations are similar to those of a classic TCP server realized with the full version of the .NET Framework .

using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using Microsoft.SPOT;
using Socket = System.Net.Sockets.Socket;

namespace ServerExample
{
    public delegate void DataReceivedEventHandler(object sender, DataReceivedEventArgs e);

    public class SocketServer
    {
        public const int DEFAULT_SERVER_PORT = 8080;
        private Socket socket;
        private int port;

        public event DataReceivedEventHandler DataReceived;

        public SocketServer()
            : this(DEFAULT_SERVER_PORT)
        { }

        public SocketServer(int port)
        {
            this.port = port;
            socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
        }

        public void Start()
        {
            IPEndPoint localEndPoint = new IPEndPoint(IPAddress.Any, port);
            socket.Bind(localEndPoint);
            socket.Listen(Int32.MaxValue);

            new Thread(StartServerInternal).Start();
        }

        private void StartServerInternal()
        {
            while (true)
            {
                // Wait for a request from a client.
                Socket clientSocket = socket.Accept();

                // Process the client request.
                var request = new ProcessClientRequest(this, clientSocket);
                request.Process();
            }
        }

        private void OnDataReceived(DataReceivedEventArgs e)
        {
            if (DataReceived != null)
            DataReceived(this, e);
        }

        private class ProcessClientRequest
        {
            private Socket clientSocket;
            private SocketServer socket;

            public ProcessClientRequest(SocketServer socket, Socket clientSocket)
            {
                this.socket = socket;
                this.clientSocket = clientSocket;
            }

            public void Process()
            {
                // Handle the request in a new thread.
                new Thread(ProcessRequest).Start();
            }

            private void ProcessRequest()
            {
                const int c_microsecondsPerSecond = 1000000;

                using (clientSocket)
                {
                    while (true)
                    {
                        try
                        {
                            if (clientSocket.Poll(5 * c_microsecondsPerSecond,
                                                                    SelectMode.SelectRead))
                            {
                                // If the buffer is zero-length, the connection has been closed
                                // or terminated.
                                if (clientSocket.Available == 0)
                                    break;

                                byte[] buffer = new byte[clientSocket.Available];
                                int bytesRead = clientSocket.Receive(buffer, clientSocket.Available,
                                                                             SocketFlags.None);

                                byte[] data = new byte[bytesRead];
                                buffer.CopyTo(data, 0);

                                DataReceivedEventArgs args = new DataReceivedEventArgs(
                                                              clientSocket.LocalEndPoint,
                                                              clientSocket.RemoteEndPoint, data);
                                socket.OnDataReceived(args);

                                if (args.ResponseData != null)
                                    clientSocket.Send(args.ResponseData);

                                    if (args.Close)
                                    break;
                            }
                        }
                        catch (Exception)
                        {
                            break;
                        }
                    }
                }
            }
        }
    }
}

The ServerSocket class can specify the port to listen on. If not specified, the default is 8080. The ServerSocket.Start method creates a new thread that waits for logon from a client (the separate thread is necessary in order not to block the application). When a client connects to the server, the server creates an object that manages the request.  When the socket. Accept method returns a value in a thread, the server resumes waiting for new logons.

The more important part of the job is carried out by the ProcessRequest method of the ProcessClientRequest class.  Internally, it verifies the availability of data, and, in a positive case, processes the request on the socket, which gives back the byte array that has been transmitted from the client.  After which, we create an object of type DataReceivedEventArgs (that as we will see) contains the data received and the IP addresses of sender and addressee. We then recall the ServerSocket.OnDataReceived method, by which the ServerSocket can be notified on arrival of new data.

If the class that has been registered for the DataReceived event (that we will illustrate in the following discussion), assigns a value to the ResponseData property of the DataReceivedEventArgs object, the byte array will be sent to the client.  Analogously, if the Close property is set to true, the logon will be closed.

Here is the DataReceivedEventArgs class:

public class DataReceivedEventArgs : EventArgs
{
    public EndPoint LocalEndPoint { get; private set; }
    public EndPoint RemoteEndPoint { get; private set; }
    public byte[] Data { get; private set; }
    public bool Close { get; set; }
    public byte[] ResponseData { get; set; }

    public DataReceivedEventArgs(EndPoint localEndPoint, EndPoint remoteEndPoint, byte[] data)
    {
        LocalEndPoint = localEndPoint;
        RemoteEndPoint = remoteEndPoint;
        if (data != null)
        {
            Data = new byte[data.Length];
            data.CopyTo(Data, 0);
        }
    }
}

Using the ServerSocket Class

Now we show a simple example using the ServerSocket class.  After we connect the FEZ Spider and the Display T35 and Ethernet J11D modules, we initialize the network interface:

void ProgramStarted()
{
    ethernet.NetworkUp += new GTM.Module.NetworkModule.NetworkEventHandler(ethernet_NetworkUp);
    ethernet.NetworkDown += new GTM.Module.NetworkModule.NetworkEventHandler(ethernet_NetworkDown);
    ethernet.UseDHCP();

    Debug.Print("Program Started");
}

private void ethernet_NetworkDown(GTM.Module.NetworkModule sender,
                                             GTM.Module.NetworkModule.NetworkState state)
{
    Debug.Print("Network Down!");
}

private void ethernet_NetworkUp(GTM.Module.NetworkModule sender,
                                           GTM.Module.NetworkModule.NetworkState state)
{
    Debug.Print("Network Up!");
}

We also want to configure the display of two texts: one that shows the IP address assigned by DHCP and the other that shows the messages that are received from server TCP.  Therefore we define a SetupWindow routine, which we will call inside ProgramStarted:

private Text txtAddress;
private Text txtReceivedMessage;

private void SetupWindow()
{
    var window = display.WPFWindow;
    var baseFont = Resources.GetFont(Resources.FontResources.NinaB);

    Canvas canvas = new Canvas();
    window.Child = canvas;

    txtAddress = new Text(baseFont, "Loading, please wait...");
    canvas.Children.Add(txtAddress);
    Canvas.SetTop(txtAddress, 50);
    Canvas.SetLeft(txtAddress, 30);

    txtReceivedMessage = new Text(baseFont, string.Empty);
    txtReceivedMessage.Width = 300;
    txtReceivedMessage.TextWrap = true;
    canvas.Children.Add(txtReceivedMessage);
    Canvas.SetTop(txtReceivedMessage, 100);
    Canvas.SetLeft(txtReceivedMessage, 10);
}

During loading of the application, we show a wait message, which will be replaced as soon as the network interface is ready:

private void ethernet_NetworkUp(GTM.Module.NetworkModule sender,
                                           GTM.Module.NetworkModule.NetworkState state)
{
    Debug.Print("Network Up!");

    txtAddress.TextContent = "IP Address: " + ethernet.NetworkSettings.IPAddress + ", port 8080";

    SocketServer server = new SocketServer(8080);
    server.DataReceived += new DataReceivedEventHandler(server_DataReceived);
    server.Start();
}

private void server_DataReceived(object sender, DataReceivedEventArgs e)
{
    string receivedMessage = BytesToString(e.Data);
    txtReceivedMessage.Dispatcher.BeginInvoke(delegate(object arg) {
        txtReceivedMessage.TextContent = "Received message: " + arg.ToString();
        return null;
        }, receivedMessage);

    string response = "Response from server for the request '" + receivedMessage + "'";
    e.ResponseData = System.Text.Encoding.UTF8.GetBytes(response);

    if (receivedMessage == "close")
        e.Close = true;
}

private string BytesToString(byte[] bytes)
{
    string str = string.Empty;
    for (int i = 0; i < bytes.Length; ++i)
    str += (char)bytes[i];

    return str;
}

In this example, we create the server on PORT 8080, register the generated event when a message is received, and, finally, start the server. When data arrives, it invokes the server_DataReceived method . We use the BytesToString utility function to recover the string contained by the bytes received, and print the message to the display.  We use notification by the Dispatcher.BeginInvoke method on the text case; this is necessary because the event is generated from one of the various threads of the interface, and therefore we must resort to this to update the thread that currently has the UI.  Else an exception will result.

Next, we create a message and assign it to the ResponseData property of DataReceivedEventArgs: recall that in the server code, seen previously, this is the way to specify a response to send to the client. In this event we also close a logon in which the client has sent the “close” message.

A client for our Server

In order to test our server, we need a client application that can send a request. We therefore create a Console Application:

class Program
{
    const string SERVER_IP = "192.168.1.102";
    const int SERVER_PORT = 8080;

    static void Main(string[] args)
    {
        TcpClient client = new TcpClient();
        Console.Write("Connecting... ");

        client.Connect(SERVER_IP, SERVER_PORT);
        Console.WriteLine("Connected\n");

        using (Stream stream = client.GetStream())
        {
            while (true)
            {
                Console.Write("Enter a string and press ENTER (empty string to exit): ");

                string message = Console.ReadLine();
                if (string.IsNullOrEmpty(message))
                    break;

                byte[] data = Encoding.Default.GetBytes(message);
                Console.WriteLine("Sending... ");

                stream.Write(data, 0, data.Length);

                byte[] response = new byte[4096];
                int bytesRead = stream.Read(response, 0, response.Length);
                Console.WriteLine("Response: " + Encoding.Default.GetString(response, 0, bytesRead));

                Console.WriteLine();
            }
        }

        client.Close();
    }
}

The only modification necessary to use this application is to change constant SERVER_IP based on IP address that is assigned to the .NET Gadgeteer application .  A start time, we can send all the strings that we want to the server.  The previous example will print to the display the received message and send an answer to the client.

The application is available for download.

ServerExample.zip

About these ads
  1. #1 by aldi john on December 5, 2011 - 7:16 PM

    Nice Works and great adventure !
    FEZ Spider is using EMX that has Fully TCPIP
    So It Should be able to Retrieve Email from Gmail using POP server using SSL

    Nice if Your Next project idea is Gadgeteer send and retrieve email from Gmail or Yahoo, … Wow !

    Cheers !

    aldi john

  2. #2 by Michael Dodaro on December 6, 2011 - 10:17 AM

    I think we’ll give this a try. Thanks, Aldi John.

  3. #3 by Marco Minerva on December 7, 2011 - 4:02 AM

    Yes, thank you for the idea, we’ll work on it!

  4. #4 by aldi john on December 8, 2011 - 7:14 AM

    Thx Marco and Mike for being agree with the Idea ,
    The challange on this project is : “the SSL”, but I believe FEZ Spider that based on EMX has Fully TCPIP capacity that make it available with SSL ? ( I heard SSL is impossible in netduino plus ? ), and I read from EMX manual , that EMX has SSL Support.
    So Then We can Give a Control to : Lets say our robot ” via email ” , Cool !!

    I can’t wait for trying also , my gadgeteer still on the way …!

  1. Setting up a wireless server with the WiFi board « Disruption: Design Interaction
  2. .NET Gadgeteer event in Pisa « Integral Design
  3. .NET Gadgeteer event in Pisa, Italy « Integral Design
  4. Wi-Fi Gadgeteer Robot controlled by Windows Phone with image streaming « Integral Design
  5. Creating an UDP Server with .NET Gadgeteer « Integral Design
  6. Creating an UDP Server with .NET Gadgeteer « Integral Design
  7. Using voice commands to control a servo « Integral Design
  8. What are people doing with .NET Gadgeteer? | MSDN Blogs

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: