This page presents the source code for a C++ server that implements our file system and communicates with the client we wrote earlier. The code here is fully functional, apart from the required interlocking for threads.
The server is remarkably free of code that relates to distribution: most of the server code is simply application logic that would be present just the same for a non-distributed version. Again, this is one of the major advantages of Ice: distribution concerns are kept away from application code so that you can concentrate on developing application logic instead of networking infrastructure.
On this page:
Implementing a File System Server in Objective-C
We have now seen enough of the server-side Objective-C mapping to implement a server for our file system. (You may find it useful to review these Slice definitions before studying the source code.)
Our server is composed of three source files:
This file contains the server main program.
This file contains the implementation for the
This file contains the implementation for the
Server main Program in Objective-C
Our server main program, in the file
Server.m, uses the structure we saw earlier:
There is quite a bit of code here, so let us examine each section in detail:
The code includes the header objc
/Ice.h, which contains the definitions for the Ice run time, and the files
DirectoryI.h, which contain the definitions of our servant implementations.
The next part of the source code is mostly boiler plate: we create an object adapter, and, towards the end, activate the object adapter and call
waitForShutdown, which blocks the calling thread until you call
destroy on the communicator. (Ice does not make any demands on the main thread, so
waitForShutdown simply blocks the calling thread; if you want to use the main thread for other purposes, you are free to do so.)
The interesting part of the code follows the adapter creation: here, the server instantiates a few nodes for our file system to create the structure shown below:
A small file system.
As we will see shortly, the servants for our directories and files are of type
FileI, respectively. The constructor for either type of servant accepts two parameters: the name of the directory or file to be created and the servant for the parent directory. (For the root directory, which has no parent, we pass a
nil parent.) Thus, the statement
creates the root directory, with the name
"/" and no parent directory.
Here is the code that establishes the structure in the above illustration shown:
We first create the root directory and a file
README within the root directory. (Note that we pass the servant for the root directory as the parent pointer when we create the new node of type
After creating each servant, the code calls
activate on the servant. (We will see the definition of this member function shortly.) The
activate member function adds the servant to the ASM.
The next step is to fill the file with text:
Recall that Slice sequences map to
NSMutableArray, depending on the parameter direction. Here, we instantiate that array and add a line of text to it.
Finally, we call the Slice
write operation on our
FileI servant by simply writing:
This statement is interesting: the server code invokes an operation on one of its own servants. Because the call happens via the pointer to the servant (of type
FileI) and not via a proxy (of type
id<FilePrx>), the Ice run time does not know that this call is even taking place — such a direct call into a servant is not mediated by the Ice run time in any way and is dispatched as an ordinary Objective-C function call. The operation implementation in the servant expects a
current object. In this case, we pass nil (which is fine because the operation implementation does not use it anyway).
In similar fashion, the remainder of the code creates a subdirectory called
Coleridge and, within that directory, a file called
Kubla_Khan to complete the structure in the above illustration.
Servant Class Definitions in Objective-C
We must provide servants for the concrete interfaces in our Slice specification, that is, we must provide servants for the
Directory interfaces in the Objective-C classes
DirectoryI. This means that our servant classes look as follows:
Each servant class derives from its skeleton class and adopts its skeleton protocol.
We now can think about how to implement our servants. One thing that is common to all nodes is that they have a name and a parent directory. As we saw earlier, we pass these details to a convenience constructor, which also takes care of calling
autorelease on the new servant.
In addition, we will use UUIDs as the object identities for files and directories. This relieves us of the need to otherwise come up with a unique identity for each servant (such as path names, which would only complicate our implementation). Because the
list operation returns proxies to nodes, and because each proxy carries the identity of the servant it denotes, this means that our servants must store their own identity, so we can create proxies to them when clients ask for them.
File servants, we also need to store the contents of the file, leading to the following definition for the
The instance variables store the name, parent node, identity, and the contents of the file. The
filei convenience constructor instantiates the servant, remembers the name and parent directory, assigns a new identity, and calls
Note that the only Slice operation we have defined here is the
write method. This is necessary because, as we saw previously, the code in
Server.m calls this method to initialize the files it creates.
For directories, the requirements are similar. They also need to store a name, parent directory, and object identity. Directories are also responsible for keeping track of the child nodes. We can store these nodes in an array of proxies. This leads to the following definition:
Because the code in
Server.m does not call any Slice operations on directory servants, we have not declared any of the corresponding methods. (We will see the purpose of the
addChild method shortly.) As for files, the convenience constructor creates the servant, remembers the name and parent, and assigns an object identity, as well as calling
Servant Implementation in Objective-C
Let us now turn to how to implement each of the methods for our servants.
FileI in Objective-C
The implementation of the
write operations for files is trivial, returning or updating the corresponding instance variable:
Note that this constitutes the complete implementation of the Slice operations for files.
Here is the convenience constructor:
After allocating and autoreleasing the instance, the constructor initializes the instance variables. The only interesting part of this code is how we create the identity for the servant.
generateUUID is a class method of the
ICEUtil class that returns a UUID. We assign this UUID to the
name member of the identity.
We saw earlier that the server calls
activate after it creates each servant. Here is the implementation of this method:
This is how our code informs the Ice run time of the existence of a new servant. The call to
add on the object adapter adds the servant and object identity to the adapter's servant map. In other words, this step creates the link between the object identity (which is embedded in proxies), and the actual Objective-C class instance that provides the behavior for the Slice operations.
add returns a proxy to the servant, of type
id<ICEObjectPrx>. Because the
contents instance variable of directory servants stores proxies of type
addChild expects a proxy of that type), we down-cast the returned proxy to
id<FSNodePrx>. In this case, because we know that the servant we just added to the adapter is indeed a servant that implements the operations on the Slice
Node interface, we can use an
The call to
addChild connects the new file to its parent directory.
DirectoryI in Objective-C
The implementation of the Slice operations for directories is just as simple as for files:
contents instance variable stores the proxies for child nodes of the directory, the
list operation simply returns that variable.
The convenience constructor looks much like the one for file servants:
The only noteworthy differences are that, for the root directory (which has no parent), the code uses
"RootDir" as the identity. (As we saw earlier, the client knows that this is the identity of the root directory and uses it to create its proxy.)
addChild method connects our nodes into a hierarchy by updating the
contents instance variable. That way, each directory knows which nodes are contained in it:
activate is very much like the activate for files: