Wireless Embedded Internetworking Short Course

David E. Culler

University of California at Berkeley

Department of Electrical Engineering and Computer Science

 

 

Lab 5 Programming with Sockets

 

 

 

 

Section 1: Embedded Network Programming

 

 

OS Networking APIs – Implementing Clients and Servers

 

Before we dig into embedded network programming, let’s get reacquainted with how we program networked applications on conventional host operating systems.   In a UNIX environment, the networking API is primarily BSD sockets.  (Windows winsock is quite similar, but with substantial differences.) Sockets allow messages to be sent and received between two end-points (a.k.a. sockets). Sockets can be used for more than just inter-networking using TCP/UDP/IP; they are also used to communicate with various system-level components such as I/O and device drivers. We focus on the inter-networking functionality that sockets provide.  Also, many are much more familiar with IPv4 programming than IPv6 programming, so we’ll show you the differences.

 

Exploring UDP Sockets

 

Networking applications tend to come in pairs, name the two sides of a protocol: client/server, talk/listen, and so on.  We’ve put together several useful examples in unix/ipv4 and unix/ipv6.  In your Linux VM, open a terminal window.

            cd unix/ipv4

     make

 

Open up a second terminal window and also cd unix/ipv4. 

 

Now start the UDP server example in one window.

            ./udpserver 1234

 

Open up a different terminal and run the client example

     ./udpclient 127.0.0.1 1234

 

Type into the udpclient.  Observe what happens.

 

 

Let’s do the same thing using IPv6. 

cd unix/ipv6

     make

./udpserver 1234

 

In the other window

cd unix/ipv6

            ./udpclient ::1 1234

 

-         What does the client do?

-         What does the server do?

-         What is the equivalent of '::1' in IPv4?

-         What does 1234 correspond to?

 

 

UDP Client

 

Okay, now that you have a feel of what these programs do, let’s open them up and see how these are implemented. Using your favorite editor, open up unix/ipv6/udpclient.c.

 

The main() function starts by parsing command-line arguments and setting up a signal handler to clean up resources when interrupted. It’s generally a good idea to have these when building a robust application.

 

Now to the real stuff. The next action in main() is to setup the server variable, which is declared as a sockaddr_in6. You can find the definition of sockaddr_in6 in /usr/include/netinet/in.h. The actual code for initializing server is shown here:

server.sin6_family = AF_INET6;

if (inet_pton(AF_INET6, argv[1], &server.sin6_addr) <= 0)

  error("Invalid IPv6 address\n");

server.sin6_port = htons(port);

 

-         What does AF_INET6 specify?

-         What does the port specify?

-         What does htons() do?

-         What does inet_pton() do?

 

Note: If you don’t know what something in UNIX does, chances are there is a man page about it. Man is the UNIX way of documenting things. For example, you can execute the following at the command-line:

            man memcpy

 

After setting up the server variable, a new socket is creating using the socket command.

     sockfd = socket(AF_INET6, SOCK_DGRAM, 0);

 

Look up the man-page for socket to find out more.

 

Now everything is setup properly to start using the socket. The client app now just sits in a while-loop sending messages to the server using sendto and receives messages using recvfrom. Lookup the man pages to find out more about them.

 

 

UDP Server

 

Let’s take a look at the server code. Using your favorite editor, open up udpserver.c. Notice that much of the code actually looks the same. The server still initializes the server variable, creates a socket, sends and receives messages. However there are subtle but important differences. Let’s take a look at each one.

 

Notice that the server variable is initialized slightly differently.

 

-         What is the IPv6 address set to? What does it mean? (hint: in.h may be useful to you again)

-         Why is specifying a port important?

 

There is also a new function call being used: bind

 

-         What does bind do?

 

The remainder of the programming is pretty much the same as the client, except that recvfrom and sendto are reversed.

 

 

If you haven’t figured out already, the control flow is as follows:

 

 

 

 

Sockets API in Other Settings

 

