http://www.developer.com/

Back to article

Palm OS Communications Primer: Net Library


August 11, 2004

The previous article told about Serial Manager. Now, we'll discuss how to develop network applications in a Palm OS environment. Obviously, we won't cover all available API calls, but will try to highlight the most common flow.

Net Library Architecture: Distant Glance

The Network Library can be naturally divided into two parts: the API itself and the protocol stack. This is a standard division for such kind of arcitectures. Your application as a client invokes API methods to get desired functionality. API translates all this calls into correspondend requests. These requests are then placed into some intermediate part—mailbox queue—and protocol stack layer reads requests from this queue and interacts with network. All responses are put back to mailbox queue, and finally and API returns answers back to client applications. Thats's the principal working flow of Palm OS Network Library.

If you want to map this schema against the OSI network model, we may state that the protocol stack provides transport, network, and data link services. TCP and UDP are transport-layer protocols, IP is a network-layer protocol, and PPP and SLIP are data link-layer protocols. Your program is responsible for providing the application, presentation, and session services of the OSI model. Thus the Net Library API plays a similar role as Berkeley sockets or the Winsock API.

If you have some experience in Unix network programming, here is the good news. As a matter of fact, you may develop your network application under a Unix environment and then (with a bit of glue here and there, of course) compile it for Palm OS. Changes will be really minor.

Stepping Into the Library

Net Lib is a system library, so it's usually preloaded at your application's runtime. Nevetheless, the standard and correct manner is first to find "Net.lib" and to load this library if necessary:

  // Find the network library
   UInt16 libRefnum = 0;
   Err error = errNone;

   error = SysLibFind("Net.lib", &libRefnum);
   if (error)
   {
      // Handle it
      return;
   }

   // Open the network library
   UInt16 netIFErrs = 0;
   error = NetLibOpen(libRefnum, &netIFErrs);
   if (netIFErrs || (error && error != netErrAlreadyOpen))
   {
       // Errors during opening Net Lib or bringing up the
       // network interface
       // Handle it somehow
   }

During above calls you're getting libRefnum—the reference number of the library. You will use it for all future calls to the rest of the network API. Please notice that when you receive netErrAlreadyOpen as an error, it means just that Net Lib is open. In this case, libRefnum is simply incremented.

Default values of interfaces to attach, IP addresses, and so forth are taken from the preference panel settings unless you want to open specific configuration by calling the following:

Err NetLibOpenConfig (UInt16 refNum,UInt16 configIndex,
                      UInt32 openFlags, UInt16 *netIFErrP);

When Net Library is no longer needed, you should release it:

error = NetLibClose(libRefnum, false);

The last parameter of this function (UInt16 immediate) controls the shutdown algorithm. If libRefnum is greater than 1, the function decreases the reference counter and returns netErrStillOpen. Otherwise, it behaves as follows:

  • If immediate is true, everything shuts down immediately. In other words, all network inrefaces, network protocol stack, and so forth are brought down, all memory is freed, and so forth.
  • If immediate is false, the OS creates a timer to wait during some period, but NetLibClose returns immediately. Net Lib is left in a "close-wait" state. If the timer expires, the Palm OS shuts libray down. If another call to NetLibOpen occurs before expiration time, the library remains open.

This brilliant solution significally increases performance because it allows you to launch another network application within a short period of time without any reinitialization.

Typical Scenarios

Before all the story, let's just declare that basically all network applications may be classified either as servers or clients. You certainly may develop some program that will combine both of them simultaniously. But, in most cases, with a Palm device you will need to create clients rather than servers. There are well-known schemas for each type of application. You can find then in any book. Here we place them for reference only:

Client:

  • Perform name resolution to obtain a list of addresses to communicate with (NetLibGetHostByName, NetLibAddrAToIN, and so forth)
  • Open a socket with the NetLibSocketOpen function using the properties returned in the above call
  • Attempt to connect to the server with the NetLibSocketConnect function
  • Exchange data with a server, using the send/recv functions
  • For connection-oriented sockets, shut down the socket with the NetLibSocketShutdown function
  • Close the socket with the NetLibSocketClose function

