Many people care that a CORBA client can communicate with a CORBA server, but they do not care how the communication happens. However, some people do care about (the principles that underlie) the low-level details of CORBA’s communication infrastructure. There may be several reasons for their interest, for example:
The OMG decided to provide an efficient and flexible inter-application communication mechanism by designing a multi-layer protocol stack, as shown in Figure 11.1. All CORBA on-the-wire protocols are designed around some common guidelines. This makes it relatively easy to build bridges between the different protocols. One of the common guidelines includes the definition of an IOR (Chapter 10), which holds the “contact details” of an object, regardless of which on-the-wire protocol is being used.
An Environment-Specific Inter-ORB Protocol (ESIOP) is an on-the-wire protocol that is optimized for a specific environment. For example:
Aside from ESIOPs, all CORBA protocols are based on the General Inter-ORB Protocol (GIOP). This protocol defines the different message types (such as request and reply messages) that can be exchanged between client and server applications and also specifies a binary format for the on-the-wire representation of IDL types (boolean, long, string, enum, struct, sequence, union and so on).
The main thing that GIOP does not specify is the actual networking technology that is used to transmit messages between clients and servers. For example, GIOP does not specify if messages should be transmitted over TCP/IP, X25, ATM or some other transport. Instead, the choice of transport mechanism is decided in a specialization of GIOP. The most well-known GIOP specialization is the Internet Inter-ORB Protocol (IIOP), which is for use on TCP/IP networks. All CORBA products are obliged to support IIOP, but they may optionally support other GIOP-based protocols or ESIOPs too. An IOR contains the contact details for all the protocols that clients can use to communicate with an object in a server.
The act of placing IDL types into a binary buffer (in preparation for transmission) is called marshaling. Conversely, extracting IDL types from a binary buffer is called unmarshaling.1 The marshaling rules used by CORBA are called Common Data Representation (CDR). CDR consists of several rules, as I now discuss.
One CDR rule is that data is marshaled using the native endian format of the computer that is sending a message. A flag in the header of GIOP messages specifies whether the message is in big-endian or little-endian format.2 If the receiving computer uses the same endian format then the data can be unmarshaled directly; otherwise byte-swapping is performed when unmarshaling numeric values. In effect, this CDR rule provides an optimization for the common case where a client and server run on machines with the same endian format.
Another CDR rule states how many bytes of memory basic data-types use and their memory-alignment requirements:
The memory-alignment rules of CDR were chosen to be similar to the memory-alignment requirements of some CPUs. It was hoped that this would facilitate optimizations in CORBA’s transport layer. However, experience has shown that the memory-alignment requirements are mainly a small coding nuisance for people implementing CORBA. Such is the benefit of hindsight.
Finally, there are rules for how compound types are marshaled in terms of the basic types:
The above discussion is not exhaustive, for example, it does not discuss the marshaling of a TypeCode or a valuetype. However, it does provide a representative example of how CDR marshaling works. The memory-alignment rules imply that a GIOP message may contain some padding bytes, which is wasted bandwidth. However, in practice, padding bytes usually account for only a few percent of a GIOP message, so the wasted bandwidth is quite minimal.
GIOP defines eight different types of messages that can be transmitted between client and server applications. Theoretically, a developer should not need any knowledge of these different types of messages. However, in practice, a knowledge of the different message types can be useful when debugging a CORBA application, particularly if the CORBA vendor product can print low-level diagnostics about the messages that are being sent and received.
The first 4 bytes of every GIOP message contain the letters G-I-O-P. This acts as a “magic number” that identifies the message as being in GIOP format.
The GIOP message types are: Request, Reply, Fragment, CancelRequest, CloseConnection, MessageError, LocateRequest and LocateReply. The following subsections discuss each of these message types.
As might be expected, Request and Reply messages are used to send requests from a client to a server and replies back from the server to a client, respectively.
A LocateRequest message is like a “ping” message that asks: “Is the object there?”. The reply is sent back in a LocateReply message. CORBA introduced these messages to make its GIOP redirection mechanism more efficient, as will be discussed in Section 11.4.
If a Request or Reply message is very large then the CORBA runtime system might decide to transmit it in several pieces rather than as one monolithic message. In such cases, the first piece is sent as a normal Request or Reply message, but a flag in the header of the message indicates that there are more pieces to follow. The remaining pieces are transmitted as Fragment messages.3
A CORBA product is not obliged to split large messages into smaller fragments, but it has the option of doing so. If a CORBA product is capable of sending Fragment messages then typically a value in a runtime configuration file specifies the maximum size of an unfragmented message. Regardless of whether or not a CORBA product can send fragmented messages, it is obliged to be able to receive fragmented messages.
A CORBA client may specify a timeout value when making a remote call. If the client does not receive a Reply message before the specified timeout has occurred then the CORBA runtime system in the client gives up waiting for the Reply message and instead throws a TIMEOUT exception back to the client application code. The CORBA runtime system in the client may also (but is not obliged to) send a CancelRequest message to the server, to let the server know that the client will ignore a Reply message if one is later sent.
The CORBA runtime system in the server can use the CancelRequest message as a hint to discard the previously-received Request. However the server can ignore this hint and, in fact, will ignore it if the server has already started dispatching the previously received Request message. This is because the CORBA runtime system in the server cannot know how to “cancel” partially-executed application-level code in the body of an operation.
CORBA allows idle socket connections to be closed. If a connection from a client to a server is closed then a new connection can be opened transparently if, later on, the client makes more calls to the server. This closing of idle socket connections enables a CORBA server to scale up to deal with thousands, or tens of thousands of interactive clients.
CORBA needs to guard against the following possibility. A client’s socket connection to a server might have been idle for some time and so the server decides to close the socket connection. However, just as the server is about to close the connection, the client sends a request to the server. If the client’s sending of the request overlaps with the server’s closing of the socket then the client might think that the server process had crashed. To prevent this scenario from occurring, a server process sends a CloseConnection message to the client immediately before closing the socket connection. When the client receives this message, it assumes that the server will ignore any requests that the client had recently sent but for which it had not yet received replies. Because of this, the client would (transparently) open a new connection to the server and re-send the requests.
If a CORBA application receives a message that is not in GIOP format then it sends back a MessageError message to say “You sent me garbage!” This message should not normally be sent if a CORBA client is communicating with a CORBA server (unless there are serious bugs in the CORBA product(s) being used).
You can force a CORBA server to send a MessageError message by getting a non-CORBA application to send a message to the CORBA server. For example, if you know which port a CORBA server is listening on then you can connect to the server with telnet. When you type something into telnet and hit the ENTER/RETURN key, the non-GIOP message will be sent to the CORBA server, and the CORBA server should send a MessageError message back (and probably close the connection). Within telnet, you will see the letters G-I-O-P followed by some non-printable characters (which is the rest of the very short MessageError message).
When a client sends a Request message to a server, it receives back a Reply message. The header of the Reply message indicates if it is:
The redirection reply message is used by CORBA vendors to implement an
implementation repository (IMR), which is discussed in
Chapter 7. IMRs add a lot of flexibility to CORBA, but there is
potentially a significant overhead to be paid for the initial
redirection. I now discuss this overhead and an optimization that
reduces it. Let us assume that a client’s first request to an object
contains a sequence<octet>
parameter that is several megabytes
large (the data might be a digitized image or a large sound file). When
the client receives the redirection reply, it will have to
retransmit the multi-megabyte request using the new IOR.
Obviously, having to transmit such a large request twice would be a
waste of bandwidth. For this reason, CORBA defines the
LocateRequest and LocateReply messages (discussed in
Section 11.3.2). A LocateRequest message is a very
compact “ping”-style message that asks: “Is the object there?”. The
reply is sent back in a LocateReply message. The intention is
that the CORBA runtime system in a client application will
(transparently) send a LocateRequest message before
sending the first “real” request. This ensures that if a redirection
occurs then the redirection will be dealt with before any (potentially
large) “real” requests are transmitted.
Use of LocateRequest messages is an optional optimization. Some CORBA products (especially older ones) never send LocateRequest messages. Some other CORBA products always send a LocateRequest message before the first “real” invocation. It is possible to imagine a CORBA product that is implemented so it normally sends a LocateRequest message but optimizes it away if the first “real” request is quite small. However, it is unlikely that many CORBA products are implemented this way because the performance gain to be made from optimizing away “unnecessary” LocateRequest messages is insignificant in real-world deployments.
The CORBA GIOP protocol infrastructure allows idle connections between a client and server to be closed, and transparently re-opened if the client later wishes to send another request to the server. This allows CORBA implementations to optimize their usage of network resources. The CloseConnection GIOP message (discussed in Section 11.3.5) provides the low-level infrastructure required for this. The CORBA standard does not define any terminology for this ability to close idle connections. Because of this lack of CORBA-provided terminology, some vendors have defined their own terminology for this capability. Both Orbix and Orbacus use the term active connection management (ACM) to refer to the automatic closing of idle socket connections. Other CORBA products might use different terminology for the same concept.
CORBA does not require that a product implement ACM; rather, it is an optional capability, though good-quality, modern CORBA products should implement it. Neither does CORBA specify what heuristics should be used to close idle connections. For example, in Orbacus, entries in a configuration file are used to specify that connections should be closed if they have been idle for a specified amount of time. Orbix uses a different mechanism: configuration entries specify the maximum number of open connections; once this limit has been reached, Orbix closes the connection that has been idle for the longest period of time. Other CORBA products may employ different heuristics for closing idle connections.
The concept of a service context is easy to understand. However, the terminology is not very intuitive. The context part of the name arises because a service context is used to pass extra “contextual” information with request and reply messages. The service part of the name arises because the first uses of service contexts were to help implement some of the CORBA Services, such as the Security Service and Object Transaction Service (OTS). However, documented APIs mean that service contexts can also be used by application programmers.
module IOP { … typedef unsigned long ContextId; struct ServiceContext { ContextId id; sequence<octet> data; }; … };
Figure 11.2: IDL definition of a service context
The IDL definition of a service context is shown in Figure 11.2. As can be seen, a service context is simply a struct that contains binary data and an integer value. The integer value is an identifier that specifies how the binary data should be interpreted. For example, one integer value specifies that the binary data contains information used by OTS; another integer value specifies that the binary data contains information used for security; and so on. Organizations can contact the OMG to request unique integer values that they can use for service contexts specific to their own needs.
The header of each Request and Reply message contains a sequence of service contexts. Unless an application makes use of, say, OTS, it is likely that most messages sent and received by the application will contain an empty sequence of service contexts. An application can use a portable interceptor (Chapter 14) to add a service context to outgoing messages and can interrogate incoming messages to see if they contain a service context that corresponds to a specified integer identifier. In this way, if an application receives a service context that it is not expecting then the service context is simply ignored.
Codeset is an abbreviation of coded character set. Examples of codesets include ASCII and Unicode. In the early versions of CORBA, ISO Latin 1 (which is US ASCII extended with accented characters) was the hard-coded codeset used to transmit parameters of type char or string. However, CORBA has matured to support wide characters (IDL types wchar and wstring).4 CORBA has also matured so that the codesets used to transmit characters and wide characters are no longer hard-coded, but rather are negotiated when a client application establishes a connection to a server. As might be expected, this is called codeset negotiation. Codeset negotiation is achieved through the following steps:
The specification of GIOP/IIOP states that it is a unidirectional protocol. A client transmits a message on a channel (GIOP terminology for what is known as a socket in IIOP) to a server and the server transmits its reply on the same channel. This sounds like bidirectional communication since messages are transmitted in both directions. However, the reason why it is said to be unidirectional is that request messages can be transmitted in only one direction. GIOP does not allow a server to transmit a request to a client on the same channel that the client uses to transmit requests to the server. This is illustrated in Figure 11.3. The client opens a channel to a server in order to invoke upon obj1, and the client passes a reference to a callback object (obj2) as a parameter to an invocation.6 Later, if the server wants to invoke an operation on this callback object then GIOP does not allow the server to send its request on the already-open channel. Instead, the server must open a new channel to the client.
When GIOP was being defined, there was some debate within the OMG over whether GIOP should be a unidirectional or bidirectional protocol. However, it was decided that GIOP should be a unidirectional protocol and almost everybody has regretted this decision ever since. In particular, unidirectional protocols have two drawbacks when callback objects are used:
To work around these problems, a bidirectional enhancement to the GIOP/IIOP specification (illustrated in Figure 11.4) was added to CORBA 2.4.