Since its original implementation, BSD Sockets have been ported to many languages other than C. Included in the unix directory is a python example of the UDP client: udpclient.py. Python is known for its ease of quickly programming new applications. Take a look at the python UDP client and compare it to the C version. You’ll quickly notice why Python has been gaining lots of popularity in recent years.

 

Additionally, the original BSD sockets API is tightly tied to the execution model defined by the OS, in this case UNIX. The BSD sockets API is written with sequential programming in mind and that is why all of the examples you’ve seen so far contain while-loops in the main body of the program. This will be different when we get to the embedded code.

 

Interacting with 6LoWPAN

 

So far, we’ve only been playing with clients and servers on the same Linux machine. This communication model is already fairly powerful as it lets you communicate with other process on the same box, while the kernel provides all of the nice buffer management and queuing needed to get messages from one process to another.

 

 

 

 

Exercise

 

Of course, we can use these same application protocols for inter-machine communication, not just inter-process communication. 

 

  • Get together with your neighbor.  One of you should run the udpserver in the Linux VM.  The other should run the udpclient.  What port should you use?  What IP address?
  • Try out the udptalk / udplisten pair.  Compare the code with udpclient / udpserver. 
  • What are the differences in the IPv4 and IPv6 versions?
  • Try using the IPv4 version with the IPv6 version.  Do they work?

 

But now let’s use the UDP client application to interact with 6LoWPAN nodes.

 

-         If you don’t already have your 6LoWPAN network running, set it up as you did in Lab 2.

-         You will need to install Udp Echo on them.

cd tos/Echo

make epic

swupdate –t build/epic/tos_image.xml i <ipaddr>

swupdate r <ipaddr>

 

Now try running the IPv6 udpclient on your Linux VM again but with <ip6addr> set to the IPv6 address of one of your sensor node.

 

            ./udpclient <ip6addr> 7

 

Is there any difference in communicating with a remote linux machine?

 

Try using the IPv4 udpclient on your Linux VM again but with <ip4addr> set to the IPv4 address of one of your sensor node.

 

IPv4 and IPv6 are treated as separate address space at the sockets API, so unfortunately you have to pick one when you build your conventional sockets application (or explicitly do both).  Since the LoWPAN router is providing the 6-to-4 translation, you can use either one to communicate with motes.  However, you need to use the address that matches the code on the linux side.

 

Programming TinyOS

 

Okay, enough with the Linux side for now. Let’s move on to the 6LoWPAN side. As a part of this exercise, we’ll get more experience with TinyOS programming and see how the event-driven execution model extends naturally to IP based communication.

 

TinyOS Modules and UDP Sockets

 

Let’s go take a look at the 6LoWPAN UDP server in tos/echo/.

 

Now open up EchoUdpP.nc. This file contains the module for the UDP server. It uses the Boot interface that we saw in the previous lab and a new Udp interface.

 

  module EchoUdpP {

    uses interface Boot;

    uses interface Udp;

  }

  implements {

    … implementation here …

  }

 

Awfully simple isn’t it.  At first glance, things look pretty different than they did on the Linux side. There’s no main, no need to create a socket, and now it looks like we’ve implemented a function called recvfrom() rather than actually using one. It’s a small snippet of code, but it exemplifies many of the TinyOS goals and design philosophy. 

 

First, notice that all logic within this module is done only within event handlers. The booted event occurs when the underlying OS is ready to go. This is equivalent to main() in a traditional program (you could argue that main() is also an event handler). It’s the first event that gets signaled. In the booted event, there’s a call that should look familiar to you: bind.  Here we issue the bind command to the Udp component.  The argument is the port to bind to.

 

  event void Boot.booted() {

    call Udp.bind( ECHO_PORT );

  }

 

The second event, recvfrom(), is signaled whenever a UDP message is received by the socket. Because this is the Echo server, all it needs to do is send the message right back using sendto(). Both of these calls should look familiar to you. Don’t worry about minor differences in the arguments for now.

 

  event void Udp.recvfrom( void *buf, uint16_t len,

                           sockaddr_in6_t *from,

                           link_metadata_t *linkmsg ) {

    call Udp.sendto( buf, len, from );

  }

 

 