Server:

  • Perform name resolution to obtain a list of addresses to serve on
  • Create a list of serving sockets, one for each address, using the NetLibSocketOpen function
  • Name the sockets with the NetLibSocketBind function
  • For TCP stream sockets, listen for incoming client connections with the NetLibSocketListen function
  • Wait for incoming data (connectionless) or connections (connection-oriented)
  • For TCP, accept a client connection with the NetLibSocketAccept function
    The accept function creates a new socket. The original socket opened by the server continues to listen and can be used to accept more connections until closed. Server applications must close the listening socket, in addition to any sockets created, by accepting a client connection
  • Exchange data with a server, using the send/recv functions
  • For applications that require compatibility with both connection-oriented and connectionless sockets, always use sendto and recvfrom
  • For connection-oriented sockets, shut down the socket with the NetLibSocketShutdown function
  • When data exchange between the server and client ends, close the socket with the NetLibSocketClose function.
    An application should call NetLibSocketShutdown before calling NetLibSocketClose

Below we will consider some aspects of each application type.

Creation and Setup

In the case of Palm OS, you must call the NetLibSocketOpen function to create the socket:

// Open a socket
UInt16 libRefnum = 0;
Err error = errNone;
...
NetSocketRef socket =
         NetLibSocketOpen(libRefnum,              // Network library
                          netSocketAddrINET,      // Address domain
                          netSocketTypeStream,    // Socket type
                          netSocketProtoIPTCP,    // Protocol
                          -1,                     // Timeout
                          &error                  // Error result
                          );
if (error)
{
// Handle it somehow
}

Because it's written in SDK's help, Net Library supports INET or Raw address domains. You may specify either TCP/IP, UDP, or Raw socket types. For simplicity, we will not discuss Raw sockets, just couple of comments; this is long story by itself.

Well, here are a few words about raw sockets anyway. Since Palm OS 3.0, the following types are supported:

  • Raw sockets in the netSocketAddrINET domain: In this case, you must bind the socket to an IP address using NetLibSocketBind, passing a NetSocketAddrINType structure for the socket address. The port field is ignored.
    For applications that use raw sockets in the INET domain, the net library checks the destination IP address of all incoming packets to see if it matches any of those raw sockets. If it does, the packet is enqueued directly into the matching socket and is not passed to the protocol stack.
    When an application sends data through raw sockets in the IP domain, the net library packages the data into a packet and passes it directly to the interface's send routine. You are responsible for forming the entire IP header, including any necessary checksums, source and destination IP address, and so on.
  • Raw sockets in the netSocketAddrRaw domain with no protocol: In this case, you must bind the socket to an interface using NetLibSocketBind, passing a NetSocketAddrRawType structure for the socket address. The instance and creator specify from which interface the caller wants to receive raw packets.
    When an interface is bound to a raw socket with no protocol, the net library places that interface into raw mode. In raw mode, the interface passes all incoming packets, no matter what the link layer protocol, to its raw receive function.
    When an application sends data through a raw socket with no protocol, the net library packages the data into a packet and passes it directly to the interface's send routine.
    The interface remains in raw mode until the raw socket is closed

Thus, the main headaches are on you.

If we've succeeded to open a socket of the desired type, we have several opportunities. Your can get or set a lot of socket options by calling NetLibSocketOptionSet; for example, linger behavior, OOB data processing, blocking mode, and so forth. The next snippet demonstrates how to cancel the blocking mode:

UInt8 bNonBlocking = 1;
Int16 nRes =
      NetLibSocketOptionSet(libRefnum, socket,
                            netSocketOptLevelSocket,
                            netSocketOptSockNonBlocking,
                            &bNonBlocking,sizeof(bNonBlocking),
                             -1, &err);

As you can see, in the meantime all the stuff is pretty similar to UNIX or Windows.

Establishing a Connection

Once a socket is opened and configured, you either can establish a connection to the remote host and/or send/receive data or setup the socket to listen to incoming connections. First, let's take a look at the client code. Depending on the socket type, the work flow will be a bit different. Stream sockets in fact require establishing a connection before any transmission operations. In case of UDP, you can provide the host address as one of parameters. If the UDP socket was previously connected, you'll get an error if you specify a host address. Hence, you may change the next code snippet according to your needs or skip it in case of UDP or Raw sockets:

