Paul Colton is a Software Engineer for Inter-National Research Instititute, Inc. in San Diego, California. He has over five years programming experience and enjoys developing computer graphics software including 3-D rendering systems. He runs an Internet-based publishing and software distribution company, MiNET, at http://www.minet.com/minit/. Paul can be reached via e-mail at pc@minet. com.
Introduction
Most imaging systems need to perform complex CPU-intensive image processing functions on data. These computations tax the software a great deal and often result in the decrease of overall software performance. As a remedy, a client/server model is useful in distributing the work.In the client/server model with Remote Procedure Calling (RPC), imaging programs can be divided into two sections, the client and the server. The client takes care of the users' needs, the user interface, file I/O, and any other user-specific items. The server performs the image processing operations. This division of labor lets the client CPU focus on the user while the server CPU focuses on the actual work. Multiple clients can connect to the same server, making each of the clients smaller in code size. Because the clients and server need not run on the same hardware, the server can benefit from a dedicated machine. If the server is running on a much faster platform than the clients', each client will attain most of the image processing speed of the server to which it is connected. Also, this configuration allows for file sharing of data between the clients.
This article describes the implementation of such a client/server-based image processing system. Furthermore, this article shows how to establish a dynamic relationship between the client and the server, and how to design the server for maximum expandability.
The Client/Server Model and RPC
The client/server model for computing is a popular method for distributed processing. This model allows processes to make requests for a task while a separate process actually does the work. Interprocess communication (IPC) is an essential part of the client/server model. IPC allows two processes to communicate and exchange data. Unfortunately, IPC is difficult to develop, debug, and use. Also, it tends to be machine-dependent.Remote Procedure Calling (RPC) was developed so programmers would not have to work at this level of complexity. RPC hides the details of IPC and makes the development of applications much easier as well as very portable. RPC allows local client applications to execute procedures on remote servers. Then the servers can send the results of the procedure back to the client.
The Image Processing Server (IPS)
The dynamics of the IPS are rooted in the server application. The IPS has three main functions:1. To receive an image from a client and decode it into a native format
2. To perform image processing functions on that native format
3. To return the image to the client in a specified format
The server does not do the decoding, image processing, and encoding directly, but calls upon three banks of supporting programs: loaders, savers, and operators. These programs are separate, self-contained executables that respectively load, save, and perform operations on images. The server source code does not contain any specific information about which image formats are supported directly. The source code only knows where the loaders, savers, and operators are physically located. The client is responsible for telling the server the format of the incoming data. The server will execute the proper image loader, if available, to convert the image into an internal format. This protocol also applies to the savers and operators.
The Image-Processing Client
The image-processing client is responsible for sending the image data to the server along with the image format. For example, if the client sends an X-Window Dump (XWD) image to the server, the server then checks if the XWD loader exists. If so, the server executes the XWD loader using the client file as the input parameter and a temporary file as the output parameter. The server gives a unique id to the new converted image and returns that id to the client. When the client needs to perform image-processing operations on this image, the client need only send the image-processing command and the image id to the server. When the client has completed sending image-processing operations to the server, it can request that the image be sent back in its final form. Again, the client sends an image format, and the server executes the proper image saver.
Dynamic Communications
Neither the server nor the client knows which loaders, savers, or operators are available. That is, the source code contains no references to any image formats or image-processing functions. Here is where the dynamics of this system come into play. The system remains very open, indeed almost infinitely expandable, by introducing only new loaders, savers, and operators, and by never modifying the original server source code.The client and server can communicate with each other to determine what is available. The client asks the server for three lists: the loaders available, the savers available, and the operators available. The server then checks its predetermined loaders, savers, and operators directories and responds with the lists. Now the client is fully aware of what image formats and operators are available and can make this information available to the user for selection.
Furthermore, a unique type of loader called an AUTO-DETECT image loader can be written. This executable does not load an image directly, but auto-detects the image format and then call the proper loader, freeing the client from knowing the format of the image, and consequently letting the client operate without having to know what loaders are available from the server.
Implementation
Developing any RPC applications first requires a communications protocol file. This file is written in the ONC RPC Language (RPCL), a C-style language that details how the client and server will exchange data. A protocol compiler inputs this file and generates the client and server stubs in C. I use the ONC RPCGEN protocol compiler. Listing 1 shows the protocol for the Image Processing Server, or IPS. To compile this protocol, use the following command:
prompt> rpcgen ips.xRPCGEN will generate the client stub, ips_clnt.c, and the server stub, ips_svc.c. It will also produce an XDR filter file, ips_xdr.c, as well as a header file, ips.h. To compile all of the pieces use the following commands:To compile the client stub:
prompt> cc -c ips_clnt.cTo compile the server stub:
prompt> cc -c ips_svc.cTo compile the XDR filters:
prompt> cc -c ips_xdr.cYou should place all of these files in one directory. I chose the name ips as my directory name.Now that the RPC stubs have been generated and compiled, you must create and compile the actual client and server applications, and put all of the pieces together. The example server in this article allows clients to perform several functions.
The server must first allow clients to connect to it. Upon connection, the server will return a welcome message to the client. Now the client can request the lists of loaders, savers, and operators, and it can also send and receive images from the server. When the server receives an image file, it returns a unique id to the client. The client uses this id to access the image for image-processing operations as well as to receive the image back upon completion of the processing functions.
The main function in the server source code is ips_1. This function has a stub in the client source code. When the client sends a request to the server via ips_1, the proper functions are called via a switch statement in the server code. Listing 2 shows the source code for the server process. To compile this program, use the following command (broken to fit column):
prompt> cc -o ips ips.o ips_svc.o ips_xdr.oThe server also requires that a few environment variables be set. These environment variables tell the server where the loaders, savers, and operators are located, as well as what directory to use for a temporary storage area. You must set the following case-sensitive variables prior to running any portion of this software:LOADERS_DIR the absolute path name of the loaders directory
SAVERS_DIR the absolute path name of the savers directory
OPS_DIR the absolute path name of the operators directory
STORAGE_DIR the absolute path name of the temporary storage directory
I created four directories at the same level as the above-mentioned ips directory; they are loaders, savers, operators, and temp. You must set all of the above environment variables to these directories. To run the server, simply type the name of the server program in the case ips. If rou are using X-Window, you can run the server in its own window. For example, you might use:
prompt> xterm -e ips &This would allow you to view the progress of the server without its output interfering in your window. If you're running the server on a single window system, you can redirect the server's output to a file or to a null device, such as /dev/null.Listing 3 shows the client program. This program shows only one example of what can be done and does not exhaust all possibilities. For the sake of clarity and size, I use no graphic interface, but a simple ASCII menuing system. This program allows a user to send an image, receive an image, get the list of loaders, savers, and operators, and execute an operator on the current image in the server. The server is not limited to storing a single image, but this client only demonstrates the use of a single image at a time.
When you execute the client, it will display the welcome message from the server and then proceed to prompt you for input. Entering a question mark will display a menu of all available options. When you send image-format type information or an operator name, be sure to type it exactly as it is shown from the list of available items. (Don't type "xwd" for "XWD".) To compile the demo client program, use the following command (broken to fit column):
prompt> cc -o demo_client demo_client.o ips_clnt.o ips_xdr.oThis client program should reside in the same directory as the server. To run the program, make sure the server is already running, then just type demo_client followed by the hostname of the machine that is running the server.Finally, you will need the supporting programs the loaders, savers, and operators. The loaders, savers, and operators need to agree on an internal format, but since they are the only ones using this format, it can be changed at any time without recompiling the server or client. As a further advantage, the clients and server can be running while you are adding or changing these programs. In this article, the internal format is simply implemented as a header containing any needed information like number of colors and image size, followed by the actual uncompressed image data. Listing 4 shows the header for the internal format. All of the supporting programs use this header.
The loader, saver, and operator all use a small library to keep the code size short. This library (Listing 5) contains some convenience routines for loading the image data and writing the image data back to a file. The library should be linked with the loader, saver, and operator code. To compile the library, use the following commands:
prompt> cc -c fileio.c prompt> ar rc libips.a fileio.o prompt> ranlib libips.a (for SunOS)I have supplied two example loaders for this article, XWD and AUTO-DETECT. Listing 6 shows the X-Window (XWD) image-format loader. You should put XWD in a different directory than the ips directory, such as loaders_source, along with the ips_header.h file. To compile this program, use the following command:
prompt> cc -o XWD xwd.c libips.aMove the compiled version of this program into the same directory pointed to by the LOADERS_DIR environment variable.Listing 7 shows source code for the second loader, AUTO-DETECT. AUTO-DETECT demonstrates the use of an auto-detect file format. It supports the detection of XWD and GIF images. Put this source code in the same directory as the XWD loader source. To compile this program, use the following command:
prompt> cc -o AUTO-DETECT auto-detect.cMove the compiled version of this loader into the LOADERS_DIR directory.Listing 8 shows an example file saver for XWD format. To compile the source code, follow the same instructions that you used to compile the XWD loader. Place this source in a savers_source directory, and then move the executable, named XWD, to the same directory as indicated by the SAVERS_DIR environment variable. (Note that the XWD loader and saver executables will have the same filenames.)
I include here the source code for one example image operator. Listing 9 shows the operator called FLIP-Y. This operator will flip an image vertically. Place the source in a operators_source directory. This source can be compiled like the others, but you should call the executable FLIP-Y and place it in the same directory pointed to by the OPS_DIR environment variable.
You now have all of the pieces you need to run both the image processing server and the demo client. As a test of the system, grab an X-Window Dump image via the xwd program and try sending it to the server and getting it back. If the server is not responding, make sure all of the above steps have been executed properly. Be sure that the image filename you supply to the client does not contain any directory information. The file should be located in the current directory.
Extending the Model
I've shortened the code listed in this article for clarity, but many options can be added to the server for even greater functionality. One issue I haven't addressed is how to handle loaders, savers, or operators that need more information. For example, a SCALE function would need to know the new size of the image. One approach is to develop a protocol between the client, server, and the loaders, savers, or operators. This protocol would allow the client to ask the server what arguments are needed by the program in question; then the client could prompt the user for this information. The client would pass the final command to the server with the arguments included.The server included in this article already allows for arguments to be passed to the savers and the operators. This option is useful for savers like JPEG, which requires that the user enter a compression ratio.
The AUTO-DETECT loader can easily be expanded to include new file formats. This system need not be limited to imagery. In fact, the server can handle any bit-oriented data. The server is indifferent to the data coming across; it only passes the data along to the supporting programs. You can easily add digital audio, video, and other formats to this system.
Conclusion
This dynamic model of a client/server system for image processing opens many possibilities: having slower systems harness the power of a faster system on a network, sharing files among clients on the network, distributing the image processing among multiple servers for improving performance, allowing the clients to concentrate the CPU on the user interface, and allowing multiple platforms to share the image-processing power of a single server. With some creativity, this system can become a very powerful and valuable tool in image processing and data handling in general.
Bibliography
Bloomer, J. Power Programming with RPC. O'Reilly & Associates, 1991.