Building a Connected Flashlight


We are going to extend the flashlight we've been developing over the past few chapters by having it periodically communicate its GPS position to the central office (HQ). (They are not very trustful of their security guards, apparently.) When the flashlight is powered on, it will register with HQ by making a TCP connection. In return, it will receive some configuration parameters (also over TCP) that it will use to drive the UDP-based updates it will send out while it is operating.

Creating the HQ Server

We will use the desktop version of .NET to create a stand-alone personal-computer server for the flashlight to communicate with (back at HQ). This is just a simple console application that will provide the configuration information to the flashlight client and display text to the output window. Because the network-related classes provided in the .NET Micro Framework are a strict subset of those offered by the .NET Framework, we will use only those common to both. Although we could use several of the helper classes available in the desktop environment, our approach will allow you to use the server code we develop here in your own .NET Micro Framework applications, if you ever have the need to develop an embedded server. Alternatively, we might have decided to implement the personal-computer side of this application within the emulator, but we wish to illustrate communicating between a personal computer and a device.

Listening for Clients

The first thing our server must be concerned with is listening for incoming TCP connections (you will recall that the TCP protocol is connection-oriented) from all the flashlights out in the field. To accomplish this, we will create a special-purpose listening Socket, specifying the proper values for address type, socket type, and protocol type:

 listenSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); 

Despite the extensiveness of these enumerations, only a few values are valid. For Address-Family, only InterNetwork is supported. For SocketType, only Stream and Dgram are supported. For ProtocolType, only Tcp and Udp are supported. Additionally, it is not possible to mix a SocketType.Stream with a ProtocolType.Udp or SocketType.Dgram with ProtocolType.Tcp.

IP Address Binding

You will also recall that a socket logically consists of the local IP address and port, the remote IP address and port, and the protocol to use in communicating between them. In creating the socket, we specified which protocol to use. Now we need to specify the local address and port. For this example, we are going to use the loopback interface because we'll be running the flashlight in the emulator on the same machine. Our choice of port is somewhat arbitrary, as long as we avoid any of the well-known ports. This is the same port that the client will use. To establish the address, we'll use an IPEndpoint object.

 IPEndPoint localEndPoint = new IPEndPoint(IPAddress.Loopback, Settings.SERVER_LISTEN_PORT); 

Here we specify that we wish to listen on the local loopback address and use a port constant defined in our Settings class (described later) as this:

 const int SERVER_LISTEN_PORT = 2600; 

Any value will do here, but be careful to avoid the well-defined ports (for example, 20, 80) and use the conventionally recognized user port range (loosely defined as 1024–65535).

Having defined the local IP address (loopback) and port, we must now bind it to the socket using the Bind method. Binding is typically done only for listening sockets, but may also be done with client sockets (that is, those initiating the connection request) if it is necessary for you to specify the local IP address and port for the connection.

 listenSocket.Bind(localEndPoint); 

If you have specified IPAddress.Any for the IP address, the system will select an adapter on your behalf (useful if you have multiple adapters). Similarly, it is possible to specify a port of 0, which will cause the system to provide one in the range of 1024–5000. This is not likely to be the best approach if you are creating a server, because you must somehow inform any potential clients of the system-selected port. If you do allow the system to select these values, you can simply read the socket instance's LocalEndPoint property to discover what they are.

Queuing Clients

Now that we have established a listening socket with the correct protocol and bound to the right IP address and port, it is time to start listening for connection attempts by our clients. This special-purpose listening socket will act like a factory of new sockets that represent an actual connection to clients as they are accepted. This is a two-step process. Before we can begin accepting client connections, we must tell this socket to start listening by calling its Listen method. This non-blocking method takes a single parameter, backlog, representing the number of incoming clients to allow to be queued before your code actually accepts them.

 listenSocket.Listen(Int32.MaxValue); 

Clients that attempt to connect when the maximum backlog number is reached will receive a connection refused error from their TCP stack. The maximum allowed value of this parameter is implementation-dependent. Typically, specifying Int32.MaxValue, as we have done here, results in the implementation choosing the largest value the implementers felt was reasonable. Another option is to use the socket option constant, though it is an odd programming construct.

 listenSocket.Listen((int)SocketOptionName.MaxConnections); 