NetIPAddr addr = NetLibAddrAToIN(libRefnum, textIP);

// Connect the socket to its destination
NetSocketAddrINType destAddr;
MemSet(&destAddr, sizeof(destAddr), 0);
destAddr.family = netSocketAddrINET;    // This should match the
                                        // second argument to
                                        // NetLibSocketOpen
destAddr.port = 8080;
destAddr.addr = addr;
Err error = 0;

result =
   NetLibSocketConnect(libRefnum,                     // Network
                                                      // library
                       socket,                        // Socket
                                                      // reference
                       (NetSocketAddrType*)&destAddr, // Destination
                                                      // address
                       sizeof(destAddr),              // Length of
                                                      // destAddr
                       -1,                            // Timeout
                       &error                         // Error result
                       );
if (result == -1)
{
   // handle error
   goto CloseSocket;
}

If you develop a server application, a set of calls will be like the following sample:

Err SetupServerSocket(NetSocketRef socket)
{
   Err error;
   NetSocketAddrINType addr;
   addr.family = netSocketAddrINET;
   addr.port = NetHToNS(8080);
   addr.addr = NetHToNL(0);

   if (NetLibSocketBind(libRefnum, socket, &addr, sizeof(addr), -1,
                        &error) != -1)
      error = errNone;

   if (NetLibSocketListen(libRefnum, socket, 5, -1, &error) != -1)
      error = errNone;

      return error;
}

To retrieve the host address, you may use NetLibGetHostByName and analogous functions.

Transmitting the Data

Let's say several words about sending/receiving. There is a rich family of such functions:

Int16 NetLibSendPB(UInt16 libRefNum, NetSocketRef socket,
                   NetIOParamType *pbP, UInt16 flags,
                   Int32 timeout, Err *errP);
Int16 NetLibSend(UInt16 libRefNum, NetSocketRef socket,
                 void *bufP, UInt16 bufLen, UInt16 flags,
                 void *toAddrP, UInt16 toLen,
                 Int32 timeout, Err *errP);
Int16 NetLibReceivePB(UInt16 libRefNum, NetSocketRef socket,
                      NetIOParamType *pbP, UInt16 flags,
                      Int32 timeout, Err *errP);
Int16 NetLibReceive(UInt16 libRefNum, NetSocketRef socket,
                    void *bufP, UInt16 bufLen, UInt16 flags,
                    void *fromAddrP, UInt16 *fromLenP,
                    Int32 timeout, Err *errP);
Int16 NetLibDmReceive(UInt16 libRefNum, NetSocketRef socket,
                      void *recordP, UInt32 recordOffset,
                      UInt16 rcvLen, UInt16 flags,
                      void *fromAddrP, UInt16 *fromLenP,
                      Int32 timeout, Err *errP);

As noted above, for each socket type you will or will not specify a host IP address, timeout period, and data buffer. Please pay attention that the stream socket allows sending a single buffer that the caller provides. For UDP sockets, you have to send a single packet at a time (1536 bytes). Otherwise, no data is sent at all. Raw sockets (supported in Palm OS version 3.0 and higher) must construct the entire IP header, including the destination address, before data can be sent; thus, the address is taken from the data to be sent. In the sample project, you will find some simple examples of different calls.

NetLibDmReceive is a Palm OS-specific function that has no equivalent in a standard network API. It receives data from a socket directly into a database record. You only provide the record's start address and offset. In all other aspects, it behaves similarly to a NetLibReceive call.

Debugging Network Applications

Net Library contains a NetLibMaster function, which provides the network statistics, interface statistics, and the contents of the trace buffer. The last feature is quite useful for debugging purposes. NetLibMaster, together with NetLibTracePrintF and NetLibTracePutS, gives you a powerful mechanism to debug either your application or network configuration issues. To get an access to the tracing capability, you can call either NetLibSettingSet for the Net Library in general or NetLibIFSettingSet for specific a network interface, passing netSettingTraceBits as target setting. The netTracingAppMsgs bit should be raised to get the above functions doing something. The default value is (netTracingErrors | netTracingAppMsgs), but if you don't see any output, take care about setting everything properly. The next code snippet illustrates all this dry theory:

