Thin Servers
Halliburton Systems Inc - THEOS Networking Series

"Thin is in"
According to the fashion industry, thin is better. Sometimes that is true in server software also. First, let's look at the difference between a "thin" server and a "thick" server. Note that there isn't really any meaning in the words "thick" and "thin" here. They are often used as substitutes for "smart" and "dumb" or "capable" and "limited".

As with most simplications, with server nomenclature it is not necessarily true that a "thick" server is "better" or "smarter" or more "capable". Almost always, though, a thick server is bigger.

In general, for this discussion, a "server" is a program running on a computer that can respond to a request for data; normally, the request for data comes from another computer, or at least another user or partition on the same computer. If you are reading this, your must be using a server. Your web browser "client" sent a request to the HSI web "server" asking for this page. The server responded with the contents of the page, then your client did some additional work to format and display it correctly.

Let's focus on servers that deal with data files; to keep things in a familiar climate, we suppose that we have some THEOS ISAM files that contain stuff like customer records, invoices, orders, and such. If we were writing a standard THEOS application to deal with these, we would be using calls like readk(), readn(), writek(), etc. For you BASIC guys, this means calls like READNEXT. The typical application program reads lots of these records to perform each transaction; an accounts receivable aging report might have to read all open invoices, read the appropriate customer record for each, and perhaps use records in some other files as controls.

So where would a "server" fit in this picture. If we were to introduce a "thin" server that provided exact look-alike calls for the ones you currently use to access ISAM files, we could have our data files on a different machine or even spread out over a bunch of machines. Why would we make the calls identical in function to the standard ones? That's why we introduced the idea of "wrapper calls" in an earlier chapter. With the proper programming, an application wouldn't even have to know if the data were stored on the same machine or a machine on another planet.

The connection between the client application and the server goes like this: The client application wrapper routine detects that the data is stored on another machine. It packages up a request for the needed data and sends it off to the server. Although a real system would have to have a method of handling variable-length data, key, and file name fields, lets look at some typical packets for the simple case of a fixed-length system.

Byte(s)            Contents
01-02              Type of request (RN=readnext, RP=readprev, etc )      
03-04              File number (returned by open, input otherwise)
05-20              16 character key (input or output)
21-80              60 characters of data (in or out)
81-82              Return status code

OK, so the deal is that the wrapper routine in the client fills in the packet and sends it off to the server. Suppose the packet requests a RN (readnext) function; the server looks up the specified file number in its internal table to find out what file is *really* being read, then issues the read. It returns the key and data in the proper fields and sets the return code to "OK" to show that it worked. If the readnext fails, the status code might be "EF" to show end of file or "PE" for parameter error (bad file number, for example).

The server sends the packet back to the wrapper, which extracts the data, key, and status and returns them as if they had come directly from the THEOS routine. The application never knows what happened.

The connection from the client to the server is via TCP; because TCP is a "connected" protocol (as opposed to UDP which is not) we will "know" if the server dies or the network fails and the wrapper routine in the client can return the appropriate error. The only problem from the application standpoint is that we now have an error that doesn't look like a standard one; on a simple ISAM read using the standard THEOS routines, we would not expect to suddenly see the file "disappear" in between reads.

Likewise, the server can "know" if the client dies without issuing a close function on its served files. The server can then close the files and unlock any locked record.

The two biggest issues to face in this environment are record locking and the number of server processes. These problems are related in an important way. Under normal THEOS conditions, since all users of a file are on the same machine, the kernel locking routines work fine to protect ISAM records. The simple approach to handling the remote case is to have a unique server started on the target machine for each client; remember the discussion of the fork() call in an earlier chapter? The server process is the agent for the individual client, so it can open as many files as the client could have and its locks are just like the client's locks. The problem with this approach is that a large number of clients means a large number of servers.

The other approach is to have only a single server that handles all client requests. This is efficient from the network and memory standpoints, but less efficient from the I/O standpoint because it single-threads all disk I/O. It also makes locking very tricky because it is not likely that the server could keep all the necessary files open at once. We would have to have a concept of "virtual" file handles to connect to files that would be opened and closed as the need arose. The server would have to maintain its own lock tables rather than depending on THEOS.

So which do we choose? Like all good technical questions, the answer to this one is a resounding "it depends". If each client is likely to need lots of files and there aren't too many clients, the server- per-client method may be the best. If each client is likely to need few files, access them infrequently, and there might be many clients, the single-server method is the choice. Every real-world example I have ever seen falls exactly in the middle of these two choices.

We have one case in which all clients on the network need access to only a single record in common: a sequence number that is generated by counting requests. This is an obvious choice for the single- server model. You might have a case of two stores, each with its own inventory data base, but with an occasional need to check stock in the other store. What would you pick here?

Incidently, it is also possible to implement security in the server. The client can pass along its username or other security information and the server can use this to allow or disallow access to files. In the next chapter, we'll extend this function.

To sum up, "thin" servers usually have these characteristics:

Sounds good, no? What's wrong with this picture? Tune in to the next chapter for the answer.
Have any questions? E-mail us at info@hsix.com


Return to Previous Page | Return to the HSI Homepage