Unfortunately, there is no way to extract the actual backlog value currently in use.

Accepting Connections

Now that our server is configured and listening for client connection attempts, we must decide on a strategy for accepting the incoming connections. Unlike the .NET Framework, which offers a variety of synchronous and asynchronous options for accepting client connections, the .NET Micro Framework provides only a single blocking method: Accept. Your server application will typically want to accept more than a single connection request, so you will likely construct a loop to handle them. Whether you put this in your main thread or spawn a worker thread that contains it will depend on your application's requirements. The following example uses the latter approach because it is slightly more complex.

 Thread workerThread = new Thread(new ThreadStart(AcceptWorker)); workerThread.Start(); // Application stuff happens here until it is time to shutdown: workerThread.Abort(); workerThread.Join(); 

The worker thread would look something like the following:

 void AcceptWorker() {    while (true)    {       using(Socket clientSocket = listenSocket.Accept())       {       // Process or dispatch client communication       }    } } 

Note the using convention, which simply ensures that the socket is properly closed when it leaves the scope. Once a connection is accepted, you will have a newly created, dedicated socket representing it. You can now read the RemoteEndPoint property, an EndPoint object that represents the client connection. Because we know a priori that we are using an IP-based socket (that is, the AddressFamily is InterNetwork), we can cast the generic EndPoint directly into an IPEndPoint:

 IPEndPoint clientEndpoint = (IPEndPoint)clientSocket.RemoteEndPoint; Console.WriteLine("Client connected: " + clientEndpoint.Address.ToString() + ":" + clientEndpoint.Port); 

In the case of our flashlight client running in the emulator, the endpoint address will be loopback (127.0.0.1) and the port will be an internally assigned value (not the value we specified).

Processing Client Requests

The actual handling of client requests is, of course, dependent on the application protocol. However, before we get into the details of our flashlight/HQ protocol, we must determine how we are going to structure our server code for carrying out this communication. For instance, we might choose to process the requests inline within the Accept loop. This has the advantage of being simple, but the disadvantage of not scaling very well. If you will recall, the backlog parameter we passed into the Listen method determined the maximum number of client connection requests that could be queued waiting for our server code to accept them. The more code that executes between Accept invocations, the more client queuing could occur.

Because we are not expecting very many flashlights to be connecting to our server and our protocol is trivial, we will keep the code simple by handling the client requests inline, as opposed to spawning yet more worker threads. All client processing (for the TCP-based exchange) is handled in a separate method, making our Accept loop consist simply of the following:

 using (Socket clientSocket = listenSocket.Accept()) {    ProcessClient(clientSocket); } 

The Flashlight Configuration Protocol

When a flashlight comes online, it must register with the server at HQ. In return, it will receive a set of configuration parameters that it will use to govern its subsequent behavior. This exchange will take place over a TCP connection. To register, the flashlight will send its unique identifier (we will use just a hard-coded string). The configuration information will consist of an update frequency (in milliseconds) and a flag field that determines which data to return-for example, location, usage statistics, its current state (on/off).

The transfer of information across a socket is accomplished by sending and receiving buffers (byte[]). There is no intrinsic notion of, or support for, transferring C# objects across the "wire" (connection). Most protocols expect certain information be sent in a certain order and in a certain format, but they also guard against errors and malicious endpoints (evil clients and spoofed servers) by validating the expected values against the actual data received using checksums, "magic numbers" (that is, the bad practice of using numbers known by both sides and used as a simple passcode), and the like. You are advised to do the same in your professional projects, but we will take some shortcuts in our closed little sample world and expect ideal operating conditions.

To help keep the client and server code synchronized, we will create a few classes, common to both our client and server applications, that contain shared definitions and settings (for example, port numbers). Normally, you would use a shared class library for this. Because we are writing an application split between the desktop and a .NET Micro Framework device, this becomes less than trivial. For this sample, we will copy the code into both the personal-computer console project and the flashlight .NET Micro Framework project.

Serialization

In the managed code world of C# and .NET, it would be convenient to transfer actual objects across the connection instead of traditional byte-oriented messages with headers, bodies, and the like, such as HTTP and countless other protocols. One way to accomplish this is to use the .NET Micro framework Serialize and Deserialize static methods of Microsoft.SPOT. Reflection. As an example of the simplicity of this approach, the next code snippet illustrates how a client might send data:

 byte[] sendBuffer = Reflection.Serialize(myObject, typeof(MyClass)); 

And this one shows how a server might receive data:

 MyClass myObject = (MyClass)Reflection.Deserialize(receiveBuffer, typeof(MyClass)); 

This method will work for device-to-device communication across sockets. However, the .NET Micro Framework does not currently expose the serialization engine on the desktop (except through the emulator), greatly complicating personal computer-to-device communication. Our approach will be to create a mini-serialization engine that supports strings and ints from flat objects (that is, the engine will not process recursively through an object graph). This is a naive and inefficient implementation (Listing 6-3) used only to demonstrate the practice of serialized wire formats. For strings, we are using the UTF8 character encoding in order to provide support for extended characters. For integer values, we are simply bit-shifting and bit-masking. Classes to be serialized or deserialized must contain a nonparameterized constructor (or have only a default constructor), which must be public, because our simple deserialization code does not know how to invoke parameterized constructors.

Listing 6-3: Serialization helpers

image from book
 public static class SerializerHelpers {    public static byte[] Serialize(object o)    {       // Create a maximally-sized buffer for serializing into       byte[] buffer = new byte[MAX_SERIALIZED_SIZE];       int bytesWritten = 0;       FieldInfo[] fieldInfos = o.GetType().GetFields(BindingFlags.Instance |       BindingFlags.Public | BindingFlags.NonPublic);       foreach (FieldInfo fieldInfo in fieldInfos)       {          if (fieldInfo.FieldType == typeof(string))          {             string theString = (string)fieldInfo.GetValue(o);             bytesWritten += FromString(theString, buffer, bytesWritten);          }          else if (fieldInfo.FieldType == typeof(int))          {             int theInt = (int)fieldInfo.GetValue(o);             FromInt(theInt, buffer, bytesWritten);             bytesWritten += sizeof(int);          }          else             throw new Exception("Unsupported type for serialization.");       }       // Create an exact-sized buffer for return       byte[] output = new byte[bytesWritten];       for (int i = 0; i < bytesWritten; i++)          output[i] = buffer[i];       return output;    }    public static object Deserialize(byte[] buffer, Type t)    {       int bytesRead = 0;       object o = t.GetConstructor(new Type[0]).Invoke(null);       FieldInfo[] fieldInfos = t.GetFields(BindingFlags.Instance |       BindingFlags.Public | BindingFlags.NonPublic);       foreach (FieldInfo fieldInfo in fieldInfos)       {         if (fieldInfo.FieldType == typeof(string))         {            string value;            bytesRead += ToString(buffer, bytesRead, out value);            fieldInfo.SetValue(o, value);         }         else if (fieldInfo.FieldType == typeof(int))         {            int value = ToInt(buffer, bytesRead);            bytesRead += sizeof(int);            fieldInfo.SetValue(o, value);         }         else            throw new Exception("Unsupported type for deserialization.");         }       return o;    }     //---     static int FromString(string theString, byte[] buffer, int offset)     {        byte[] tmpBuffer = Encoding.UTF8.GetBytes(theString);        // Prefix string with int length        FromInt(tmpBuffer.Length, buffer, offset);        // Copy to output buffer        int i = offset + sizeof(int);        foreach (byte b in tmpBuffer)           buffer[i++] = b;        return i - offset;     }     static int ToString(byte[] buffer, int offset, out string theString)     {        // Strings are prefixed with an integer size        int len = ToInt(buffer, offset);        // Encoding's GetChars() converts an entire buffer, so extract just the string        part        byte[] tmpBuffer = new byte[len];        int dst = offset + sizeof(int);        for (int i = 0; i < len; i++, dst++)           tmpBuffer[i] = buffer[dst];        theString = new string(Encoding.UTF8.GetChars(tmpBuffer));        return dst - offset;     }     static void FromInt(int theInt, byte[] buffer, int offset)     {        buffer[offset] = unchecked((byte)(theInt));        buffer[offset + 1] = unchecked((byte)(theInt >> 8));        buffer[offset + 2] = unchecked((byte)(theInt >> 16));        buffer[offset + 3] = unchecked((byte)(theInt >> 24));     }     static int ToInt(byte[] buffer, int offset)     {        return (int)(           (buffer[offset] & 0x000000FF) |           (buffer[offset + 1] << 8 & 0x0000FF00) |           (buffer[offset + 2] << 16 & 0x00FF0000) |           (buffer[offset + 3] << 24)           );     }     const int MAX_SERIALIZED_SIZE = 1024;    } 
image from book

Flashlight Identity

The first message sent by the client and expected by the server is the flashlight's identity, which is simply its name and serial number. Listing 6-4 contains the definition of this shared class:

Listing 6-4: The flashlight identity message

image from book
 public class IdentityMessage {    IdentityMessage() // for serialization    { }    public IdentityMessage(string name, int number)    {       this.name = name;       this.number = number;    }    public string Name    {       get { return name; }    }    public int Number    {       get { return number; }    }    string name;    int number; } 
image from book

Using this class, it becomes straightforward to send and receive the flashlight's identity across a socket connection by using the serialization methods discussed in the preceding section about serialization. The client will instantiate an IdentityMessage using the parameterized constructor and send it across the connection (described in the following code), so our ProcessClient method must receive and deserialize it.

 // Create a receive buffer large enough to handle the maximum amount possible from socket int maxBufferLen = (int)listenSocket.GetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReceiveBuffer); byte[] receiveBuffer = new byte[maxBufferLen]; // We are expecting an IdentityMessage int receivedLen = clientSocket.Receive(receiveBuffer); IdentityMessage identityMessage = (IdentityMessage)SerializerHelpers.Deserialize(receiveBuffer, typeof(IdentityMessage)); Console.WriteLine("Flashlight identity: " + identityMessage.Name + " #" + identityMessage.Number); 

Receiving Data

To obtain a socket's internal receive buffer size, specify the ReceiveBuffer socket option name.

 int len = (int)socket.GetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReceiveBuffer); 

This value (8 KB in our desktop configuration) is independent of individual message sizes or even the TCP packet size. This is simply the upper bound on the number of bytes that can be returned from a call to the Receive method, which you invoke to move the data from the protocol stack into your application. There are several variants of this method, but each accepts a buffer (byte[]) parameter that serves as the destination for the transfer. This buffer should either be the exact size of the data you expect to receive (if fixed-length) or a value smaller than that of the internal receive buffer (if the data received is of variable length). A buffer that is bigger than the maximum internal buffer is simply wasteful.

Polling

It is also possible to poll the socket to test if data has been received (as shown in the following code), which might be useful in cases where you don't want to block your thread waiting to receive data from a client.

 // Polling example: if (clientSocket.Poll(-1, SelectMode.SelectRead)) // Do actual read 

The Poll method also supports determining whether a socket is writeable (SelectMode.SelectWrite) and if the socket has an error (SelectMode.SelectError).

Connecting to HQ from the Flashlight

Now that we have begun programming our server back at HQ, we need to write the .NET Micro Framework code that will run on the flashlight to connect to it. As you'll see, most of the concepts transfer directly without modification. The .NET Micro Framework support is a strict subset of that found in the .NET Compact Framework and the .NET Framework. Because this code is written from the client perspective, we simply need to reverse the logic from the server perspective. We are going to encapsulate the exchange with the server in a single method, UpdateConfigurationFromHQ, the first portion of which is shown in Listing 6-5.

Listing 6-5: Updating the configuration from HQ (part one)

image from book
 public void UpdateConfigurationFromHQ() {    using (Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream,    ProtocolType.Tcp))    {       socket.Connect(new IPEndPoint(IPAddress.Loopback, Settings.SERVER_LISTEN_PORT));   // First, we send our identity to the server IdentityMessage identityMessage = new IdentityMessage("Flashlight v1.0", 99); byte[] sendBuffer = SerializerHelpers.Serialize(identityMessage); socket.Send(sendBuffer); 
image from book

There is not much remarkable about this code, other than to note the absence of a DNS lookup (described earlier in this chapter) because we have simply hard-coded the loopback address.

Flashlight Configuration

In response, the server will send client configuration information in the form of a populated ConfigurationMessage shared class. This class (Listing 6-6) is similar to the IdentityMessage class. An enumeration defines a set of configuration options the server requests from the client. These form a bit field (or flags) and are logically ORed together. Internally, this bit field is represented as an int because of the limitations of our simple serializer. The user is shielded from this detail by a correctly typed property.

Listing 6-6: The flashlight configuration message

image from book
 public class ConfigurationMessage {    [Flags]    public enum ConfigurationOptions    {       None = 0,       GpsLocation = 1,       OnDuration = 2,    }    public int UpdateFrequencyMS;    public ConfigurationOptions Options    {       get { return (ConfigurationOptions)options; }       set { options = (int)value; }    }    int options; } 
image from book

The server uses the same socket it used for receiving the flashlight's identity to respond with this configuration message:

 ConfigurationMessage configurationMessage = new ConfigurationMessage(); configurationMessage.Options = ConfigurationMessage.ConfigurationOptions.GpsLocation; configurationMessage.UpdateIntervalMS = 60 * 1000; byte[] sendBuffer = SerializerHelpers.Serialize(configurationMessage); clientSocket.Send(sendBuffer); 

Here, we have hard-coded the server to request only GPS location information and to set the client's update interval (in milliseconds) to every 1 minute. The corresponding client code is shown in Listing 6-7.

Listing 6-7: Updating the configuration from headquarters (part two)

image from book
    // Create a receive buffer large enough to handle the maximum amount possible from       socket    int maxBufferLen = (int)socket.GetSocketOption(SocketOptionLevel.Socket,                        SocketOptionName.ReceiveBuffer);    byte[] receiveBuffer = new byte[maxBufferLen];    // Then, it sends our configuration information    socket.Receive(receiveBuffer);    configurationMessage = (ConfigurationMessage)SerializerHelpers.Deserialize(                           receiveBuffer, typeof(ConfigurationMessage));    Debug.Print("Configuration from server: update interval = " +    configurationMessage.UpdateIntervalMS.ToString() + ", options = " +    configurationMessage.Options.ToString());    } } 
image from book

Updating HQ Using UDP

Now that we've covered communication between TCP client and server and have established the flashlight's configuration parameters, it is time to cover sending and receiving the update messages using UDP.

As we've already discussed, UDP is a connectionless protocol, which, in practical terms, means that neither the server (HQ) nor the client (flashlight) must explicitly establish a socket connection before communication can take place, as was the case for the TCP example.

For the server, we will spawn a worker thread that will wait on a socket and port for inbound UDP messages.

 // Spawn a worker thread to process UDP update messages Thread workerThread = new Thread(new ThreadStart(UpdateWorker)); workerThread.Start(); // ... void UpdateWorker() {    Socket updateSocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram,    ProtocolType.Udp);    IPEndPoint updateEndPoint = new IPEndPoint(IPAddress.Loopback, Settings.UPDATE_PORT);    updateSocket.Bind(updateEndPoint); 

Aside from the use of UDP-oriented configuration information, this code should look very familiar. Note that binding is necessary here because we are using a specific port.

Because the server is receiving data, it needs a receive buffer. We use the same logic as before in creating one that matches the socket's own receive buffer:

 // Create a receive buffer large enough to handle the maximum amount possible from socket int maxBufferLen = (int)updateSocket.GetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReceiveBuffer); byte[] receiveBuffer = new byte[maxBufferLen]; 

Next, we enter an infinite loop that will receive and process the inbound update messages. (Here, we simply write the data to the console, but you can easily imagine storing the data in a database for logging and processing.) Of particular note is the use of the ReceiveFrom socket method. This is the connectionless mechanism for receiving data. The EndPoint ref parameter will be populated with the sender's IP address, which could be used to correlate with the flashlight identity, for instance.

 while (true) {    EndPoint remoteEndPoint = new IPEndPoint(IPAddress.Any, 0);    updateSocket.ReceiveFrom(receiveBuffer, ref remoteEndPoint);    GpsUpdateMessage gpsUpdateMessage = (GpsUpdateMessage)SerializerHelpers.Deserialize(    receiveBuffer,typeof(GpsUpdateMessage));    Console.WriteLine("GpsUpdateMessage received from " + remoteEndPoint.ToString());    Console.WriteLine("- Lat: " + gpsUpdateMessage.Latitude);    Console.WriteLine("- Lon: " + gpsUpdateMessage.Longitude);    Console.WriteLine("- T/S: " + gpsUpdateMessage.Timestamp); } 

We are also only processing the GPS update message because we hard-coded this in the configuration. As an exercise, you might try extending this code to handle other types of update messages.

On the client side of things, again, we simply reverse the logic and SendTo the server. Here, too, we use a worker thread that will prepare and send the configured update message(s), then wait the server-specified amount of time before sending a new update.

 void UpdateWorker() {    Socket updateSocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram,    ProtocolType.Udp);    EndPoint serverEndPoint = new IPEndPoint(IPAddress.Loopback, Settings.UPDATE_PORT);    // note: no need to Bind 

Notice here that there is no need to bind the server EndPoint to the socket because the internal stack will use any available port to send the data.

The configuration update loop simply sends one message per specified option (from HQ). Because serializing and sending messages will happen repeatedly, this code has been moved into a separate helper method. Listing 6-8 shows both the loop and this helper.

Listing 6-8: Update loop and send helper

image from book
 while (true) {    if ((configurationMessage.Options & ConfigurationMessage.ConfigurationOptions.         GpsLocation) != 0)    {       GpsUpdateMessage gpsUpdateMessage = new GpsUpdateMessage();       gpsUpdateMessage.Timestamp = DateTime.Now.ToString();       gpsUpdateMessage.Latitude = Microsoft.SPOT.Math.Random(int.MaxValue);       gpsUpdateMessage.Longitude = Microsoft.SPOT.Math.Random(int.MaxValue);       SendUpdateMessage(updateSocket, serverEndPoint, gpsUpdateMessage);    }       if ((configurationMessage.Options & ConfigurationMessage.ConfigurationOptions.            OnDuration) != 0)       {          // ...       }       // Wait specified amount of time before next update       Thread.Sleep(configurationMessage.UpdateIntervalMS);    } } void SendUpdateMessage(Socket updateSocket, EndPoint serverEndPoint, IUpdateMessage message) {    byte[] sendBuffer = SerializerHelpers.Serialize(message);    try    {       updateSocket.SendTo(sendBuffer, serverEndPoint);       Debug.Print("Message sent to: " + serverEndPoint.ToString());    }    catch (SocketException se)    {       Debug.Print("socket exception; code = " + se.ErrorCode.ToString());    } } 
image from book

Extending the UDP Example for P2P Flashlights

The previous example has demonstrated how to use UDP to communicate between two nodes on a network. The flashlight client treated HQ as a server, as it did in the TCP example. However, this example can form the basis for a P2P scenario in which multiple flashlights exchange information-the mechanics are no different, though there is one important additional consideration: discovery.

You will need to devise a discovery scheme in order to allow one peer node (flashlight) to find and communicate with another. This is usually done through a central server that maintains a list of all active and available peers. We could extend the HQ server to answer requests for providing nearby flashlight IP addresses.

You will also need to add a listener thread, just as we did for the HQ server UDP example, to the flashlight client. This thread will process the messages from peer flashlights.

You have all the individual pieces needed to make this work, so put them to the test and try implementing this as an exercise.




Embedded Programming with the Microsoft .Net Micro Framework
Embedded Programming with the Microsoft .NET Micro Framework
ISBN: 0735623651
EAN: 2147483647
Year: 2007
Pages: 118

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net