///////////////////////////////////////////////////////////////////
// Enable tracing
///////////////////////////////////////////////////////////////////
void SetupTracing()
{
   Err   err;
   DWord value;
   Word  settingSize;
   Word  index;
   Byte  traceRoll;

   value = 0x0800;
   settingSize = sizeof(value);
   err = NetLibSettingSet(libRefnum, netSettingTraceSize, &value,
                          settingSize);
   if (err)
   {
      // Handle error
      return;
   }

   // Set tracing bits
   value = netTracingErrors | netTracingMsgs | netTracingAppMsgs;

   // Set trace level for stack
   NetLibSettingSet(libRefnum, netSettingTraceBits, &value,
                    settingSize);

   // Set rollover to none
   traceRoll = false;
   NetLibSettingSet(libRefnum, netSettingTraceRoll, &traceRoll,
                    sizeof(traceRoll));

   // Set trace bits also for all attached interfaces
   for (index = 0; 1; index++)
   {
      DWord ifCreator;
      Word  ifInstance;

      err = NetLibIFGet(libRefnum, index, &ifCreator, &ifInstance);
      if (err)
      {
         err = 0;
         break;
      }

      NetLibIFSettingSet(libRefnum, ifCreator, ifInstance,
                         netIFSettingTraceBits, &value, settingSize);
   }
}

...
///////////////////////////////////////////////////////////////////
// Do actual traces
///////////////////////////////////////////////////////////////////
NetLibTracePrintF(libRefNum, "Error occured is %x\n", error);
...

///////////////////////////////////////////////////////////////////
// Extract tracing data
///////////////////////////////////////////////////////////////////

void ShowTracing()
{
   NetMasterPBType pb;
   Err             err;
   Char            text[100];
   DWord           oldTrace;
   Int             i;
   DWord           value;
   Word            settingSize;
   Boolean         setTraceBits = false;
   Word            index;
   Byte            traceRoll;
   CharPtr         cmdP;

   // Temporarily disable tracing
   settingSize = sizeof(oldTrace);
   NetLibSettingGet(libRefnum, netSettingTraceBits, &oldTrace,
                    &settingSize);
   value = 0;
   err = NetLibSettingSet(libRefnum, netSettingTraceBits, &value,
                          settingSize);

   if (oldTrace == 0)
      StdPutS("Tracing not on\n");
   else {
      StdPutS("From oldest to newest...");
      StdPutS("\nTICKS EVENT  ROUTINE\n");
   }

   // See what the oldest entry is
   for (i=0; 1; i++) {
      pb.param.traceEventGet.index = i;
      pb.param.traceEventGet.textP = text;
      err = NetLibMaster(libRefnum, netMasterTraceEventGet, &pb,
                         AppNetTimeout);
      if (err) {
         printf("Error: %s\n", appErrString(err));
         break;
      }
   }

   // Print them in oldest to newest order
   for (i=i-1; i>=0 ; i--)
   {
      pb.param.traceEventGet.index = i;
      pb.param.traceEventGet.textP = text;
      err = NetLibMaster(libRefnum, netMasterTraceEventGet, &pb,
                         AppNetTimeout);
      if (err) break;
      // User 'text' buffer as you want to
      ...
   }

   // Restore tracing level
   err = NetLibSettingSet(libRefnum, netSettingTraceBits, &oldTrace,
                          settingSize);
}

Download

You may download the sample project here.

About the Author

Alex Gusev started to play with mainframes at the end of the 1980s, using Pascal and REXX, but soon switched to C/C++ and Java on different platforms. When mobile PDAs seriously rose their heads in the IT market, Alex did it too. Now, he works at an international retail software company as a team leader of the Mobile R department, making programmers' lives in the mobile jungles a little bit simpler.

Sitemap | Contact Us

Thanks for your registration, follow us on our social networks to keep up-to-date