THE SESSION LAYER
The session, presentation, and application layers form the upper layers in the OS1 Reference Model. In contrast to the lower four layers, which are concerned with providing reliable end-ta-end comlLunication, the upper layers are concerned \-vith providing user-oriented services. They take the bare-bones, error-free channel provided by the transport layer, and add additional features that are useful to a wide variety of upplications, so that the writers of these applications will not each have to re-implement these features over and over as part of each separate prograrn. The session layer is basically an invention of ISO. Prior to the OS1 model, no existing network had a session layer (although some of the OS1 session services are present in SNA, albeit scattered over several layers), During the development of OSI, there was considerable debate about the need for session layer. The British proposal to ISO, for example, had only five layers and did not include a session layer. Although a majority of the ISO committee eventually decided to include a session layer, it should be clear from the brevity of this chapter that the session layer is a "thin" layer, with relatively few features compared to the lower layers. Furthermore, when a session layer connection is established, options can be selected to disable most of the available features. It is not nearly as important as, say, the transport layer, and many applications do not need even the few features that it does have, Nevertheless, it is now part of the OSI Reference Model, so let us turn to l! and see what services it offers and how they work. A session bears a close resemblance to a transport connection, but the two are not identical. Usually, when a request comes into the session layer to establish a session, a transport connection must be established to carry the connection. When the session is terminated, the transport connection is released. In this example there is a one-to-one mapping between the session and transport connection. 7.1.1. Services Provided to the Presentation Layer The session layer provides services to the presentation layer. Its position in the hierarchy is shown in Fig. 71. This figure is analogous to Fig. 6-1, except that the service access points are called session service access points (SSAPs) and the protocol data units are called session protocol data units (SPDUs). Analogously with the transport layer, we will use the terms session service provider and session entity interchangeably.
a
However, other mappings are also possible. Consider the case of an airline with reservation offices in many cities. Each office has agents with terminals connected to a minicomputer in the local office. The minicomputers are connected by a wide-area network to the main computer holding the reservation data base. Whenever an agent answers a call, a session is established to the main computer. When the call has been processed, the session is terminated, but there is no need to go to the trouble of releasing the underlying transport connection, because it will surely be needed again within a few seconds. It is simpler to allow sequential sessions to use the same transport connection, as shown in Fig. 7-2(b). A third possible mapping between sessions and transport connections is given in Fig. 7-2(c). Here we see a session that spans multiple transport connections. If for example, a transport connection fails (for whatever reason), the session layer can establish a new transport connection, and continue the session over the new one. If the transport entities reside in the hosts. this situation should not occur because the transport entities are expected to recover from network layer (i.e., subnet) failures themselves. However, if the transport entities are external to the hosts, the problem of recovery from external failures is pushed up to the session layer because it then becomes the lowest layer of software that can survive subnet crashes. The main function of the session layer is to provide a way for the session users (e.g., presentation entities or sometimes just ordinary user processes) to establish connections, called sessions, and transfer data over them in an orderly way. A session might be used for a remote login from a terminal to a distant computer, or for a file transfer, or for any of many other purposes. Although connectionless primitives are available in the session layer, a connectionless session cannot make any use of the user-oriented features for which the session layer was designed. As an aside, it is not permitted to multiplex several sessions onto a single transport connection simultaneously the way the transport layer can multiplex several transport connections onto a network connection. At any instant of time, each transport connection carries at most one session. Multiplexing is done to reduce cost or improve performance, which are transport layer functions.
7.1.2. Data Exchange The most important feature of the session layer is data exchange. A session, like a transport connection, goes through three phases: establishment, use, and release. The primitives provided to the presentation layer for establishing, using, and releasing sessions are very similar to the primitives provided to the session layer for establishing, using and releasing transport connections. In many cases, all the session entity has to do when a primitive is invoked by the session user, is invoke the corresponding transport primitive to get the work done. For example, when a session user issues an S-CONNECT.request primitive to establish a session, the session provider just issues a T-CONNECT.request to establish a transport connection (assuming that no transport connection happens to be available). Similarly, session establishment, like transport connection establishment, involves negotiation between the peers (users) to set the values of various parameters. Some of these parameters actually pertain to the transport connection, such as quality of service and the flag telling whether or not expedited data are permitted. These are just passed to the transport connection without modification. Others are specifically related to the session layer. For example, if a session is being established between two computers for the purpose of exchanging electronic mail in both directions, one of the session parameters might specify which side was to go first. Nevertheless, despite these similarities, important differences do exist between a session and a transport connection. Chief among these is the way sessions and transport connections are released. Transport connections are terminated with the T-DISCONNECT.request primitive, which gives an abrupt release and may result in data in transit at the time of the release being irretrievably lost. Sessions are ended with the S-RELEASE .request primitive, which results in an orderly release (also called a graceful release) in which no data are ever lost. An abrupt release, like TDISCONNECT.request, is also available in the form of S-U-ABORT.request. The difference between an orderly and an abrupt release can be seen in Fig. 7-3. In the abrupt release, as soon as either user has issued the appropriate primitive to disconnect, no more data can be delivered to it. In Fig. 7-3(a), once B has invoked T-DISCONNECT.request, its end of the connection is immediately released. It may not receive the message that is currently in transit for it, even if that message arrives before the DR TPDU arrives at A. Furthermore, A may not refuse the request to release the connection. The transport service provider just issues a TDISCONNECT.indication and that is that. It is like a telephone connection: when one party hangs up, the connection is terminated, no matter what the other one wants. Orderly release works differently. It uses a full handshake, with request, indication, response, and confirm primitives. In Fig. 7-3(b), even after B has issued an S-RELEASE.request primitive, it can still accept messages until A has confirmed the release. Just issuing an S-RELEASE .request does not terminate the session by itself.
The remote user must agree. If the remote user wants to continue the session, it can reject the attempt to release (by setting a parameter in the S-RELEASE.response primitive), and the session continues as if nothing had happened. A session is only terminated when both parties have had enough. If you think it strange that orderly release is present in the session layer but not in the transport layer, rest assured that you are not alone. When drafting the transport layer standard for the U.S. Government, the National Bureau of Standards (NBS) also thought it pretty strange, and decided to add an orderly release option to the transport layer, thus solving the problem and simultaneously making the U.S. standard different from that used by the rest of the world. People in the standards business frequently have to choose between doing it right and doing it the way everybody else does it. This is why there are so many standards. Addressing is another area in which the session and transport layers differ, although only slightly. To establish a session, one must specify the SSAP address (rather than TSAP address) to connect to. Although the standards do not tell how SSAP addresses are to be constructed, it is likely that in practice an SSAP address will consist of a TSAP address plus some additional identifying information. One more way in which session data exchange differs from transport data exchange is the number of different kinds of data. The transport layer has two logically independent data streams, regular data and expedited data. The session layer has both of these, and two more (typed data and capability data). The other two streams relate to features of the session layer that we have not yet covered, so we will defer our discussion of them until later in this chapter. 7.1.3. Dialog Management In principle, all OSI connections are full duplex, that is, PDUs can move in both directions over the same connection simultaneously; Full-duplex communication IS shown in Fig. 7-4(a). However, there are many situations in which the upper-layer software is structured to expect the users to take turns (half-duplex communication). This design has nothing to do with the limitations of some ancient terminals still in existence that cannot work in full-duplex mode, but with designing upperlayer software in a convenient way.
As an example, consider a database management system that can be accessed from remote terminals (e.g., airline reservation or home banking). The most natural mode of operation is for the user to send a query to the database system and then wait for the reply. Allowing users to send a second and a third query before the first one has been answered needlessly complicates the system. Logically, it is desirable to operate the system in half-duplex mode: either it is the user's turn to transmit or the database system's. Keeping track of whose turn it is to talk (and enforcing it) is called dialog management, and is one of the services that can be provided by the session layer when requested. The way that dialog management is implemented is by the use of a data token. When a session is established, half-duplex operation is one of the options that can be selected. If half-duplex operation is chosen, the initial negotiation also determines which side gets the token first. Only the user holding the token may transmit data; the other one must remain silent. When the token holder has finished transmitting, it passes the token to its peer using the S-TOKEN-GIVE.request primitive, as shown in Fig. 7-4(b). What happens if the user not holding the token wants to transmit some data? It . can politely ask for the token using the S-TOKEN-PLEASE.request primitive. The token holder can agree, and pass the token, or it can refuse, in which case the other user will just have to wait (or send an emergency message using expedited data, which does not require the token). If full-duplex operation is selected when the session is established, no token is used for data transmission. 7.1.4. Synchronization Another service of the session layer is synchronization, whit:h is used to move the session entities back to a known state in the event of an error or disagreement. At first glance, such a service would seem unnecessary because the transport layer has been carefully designed to transparently recover from all communication errors and subnet crashes. However, closer study shows that the transport layer has been designed to mask only communication errors. It cannot r~cover from upper layer errors. As an example of the problems that can occur, consider the teletex service that is gradually replacing telex (TWX) for sending hard copy messages from one company to another. Subscribers to this service use a device containing an embedded CPU, k~yboard, display, printer, modem, telephone and sometimes a disk. The user first composes a message using the CRT and keyboard. Then the CPU calls up the company to which the message is to be sent, establishes a session, and transfers the message. If the receiving device has no disk storage, a real possibility in the cheaper versions, incoming messages must be printed in real time, as they are received. As soon as each SPDU is received, it is acknowledged and text is given to the printer to print. Now suppose that a paper or ribbon jam occurs. Even if the human operator notices the problem quickly and hits a button on the device to stop printing, some information may be lost. Since it has already been acknowledged, the sender will no longer have a copy, and the message transmission will fail. There is nothing that the transport layer can do about this problem. After all, it has done its job perfectly: it moved all the bits reliably from sender to receiver. Only after handing the SPDU to the session layer did it send back an acknowledgement. It is not the transport layer's fault that the upper layers dropped the ball. The solution lies in the session layer. The session users can split the text up into pages and insert a synchronization point between each page, as shown in Fig. 7-5. In case of trouble, it is possible to reset the state of the session to a previous synchronization point and continue from there. Of course, in order to make this process, called resynchronization, possible, the sending session user (not the session entity) must continue to hold data as long as it might be needed. It is important to understand what the semantics of synchronization are in the session layer. Session users can insert synchronization points in the message stream. Each synchronization point bears a serial number. When one user issues a primitive to request a synchronization point, the other one gets an indication. Similarly, when one issues a primitive to l'esynchronize, the other one gets an indication of that, too. The saving of messages and subsequent retransmission later is all handled above the session layer. All the session layer provides is a way of conveying numbered synchronization and resynchronization signals across the network. The mechanism is actually slightly more complex than we have sketched so far. Two different kinds of synchronization points exist, major and minor, each with its own primitives. The units delimited by major synchronization points are called dialog units, and usually represent logically significant pieces of work. When transmitting a book, for example, the chapters might be delimited by major synchronization points and the pages by minor ones. Figure 7-6 shows a session with three major and four minor synchronization points. 7.1.5. Activity Management
Another key feature of the session layer, closely related to synchronization, is activity management. The idea behind activity management is to let the user split •the message stream up into logical units called activities. Each activity is completely independent of any
other activities that may have corne before it or will corne after it. Major and minor synchronization points differ in several ways. For one thing, when resynchronizing, it is only possible to go back as far as the most recent major synchronization point and no further. Thus in the time interval between synchronization points 6 and 7 in Fig. 7-6, it is permitted to resynchronize back to 6, but not to l, 2, 3, 4, or 5. By erecting a brick wall at each major synchronization point, the sender knows that data that were sent before it can be safely discarded. Minor synchronization points do not have this property. Just before synchronization point 6 in Fig. 7-6 it is permitted to resynchronize to l, 2, 3, 4, or 5. The choice of which type of synchronization point to use and where to use it is up to the session users, of course. Major synchronization points are so significant that each one inserted into the data stream is explicitly confirmed (acknowledged). Minor synchronization points are not confirmed. On a satellite channel, where the minimum delay to send a message and get an acknowledgement is 540 msec, this distinction can be significant. Setting a synchronization point, either major or minor, requires possession of the relevant tokens. Two independent tokens are available (for major and minor synchronization). They are distinct from each other and also distinct from tht: token used to control data flow in half-duplex connections. When resynchronization occurs, all the tokens are restored to the positions they had at the instant that the synchronization point was set. It is up to the user to determine what an activity is. As a first example, consider a session that has been set up for the purpose of transferring several files between two computers. Some way is needed of marking the place where one file ends and the next one begins. Using the ASCII FS (File Separator) character is not a good idea because if the files contain binary information, this character might appear in the data and accidentally signal end of file when that is not intended. Using some form of character stuffing, as is done in the data link layer, is a possibility, but in the session layer the stuffing will probably have to be done in software rather than hardware, so it will be slow. What is really needed is some way to insert a marker into the message stream that is itself distinct from a data message (sometimes called out of band information). One way of achieving this goal is to define each file transfer as a separate activity, as illustrated in Fig. 7-7. In this figure, before each file transfer is started, the sender issues an S-ACTlVITY-START.request primitive. This comes in on the other side as an S-ACTlVITY-START.indication to mark the start of the file. Similarly, after each file transfer is completed, the S-ACTlVITY-END primitive can be used to denote end-of-file.
It is important to emphasize here that the choice of what constitutes an activity is made by the users, not the session layer. All the session layer does is ensure that when one of the S-ACTlVITY requests is made by one user, the other user gets the corresponding indication. When such requests are made and how the recipient reacts to the indications are of no interest to the session layer. The session layeris only concerned with the execution of the primitives, not their meaning (semantiCs) or use. It is worth noting that an activity covers all traffic sent in both directions. As a second example of how activity management can be used, consider a home banking system in which people can pay bills by using their personal computers to transfer money from their accounts to those of the companies issuing the bills. The program running on the personal computer might start out by asking for the number of the account to be debited and send that information to the bank as the first message. Then it could successively ask for the number of the account to be credited and the amount, and send these items in messages two and three. When the first message arrives at the bank's computer, the disk record containing the account to be debited is located and locked to prevent any concurrent access. When the second message arrives, the record for the account to be credited is also locked. When the third message arrives, the money is transferred and both accounts are unlocked. Imagine what would happen if an electrical power failure blacked out the user's home just after the first message had been sent, received at the bank, and processed. The transaction would never be completed and the locked account would remain locked forever. To avoid situations like this, the banking transaction might be structured as a session layer activity. After receiving the S-ACTIVITYSTART. indication, the bank's computer could simply accumulate incoming messages until the S-ACTIVITY-END . indication signaled that there were no more. Only then would processing and locking begin. In this way, no external failures could cause the bank's computer to get stuck half way in a transaction. The technique of collecting messages in an input buffer until all of them have arrived before starting to process any of them is called quarantining. In early drafts of the session layer, quarantining was an explicit session service. However, later, the ISO committee realized that quarantining could equally well be accomplished using activity management, so the quarantine service was not included in the published standard. Our third and last example of activity management makes use of a property that we have not yet mentioned: activities can be interrupted (i.e., suspended) and later restarted without loss of information. Consider the case of someone who has started to download a very long file from his computer at work to his personal computer at home. Half-way through the transfer, he needs to make an urgent telephone call and needs to look up the telephone number in his on-line telephone directory at work, preferably without ruining the file transfer.
The solution to this problem is given in Fig. 7-8. The file transfer is started as an activity. Part way through it is possible to issue an S-ACTIVITY!lVTERRUPT.request to suspend the file transfer. Then another activity can be started and completed, and finally the original activity can be resumed from the ;mint where it was interrupted. Activity management is the primary way to structure a session. For this reason, it is essential that both parties agree on what the activity structure is. A problem ;:ould arise if both of them tried to start activities simultaneously. To prevent this event from occurring, activity management is controlled by a token (in fact the same token used for major synchronization points). To invoke an activity service, a user must be in possession of the activity token. This token can be passed and requested independently of the data and synchronize-minor tokens. Actually, the situation is somewhat more complicated than we have just sketched. It started out as we have described it, but later on, ISO realized that problems could occur if one user started an activity while the other was doing a minor synchronization. To prevent this situation, the rules were changed to require a user to hold both the activity and synchronize-minor tokens, as well as the data token (if used) before initiating an activity or a synchronization operation. This strategy eliminated the original problems, but created a new one: what happens if one user holds the activity token and the other one holds the synchronize-minor token, and both users want both tokens. Each sits in a loop issuing S-TOKEN-PLEASE primitives, getting nowhere. A deadlock ensues. The only solution is for all applications to try to be very, very careful. In retrospect, a single token might have been better. Activities are intimately related to synchronization points. When an activity is started, the synchronization serial numbers are reset to 1 and a major synchronization point is made. It is possible to make additional synchronization points, both major and minor, within an activity Because the start of each activity also corresponds to a major synchronization point, once an activity has started, it is not possible to resynchronize to a point earlier than the start6f that activity. ·In particular, it is not possible to resynchronize to a synchronization point in a previous activity. 7.1.6. Exception Reporting Another session layer feature is a general-purpose mechanism for reporting unexpected errors. If the user runs into trouble, for whatever reason, this trouble can be reported to the peer using a S-U-EXCEPTION-REPORT.request primitive. Some user data may be transferred using this primitive. The user data typically will explain what happened. As an example, suppose a programmer designs a complex special-purpose layer 7 protocol for some specific application. During debugging of the protocol (or even after it is supposedly debugged) protocol errors may occur. These can be reported to the peer using the session layer exception reporting mechanism. Exception reporting does not only apply to user-detected errors. The service provider can generate an S-PEXCEPTlON-REPORT.indication to notify the user about internal problems within the session layer or problems reported to it from the transport or lower layers. These reports contain a field describing the nature of the exception. It is up to the user to decide what action to take, if any. 7.1.7. The OSI Session Service Primitives In this section we will systematically examine all the OSI session primitives and describe the function of each one. They are listed in Fig. 7-10. Each line in the table corresponds to between I and 4 primitives as noted. Potentially, each primitive type has request, indication, response, and confirm versions. However, not all the combinations are valid. S-DATA, for example, only has request and indication. Acknowledgements (i.e., response and confirm) are taken care of by lower layers. Although there are 58 connection-oriented session service primitives they can be better understood by dividing them into seven groups: 1 .
Connection establishment.
2
.
Connection release.
3
Data transfer.
4
Token management.
5
Synchronization.
6
Activity management.
. . . .
7 Exception reporting. has four tokens, as shown in Fig. 7-11. The S-TOKEN-GIVE.request primitive can be used to pass one or more tokens to the peer entity. Parameters specify which tokens are to be given. The S-TOKEN-PLEASE.request primitive can be used to announce that the user issuing the primitive wants the specified tokens. Finally, the 'S-CONTROL-GIVE.request can be used to surrender all the tokens at once. It can only be used outside activities. Logically, this primitive is not necessary, but it was included at the request of CCITT for compatibility with their teletex protocol, which works in terms of control, rather than in terms of tokens. .
Data token
Data transfer in half-duplex mode
Release token
I nitiation of orderly release
--
Synchronize-minor token
Insertion of minor sync points
Major/activity token
Activity or major sync operations
Fig. 7-11. The session layer tokens.
The fifth group contains the synchronization primitives. Primitives are provided for both major and minor synchronization, as well as resynchronization. All of the primitives are confirmed. Each primitive specifies the serial number of the synchronization point that it wants to set or go back to. These serial numbers are in the range 0 to 999,999. All synchronization primitives require possession of the relevant tokens. The sixth group of primitives relates to activity management. Activities can be started, interrupted, resumed, and discarded (abandoned). Like synchronization, activity management is controlled by tokens. The seventh and last group contains the exception reporting primitives. Note that SoP-EXCEPTION, like S-PABORT cannot be requested. The service provider decides when and if to issue it. The OSI primitives are implemented by the session entity using the session protocol. We will discuss this protocol later in this chapter. 72. REMOTE PROCEDURE CALL The primary concern of the session layer is managing the dialog and dealing with errors (e.g., system crashes) occurring above the transport layer. In the OSI Refelence Model, a connectionless session makes little sense. However, there has been considerable research at universities and in industry on a radically different model for dialog and error control based on the connectionless model. This work, which goes under the name RPC (Remote Procedure Call), has been widely implemented in networks and (especially) distributed systems. RPC does not fit into the OSI Reference Model especially well. It has been designed to be fast, and therefore does not contain a multilayer structure. Logically, remote procedure call is concerned with roughly the same issues as the session layer (although from a very different perspective), so we will examine it in this chapter. Please note, however, that RPC can also be implemented in the application layer (although less efficiently), so one sometimes finds discussions of it in that context. 7.2.1. The Client-Server Model Up until now, we have tacitly assumed that the two processes communicating over a session or transport connection are symmetric. In practice, this assumption is frequently violated. A common example is a network of diskless personal computers or workstations, called clients, that are communicating over a network with a file server having a disk on which all the files are stored. In this system, clients access their data by sending requests to the server, which carries out the work and sends back the replies. Communication always takes the form of request-reply pairs, always initiated by the clients, never by the server. This model is called the client-server model. While it is obviously possible to establish sessions between clients and servers and then use half-duplex communication over these sessions, the high overhead caused by multiple layers of connections is frequently unattractive for applications, such as file servers, where performance is critical. A fully connectionless form of communication built right on top of the raw datagram facility (especially on LANs) is often a much better choice. Even if the performance problems can be solved by using connectionless mode, the model still has a major flaw: the conceptual basis of all communication is I/O (input/output). Programs communicate with other programs using commands such as X-DATA.request and X-DATA.indication, the former of which is I/O and the latter of which is an interrupt. These are hardly the proper tools for building wellstructured applications. The RPC school of thought approaches the client-server model from a completely different perspective. In this view, a client sending a message to a server and getting a reply is like a program calling a procedure and getting a result. In both cases, the caller initiates an action and waits until it is completed and the results are available. Although in the ordinary (local) case, the procedure runs on the same machine as the· caller, and with RPC it runs on a different machine, the caller need not be aware of this distinction. To help hide the difference between local and remote calls even more, it is possible to embed RPC in the programming language. Suppose, for example, that we 'provide each of the file server's clients with a (library) procedure, read, that can be called with three parameters: an identifier telling which file to read, a buffer to read the data into, and a count of the number of bytes to read. A call like read(fileid, buffer, count) is then an ordinary call to a local procedure (i.e., a procedure included in the caller's address space by the linker).
This procedure sends a message to the file server and waits for the reply. Only after the reply arrives, does read return control to the caller. The beauty of this scheme is that client-server communication now takes the form of procedure calls instead of I/O commands (or worse yet, interrupts). All the details of how the network works can be hidden from the application program by putting them in the local procedures such as read. These procedures are called stubs. In this example, the stub procedure actually transfers data, but a stub procedure can equally well send a message requesting the server to perform an arbitrary operation. For example, a call delete( filename) might cause the stub procedure, delete, to send a message to the file server asking it to destroy the specified file. By providing appropriate stub procedures, we can have the client invoke arbitrary actions on the server in a way that is much more natural for the applications programmer to deal with than I/O and interrupts. The ultimate goal is to make a remote procedure call look no different than a local procedure call. 7.2.2. Implementation of Remote Procedure Call In this section we will take a closer look at how RPC is implemented. More information can be found in the work of Birrell and Nelson (1984). Figure 7-13 shows one way of implementing a remote procedure call system. In this figure, the remote call takes ten steps. Step 1 consists of the client program (or procedure) calling the stub procedure linked within its own address space. Parameters may be passed in the usual way. The client does not notice anything unusual about this call because it is a normal, local call. The client stub then collects the parameters and packs them into a message. This operation is known as parameter marshalling. After the message has been constructed, it is given to the transport layer for transmission (step 2). In a connectionless LAN system, the transport entity will probably just attach a header to the message and put it out on the network without further ado (step 3). In a WAN, the actual transmission may be more complicated. In many systems, step 2 is a trap to the operating system. When the message arrives at the server, the transport entity there passes it to the server stub (step 4), which unmarshalls the parameters. The server stub then calls the server procedure (step 5), passing the parameters in the standard way. The server procedure has no way of telling that it is being activated remotely because its immediate caller is a local procedure that obeys all the standard rules. Only the stubs know that something peculiar is going on. After it has completed its work, the server procedure returns (step 6), the same way as any other procedure returns when it is finished. It may also return a result to its caller. The server stub then marshalls the result into a message and hands it off at the transport interface (step 7), possibly by making a system call, just as in step 2. After the reply gets back to the client machine (step 8), it is handed to the client stub (step 9). Finally, the client stub returns to its caller, the client procedure. Any value returned by the server in step 6 is given to the client in step 10. The purpose of the whole mechanism of Fig. 7-13 is to give the client procedure the illusion that it is making a direct calion the distant server procedure. To the extent that the illusion succeeds and the client cannot tell that the server is remote, the mechanism is said to be transparent. However, a closer inspection reveals some difficulties in achieving full transparency. The principal problem occurs with the parameter passing. Passing integers, floating point numbers, and character strings by value is easy. The client stub just puts them in the message. At worst, a conversion to some network standard format might be needed (such conversions are part of the presentation layer, and will be discussed at length in the next chapter). Passing structures, records, or arrays of these types is equally straightforward. The trouble comes when the language allows parameters to be passed by reference, rather than by value. For a local call, a pointer (the address of the parameter) is normally passed to the called procedure. The called procedure knows that it is dealing with (! reference parameter, so it can follow the pointer to access the parameter. This strategy fails completely for a remote call. When the compiler produces code for the server, it knows nothing about RPC, and generates the usual instruc, tions for following pointers. Of course the object being pointed to is not even on the server's machine, and even if it were, it would not have the same address there as it had on the client's machine. As a result, when the server tries to use a reference parameter, it gets the wrong value and the computation fails. One possible solution is to replace the call-by-reference parameter mechanism by call-by-copy/restore. With copy/restore, the client stub locates the item being pointed to, and passes it to the server stub. The server stub puts it in memory somewhere. and passes a pointer to it to the server procedure. The server is then able to access the item in the usual way. When the server procedure returns control to the stub, the stub sends the (possibly modified) data item back to the client stub, which uses it to overwrite the original reference parameter. Although the copy/restore mechanism frequently works, it can fail in certain pathological situations.
Consider, for example, the program of Fig. 7-14. When this program runs locally, both of the parameters in the call to doubleincr are pointers to a, which gets incremented twice. The number 2 is printed. Now let us see what happens if doubleincr is called as a remote procedure using copy/restore. The client stub processes each parameter separately, so it sends two copies of a· to the server SI ub The server procedure increments each copy once, and both are passed back to the client stub, which then restores them sequentially. First a is restored to I and then it is restored to I again. Thus the final value is 1 instead of the correct answer, 2. Pointers give problems similar to reference parameters. They are especially troublesome if they point into the middle of conLplex lists or graphs or data structures involving variant records. Procedure or function parameters also are difficult to handle, although it may be possible for the server stub to replace them by procedures local to the server's machine that invoke the called procedures back on the client's machine using reverse RPC. Many RPC systems finesse the whole problem by prohibiting the use of reference parameters, pointers, and procedure or function parameters on remote calls. Such a decision makes the implementation easier, but breaks down the transparency because the rules for local and remote calls are then different. Let us now turn from parameter passing to another implementation issue: How does the client stub know who to call? In traditional connection-oriented networks, sessions are established between SSAPs, each of which has a fixed "telephone number." For RPC a simpler, yet more dynamic, scheme is needed. Birrell and Nelson (1984) have described a scheme involving not only clients and servers, but also a specialized kind of data base system. In their method, illustrated in Fig. 7-15, when a server is booted, it registers with the data base system by sending a message containing its name (as an ASCII string), its network address (e.g., an NSAP, TSAP, or SSAP), and a unique identifier (e.g., a random 32-bit integer). This registration is done by having the server call a procedure export, which is handled by the stub (steps 1 and 2).
Fig. 7-15. Client-server binding is done via a data base.
Later, when the client makes its first call (step 3) and its stub is faced with the problem of locating the server, the stub sends its name, which is also the server'L name, in ASCII, to the data base system (step 4). The data base system then returns the server's network address and the unique identifier (step 5). This process is called binding. From this point on, the stub knows how to locate the server, so binding is not required on subsequent calls. The unique 32-bit identifier is included in each RPC call. It is used by the transport entity on the server's machine to tell which of the potentially many server stubs to give the incoming message to. It also has another role. If the server crashes and reboots, it re-registers with the data base system using a new unique number. Attempts by clients to communicate with it using the old unique identifier will fail, making them aware of the crash and forcing them to rebind. Another key implementation issue is the protocol used. In the simplest case, the RPC protocol can consist of two messages: a request and a reply. Both the request and the reply contain the unique number identifying the server, a transaction identifier, and the parameters. When sending a request, the client stub (in some systems) may set a timer. If the timer goes off before the reply comes back, the stub can query the server to see if the request arrived. If not, it can retransmit it. The purpose of the transaction identifier is to allow the server to recognize and reject duplicate requests. Recognizing duplicate requests is only possible if the server keeps track of the most recent transaction identifier from each client.
A final implementation issue is exception handling. Unlike local procedure calls, where nothing can go wrong, many things can go wrong with RPC, for example, the server could be down. If the programming language allows it, the occurrence of an RPC error should not give control back to the caller, but should raise an exception to be handled by an exception handler. The design of the exception handling mechanism is language dependent, but a method is clearly needed to distinguish unsuccessful calls from successful ones.