Finding the server's ND/PID/CHID

Updated: April 19, 2023

You've noticed that in the ConnectAttach() function, we require a Node Descriptor (ND), a process ID (PID), and a channel ID (CHID) in order to be able to attach to a server. So far we haven't talked about how the client finds this ND/PID/CHID information.

If one process creates the other, then it's easy—the process creation call returns with the process ID of the newly created process. Either the creating process can pass its own PID and CHID on the command line to the newly created process or the newly created process can issue the getppid() function call to get the PID of its parent and assume a “well-known” CHID.

What if we have two perfect strangers? This would be the case if, for example, a third party created a server and an application that you wrote wanted to talk to that server. The real issue is, “How does a server advertise its location?”

There are many ways of doing this; we'll look at four of them, in increasing order of programming “elegance”:

  1. Open a well-known filename and store the ND/PID/CHID there. This is the traditional approach taken by UNIX-style servers, where they open a file (for example, /etc/httpd.pid), write their process ID there as an ASCII string, and expect that clients will open the file and fetch the process ID.
  2. Use global variables to advertise the ND/PID/CHID information. This is typically used in multithreaded servers that need to send themselves messages, and is, by its nature, a very limited case.
  3. Use the name-location functions (name_attach() and name_detach(), and then the name_open() and name_close() functions on the client side).
  4. Take over a portion of the pathname space and become a resource manager. We'll talk about this when we look at resource managers in the Resource Managers chapter.

The first approach is very simple, but can suffer from “pathname pollution,” where the /etc directory has all kinds of *.pid files in it. Since files are persistent (meaning they survive after the creating process dies and the machine reboots), there's no obvious method of cleaning up these files, except perhaps to have a “grim reaper” task that runs around seeing if these things are still valid.

There's another related problem. Since the process that created the file can die without removing the file, there's no way of knowing whether or not the process is still alive until you try to send a message to it. Worse yet, the ND/PID/CHID specified in the file may be so stale that it would have been reused by another program! The message that you send to that program will at best be rejected, and at worst may cause damage. So that approach is out.

The second approach, where we use global variables to advertise the ND/PID/CHID values, is not a general solution, as it relies on the client's ability to access the global variables. And since this requires shared memory, it certainly won't work across a network! This generally gets used in either tiny test case programs or in very special cases, but always in the context of a multithreaded program. Effectively, all that happens is that one thread in the program is the client, and another thread is the server. The server thread creates the channel and then places the channel ID into a global variable (the node ID and process ID are the same for all threads in the process, so they don't need to be advertised.) The client thread then picks up the global channel ID and performs the ConnectAttach() to it.

The third approach, where we use the name_attach() and name_detach() functions, works well for simple client/server situations.

The last approach, where the server becomes a resource manager, is definitely the cleanest and is the recommended general-purpose solution. The mechanics of “how” will become clear in the Resource Managers chapter, but for now, all you need to know is that the server registers a particular pathname as its “domain of authority,” and a client performs a simple open() of that pathname.

Note: I can't emphasize this enough:

POSIX file descriptors are implemented using connection IDs; that is, a file descriptor is a connection ID! The beauty of this scheme is that since the file descriptor that's returned from the open() is the connection ID, no further work is required on the client's end to be able to use that particular connection. For example, when the client calls read() later, passing it the file descriptor, this translates with very little overhead into a MsgSend() function.