TinyOS Interfaces

 

You’ll also find that most of this code looks like basic C-code with a few added key words. The nesC language is just that. The event keyword signifies call flow coming up through an interface. The call keyword signifies call flow going down into the interface. The keywords indicate that the functions belong to an interface being used or provided by the component. Interface functions are specified in nesC interface files. The UDP interface is specified in:

 

            kernel/interfaces/Udp.nc

 

The keywords also act as hints to the nesC compiler so that it can verify that all interface functions are implemented. Any module using the UDP interface must implement the recvfrom() handler and is free to call any of the commands, such as bind().

 

To see what interfaces the kernel provides, open up src/BinKernel.nc. Notice that it has both Boot and UDP. It has a lot of other interfaces too. Another interesting note is the Timer interface, which is indexed by the parameter id. This is called a parameterized interface and simply represents multiple instances of the Timer interface. We have already seen timers, but we’ll see more of them.

 

As a side note, the nesC compiler takes nesC code and transforms it to C-code, which is then passed to a C-compiler, such as GCC. To view the generated C-code, first compile it:

 

            make epic

 

Now look in build/epic/app.c.  (Remember, this is compiler generated code.  It is not for human consumption.)

 

TinyOS Configurations

 

Now take a look at EchoUdpC.nc. This file contains a configuration for the UDP server. This is where the power of modularity takes place in TinyOS. Configuration files simply ‘wire’ modules together. In other words, when a module uses an interface, the configuration file specifies which specific implementation to use. In this case, both Boot and UDP are implemented by the kernel.

 

-         Why don’t we need to create a socket as we did with the socket call in Linux?

 

Wiring is a perfect example of TinyOS’ design goal philosophy of static binding. In a traditional OS, calling the sockets command allocates some state (specifically, a file descriptor). When you have very limited memory and resources, dynamic allocation can make it hard to be predictable. Another example of dynamic allocation is malloc. You will rarely see malloc in TinyOS. Instead, all variables, buffers, etc. must be declared explicitly within the module and allocated at compile time.

 

-         Why does static binding and allocation make the system more predictable?

-         What capabilities do you lose if you do not allow dynamic allocation/binding?

 

Notice, on the Linux side all system calls are blocking.  Once the call is made, the thread blocks until the call completes.  Recvfrom just hangs.  If you wanted to anything else, like sample or handle timer events you would need to have previously forked a separate thread to do so.  In the event driven model it is natural to continue to service all sorts of events while network protocols are on-going.  It can handler timer events, input events, do processing, and so on.  This suggests our open ended exercise.

 

Open ended Exercise – UDP based embedded server.

 

Now that you have seen an echo server and you have developed embedded applications that have timers and capture various binary inputs, put these two together into an application of your own design.  You can use updclient to send commands to the mote.  Instead of echoing the incoming packet, the server on the mote responds with the requested information.  For example, this might be the number of binary events since the last request or over some period of time.  You might want to include a sequence number in the response so the server can potentially tell when requests or responses get dropped.  You could write a more sophisticated server that makes regular requests, keeps sequence numbers, times out on lost responses and reissues them.  Whatever you would like.  We’ll discuss what comes out.

 

Discussion

 

In this lab we have built an UDP/IP based embedded network application in which the infrastructure essentially polled the embedded device by sending requests to it.  This is a natural use of a remote procedure call (RPC) paradigm.  Of course, in conventional networks all sorts of RPC style distributed system architectures have been developed, including Sun RPC, Microsofts DCOM, Corba, XML-RPC.  And in the world of industrial instrumentation, there are many command/response protocols used.  In lab 5 you probably sent text strings back and forth.  You could instead have sent binary packets according to some packet format.  This is in fact what most instrumentation standards do.  The entire area of low-power wireless embedded RPC distributed systems design is an almost completely unexplored research area.  In the past, the only mechanism available was active messages within the embedded network and a dedicated gateway device at the egress point.  The presence of IP based communication allows the ends of the RPC to be on completely different networks.