Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc. 675 Mass Ave, Cambridge, MA 02139, USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed.
The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Library General Public License instead.) You can apply it to your programs, too.
When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things.
To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it.
For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights.
We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software.
Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations.
Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all.
The precise terms and conditions for copying, distribution and modification follow.
NO WARRANTY
If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found.
one line to give the program's name and an idea of what it does. Copyright (C) 19yy name of author This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
Also add information on how to contact you by electronic and paper mail.
If the program is interactive, make it output a short notice like this when it starts in an interactive mode:
Gnomovision version 69, Copyright (C) 19yy name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program.
You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. signature of Ty Coon, 1 April 1989 Ty Coon, President of Vice
This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Library General Public License instead of this License.
The InterAgent Communications Model (ICM) is a model of communication which is oriented towards the needs of inter-agent communication. This manual introduces the ICM, shows how it can be used and gives a complete listing of the Application Programmer's Interface (API) to the ICM.
Some of the key features and benefits of this model are:
This manual describes a communications model that is tailored to the requirements of inter-agent communication; particularly in the context of Internet-based agents. The ICM is based on a store-and-forward messaging architecture not unlike the e-mail model of interpersonal communication. Like e-mail, the ICM relies on globally unique identifiers which are ultimately based on an agent's geographic location; however these identifiers refer to active processes rather than people. Also like e-mail, the ICM permits truly asynchronous communication between agents: agents do not need to rendezvous with other in order to communicate. However, unlike e-mail, ICM messages are structured: they can contain strings, numbers, records, lists of numbers and strings, executable code and even circular and arbitrary graph structures. This allows a complex message to be sent without expensive and error prone parsing of a text message.
Apart from the basic messaging service, the ICM also supports mobile agents -- agents can be cloned and tracked as they move between host computers - and it also supports agents running on mobile computers. A mobile computer -- or more accurately an intermittently connected computer -- offers special opportunities for agents: a mobile agent may be `born' on a PDA; `hop' from the PDA on to the Internet where it may move from host to host as it performs its tasks and then at some later date `hop' back on to the PDA where the owner can recover the results of the agent's work. The ICM supports this model and also allows the agent to be in communication with other agents and agent resources wherever it is located.
An important consideration for any inter-agent model is performance and scalability. If large numbers of agents may be owned and operated by many people -- not all of whom necessarily belonging to the same organizations -- then it is important that any inter-agent communications systen is scalable to large numbers of agents scattered over the Internet as a whole. In addition, it is important that agents that can move are able to move between organizations as well as within a single organization. The ICM presented here is intended to meet these requirements.
The scope of the ICM is limited to the lower level aspects of inter-agent communication: the higher level format of those messages -- and the semantics attached to individual messages -- is not part of the scope of the ICM. Typically, messages will be structured using an Agent Communication Language -- possibly in terms of KQML or FIPA ACL. The ICM allows efficient support for such styles of messages but does not prescribe the format of ACL messages.
The ICM is built from three essential components: a low level message format that encodes the arbitrary data structures typically found in messages in an efficient way, a communications server that acts as the inter-agent mail service and an API that implements access to the ICM from various programming languages.
The ICM allows messages to be transmitted asynchronously -- the sender and receiver do not need to be executing concurrently for messages to be sent -- it allows agents to migrate between hosts and it allows communication with agents residing on intermittently connected computers -- such as Personal Digital Assistants.
The ICM presented here is based on the April
communications model. The
main differences are that references to April
have been removed and
replaced with the more generic ICM label; and some of the details of the
message format have been adjusted to allow more general program code to
be transmitted in messages.
Each host that can support inter-agent communications contains a special CS. The function of the CSs is to provide reliable inter-agent communication. Sending a message from one agent to another is normally a three stage process: from the agent to its local CS, then the message is forwarded to the recipient's local CS and finally the message is read by the recipient from its local cummunications server (CS). For the agents involved the intermediate communication steps are transparent: an agent sends a message to another agent in a single step, and receives messages from other agents also as a single action.
The CSs provide a number of benefits to agents involved in inter-agent communication:
The ICM uses a system of globally unique identifiers called
handles. A handle
is a combined structure that embeds
within it a globally unique identifier for the agent, together with
additional location information that serves to locate the actual address
of the agent.
A handle
may be written using an expression such as:
target:name@home/[locations]
where:
protocol://addressThis is a reference to an address associated with a different protocol. For example, an agent that communicates via email might use an location of the form:
email://myagent@general.comThe CS is able to route messages of any protocol provided that appropriate handlers have been `plugged in'.
The agent's name and home are always sufficient to uniquely identify the agent -- irrespective of the contents of the locations field. The purpose of the home field is to provide a means for a `naming authority' to issue names. It is not necessary for home to be an actual host name of a computer; however the various API functions that compute handles assume that it is a host name of a computer in order to compute default values for the locations field of the handle.
There are several situations where a handle may have more than one location embeded within it: for example, where an agent is mobile and has moved to a different location, or where a host computer is using a CS that is not located on the computer itself -- it may be sharing the CS with several computers in a local area network.
From the point of view of the ICM CSs, the list of locations has a disjunctive semantics: in the case that an ICM CS receives a message for an agent that is not registered with it, it is sufficient for the CS to deliver the message to any of the locations identified in the list.
More specifically, the addresses in the list are taken in order: if a CS cannot contact a particular location -- it may be associated with a mobile computer, or it may be on the `wrong side' of a firewall -- then the CS will attempt the next in the list.
Where a CS is required to forward a message, the message is forwarded to the first CS in the list that is able to handle the message. Again, if there is no such CS, then the message is held until a suitable CS registers with this CS.(2)
If the list of locations is empty, then it is understood that the message is intended for an agent on the same host computer as the CS. The CS is then able to make a `final' determination of the message: it can either deliver the message, hold the message or reject the message.
For an agent to send a message to another agent, the sender agent formats the message using the ICM's low level message format and then sends the message to the local CS. Depending on the particular platform architecture and implementation language for the agent, this can often be achieved in a single call to the ICM API:
icmFmtSendMsg(conn,toAgent,fromAgent,opts,"%(say%*s%)", listLength(itemList),itemList);
(assuming that the agent is written in `C') where itemList
is a
`C'-level list of char *
pointers.
The string "%(say%*s%)"
is a format string; along the
lines of the format string in other standard C library functions, which
describes the structure of the message. The particular details of how a
message is described necessarily depends on the programming language
used to access the API. In higher level languages, such as Prolog, a
message send can be expressed more simply:
:-icmSendMsg(toAgent,fromAgent,[],say([goal,foo]))
In April
this is accomplished even more simply:
say([goal,foo]) >> toAgent
Once the icmSendMsg
is executed (see section icmSendMsg
), the sender
agent can immediately continue with other activities: there is no need
to wait to see if the receiver has received the message.
The local CS has to be able to handle incoming and outgoing messages from multiple sources. In particular, if a long message is being sent, the CS must be able to handle it and handle other short messages from other agents on the same host at the same time. This is an important attribute since it prevents a rogue agent from denying service to other agents simply by sending a very long message.
Once the CS has consumed a message it must determine the location of the recipient. In the case of an agent on the same physical host then the CS stores the message until it is called for by the receiving agent. If the message is intended for a different host, then the CS `contacts' the CS on the remote host and forwards the message to that CS. When the remote CS receives the message it stores the message until it is called for by the receiver.
When an agent is ready to handle a message it simply reads the message
from its local CS. Within the ICM API this can be handled with a
icmWaitMsg
call -- see section icmWaitMsg
:
icmWaitMsg(conn,&toAgent,&fromAgent,&opts, "%(say%*#%)",&listLen,itemList)
which waits until a message that matches the pattern `"%(say%*#%)"' has been received over the previously opened connection. The pattern denotes a message that has the `shape':
(say, [...])
If a message is received that matches the pattern, the variables
toAgent, fromAgent and opts are updated with the
recipient agent's handle (presumably the handle of the program that is
executing the call to icmWaitMsg
), the sender agent's handle and an
opaque pointer to a list of options which give additional
information about the message received (see section icmOption
).
Alternatively, the next available message can be retrieved using
icmGetMsg
(see section icmGetMsg
). Messages which have been received
using icmGetMsg
or sub-messages which are extracted from received
messages can be further analysed using icmScanMsg
(see section icmScanMsg
). For example the above icmWaitMsg
function
call can be approximated by first of all `getting' a message with
icmGetMsg
:
icmGetMsg(&toAgent,&fromAgent,&opts,&msg,&seqNo)
followed by a call to icmScanMsg
to analyse the returned
message:(3)
if(icmScanMsg(msg,"%(say%*#%)",&listLen,itemList))...
The icmWaitMsg
/icmScanMsg
functions both match the message
against a particular pattern and converts the contents of the message
into a format suitable for further processing by the programming
language. Further uses of the icmScanMsg
function on the
individual elements of the `itemList' can further break down the
structure and extract relevant data.(4)
The ICM API allows the same message buffer to be scanned in alternative ways -- allowing a receiving agent to receive a message and then determine the content of the message.
A given ICM CS can receive messages which are not ultimately intended for it.(5) When a CS receives a message it has several options available to it:
icmOption
) or which have no obvious mode of delivery.
It is the purpose of this section to detail the precise rules that a CS should follow when receiving messages.
When a CS receives a message for a registered agent then -- by virtue of the fact that the agent has registered with this CS -- the CS has independent confirmation of the address for that agent. The CS should hold the message until the agent reads it -- this can be handled by attempting to send the message to the agent and blocking until the agent actually reads the message.(6)
Note that even though the agent may be registered with this CS, the message will still require forwarding to another CS.
When a CS receives a message for an agent that is not registered with it, then the CS must attempt to forward the message -- typically to another CS. The selection of which CS to forward the message to is based on the locations field of the recipient's handle -- as identified within the message structure itself -- see section Message envelope.
A handle has associated with it a list of potential locations that may receive the message. The CS searches this list -- from left to right -- attempting to contact a CS at the identified locations.
The first `remote' CS that is reachable is used.(7) The message is forwarded to the remote CS in the same fashion that a message is delivered to an agent: by sending the message and blocking until the remote CS actively reads it.
Note that if the list of locations associated with the recipient agent handle is empty, then the CS may reject the message or hold the message for some period of time. If the CS rejects the message, it simply discards it and sends a rejection message to the sender of the message:(8)
(icmFailed, sender, recip, msg)
If the CS holds the message, at a later stage, the agent that is responsible for receiving the message can register with the CS -- giving the CS a means of delivering its responsibility.(9)
Note that when a message is transferred from a CS to a new CS the `recipient' handle which is passed within the message should not be identical to the recipient handle as received by the CS. The location of the current CS should be removed from the list of locations associated with the recipient's handle. This prevents a circular loop from forming whereby messages are forwarded endlessly between CS whilst never reaching their intended destination.
While it would be desirable to ensure that messages arrived in order -- it is difficult to ensure this in a context where agents may be mobile, or where mobile computers are involved.
In the case of mobile agents, when an agent sends a message to a mobile agent that has moved, some message forwarding must take place. However, while a message is forwarded, the mobile agent may have `updated' its communications partner, and therefore a subsequent message could be sent direct to the mobile agent's new (and real) location. Due to the potentially lengthy process involved in redirecting messages, the direct message is liable to reach the mobile agent first. Furthermore, when a mobile agent de-registers from one CS on its way to another, the first CS may no longer have the responsibility or ability to forward messages to the mobile agent -- in which case it may reject such messages.
In order to `solve' this problem, agents would need to keep track of the number of messages they have ever sent to other agents; and they would have to keep track of the number of messages sent to them by other agents. By doing this, an agent could re-impose an order on incoming messages. However, this would greatly increase the burden on agents -- and the ICM system -- for all messages; and unfortunately, is prone to a number of other problems (such as recovering these tables when an agent is restarted after having failed).
In the case of mobile computers, the CSs on intermittently connected computers are not guaranteed to be on-line all the time. This also leads to message re-routing on a dynamic basic, with the consequent problems in ensuring message ordering.
For these reasons, it is not required that an ICM system ensure that messages arrive in the order they are sent. On the other hand, we do expect a `best effort' to make sure that messages are delivered in order.
For those situations where it is important that a conversation between two agents preserves the order of messages, this can readily be achieved by adding counters to the messages -- in the message body. These counters should be maintained by the application -- either directly or via some kind of message filter. Care needs to be exercised by the application constructors that these message counters can be preserved where necessary.
In some situations it may be desirable to allow messages between agents to `penetrate' firewalls. There are many ways to achieve this; for example, by so-called `HTTP tunelling' for example, where an ICM message send/receive is re-packaged as an HTTP `PUT'/`GET' combination. However, such tunelling techniques deprive the system administrators of an opportunity to actively control ICM message traffic.
An alternative method is to use the ICM handle
's facility to
contain more than one location. The technique is based on placing an ICM
CS on the `firewall' machine.
A key feature of a firewall machine is that it is able to simultaneously `see' hosts on both sides of the firewall -- on the external Internet and on the internal Intranet. On the other hand, hosts within the Internet are not able to see hosts inside the firewall, and -- on some configurations -- vice-versa: hosts on the Intranet cannot see hosts on the Internet at large.
When an agent -- which is resident on an external host -- wishes to send a message to an agent on an Intranet-based host it must use a handle which mentions both the actual location of the internal host and the location of the firewall host:
msg >> agentB@dest.com/[dest.com,firewall.com]
The CS on the source host machine will not be
able to directly contact the CS on the dest
host machine; and therefore will not be able to deliver the message
directly. However, since the location of the firewall machine is also in
the handle
, and since it is accessible from the outside,
the source machine's communication server will be able to deliver
the message to the CS on the firewall machine.
The CS on the firewall machine will receive the message and since the recipient is not registered with the firewall machine will attempt to deliver the message. This time however, the internal dest machine is accessible to it, and can therefore deliver the message to the dest machine.
Conversely, if an agent which is within an Intranet wishes to send a message to an agent on the external Internet, is constructs a handle for the external agent which refers to the firewall machine location as well as the desired destination machine.
When the Intranet-based CS attempts to locate the external host it will not be able to -- assuming that external hosts are hidden from within the firewall. By default, the message is forwarded to the firewall machine which then forwards it to the external host.
The primary benefit of this architecture -- compared to various tunelling techniques -- is that it allows system administrators to control the message traffic more transparently. For example, by attaching various filters to the CS, a system administrator can prohibit messages which contain program code fragments, or it may restrict external access to a sub-set of the Intranet's host computers.
In addition, this strategy scales well to situations where there are mulitple layers within the Intranet: with firewall machines acting as barriers between each layer of the Intranet.
In this situation, when an agent needs to contact agents outside its
immediate Intranet region, it must use a handle
which mentions
the required intermediate firewall locations.
Note that in a typical situation -- of an internal agent having a
conversation consiting of a sequence of message with an external agent
-- typically only the originating agent that needs to know about the
intervening firewalls. The external agent simply uses the replyto
field of a message when making replies -- and this handle
may be
constructed to refer to all the required firewall machines.
Mobile agents are -- from the point of view of the ICM -- executing entities that can move across host computers. There may be many applications where mobile agents represent a natural technology for solving certain classes of problems. On the other hand, executing `foreign' programs represents a significant security risk that may outweigh many of the benefits. It is not our intention to justify the utility of mobile agents -- we identify here some of the issues involved -- from the point of view of communications systems -- and show how the ICM can support agent mobility.
In order to support mobile agents, a number of issues need to be resolved: the executable code of the agent -- together with its execution state -- must be transferred between one host and another; in addition is it necessary to transfer `permission' to execute and access resources on a remote host, and it must be possible to redirect communications so that any messages for the agent are correctly delivered to the agent's new location. It is the last issue which raises the most concerns in any inter-agent communications system.
It is not possible to handle the questions of transferring code and execution rights without consideration of the particular platforms involved. Generally, a single language platform makes it much simpler for agents to be fully mobile. The ICM does not itself address these issues, although the low-level message format is designed to allow executable programs to be transferred using inter-agent messages.
On the other hand, certain aspects of an agent's movements are properly handled by the ICM: in particular the reliable re-direction of messages to an agent's new location and tracking the current whereabouts of the agent. This reduces to managing an agent's identity -- in so far as an agent's identity can be linked with its ability to send messages and to receive messages.
Recall that the pair agent name and home serve to uniquely
identify an agent. This is true even when the agent has moved. However,
when an agent has moved, its location will probably not be the same as
home: we still require an ability to send messages to the mobile
agent wherever it is actually located. We can
do this by combining dynamic registration of a mobile agent and extended
use of the list of locations in an agent's handle
.
Normally, for static agents, the `minimal' handle for the agent contains a single location -- the location of the agent's home computer. For mobile agents we generally require at least two locations: the current address of the agent and its home base location.
When a mobile agent sends a message to another agent, it will generate a
return handle
which contains its current address and its home
base address. The replying agent can then use this handle
to send
a reply. If the mobile agent's handle
mentions its current
location first then the reply will normally go to that
location. However, there is always the possibility that a mobile agent
moves before a reply message can be sent -- which is why we also need
the mobile agent's home base in the handle
.
There are two sub-cases for mobile agents that we must consider, and at least one major policy issue: the mobile agent's home base may not itself be stable -- it may be only intermittently connected to the network -- and we must decide policy for redirecting messages to an agent that has moved.
A static home base is one which can be reasonably assumed to be on-line for the duration of the mobile agent's lifetime. In that situation, we can use the home base location as follows:
Suppose an agent (B) moves from its home base (HB) to a new base (CB), and while
there sends messages to another agent (A), and before A has an
opportunity to reply to B, B moves to a new location (CB'). The
handle
that A has of B looks like:
B@HB/[CB,HB]
whereas its `correct' handle
is really:
B@HB/[CB',HB]
When A's communication server sends A's reply message to the communication server on CB, B will no longer be registered there.(10) According to the message forwarding rules identified in section Communications servers and message handling, the communication server on CB will therefore deliver the message to the communication server on HB.
By dynamically registering its real address with the HB's communication server, in the form of a special forwarding message, the agent can ensure that HB's communication server can forward the message to B's current location. The ICM supports this by allowing agents to register forwarding addresses with CSs.
Thus, for the most part, we expect that a mobile agent will receive replies directly at its current location; but in the case that the agent moves before it can receive the message, the message will be redirected with at most one additional `hop'.
An agent registers a forwarding address with another agent simply by
providing a new `default' handle
-- with a new list of locations
-- for the agent.
An intermittent home base is one which cannot be reasonably assumed to be on-line for the duration of the mobile agent's lifetime. For example, if an agent's home base is a PDA, then we can easily assume that the PDA may be disconnected from the network while the mobile agent is executing. In fact, dealing with such home bases is one primary motivation for allowing mobile agents: it allows user's to perform network-tasks while they themselves are off-line.
In order to allow a mobile agent to continue to reliably receive messages even while it moves, we can use a variation of the technique for static home bases: the mobile agent `designates' a proxy computer -- which is going to be permanently available on the network.
In order to use a proxy, the agent registers forwarding addresses with
the proxy, and ensures that the proxy's location is included with the
locations in the mobile agent's handle
.
Forwarding addresses can be set up using special API functions -- which translate into messages sent to the CS itself which are commands to construct and modify an agent's forwarding address.
In order for the ICM to be able to redirect messages to a mobile agent,
it must be possible for an agent to explicitly inform a CS of its
location. When an agent registers with a communication server --
see section icmRegisterAgent
-- it also establishes the agent's initial
location.
The icmRequestForwarding
API function --
see section icmRequestForwarding
-- can be used by an agent to request that
messages be forwarded.
The process by which an agent moves is slightly elaborate in order to protect the agent and the new host computer. We can describe this in terms of a sequence of messages between the agent, its home base (or proxy) CS and the execution server on a remote computer that will be responsible for executing the mobile agent:
In order for an agent to return, all that is necessary is for the returning agent to cancel the forwarding address and to send a final `home-coming' message to its original incarnation. If the original agent is still present then, when the forwarding is cancelled, it immediately becomes available to receive messages -- including the final homecoming message sent by the returning agent.
In the case that the original agent terminated itself, an agent coming home is very similar to an agent migrating to another host: the agent must send a package of code to its home base and negotiate with its home base for permission to execute code. The main difference is that instead of a new forwarding address being set up, the original forwarding is cancelled.
There are a number of special considerations to do with agent migration:
When an agent is en-route between hosts it is potentially incommunicado since it is neither executing on its original host nor on its new host. Clearly, no en-route agent is in a position to send any messages; however the issue is what happens to messages sent to the agent while it is en-route? The ICM with its combination of a store-and-forward architecture and the protocol for agent migration outlined above means that messages are not lost. Any messages which are sent to an agent while it is en-route can be read by the agent once it has re-established itself at its new host.
When an agent moves rapidly -- while is it involved in coversations with another agent -- it may give rise to situations where messages arrive out of sequence.
Consider the case that an agent A
, currently located on host
C1
, is in conversation with an agent B
(which may be
anywhere). Agent B
`believes' that agent A
has as its
handle A/[C1]
, and so if it sends a message to A
it will
use that handle.
However, suppose that before B
sends this
message, agent A
moves to host C2
-- and then it informs
B
of its new location after agent B
has sent a
message to agent A
's old address. Agent B
then sends a
second message to agent A
at its new address.
Since agent A
is no longer at host C1
, the first message
that agent B
sent to agent A
will go via agent A
's
home location -- since the CS on C1
is no longer responsible for
agent A
's messages. The second message will go directly to agent
A
at its current location -- which may be a shorter route than
for the first message. As a result, the second message may arrive at
agent A
before the first message.
Since this is potentially a distributed environment, where the host
computers C1
and C2
may be a long distance apart, and may
be owned by different people, it is not possible to guarantee message
ordering for mobile agents. For this reason, where it is important that
agents A
and B
preserve the ordering of messages between
them, they must use a `higher-level' protocol on top of the normal
message communications.
Such a message ordering protocol can be readily implemented by the two
agents if they include in each message a sequence number which
represents the number of messages sent to each other: every message from
agent A
to agent B
incorporates a number which identifies
how many messages A
has already sent to B
. Conversely,
messages from agent B
to agent A
have a similar sequence
number. Using this number the two agents can ensure that their message
traffic is correctly synchronized no matter how often agent A
moves.(11)
An agent which has migrated one or more times must necessarily have a double -- or even triple or more -- identity: its original name and the name(s) it acquires on migration. This makes certain operations more difficult; in particular if another agent relies on the agent's handle as a way of ensuring integrity of communications then there may be a problem. Consider the simple scenario where an agent moves while another agent sends it a message:
A@HL/[HL]
sends message M
to B@H2/[H2]
B@H2/[H2]
migrates to B@H2/[H3,H2]
B@H2/[H3,H2]
reads the re-directed message M
from
A@HL/[HL]
and sends a reply R
.
A@HL/[HL]
receives reply message R
from B@H2/[H3]
when it is expecting a reply from B@H2/[H2]
.
Since agent A
has no direct control over agent B
, and
since they may be executing on different computers, agent A
has to be
able to verify that the reply came from a legitimate version of B
and
not an imposter. Unfortunately, it is not possible for agent A
to be
able to rely on the communications channel to ensure that the reply
message really did come from a legitimate source. In order to ensure
this, all messages between the migrating agent B
and other agents should
incorporate the original identity of the agent -- possibly in encrypted
form.
Agent tracking refers to finding out an agent's current location. In principle this can be done simply by requesting that the CS -- which has the agent's current location encoded as an alias -- resolve an agent handle into an actual aliased handle. However, there are certain fundamental problems due to the asynchronous nature of a distributed system. In particular, it is always possible for an agent to move after another agent has requested its location.
An agent may disappear from a host before cancelling all the aliases that it has established. This is particularly true for migrating agents since they rely on their host computers for executon time, and an agent may not be executing on its owners computer. This is a more general case of an agent disappearing from a CS without properly deregistering itself.
In the last resort, a CS can handle the latter case when the agent platform on which an agent is executing itself disconnects from the CS or terminates abnormally. In that situation, the CS is able to automatically deregister and remove all aliases pertaining to agents executing on the failed agent platform. However, that is a relatively crude mechanism and is not always sufficient.
An alternative method is to establish monitors on particular agent handles; and to allow remote monitoring of agent handles. A monitor on an agent handle is a promise -- on the part of the agent platform to another party -- that when a given agent terminates -- whether normally or abnormally -- then the agent platform itself will send a message indicating that the monitored agent has terminated.
Once a system of agent monitors is in place for agent platforms, it is straightforward to extend it to remote monitoring: the CS network can forward monitoring requests and termination signals.
The ICM can certainly use an agent monitoring system, but it relies on the `honesty' of individual agent platforms to fully benefit from it. In particular, a CS can establish a monitor on the `target' of an agent alias. When the aliased agent terminates, the CS may automatically remove any alaises established that point to the aliased agent; potentially allowing the original agent to resume.
An intermittently connected computer is one which is not permanently connected to the Internet. Some examples of such devices are Personal Digital Assistants (PDAs) and computers which only have dial-up access to the Internet. The ability of an agent to move between hosts is especially useful in the context of such devices: it allows an agent to perform a task independently of the agent's user.
The primary problem to be solved in supporting intermittent connections is the safe handling of messages to agents which are currently off-line. This problem is similar to the problem of handling messages for agents which are busy or inactive; however, in the case of a disconnected PDA, it is the PDA's CS which is off-line.
The strategy that we have outlined earlier of storing-and-forwarding messages on CSs helps but is not completely sufficient: if an agent is off-line then the receiving CS is able to buffer the message until the agent comes back. If the agent's CS itself is off-line then we must use an alternative place to store messages.
The solution is to introduce another, permanent, CS into the network; the role of this CS being to collect messages for host computers which are currently off-line until their return. In the ICM this is achieved by using a `virtual host' and attaching a CS to that virtual host. In effect, the CS on the PDA itself registers with the permanent CS; at the same time whenever an agent registers with the PDA's CS, teh agent is informed of the permanent CS also.
For example, if a PDA device has a host name of pda.someone.com
then we might establish a virtual host with the name
icmhost.someone.com
. An agent registering with the CS on
pda.someone.com
is given two locations: the location of
pda.someone.com
and the location of the virtual host:
icmhost.someone.com
. Any messages that the agent on
pda.someone.com
sends to another agent will have both locations;
and therefore any agent making a reply will have the use of an agent
handle with at least those two locations.
If a CS on a remote host is required to forward a message to an agent on
pda.someone.com
-- when it is off-line -- then the message is
re-directed to the CS on the virtual host -- icmhost.someone.com
-- instead. This is achieved by the sender's CS: when this CS attempts
to forward messages to the CS on pda.someone.com
it is unable to;
therefore the sender's CS attempts a different location in the handle --
the location of icmhost.someone.com
.
Since the CS on pda.someone.com
registered with
icmhost.someone.com
's CS, the latter is already primed to keep
the message until pda.someone.com
's CS re-attaches itself. At
that point, messages can be forwarded to the CS on the PDA and
ultimately the agents on it also. Conversely, any messages sent by agent
onboard the PDA while it was off-line can now be sent as normal.
Note that this architecture permits inter-agent communication even when both agents are on separate PDAs: at each stage in the communication the message is buffered while waiting for the sending PDA to be connected, or waiting for the receiving PDA to be connected or both. Note also that this service of supporting PDAs and similar devices is completely transparent to both sending and receiving parties of a message: there is no need for a special form of message send.
It is necessary to run a copy of the communications server on each host that will be running agent programs. However, it is only necessary to have a single communications server on a given host -- no matter how many agent applications will be running on that host.
On each host, the commserver is invoked using the command:
% icm
(where %
represents Unix command prompt). This will check to see
if there is already a commserver running in the local domain, and abort
if there is one.
The icm
is designed to be executed as a background process; when
started, it detaches itself as a daemon and the icm
command
immediately terminates. Therefore it is safe to install it as part of
the main Unix boot initialization process.(12)
-P port
-n name
icm@lipo.doc.ic.ac.ukUsing the
-n
option affects the agent part of this name --
in this case the name icm
.
-l location
-L file
-L
option overrides this.
The special file name of -
when used with this
option, causes log messages to be sent to standard error -- in which case the
icm
will not detach itself as a daemon.
The special log file name of +
causes logging information to be sent to
the standard system log. Note that the icm
may generate a large
number of log messages, and so it may not be advisable to use this option.
In this chapter we outline the complete API for the ICM. This API is described in terms of a C language binding; a Java, or C++ language binding should be immediately inferable from this description. This API focuses on the agent platform's interface to the ICM: these functions are intended to be invoked by the agent platform on behalf of individual agents that are supported by the platform. There is a related protocol for use between communications servers that is not part of the API but is part of the ICM standard.
Note that a compliant implementation of the ICM API is thread-safe. That means that all ICM API functions -- unless otherwise stated -- may be freely used by multiple threads in a single application.
icmStatus
typedef enum{icmOk, icmFailed, icmEndFile, icmError} icmStatus;
Most of the functions in the ICM API return a status result indicating
the success or otherwise of the call. Informally, icmOk
means
that the previous operation was a sucess, icmFailed
means that the
parameters to the API call were valid but the operation failed to
complete and icmError
means that an invalid operation was
requested. icmEndFile
is used when an end-fo-file or equivalent
condition is detected.
icmHandle
icmHandle
is an opaque type, and is used to identify agents to the ICM API.
typedef struct _icmHandle_ *icmHandle;
Agent handles can be explicitly created -- using the
icmMakeHandle
function -- see section icmMakeHandle
-- and can also
be created automatically as a result of receiving a message.
icmConn
icmConn
is an opaque type returned by the ICM API function
icmInitComms
(see section icmInitComms
) if a sucessful connection has
been made. This connection value should be used by the agent platform
whenever a message is to be received or sent using the ICM API.
typedef struct _icmConnection_ icmConn;
icmOpt
icmOpt
is an enumerated type that is used to identify an
option type. Its definition is:
typedef enum {icmLeaseTime,icmReplyto,icmReciptRequest,icmAuditTrail} icmOpt;
icmLeaseTime
icmSendMsg
and icmRegisterAgent
. The
value associated with this option is a Unix-style time value -- i.e., a
number of seconds since Unix epoch or January 1st, 1970.
icmReplyto
icmSendMsg
to override to the reciever of the
message where any reply should be directed to; rather than the sender of
the message.
icmReceiptRequest
ICM
interprets the final delivery of the
message contents to the TPC/IP connection opened by the agent's system
as delivery of the message. Whether the agent reads the message that was
`piped' in to it or not cannot be confirmed by the ICM.
icmAuditTrail
icm
communications servers' handles be attached to the
message. The receiver of the message may query this list for information
about the routing of the message.
icmOption
icmOption
is an opaque type that is used to encapsulate message send
and other options.
typedef struct _icm_msg_option_ *icmOption;
icmMsg
icmMsg
is an opaque type that refers to a message structure; as
received by icmGetMsg
or sent via icmSendMsg
.
icmDataPo
icmDataPo
values are used when `uninterpreted' values are extracted
from or embedded in messages. Such a value can contain anything -- from
a block of text to program code -- depending on the context.
typedef struct { long size; /* length - in bytes - of data block */ char *data; /* pointer to the data block */ } icmDataRec, *icmDataPo;
icmOpaquePo
icmOpaquePo
values are used when `anonymous' or opaque values are
extracted from or embedded in messages. Opaque values have a name and a
value - the latter typically being a icmDataPo
. These are
represented in the structure:
typedef struct { char *name; /* owner of the value */ icmDataPo val; /* pointer to the data block */ } icmOpaqueRec, *icmOpaquePo;
icmIdleProc
This type refers to a procedure that can be invoked as part of the
activities of icmEventLoop
(see section icmEventLoop
).
void (*icmIdleProc)(icmConn conn,void *client);
icmMsgProc
This type refers to a function value which is occasionally invoked on messages; particularly in the case that a watch has been set on a particular handle.
typedef icmStatus (*icmMsgProc)(icmConn conn, icmHandle recip, icmHandle sender, icmOption opt, icmMsg msg, void *client);
When such a function is invoked, conn
identifies the connection,
recip
, identifies the intended recipient of the message,
replyto
identifies the agent handle to send replies to and
sender
identifies the originator of the message.
These functions are used to allocate, analyse and release opaque agent handle objects.
icmMakeHandle
The icmMakeHandle
function constructs an opaque agent identification
handle for use by the ICM in other calls -- such as icmRegisterAgent
.
icmHandle icmMakeHandle(char *target,char *name,char*home, int count, char **locations);
handle
. If host is NULL
then the current or local host name is assumed.
The issuance of an icmHandle
is no guarantee that the agent exists, or
would be permitted to operate by the local CS.
The return value is an opaque identifier of the correct form for use in
other ICM API calls. Until the handle is released -- using an
icmReleaseHandle
call -- the value returned by icmMakeHandle
can be
assumed to be stable.
icmSameAgentHandle
This function tests to see if two handles refer to the same agent. The target and location fields are not checked in this comparison.
icmStatus icmSameAgentHandle(icmHandle h1,icmHandle h2)
The return result from this function is:
icmOk
icmFailed
icmError
icmAnalyseHandle
The icmAnalyseHandle
function `unpacks' an opaque handle into its
constituent components:
icmStatus icmAnalyseHandle(icmHande handle char **target, char **name, char **home, char ***locations, int *locCount)
The returned values -- name, home, locations and target -- reflect the basic components of an agent handle:
char ***
array that gives the possible
locations associated with the handle. The number of locations is
returned in locCount.
Note that the string pointers which are returned by
icmAnalyseHandle
address data which should not be modified by the
calling application. To do so may cause unintended side-effects --
probably unpleasant ones.
icmAnalyseHandle
returns icmOk
if a valid handle was passed in;
icmError
otherwise.
icmKeepHandle
The ICM library tries to garbage collect handles that are created --
especially those created automatically during a message receive. The
icmKeepHandle
function informs the ICM library that a particular
handle should be `kept' even if the library itself no longer has a need
for it.
icmStatus icmKeepHandle(icmHandle handle);
When a handle has been marked in this way, it should be released using
icmReleaseHandle
-- see below --
when the programmer no longer needs it. The library keeps track of the
number of times a handle is kept, and only actually releases the handle
when it has been released a corresponding number of times.
icmReleaseHandle
The icmReleaseHandle
function `releases' a handle that had been
created -- either explicitly or automatically -- by the ICM API. In most
situations, it should not be necessary to release agent handles as they
are cheap to produce.
icmStatus icmReleaseHandle(icmHandle handle)
icmReleaseHandle
returns icmOk
if handle is a
valid handle; otherwise it returns icmError
. In either case, after a
call to icmReleaseHandle
, the handle is no longer a valid agent handle
structure.
icmHandleName
The icmHandleName
function `unpacks' an opaque handle and
generates a string buffer based on the handle:
icmStatus icmHandleName(icmHande handle,char *buffer,int buffsize);
The handle's component parts are marshalled into a string buffer -- which is supplied as a parameter -- in a canonical form. The string buffer is formatted according to:
target:agent@home/[locations]
where target is the target (if any) associated with the handle, agent is the agent name, home is the agent's home base location and locations is a comma-separated list of the agent's possible locations.
If there is no target associated with this handle then the leading
target:
field is omitted. If the current location is not
known, or is the same as the home base then the trailing
/[location]
field is also omitted.
icmHandleName
returns icmOk
if a valid handle was passed in;
icmError
otherwise.
icmParseHandle
The icmParseHandle
function parses a string which represents a
handle using the standard notation and returns a icmHandle
corresponding to the string.
icmHandle icmParseHandle(char *buffer);
The string buffer should contain a representation of a
handle
in canonical form:
target:agent@home/[locations]
where target is the target (if any) associated with the handle, agent is the agent name, home is the agent's home base location and locations is a comma-separated list of the agent's possible locations.
If there is no target associated with this handle then the leading
target:
field may be omitted. Similarly, the list of locations
may be empty, in which case the trailing /[location]
field
may also be also omitted.
icmParseHandle
returns a non-null icmHandle
if a valid handle description was passed in; NULL
otherwise.
The icmCommServerHandle
function is a convenience function which
returns an icmHandle
that refers to a communications server.
icmHandle icmCommServerHandle(char *host)
This function returns an icmHandle
that can be used to
communicate with the communications server located on the machine
host. If host is NULL, then the local communications server
is assumed.
The form of this handle is:
icmCommServer@host/[host]
where host is the name of the host machine -- as returned by
icmMachineName
-- see section icmMachineName
.
These functions provide for connections to the local CS, registering agents with the local CS and managing agent aliases.
icmInitComms
The icmInitComms
function initializes the agent platform's link to
the local communications server.
icmStatus icmInitComms(int port,char *host,icmConn *conn)
this initializes the communications API, and attempts to establish an
initial connection to the local CS. Normally, port will be zero and host
will be NULL -- in which case the default communications port number --
4549
-- and the communications server on the local host computer will be used.
If port is not zero then the CS that is listening to that port will be contacted. If host is not NULL, then the CS on host will be contacted instead of the local one.
The returned conn value is required for many of the ICM API functions: whenever the agent platform may send or receive a message. Typically, the agent platform will only require one connection to the CS; which will be held so long as the agent platform is functioning.
The return value of icmInitComms
reflects the success of the connection
attempt:
icmOk
icmFailed
icmError
icmInitComms
also makes use of certain environment variables:
ICM_COMMSERVER
ICM_COMMSERVER
environment variable is set, then this
variable identifies the default host computer where the commserver is
located. Normally, the current host computer is used to identify the
location of the commserver.
ICM_COMMS_PORT
ICM_COMMS_PORT
environment variable is set, then the
default communications port associated with the communications server is
read from this environment variable.
icmCloseComms
The icmCloseComms
function closes the connection to the
CS. Typically, this is done once by the agent platform just prior to the
agent platform terminating.
icmStatus icmCloseComms(icmConn conn);
icmCloseComms
has the effect of deregistering any agents
registered with the CS using the conn connection -- unless those
agents were detached first using icmDetachAgent
(see section icmDetachAgent
).
icmCloseComms
returns icmOk
unless there had been no prior
successful connection to the CS, in which case it returns icmError
.
icmDisconnect
The icmDisconnect
function closes the connection to the CS in a
way that can be re-started later. Any registered agents are not
automatically deregistered as a result of a disconnect -- they are
marked as being detached.
icmStatus icmDisconnect(icmConn conn);
Any messages which are due for any local agents will be held by the CS
until a subsequent icmReconnect
call. The communications channel
is also closed as a result of the disconnect; however the conn is
still valid and should not be discarded except through a call to
icmCloseComms
call -- see section icmCloseComms
.
icmDisconnect
returns icmOk
unless there had been no prior
successful connection to the CS, in which case it return
icmError
.
When the agent needs to re-attach itself to the
communications server, it should invoke icmReconnect
--
see section icmReconnect
-- which will reestablish a connection to the
communications server and automatically reattach the agents that were
connected using this connection.
icmReconnect
The icmReconnect
function reconnects an agent platform to the CS
after it has been disconnected with an icmDisconnect
call. Once
the agent platform has successfully reconnected, it may download
messages intended for agents that are onboard the platform.
icmStatus icmReconnect(icmConn conn);
icmReconnect
returns
icmOk
icmFailed
icmError
icmDetachComms
This function can be used to request that the local CS should detach itself from the network. This may be important when, for example, the physical host computer is also detaching itself from the network.
After detachment, any messages for any agents registered with with local CS will either be forwarded to other locations -- depending on whether the handles of the recipients included alternative addresses -- or held over until the communications server reconnects.
icmStatus icmDetachComms(icmConn conn);
icmDetachComms
returns
icmOk
icmFailed
icmError
Note that a CS may detach itself automatically from the network; for example, if it is able to detect that its host computer has been or is about to be physically removed from the network.(13)
icmReattachComms
This function requests the CS to reattach itself to the network. If successful, then any outgoing messages to other hosts are forwarded, and incoming messages from other hosts may be read.(14)
icmStatus icmReattachComms(icmConn conn);
icmReattachComms
returns
icmOk
icmFailed
icmError
Note that a CS may reattach itself automatically to the network; for example, if it is able to detect that its host computer has been previously disconnected and the network is now available again.
icmEventLoop
The icmEventLoop
function can be a convenient way of accessing the
message channel. It `takes over' the application -- at least the thread
that invokes the icmEventLoop
function -- and reads messages from the CS
whenever it can.Whenever a message arrives, then a callback function is
invoked to allow the application to process the message.
icmStatus icmEventLoop(icmConn conn,icmMsgProc proc,void *client);
The return value from icmEventLoop
is only interesting in the case that
something went wrong with the initial call:
icmOk
icmError
The icmEventLoop
function will not normally return; instead it processes
incoming messages -- from the previously established connection
conn. For each message that it reads, it invokes the proc function as
follows:
proc(conn,recip,sender,opts,msg,client)
where conn is the connection identifier, recip is the handle
of the recipient agent, sender is the handle of the sending agent,
opts is the set of options associated with the message, msg is the
message itself and client is the value passed in to the call to
icmEventLoop
.
In order to terminate the event loop, the proc function may invoke the
icmCloseComms
function. If it does, then some time after the
proc call returns, the event loop itself will terminate.
icmSetIdleProc
The icmSetIdleProc
function establishes an `idle' procedure
within an event loop. It only has effect if icmEventLoop
has been or
will be called for a particular connection.
During the event loop, immediately prior to each attempt to read a
message from the CS, the icmEventLoop
function will call the idle
procedure. This allows an application to perform other background
activities concurrently with the event loop. However, there is no
guarantee when the idle procedure will be called, or with what frequency. If
true concurrency is required, then the programmer is advised to
structure the application with multiple threads of activity -- with one
thread devoted to handling messages, and other threads to other parts of
the application.
icmStatus icmSetIdleProc(icmConn conn,icmIdleProc idle,void *client);
The return value from icmSetIdleProc
is only interesting in the
case that something went wrong with the initial call:
icmOk
icmError
The call to idle takes the form:
idle(conn,client)
icmTerminateEventLoop
The icmTerminateEventLoop
function is used within the message
handler or idle procedure of an event loop -- as established using
icmEventLoop
.
void icmTerminateEventLoop(icmConn conn)
Calling icmTerminateEventLoop
within such a handler will have the
effect of terminating the event loop some short time after the message
handler or idle procedure itself returns.
icmRegisterAgent
The icmRegisterAgent
function registers an agent with the CS.
icmStatus icmRegisterAgent(icmConn conn,icmHandle hdl,icmOption opts, icmHandle *real)
The icmRegisterAgent
function returns:
icmOk
-l
address
options) then the real handle that is returned will
contain additional locations.
More precisely, the list of locations returned is the union of any
locations supplied in the hdl handle, the address of the
communication server's machine and any addresses given at the icm
command line -- also as -l loc
options.
icmFailed
icmError
icmDeregisterAgent
The icmDeregisterAgent function removes the entries corresponding to an agent from the CS. Agents are also automatically deregistered from the CS by a call to the icmCloseComms API function.
icmStatus icmDeregisterAgent(icmConn conn,icmHandle handle);
icmDeregisterAgent
returns:
icmOk
icmFailed
icmError
icmDetachAgent
The icmDetachAgent
function is used by an agent when it wishes to
temporarily detach itself from its communications server. A detached
agent is still registered with the communications server, and
therefore the CS will hold messages intended for it.
icmStatus icmDetachAgent(icmConn conn,icmHandle handle,icmOption opts);
icmDetachAgent
returns:
icmOk
icmFailed
icmError
icmAttachAgent
The icmAttachAgent
function is used by an agent when it wishes to
re-attach itself to its communications server. When the agent has
reattached itself to its communications server, any outstanding messages
for it will be delivered.
icmStatus icmAttachAgent(icmConn conn,icmHandle handle,icmOption opts);
icmAttachAgent
returns:
icmOk
icmFailed
icmError
icmMonitorAgent
The icmMonitorAgent
establishes a watch on a particular agent --
for example, waiting for the watched agent to terminate.
icmStatus icmMonitorAgent(icmConn conn,icmHandle ourH,icmHandle agent)
where ourH is the handle of the `client' agent, and agent is the `target' agent that is to be monitored.
This function terminates once the monitor has been established -- or failed to be established. After that, monitoring messages will be sent to the requesting client reflecting the behaviour of the monitored agent:
(icmMonitorAgent, icmRegisterAgent, agent)
(icmMonitorAgent, icmDeregisterAgent, agent)
(icmMonitorAgent, icmDetachAgent, agent)
(icmMonitorAgent, icmAttachAgent, agent)
Note that the `granularity' of this information should be considered to be quite coarse: one agent may contain many targets, and therefore many semi-independent threads of activity within it.
icmUnMonitorAgent
The icmUnMonitorAgent
function removes a watch that had been
previously established for a handle.
icmStatus icmUnMonitorAgent(icmConn conn,icmHandle ourH,icmHandle agent)
icmUnMonitorAgent
returns:
icmOk
icmFailed
icmError
icmRequestForwarding
The icmRequestForwarding
function requests that address forwarding be
established for a handle. This is used in supporting agent migration.
icmStatus icmRequestForwarding(icmConn conn, icmHandle h1)
The handle h1 should contain the agent's identity, and its new list of locations. If the request is successful, then the local CS will redirect all further messages intended for the agent to the locations referred to in h1.
icmRequestForwarding
returns:
icmOk
icmFailed
icmError
icmCancelForwarding
The icmCancelForwarding
function requests that address forwarding
be removed for a handle. This is used in supporting agent migration.
If no address forwarding is in place for an agent then the communications server will hold messages for the agent until the agent registers and reads them.
icmStatus icmCancelForwarding(icmConn conn,icmHandle h1)
There must have been previously an address forwarding established for h1; otherwise the call will fail.
If the request is successful, then the local CS will allow messages intended for h1 to be read by h1.
icmCancelForwarding
returns a status result as follows:
icmOk
icmFailed
icmError
icmPingAgent
The icmPingAgent
function can be used to check the current
availability of a local or a remote agent.
icmStatus icmPingAgent(icmConn conn,icmHandle handle,icmHandle *real)
where handle is the handle of an agent that has been registered with the communications server.
icmPingAgent
returns:
icmOk
icmFailed
icmError
Note: Due to the nature of the Internet, this function may take a considerable amount of time to execute. The programmer is recommended to arrange the application using multiple threads, or to perform the same function by means of message communication to the CS, to avoid unnecessary delays in the application.
icmListAgents
The icmListAgents
function can be used to determine the agents
that are currently registered with the CS.
icmStatus icmListAgents(icmConn conn,icmHandle who,icmHandle *list, int max)
icmListAgents
returns:
icmOk
icmFailed
icmError
Note: Due to the nature of the Internet, this function may take a considerable amount of time to execute. The programmer is recommended to arrange the application using multiple threads, or to perform the same function by means of message communication to the CS, to avoid unnecessary delays in the application.
icmSendMsg
icmSendMsg
is the main function used to send a message.
icmStatus icmSendMsg(icmConn conn,icmHandle recip, icmHandle sender,icmOption opts,icmMsg msg);
conn is the connection on which to send the message, recip
is the handle of the recipient of the message, sender should be
the handle of the sending application, opts is an icmOption
structure that encodes any message options -- such as setting the reply
address or the lease time of the message (see section icmOption
). msg
is the message to send.
icmSendMsg
returns:
icmOk
icmFailed
icmError
icmFmtSendMsg
The icmFmtSendMsg
function combines an icmFormatMsg
and an
icmSendMsg
into a single call.
icmStatus icmFmtSendMsg(icmConn conn,icmHandle recip, icmHandle sender,icmOption opt, char *fmt,...);
where the fmt is a format control string as in icmFormatMsg
-- see section icmFormatMsg
-- and additional components for the message are
passed in subsequent arguments as in icmFormatMsg
.
icmFmtSendMsg
returns:
icmOk
icmFailed
icmError
icmGetMsg
The icmGetMsg
function attempts to read the next message from the
CS. The returned message may be for any of the agents that have been
registered with the CS.
icmStatus icmGetMsg(icmConn conn,icmHandle *recip, icmHandle *sender,icmOption *opts, icmMsg *msg,long *seqNo);
icmGetMsg
returns the next available message in the input queue;
whose message number is greater than the value addressed by
seqNo. If seqNo is NULL, then the first available message is
retrieved(15); otherwise, to get the first available
message, seqNo should be initialized with -LONG_MAX
.
icmGetMsg
returns:
icmOk
icmReplaceMsg
(see section icmReplaceMsg
). The returned msg may be parsed -- using
icmScanMsg
to determine the nature of the message. The returned
msg should be released -- once properly processed by the application --
using icmReleaseMsg
.
The icmOption
structured returned in opts should be
released using icmReleaseOptions
(see section icmReleaseOptions
) --
unless the message is replaced in the message queue using
icmReplaceMsg
.
icmFailed
icmError
icmEndFile
icmReplaceMsg
The icmReplaceMsg
function replaces a message back in the
incoming message queue. This is useful in situations where the client
reads a message using icmGetMsg
but then decides that the message
cannot be processed at this time.
icmStatus icmReplaceMsg(icmConn conn,icmHandle recip, icmHandle sender,icmOption opt, icmMsg msg,long seqNo);
The message is replaced in the input queue at a point which is based on
the value of seqNo: the message is placed immediately after any
messages whose own sequence number is less than seqNo. Using
a value of -LONG_MAX
has the effect of placing the message at the
front of the current message queue.
A message that has been replaced is available once again to be retrieved
using icmGetMsg
.
Note that the seqNo is not `retrievable' -- each message that is
placed in the queue is assigned a potentially new sequence
number. However, if the seqNo used is the same as that returned by
a prior call to icmGetMsg
, then the relative order of messages in
the queue is preserved in the expected way.
icmReplaceMsg
returns:
icmOk
icmReleaseMsg
if required.
icmError
icmWaitMsg
The icmWaitMsg
function attempts to read the next message from the
CS. The returned message may be for any of the agents that have been
registered with the CS.
icmStatus icmWaitMsg(icmConn conn,icmHandle *recip, icmHandle *sender,icmOption *opt, char *fmt,...)
icmWaitMsg
returns the next available message in the input queue
that matches the pattern denoted in fmt. The format string
fmt is the same as for an icmScanMsg -- see section icmScanMsg
-- function call; the arguments following
fmt should be of the appropriate type for the format.
icmWaitMsg
returns:
icmOk
icmGetMsg
or icmWaitMsg
function call with a
different pattern will `pick up' messages which are skipped over in this
icmWaitMsg
call.
icmFailed
icmError
icmEndFile
icmMsgAvail
The icmMsgAvail
function tests to see if a message is available
-- without attempting to read the message.
icmStatus icmMsgAvail(icmConn conn,long from);
The message input queue is tested to see if there is a message whose
sequence number is greater than from; if there is, or if there is
a message pending from the communications server, then
icmMsgAvail
return icmOk
; otherwise it returns
icmFailed
.
icmReleaseMsg
The icmReleaseMsg
function releases a message that had been
created earlier -- either using an icmFormatMsg
function or
through icmGetMsg
or icmWaitMsg
function calls.
icmStatus icmReleaseMsg(icmMsg msg);
icmReleaseMsg
returns:
icmOk
icmError
icmNewOpt
icmOption icmNewOpt(icmOption prev,icmOpt opt,...)
This function returns an opaque pointer to an options structure -- the
first call to icmNewOpt
should use NULL for prev. The
option encodes an option for a subsequent message or agent
registration. Which type of option is encoded depends on opt:
icmLeaseTime
icmNewOpt
should be a number which
indicates the lease time of the message -- in absolute seconds after
Unix epoch.
E.g.:
option = icmNewOpt(NULL,icmLeaseTime,when);
icmReplyto
icmReceiptRequest
icmFormatMsg
(for example). This message will be sent by the
recipient when the message has been received -- independently of any
other messages the recipient may send.
If the icmLeaseTime
option is set, and if the message is
discarded due to message lease expiry, then the message:
(icmLeaseExpired,msg)is sent.
icmAuditTrail
icmReleaseOptions
The icmReleaseOptions
function garbage collects the
icmOption
structure it is passed -- afterwards the structure is
invalid.
icmStatus icmReleaseOptions(icmOption opts)
icmreleaseOptions
returns icmOk
if possible.
icmIsOption
The icmIsOption
function queries an icmOption
structure
for a particular feature.
icmStatus icmIsOption(icmOption opts,icmOpt o,...)
Depending on o, icmIsOption
returns a value via a pointer
to the result variable that follows the o parameter:
icmLeaseTime
icmIsOption
should be a pointer to a C
time_t
variable. If there is an icmLeaseTime
option in the
opts structure, then the lease time is returned and
icmIsOption
returns icmOk
, otherwise icmIsOption
returns icmFailed
.
The expiry time is expressed in absolute seconds after Unix epoch.
icmReplyto
icmHandle
variable. If an icmReplyto
option had been specified in
opts, then the reply handle is set in the variable and
icmOk
is returned. If no icmReply
is given then
icmFailed
is returned.
icmReceiptRequest
icmMsg
variable. If an icmReceiptRequest
option had been specified in
opts, then the requested form of the receipt is set in the
icmMsg
variable and icmOk
is returned. This is the message
that should be sent to the originator of the message -- or if a
icmReply
is given then to the relevant agent.
If no icmReceiptRequest
is requested then icmFailed
is
returned.
icmAuditTrail
icmHandle*
pointer. This is
bound to a NULL terminated list of handles that represents the audit
trail of the message.
The programmer should not modify any of the values returned from
icmIsOption
-- they will be released when
icmReleaseOptions
is invoked.
These functions are used to create and to parse ICM messages. The
underlying data structure of an ICM message is a self-parsing stream of
bytes. This data type is marshalled into the opaque type
icmMsg
; a value of this type may have additional information
embedded within it.
icmFormatMsg
The icmFormatMsg
function constructs a message based on a format
string -- which is a normal C string -- together with additional data
values. The format string controls the shape and structure of the
message; and the contents of the message are extracted from normal data
values native to the programming language.
icmMsg icmFormatMsg(icmStatus *return, char *fmt, ...);
The format string consists of a sequence of conversion items which reflect the particular nature of the object to be plugged in. Within the format string, white space is ignored: it can be used to make the format string more readable. While the format string may contain a complex structure, with many components, a given format string may specify only one overall structure.
A non-white space character -- which is not a conversion item, is assumed to be part of a symbol entry in the message.
Each conversion item is preceded by a %
character, followed by one or
more other characters that define the meaning. Many of the conversion
items `consume' a real value. The pointer to this value is to be found
in the arguments following the format string. Each following argument
contains either a value or a pointer to a value; the exact
interpretation will depend on the conversion character encountered.
%d
%i
%f
%s
%S
icmDataPo
pointer; the contents
of which is placed in the message as a block of uninterpreted bytes.
%h
icmHandle
; the contents
of which is placed in the message as a handle object.
%x
icmOpaquePo
; the contents of
which is placed as an opaque value into the message.
%C
icmDataPo
pointer; the contents
of which is placed in the message as a code block.
%(
%
) marker. Each conversion
specifier between the %(
and %)
markers correspond to
different elements of the tuple.
%)
%[
%]
marker. Each conversion specifier between the
%[
and %]
markers correspond to different elements of the
tuple.
%]
%,
%R
"...%Rfoo%(%d%d%)..."denotes a constructor record of two elements -- both numbers -- whose identifier is the symbol
foo
.
%{
icmFormatMsg
depending on the type of the embedded
format specifier.
icmFormatMsg
can only determine the type of relatively simple
values -- such as a numeric value, or a list of numeric values. It is
not able to guess at the type of messages which are introduced
dynamically -- using the %#
format specifier for example.
See section Encapsulated values for a more detailed description and how
they are represented.
%}
%{%}
markers.
%"
%#
icmMsg
value.
%*
%*
conversion
takes two parameters from icmFormatMsg
: the number of elements of
the list and the array of values themselves.
The type of the elements of the array, and their corresponding
interpretation within the message, are determined from the character
that follows the %*
, which may be any one of:
%*d
icmFormatMsg
should be an array of long
numbers.
%*i
icmFormatMsg
should be an array of long long
numbers.
%*f
icmFormatMsg
should be an array of pointers to
double
values.
%*s
icmFormatMsg
should be an array of char *
pointers.
%*S
icmFormatMsg
should be an array of
icmDataPo
values.
%*h
icmFormatMsg
should be an array of icmHandle
values.
%*C
icmFormatMsg
should be an array of
icmDataPo
values.
%*#
icmFormatMsg
should be an array of
icmMsg
values.
icmFormatMsg
returns:
icmOk
icmFailed
icmError
icmScanMsg
The icmScanMsg
function parses a message based on a format string
-- which is a normal C string. It can be used to both verify that a
message matches a particular patten and to extract C values from the
message. The format string controls the expected shape and structure of
the message; and the contents of the message are extracted into normal
data values native to the programming language.
icmStatus icmScanMsg(icmMsg msg, char *fmt, ...);
The format string consists of a sequence of conversion items which reflect the particular nature of the object to be plugged in. Within the format string, white space is ignored: it can be used to make the format string more readable. While the format string may contain a complex structure, with many components, a given format string may specify only one overall structure.
A non-white space character -- which is not a conversion item, is
assumed to be part of a symbol entry in the message. If the actual
message does not have the corresponding symbol then the icmScanMsg
function will fail.
Each conversion item is preceded by a %
character, followed by one or
more other characters that define the meaning. Many of the conversion
items `generate' a real value; the value generated is placed into the
location pointed at by the appropriate argument following the conversion
format. Successive arguments of icmScanMsg
refer to successive
conversion items in the fmt string.
%d
long *
.
%i
long long *
.
%f
double *
.
%s
char
**
pointer which is passed into icmScanMsg
.
Note that it becomes the programmer's responsibility to release the
memory occupied by the string data -- using the standard C function
free
.
Note that if the message item is not a symbol -- with the result that
icmScanMsg
will fail -- then no memory is allocated.
%S
icmScanMsg
should be
an icmDataPo
pointer. A copy of the message data -- together with
its size -- is placed in the icmDataPo
structure passed in to icmScanMsg
.
Note that it becomes the programmer's responsibility to release the
memory occupied by the string data -- using the standard C function
free
. Note that if the message item is not a string -- with the
result that icmScanMsg
will fail -- then no memory is allocated.
%C
icmScanMsg
should be
an icmDataPo
pointer. A copy of the message data -- together with
its size -- is placed in the icmDataPo
structure passed in to icmScanMsg
.
Note that it becomes the programmer's responsibility to release the
memory occupied by the code data -- using the standard C function
free
. Note that if the message item is not a code block -- with the
result that icmScanMsg
will fail -- then no memory is allocated.
%x
icmOpaquePo
record -- which is passed in as a parameter. The
values returned should be free
d after use.
%h
icmScanMsg
should be a pointer to an icmHandle
variable. This is filled in with the handle recovered from the message.
%"
icmExpectData
function call. In addition, the data
in the message is checked against the embedded string within the format
string -- the string of characters up to a second %"
pair. If the
embedded message is not a string, or if it has a different value to the
explicit string data then icmScanMsg
returns icmFailed
.
%(
%)
marker. Each conversion
specifier between the %(
and %)
markers correspond to
different elements of the tuple.
The elements of the tuple are loaded into succesive entries of the args
array.
%)
%[
%]
marker. Each conversion specifier between the
%[
and %]
markers correspond to different elements of the
list.
The elements of the list are loaded into succesive args.
%]
%,
%R
"...%Rfoo%(%d%d%)..."denotes a constructor record of two elements -- both numbers -- whose identifier is the symbol
foo
.
%{
%}
%{%}
markers.
%#
icmMsg
value which is
stored in the location addressed by args[i]
where this is the
ith item encountered. This message must subsequently be released
-- using an icmReleaseMsg
call unless the icmScanMsg
call
as a whole fails.
%@
args[i]
, where this is
the ith item encountered. The corresponding message element is
skipped.
%*
%*
conversion format: the
first is a pointer to a long
integer and the second should be an
array of suitable values -- one that is large enough to hold the values
extracted from the list. The initial value of the `element counter' is
the length of the array that is passed in; on successful parsing of the
message, this count is updated with the actual number of elements loaded
into the array.
If the array is not long enough to hold all the elements of the list,
then icmScanMsg
will return icmError
.
The expected type of the list element is determined from the character
following the %*
specifier:
%*d
long
values.
%*i
long long
values.
%*f
double
values.
%*s
char *
values.
Note that it becomes the programmer's responsibility to release the
memory occupied by these C strings after they are processed.
%*S
icmDataRec
values.
Note that it becomes the programmer's responsibility to release the
memory occupied by these data strings after they are processed.
%*C
icmDataRec
values.
Note that it becomes the programmer's responsibility to release the
memory occupied by these code blocks after they are processed.
%*h
icmHandle
values.
%*#
icmMsg
array.
Note that it becomes the programmer's responsibility to release the
messages generated by this conversion -- using the icmReleaseMsg
function.
%**
%**
format specifier. This matches a list in the
message, but merely counts the elements of the list. The length of the
list within the message is stored in the long *
count field
passed in and there is no associated array argument.
icmScanMsg
returns:
icmOk
icmFailed
icmScanMsg
function.
icmError
icmScanXMsg
The icmScanXMsg
function is similar to the icmScanMsg
function (see section icmScanMsg
), except that the initial index into the
message is specified.
icmStatus icmScanXMsg(icmMsg msg, long start, long *exit,char *fmt, ...);
The initial point of entry is given by start, and the index of the immediately following message component is returned in exit.
Note that a call to icmScanMsg
is equivalent to a call to
icmScanXMsg
of the form:
icmScanXMsg(msg,0,NULL,format,...)
The interpretation of the other arguments and return code is the same as
for icmScanMsg
.
The low-level message parsing functions form a collection which allows
an application to parse a message in piece-meal fashion. This is most
suitable in situations where the agent platform is a higher-level
language than C or Java -- e.g., Prolog, LISP or April
-- and the native
programming language already has adequate methods for representing
complex structures. In this situation, the low-level message parsing
functions can be used to parse the message and build a language specific
structure from the message.
icmElTag
This type enumerates the possible type of individual message elements:
typedef enum {icmInt=0x10, icmFlt=0x20, icmNegFlt=0x30, icmSym=0x40, icmHandle=0x50, icmData=0x60, icmCode=0x70, icmNil=0x80, icmList=0x81, icmApply=0x82, icmTuple=0x90, icmTag=0xa0, icmRef=0xb0, icmShort=0xc0} icmElTag;
Note that the ordinal values associated with these tags reflects the underlying encoding of ICM messages.
icmMsgElType
The icmMsgElType
function returns the type of the `next'
component of a message.
icmStatus icmMsgElType(icmMsg msg, int index,icmElTag *type);
This function examines the message component -- defined by the pair: msg/index and returns in type the type of the message element. To examine the beginning of the message use an index of 0; otherwise valid values of index must come from the returned values of other low-level message parsing functions.
icmMsgElType
returns:
icmOk
msg[index]
is type.
icmFailed
icmEndFile
index
was too large for the message
icmError
msg[index]
combination is not valid.
Once a message element's type and size have been identified, its value can be extracted using one of the following element decoding functions.
icmMsgElSize
The icmMsgElSize
function returns the size of the current
component of a message. Depending on the type of structure accessed, the
size returned is the either number of bytes needed to hold the value or
the `length' of the list or tuple.
icmStatus icmMsgElSize(icmMsg msg, int index,long *size);
This function examines the message component -- defined by the pair: msg/index and returns in size the size of the message element.
icmMsgElSize
returns:
icmOk
msg[index]
could be determined.
icmFailed
icmEndFile
icmError
msg[index]
combination is not valid.
Where the element is a list, icmElSize
returns 0
for an
empty list and 1
for a non-empty list. Where the element is a
tuple, icmElSize
returns the arity of the tuple.
Once a message element's type and size have been identified, its value can be extracted using one of the following element decoding functions.
icmExpectInt
The icmExpectInt
function verifies that there is an integer value
at a particular point in a message.
icmStatus icmExpectInt(icmMsg msg, int x, int *newx, long *result);
This function will succeed if there is an integer value encoded in the
message at msg[x]
; the value of the integer is returned in
result and the index of the following message element is returned
in newx.
icmExpectInt
returns:
icmOk
msg[x]
is
an integer value.
icmFailed
msg[x]
was not an integer value.
icmEndFile
icmError
msg[x]
combination is not valid.
icmExpectFlt
The icmExpectFlt
function verifies that there is a floating point
value at a particular point in a message, and returns the floating point
number.
icmStatus icmExpectFlt(icmMsg msg, int x, int *newx, double *result);
This function will succeed if there is a floating point value encoded in
the message at msg[x]
; the value of the float is returned in result and
the index of the following message element is returned in newx.
icmExpectFlt
returns:
icmOk
msg[x]
is a floating point value.
icmFailed
msg[x]
was not a floating point value.
icmEndFile
icmError
msg[x]
combination is not valid.
icmExpectSym
The icmExpectSym
function verifies that there is a symbol value
at a particular point in a message, and returns the symbol as a C string
pointer.
icmStatus icmExpectSym(icmMsg msg,int x,int *newx,char *buffer,long len);
This function will succeed if there is a symbol value encoded in the
message at msg[x]
; the symbol is returned in result -- as a C string
-- and the index of the following message element is returned in newx.
Note that the C buffer is supplied to the icmExpectSym
function,
and that len should be large enough to hold the expected C string
pointer.
icmExpectSym
returns:
icmOk
msg[x]
is a symbol value.
icmFailed
msg[x]
was not a symbol value.
icmEndFile
icmError
msg[x]
combination is not valid, or the supplied buffer was
not long enough.
icmExpectHandle
The icmExpectHandle
function verifies that there is a
handle
value at a particular point in a message, and returns the
handle
as an icmHandle
pointer.
icmStatus icmExpectHandle(icmMsg msg,int x,int *newx,icmHandle *result);
This function will succeed if there is a handle
encoded in the
message at msg[x]
; the handle
is returned in result -- as
an icmHandle
pointer -- and the index of the following message
element is returned in newx.
icmExpectHandle
returns:
icmOk
msg[x]
is a valid handle.
icmFailed
msg[x]
was not a valid handle.
icmEndFile
icmError
msg[x]
combination is not valid.
icmExpectData
The icmExpectData
function verifies that there is a data block
value at a particular point in a message, and returns the data as a
combination of a C void pointer and the length of the data block.
icmStatus icmExpectData(icmMsg msg,int x,int *newx, void *buffer,long *len,long size);
This function will succeed if there is an uninterpreted string/data
block value encoded in the message at msg[x]
; then the data is copied
into buffer -- which has a maximum length of size bytes.
The index of the following message element is returned in newx.
The found size of the data block is returned in len.
icmExpectData
returns:
icmOk
msg[x]
is a data block value.
icmFailed
msg[x]
was not a data block value.
icmError
msg[x]
combination is not valid, or the supplied
buffer was not large enough to hold the data.
icmExpectOpaque
The icmExpectOpaque
function verifies that there is an opaque value
at a particular point in a message. Opaque values have an owner and a
value; these are conventionally a symbol for the owner, and a data block
for the value. However, neither are decoded by the
icmExpectOpaque
function -- the application should follow the
call by a call to icmExpectSym
and icmExpectData
respectively.
icmStatus icmExpectOpaque(icmMsg msg,int x,int *newx);
This function will succeed if there is an opaque value encoded in the
message at msg[x]
. The index of the owner element is returned in
newx.
icmExpectOpaque
returns:
icmOk
msg[x]
is an opaque value.
icmFailed
msg[x]
was not an opaque value.
icmError
msg[x]
combination is not valid, or the supplied buffer was
not long enough.
icmExpectCode
The icmExpectCode
function verifies that there is a code block
value at a particular point in a message.
icmStatus icmExpectCode(icmMsg msg,int x,int *newx, void *buffer,long *len,long size);
The code data is copied into buffer -- which has a maximum length of size bytes. The index of the following message element is returned in newx. The found size of the code block is returned in len.
Note that the language of the code block is not defined by the ICM; in order to further distinguish the program code, there should be additional internal evidence within the code block.
icmExpectCode
returns:
icmOk
msg[x]
is a code block value.
icmFailed
msg[x]
was not a code block value.
icmError
msg[x]
combination is not valid, or the supplied buffer was
not long enough
icmExpectNil
The icmExpectNil
function verifies that there is an empty list at
a particular point in a message.
icmStatus icmExpectNil(icmMsg msg,int x,int *newx);
This function will succeed if there is an empty list encoded in the
message at msg[x]
; the index of the following message element is
returned in newx.
icmExpectNil
returns:
msg[x]
is an empty list.
icmFailed
msg[x]
was not an empty list.
icmError
msg[x]
combination is not valid.
icmExpectList
The icmExpectList
function verifies that there is a non-empty
list at a particular point in a message.
icmStatus icmExpectList(icmMsg msg, int x, int *newx);
This function will succeed if there is a non-empty list encoded in the
message at msg[x]
; the index of the message element corresponding
to the head of the list is returned in newx.
icmExpectList
returns:
icmOk
msg[x]
is a non-empty list.
icmFailed
msg[x]
was not a non-empty list.
icmError
msg[x]
combination is not valid.
icmExpectApply
The icmExpectApply
function verifies that there is an
apply structure at a particular point in a message. Apply structures
consist of a head and a tail: conceptually, the head refers to the
function and the tail the arguments.
icmStatus icmExpectApply(icmMsg msg, int x, int *newx);
This function will succeed if there is an apply structure encoded in the
message at msg[x]
; the index of the message element
corresponding to the head of the apply is returned in newx.
icmExpectApply
returns:
icmOk
msg[x]
is an apply structure.
icmFailed
msg[x]
was not an apply structure.
icmError
msg[x]
combination is not valid.
icmExpectTuple
The icmExpectTuple
function verifies that there is a tuple at a
particular point in a message.
icmStatus icmExpectTuple(icmMsg msg, int x, int *newx,int *arity);
This function will succeed if there is a tuple encoded in the message at
msg[x]
; the index of the message element corresponding to the first
element of the tuple is returned in newx and the arity of the tuple is
returned in arity.
icmExpectTuple
returns:
icmOk
msg[x]
is a tuple.
icmFailed
msg[x]
was not a tuple.
icmError
msg[x]
combination is not valid.
icmExpectTag
The icmExpectTag
function verifies that there is a tagged
structure at a particular point in a message.
icmStatus icmExpectTag(icmMsg msg,long index,long *newx,long *tag)
This function will succeed if there is a tagged structure encoded in the
message at msg[x]
; the message index of the structure that is tagged is
returned in newx and the tag number is returned in tag.
icmExpectTag
returns:
icmOk
msg[x]
is a tagged structure.
icmFailed
msg[x]
was not a tagged structure.
icmError
msg[x]
combination is not valid.
Note that tagged structures are typically used to represent circular or general graph structures.
icmExpectRef
The icmExpectRef
function verifies that there is a reference to a
tagged structure at a particular point in a message.
icmStatus icmExpectRef(icmMsg msg,long index,long *newx,long *tag)
This function will succeed if there is a reference to a tagged structure
encoded in the message at msg[x]
; the tag number of the referenced
structure is returned in tag and the message index of the following
message element is returned in newx .
icmExpectRef
returns:
icmOk
msg[x]
is a reference to a tagged structure.
icmFailed
msg[x]
was not a reference to a tagged
structure.
icmError
msg[x]
combination is not valid.
icmSkipElement
The icmSkipElement
function `skips over' a complete component of
a message.
icmStatus icmSkipElement(icmMsg msg, int x, int *newx);
This function will succeed if there is a complete -- well formed --
message sub-structure at msg[x]
and returns the
message index of the following message element is returned in newx . If
the message element was a list -- or an apply pair or a tuple -- then
all the elements of the list or tuple are skipped over as well.
icmSkipElement
returns:
icmOk
icmFailed
msg[x]
was not a well formed structure.
icmError
msg[x]
combination is not valid.
The low-level message generation functions form a collection which allows an application to construct a message in piece-meal fashion. Note that it is the responsibility of the programmer to ensure that a well-formed message is constructed using these primitives.
icmNewMsg
The icmNewMsg
function constructs a new `empty' message that can
be extended with other message generation functions.
icmStatus icmNewMsg(icmMsg *msg);
icmNewMsg
returns:
icmOk
icmError
icmPlaceInt
The icmPlaceInt
function extends a message structure by placing
an integer value at the current end of the message.
icmStatus icmPlaceInt(icmMsg msg,long i);
icmPlaceInt
returns:
icmOk
icmError
icmPlaceFlt
The icmPlaceFlt
function extends a message structure by placing a
floating point value at the current end of the message.
icmStatus icmPlaceFlt(icmMsg msg,double f);
icmPlaceFlt
returns:
icmOk
icmError
icmPlaceSym
The icmPlaceSym
function extends a message structure by placing a
symbol -- encoded as a C string -- at the current end of the message.
icmStatus icmPlaceSym(icmMsg msg,char *sym);
icmPlaceSym
returns:
icmOk
icmError
icmPlaceHandle
The icmPlaceHandle
function extends a message structure by
placing a handle
-- encoded as described in section Handle encoding
-- at the current end of the message.
icmStatus icmPlaceHandle(icmMsg msg,icmHandle hdl);
icmPlaceHandle
returns:
icmOk
icmError
icmPlaceData
The icmPlaceData
function extends a message structure by placing
a data block -- encoded as a void * pointer and the length in bytes of
the data block -- at the current end of the message.
icmStatus icmPlaceData(icmMsg msg,void *data,long size);
icmPlaceData
returns:
icmOk
icmError
icmPlaceCode
The icmPlaceCode
function extends a message structure by placing
a code block -- encoded as a void * pointer and the length in bytes of
the code block -- at the current end of the message.
icmStatus icmPlaceCode(icmMsg msg,void *code, long size);
icmPlaceCode
returns:
icmOk
icmError
icmPlaceNil
The icmPlaceNil
function extends a message structure by placing
an empty list at the current end of the message.
icmStatus icmPlaceNil(icmMsg msg);
icmPlaceNil
returns:
icmOk
icmError
icmPlaceList
The icmPlaceList
function extends a message structure by placing
a non-empty list marker at the current end of the message.
icmStatus icmPlaceList(icmMsg msg);
icmPlaceList
returns:
icmOk
icmError
Note that immediately following the non-empty list marker the head of the list, followed by the tail of the list should be generated.
icmPlaceApply
The icmPlaceApply
function extends a message structure by placing
an apply marker at the current end of the message.
icmStatus icmPlaceApply(icmMsg msg);
icmPlaceApply
returns:
icmOk
icmError
Note that immediately following the apply marker the head of the apply, followed by the tail of the apply should be generated.
icmPlaceTuple
The icmPlaceTuple
function extends a message structure by placing
a tuple marker at the current end of the message.
icmStatus icmPlaceTuple(icmMsg msg,long arity);
icmPlaceTuple
returns:
icmOk
icmError
Note that immediately following the tuple marker all the elements of the tuple should be generated in sequence.
icmPlaceTag
The icmPlaceTag
function extends a message structure by placing a
tag marker at the current end of the message.
icmStatus icmPlaceTag(icmMsg msg,long tag);
icmPlaceTag
returns:
icmOk
icmError
Note that immediately following the tag marker the tagged structure itself should be generated.
icmPlaceRef
The icmPlaceRef
function extends a message structure by placing
a tag reference at the current end of the message.
icmStatus icmPlaceRef(icmMsg msg,long tag);
icmPlaceRef
returns:
icmOk
icmError
Note that each reference placed should have a corresponding tag defined at some point within the msg.
icmPlaceMsg
The icmPlaceMsg
function extends a message structure by placing a
complete sub-message at the current end of the message.
icmStatus icmPlaceMsg(icmMsg msg,icmMsg sub);
icmPlaceMsg
returns:
icmOk
icmError
The ICM library has built into it a number utility functions which, though not essential to users of the ICM API, may be none-the-less useful in constructing applications invloving the ICM API.
The hash facilities included in this section are not `critical' to the ICM, however, they are used by the ICM and may prove useful to the programmer for other purposes.
icmHashPo
The icmHashPo
is an opaque type that refers to a hash table
created and manipulated by the ICM hash functions.
typedef void *icmHashPo;
icmHashFun
typedef long (*icmHashFun)(void *);
The icmHashFun
function type is a function prototype that refers
to the specific `hashing' function used by the ICM
hash table
functions.
Its input is a void *
-- i.e., a value whose type depends on the
particular use of the hash functions. Its output is a hash value that
will be used by the hash table search functions.
icmCompFun
typedef int (*icmCompFun)(void *A,void *B);
The icmCompFun
function prototype refers to a function that is
used to compare two values for equality. icmCompFun
should return
0 when A and B are equal, and a non-zero value when they are
different.
icmDestFun
typedef icmStatus (*icmDestFun)(void *N,void *R);
The icmDestFun
function prototype refers to a function that will
be called when an entry in the hash table is deleted. This represents an
opportunity to release space allocated when a hash table entry is
created. The N argument will be the `name' of the hash table
entry, and R is the `value' of the hash table entry.
The destroy function should return icmOk
if the element's
structures could be released satisfactorily. If the called function
returns icmFailed
or icmError
then the element is NOT
removed from the table.
icmProcFun
typedef icmProc (*icmProcFun)(void *N,void *R,void *C);
The icmProcFun
function prototype refers to a function that will
be called when the whole of the hash table has to be processed. The
N argument will be the `name' of the hash table entry, the R
is the `value' of the hash table entry, and C is `client' data
passed into the icmProcTable
function.
The value returned by this function will govern the returned value from
icmProcTable
function.
icmNewHash
The icmNewHash
function constructs a new hash table.
icmHashPo icmNewHash(long size,icmHashFun hash, icmCompFun cmp,icmDestFun dest);
The table returned by icmNewHash
is sufficient to accomodate at
least size entries (its actual size may be different). The
function used to generate keys is given by hash, cmp can be
used by the hash functions to compare values and dest is used
whenever an entry is deleted from the table.
The hash and cmp functions should not be NULL when a new table is created. However, if dest is NULL, then no function will be invoked when elements are deleted from the table.
Normally, icmNewHash
returns the address of a new table; if it
returns NULL then it was not possible to construct the table.
icmDelHash
The icmDelhash
deletes a hash table previosuly generated by a
call to icmNewHash
. Before releasing the space for the table
itself, icmDelHash
first of all removes all elements from the
table -- this may result in the dest function being cvalled for
the elements being deleted.
icmStatus icmDelHash(icmHashPo hp);
icmDelHash
returns:
icmOk
icmFailed
icmFailed
is returned, the table still exists; however, it is
undecided how many of the table's elements are still in the table.
icmError
icmInstall
This function installs a new entry in the table.
icmStatus icmInstall(void *n,void *r,icmHashPo tbl);
This installs a new entry with `name' n and `value' r. Note
that icmInstall
does not create any additional structures to
support n or r: it is the programmer's responsibility to
ensure that n and r are valid for the expected lifetime of
the entry.
icmInstall
returns:
icmOk
icmError
icmSearch
This function searches for an entry in the table.
void * icmSearch(void *n,icmHashPo tbl);
This searches the table for an entry whose name is n -- as defined by the comparison function installed in the table when it was created. The return value is the value associated with that name, or NULL if the search failed.
icmUninstall
This function attempts to remove an entry from the table.
icmStatus icmUninstall(void *n,icmHashPo tbl);
This attempts to remove the entry associated with `name' n from the table tbl. If there is a dest function associated with the table then this is invoked prior to the entry being deleted from the table.
icmUninstall
returns:
icmOk
icmOk
then the entry is
deleted and icmUninstall
also returns with icmOk
.
If there is no entry associated with n then icmUninstall
also returns with icmOk
.
icmFailed
icmFailed
then the entry
is NOT deleted, and icmUninstall
also returns with
icmFailed
.
icmError
icmError
then the entry
is deleted, and icmUninstall
also returns with
icmError
.
icmProcessTable
The icmProcessTable
function applies a user-supplied function to
every element currently in the hash table.
icmStatus icmProcessTable(icmProcFun fun,icmHashPo tbl,void *client);
For every valid entry currently in tbl the fun function is invoked; each call to fun takes the form:
fun(N,R,client)
where N is the name component of the entry, R is the value
component and client is as given in the call to
icmProcessTable
.
icmProcessTable
returns:
icmOk
icmOk
icmFailed
icmFailed
return from fun.
Note that all entries are processed, even when the fun function
returns icmFailed
on one or more entries.
icmError
icmError
return code. In this case, further processing of the table is prevented:
the first entry that results in an icmError
also terminates
processing of the table.
Note that due to the nature of hash tables, it is not possible to guarantee a particular order when processing the elements of the table: neither the order in which entries are added to the table, nor any possible relative ordering based on the keys, will affect the order of processing.
These functions are made available from the ICM implementation to make it simpler to access hostnames in a standard way.
icmGetHostname
The icmGetHostname
accesses the `real' host name of a mahine. It
is able to resolve relative hosts names -- such as prospero
--
and determine if the host is known and its canonical, fully qualified
name such as: prospero@net.fla.fujitsu.com
.
char *icmGetHostname(char *host);
icmGetHostname
returns NULL if the host is unknown. It is able to
determine a hostname from a name given as an IP quartet as well as a
DNS hostname format.
Note that the ICM library caches host names -- for a period of up to one day. This can lead to a performance improvement compared to the standard Unix library functions.
icmMachineName
The icmMachineName
returns the `real' hostname of the current
machine; if it is possible to determine.
The ICM library includes a `data structure pooling' mechanism. The
intention of this mechanism is to supplement the existing memory
management functions of malloc
and free
. The ICM data
structure pooling mechanism has the equivalent of malloc
and
free
; however when released back into a pool, a data structure is
not free
d, instead it is kept in a linked list of equivalently
sized objects. When a new object is requested from the pool, this list
can be consulted directly without requiring a potentially expensive
malloc
.
icmPoolBase
This opaque pointer type is used to denote a pool of empty data structures.
typedef struct _poolbase_ *icmPoolBase;
icmInitPool
The icmInitPool
function must be used to create a pool for a
particular type. The form of this function is:
icmPoolBase icmInitPool(size_t elsize, int initial);
where elsize is the size of each element of the data type --
usually computed using the sizeof
operator -- and initial
is advise to the pool manager to create at least initial elements
in the pool.
icmAllocPool
The icmAllocPool
function is used to allocate an empty data
structure from the pool of available objects. If the pool is empty, then
icmAllocPool
automatically uses malloc
to create additional
empty objects.
void *icmAllocPool(icmPoolBase base)
Note that the size of the element is determined by the initialization of
the base. Note also, that the return value is a void *
.
This value should be type caste into the appropriate type before use.
Important:
A value that has been allocated using
icmAllocPool
should not be freed usingfree
-- useicmFreePool
(see sectionicmFreePool
) instead.
icmFreePool
The icmFreePool
function releases a previously allocated object back
into the pool.
void icmFreePool(icmPoolBase base, void *p)
Important:
Only values that have been allocated using
icmAllocPool
should be freed usingicmFreePool
.
The ICM offers a command-line processing option package that can be used to simplify the processing of command-line options.
icmOptionCallback
typedef icmStatus (*icmOptionCallback)(char opt,char *optarg,void *client);
This type defines a type of function that is called for each potential option on the command line.
icmGetOptions
The icmGetOptions
function can be used to process the
command-line options is a regular and standard way.
int icmGetOptions(int argc,char **argv,char *fmt, icmOptionCallback opt,void *client);
The first two arguments are the standard arguments representing the C command-line arguments to a program. The fmt argument specifies which command-line options are valid and opt/client are used to process the options individually.
The fmt string consists of a series of letters, each may be
suffixed by a :
character if the option is associated with a
value. For each command-line option encountered by icmGetOptions
,
the callback function opt is called; each call is of the form:
opt(opt,arg,client)
where opt is a character representing the particular option, if
the option was identified as having a data value associated with it
(such as a file name) then the appropriate value from the command line
arguments is passed in arg, and client is the client data
passed into icmGetOptions
.
The proc call should return icmOk
if the actual option and
data are valid, icmFailed
if there were of valid form but
otherwise rejected and icmError
if there was an error in
processing the option.
icmGetOptions
returns the index of the first non-option entry in
the command-line argument vector argv.
icmGetOptions
`understands' certain standard options to relate to
the ICM itself. These options are:
-L <file>
-
is speficied for the log file name, then log
messages are directed to the standard output stream.
Many of the API functions are actually commands to the communications server. These commands are delivered to the CS using the normal message passing mechanism: the difference is that the message is intended for the CS itself rather than another agent.
Here, we detail the standard messages that any CS must be able to understand, and the expected format of the reply.
The CS specific API functions are implemented in terms of this protocol. Normally, it is sufficient for an application to simply use the API; however, in some situations it may be desireable for an application to directly use the protocol.
Each CS has a standard well-known name. This name takes the form:
icmCommServer@home/[home]
The local CS always has the name:
icmCommServer@hostname/[hostname]
where hostname is the full canonical name of the local host
machine. It is possible to acquire this name using the API function
icmMachineName
-- see section icmMachineName
.(17)
On occasion, it is necessary to generate a handle to a remote
CS, the API function icmCommServerHandle
--
see section icmCommServerHandle -- can be used for this purpose.
Each message that is communicated to/from the CS is packaged into a standard message envelope. The message envelope is a standard wrapper for the message being transmitted; it contains information such as the sender of the message, the recipient of the message as well as the message itself.
The top-level of the message envelope takes the form of a 4-tuple:
(recipient,sender,options,message)
icm
network on the appropriate way of treating the
message.
The message timeout option allows the sender of a message to control the valid lifetime of a message. The format of this option is:
(icmLeaseTime, when)
where when is a Unix epoch time value(18) which identifies when the message loses its validity.
If a CS receives a message which has timed out then it is free to discard the message. However, it is not required to discard timed-out messages; and generally a CS should add a `fuzz' factor to any timeout: the reason being that few computers have totally accurate system clocks.
The replyto indicator is used when the sender of a message would prefer that any reply should be directed to a third party. The format of this option is:
(icmReplyTo, agent)
This information should be taken at an advisory level only: any agent is free to reply to messages or not -- depending on its `mood'. However, a well behaved agent would typically heed this request.
In addition to a message being wrapped in a standard envelope, it may also be `packetized'.(19) A packetized message consists of a sequence of messages; individual packets of a packetized message have the same form as a regular non-packetized message, except that they have several additional pairs in the options list:
(icmMsgNumber, msgno) (icmPacketNumber, pno) (icmPacketCount, count)
where:
The body portion of each packet is encoded as a data block using the block encoding (see section String or data block encoding).
Once a complete message has been processed, the blocks of the various messages may be concatenated together to form a complete message -- in the normal icm envelope format, including the usual sender , recipient, msg and option fields.
The primary benefit of message packetization is that mixed size messages which emanate from a single source but are destined for different ultimate destinations can be intermingled -- allowing a small message to be sent even while sending a large message at the same time. Of course, if mixed size messages are sent from a single source to a single destination they must still be read in the original order they were sent.
In order to register an agent with a CS, it is
necessary to send it an icmRegisterAgent
message. Note that an
application can send messages as soon as it has established a connection
with icmInitComms
-- see section icmInitComms
.
The form of the icmRegisterAgent
message is:
(icmRegisterAgent, agent, options)
where agent is a handle that identifies the agent to be registered and options is a list of optional modifications to the request for registering the agent.
The allowed options are:
(icmLeaseTime, when)
The CS responds to an icmRegisterAgent
request
with its own message:
(icmRegisterAgent, icmOk, handle)
(icmRegisterAgent, icmFailed, handle)
In the situation where an agent only registers with the local
communication server, the API function icmRegisterAgent
--
see section icmRegisterAgent
-- can be used. However, where an agent
registers with more than one CS, or where the API
functions are not appropriate, an agent can register directly using this
message protocol.
In order to de-register an agent that had previously been registered
with a CS, the icmDeregisterAgent
message can be used. The form of the message is:
(icmDeregisterAgent, agent)
This message should be sent to the CS that the agent has registered
with. In the situation where an agent only registers with the local
communication server, the API function icmDeregisterAgent
--
see section icmDeregisterAgent
-- can be used.
The CS replies with its own message:
(icmDeregisterAgent, icmOk, handle)
(icmDeregisterAgent, icmFailed, handle)
The icmDetachAgent
protocol is used by an agent when it wishes to
temporarily detach itself from its CS. A detached
agent is still registered with the CS, and
therefore the CS will hold messages intended for it.
The form of the message is:
(icmDetachAgent, agent)
This message should be sent to the CS that the agent has registered with.
The CS replies with its own message:
(icmDetachAgent, icmOk, handle)
(icmDetachAgent, icmFailed, handle)
The icmAttachAgent
protocol is used by an agent when it wishes to
re-attach itself to its CS -- after it had
previously detached itself. When the agent has reattached itself to its
CS, any outstanding messages for it will be delivered.
The form of the message is:
(icmAttachAgent, agent)
This message should be sent to the CS that the agent has registered with.
The CS replies with its own message:
(icmAttachAgent, icmOk, handle)
(icmAttachAgent, icmFailed, handle)
The icmDisconnect
message detaches all the agents connected via a
given connection.
The form of the message is:
icmDisconnect
This message should be sent to the CS that the agent has registered with.
The CS replies with its own message:
(icmDisconnect, icmOk, tag)
The icmReconnect
message reattaches all the agents connected via a
given connection, after a connection has been reestablished.
The form of the message is:
(icmReconnect, tag)
where tag was the integer value returned by the communications server at the time of the last disconnect.
The CS replies with its own message:
(icmReconnect, icmOk)
The agent monitoring functions allow applications to be informed when an agent terminates, detaches or re-attaches.
The icmMonitorAgent
requests that the CS
monitors the status of another agent. The
`icmMonitorAgent' message should be sent to the communication
server where the agent may be registered.(21)
(icmMonitorAgent, agent)
There are two `phases' to this interaction with the communications server: in the initial phase, when the CS responds with a success code:
(icmMonitorAgent, icmOk, agent)
(icmMonitorAgent, icmFailed, agent)
The second phase occurs when some event occurs in the monitored agent. Essentially, CSs recognise and react to four events:
(icmMonitorAgent, icmRegisterAgent, agent)
(icmMonitorAgent, icmDeregisterAgent, agent)
(icmMonitorAgent, icmAttachAgent, agent)
(icmMonitorAgent, icmDetachAgent, agent)
Note that the `granularity' of this information should be considered to be quite coarse: one agent may contain many targets, and therefore many semi-independent threads of activity within it.(22)
The icmUnMonitorAgent
requests that the CS
removes any monitors the sender has for the named agent. The
icmUnMonitorAgent
message should be sent to the communication
server where the agent may be registered.
(icmUnMonitorAgent,agent)
The CS returns with:
(icmUnMonitorAgent, icmOk, agent)
(icmUnMonitorAgent, icmFailed, agent)
When an agent requests message forwarding, it is the responsibility of the CS to implement it. This message protocol explains the precise commands that must be understood by the CS.
The icmRequestForwarding
message requests that a communications
server forwards messages. The form of this request is:
(icmRequestForwarding, handle)
where handle is a handle that identifies the new location(s) for the agent. Note that handle should identify the same agent as the agent requesting the address forwarding -- as determined by the sender of the message to the CS.
Address forwarding `works' by modifying the identified agent's location. The list of locations referred to in handle gives the agent's new location.
The CS responds to this message with one of:
(icmRequestForwarding, icmOk, handle)
(icmRequestForwarding, icmFailed, agent)
Note that in the case that the forwarding address does not include the `original' location of the agent, it implies that the agent has detached itself from the CS.
The icmCancelForwarding
message cancels any message forwarding
for the agent. The `known' location of the agent resumes to that
established when the agent initially registered with the communications
server.
(icmCancelForwarding, handle)
Note that the request to cancel forwarding must come from the agent that
requested the message forwarding: i.e., that the
agent@home
portion of handle is the same as
that of the sender of the icmCancelForwarding
request.
The CS replies with:
(icmCancelForwarding, icmOk, agent)
(icmCancelForwarding, icmFailed, agent)
The following mesage protocols can be used to determine the current location and live-ness of agents.
The icmLookupLocation
message protocol can be used to determine
the current location of an agent.
(icmLookupLocation, agent)
The CS responds with:
(icmLookupLocation, icmOk, handle)
(icmLookupLocation, icmFailed, agent)
The icmPingAgent
message can be used to tell if an agent is
currently registered and attached.
(icmPingAgent, agent)
The CS responds with:
(icmPingAgent, icmOk, handle)
(icmPingAgent, icmFailed, agent)
The icmListAgents
message requests that the CS
furnish a list of the currently registered and attached agents.
icmListAgents
The CS reponds with:
(icmListAgents, [h1, ..., h2])
(icmListAgents, icmFailed)
icmFailed
message.
The ICM is able to support messages intended for non-ICM based message transport systems. A handle that refers to a non-ICM transport delivery system would normally have the form:
tgt:name@home/[ICM-address,...,proto://address,...]
In order for a CS to be able to deliver messages to non-ICM message
transport systems there has to be a `plug-in' for that message
system. A plug-in for a message system takes the form of a regular
agent; e.g., if an email plug-in is to be registered, then the email
`agent' registers itself with the CS in the normal manner.
When the CS attempts to use an external address to deliver a message it
looks for an agent that has registered with it using the proto
name -- for example, to deliver to an email://
... address,
the CS looks for an agent called email
that is registered locally
with it. If such an agent is found, then the message is forwarded to
that agent (which is then responsible for actually using the sendmail
protocol to deliver the message.
When the CS delivers a message to a protocol agent, it is in `full-form' including the message envelope:
(icmFwdMsg,location, (recipient,sender,options,message))
where location is the location that the proto
agent should
use (extracted from the location field of the recipient handle of the
original message). This envelope is the envelope of the message to the
intended recipient of the message; the complete message to the protocol
agent itself looks like:
(proto,icmCommServer@local,[], (icmFwdMsg,location, (recipient,sender,options,message)))
i.e., the message to the proto
agent comes from the CS, but the
content of the message is a message envelope to be delivered to the
external transport mechanism.
Note that recipient will not include in its list of locations the location referred to by this protocol. For example, if the original recipient handle was:
foo@bar/[email://foo.bar.com,123.456.789.012]
then the recipient handle passed in the envelope to the
email
protocol handler will be:
foo@bar/[123.456.789.012]
There may be several addresses in a list of locations, and there may be
alternate addresses for the same protocol also. However, the protocol
agent should be careful to only attempt the delivery of the message to
the address identified in the icmFwdMsg
message.
It is the responsibility of the proto
col agent to reformat the
contents of the message in an appropriate manner for the message
transport protocol.
If the protocol agent cannot deliver the message, then it should reply
to the CS with an icmFwdFailed
message:
(icmFwdFailed,reason,badLoc,envelope)
where reason was the reason that the message could not be delivered, badLoc was the `bad location' that the protocol agent couldnt (or wouldnt) access, and envelope is the envelope and original message that was supposed to have been delivered.
The CS responds to this message by attempting to use the next available location from the recip handle to deliver the message.(23)
In this example, we construct a `minimal' ICM
compatible
application, written in C -- which initializes contact with the CS,
registers a name -- echo
-- and then waits for messages. It
responds to all messages by replying with the same message embedded in
the structure
(echo, message)
to the agent which sent the message.
The full text of this program is listed in section Listing of echo
server.
The program is divided into three phases -- an initialization phase, a
main loop and a termination phase. In the initialization phase we
establish contact with the CS -- after parsing command line
options -- and register the name echo
with the CS.
In the main phase we wait for messages and respond to them. In this
program, we do not use a `busy' wait since that would waste a large
amount of CPU resources. The ICM API has a standard function call --
icmEventLoop
-- which can be used to implement a simple `main'
program that will call one of our own functions every time a message is
received. Alternatively we can make use of the lower level UNIX
select
function to pause the program until there is some data on
the ICM
's communications socket.
The termination phase is mainly concerned with tidying up; in particular
with deregistering the name echo
and closing down the API. This
phase is actually implemented as a series of hook functions that will be
called when the user presses `control-C' at the keyboard. (Like many
servers, this one has no natural termination condition.)
In order to send messages to and receive messages from the commserver we
must first of all connect to the CS. The ICM
API
offers convenience functions to make this process easier.
In order to access ICM
's API properly, the standard ICM
header file needs to be included:
#include "icm.h"
This header file ensures that all of the ICM
's functions and data
structures are properly declared. Apart from this file, others may be
needed for other access to Unix system services.
Note that in order for the C compiler to be able to find this header file it may be necessary to set a comand-line option to the C compile:
gcc -I icmDir/include ...
Note that, typically, icmDir is set to opt/icm
; but this
depends on the installation of the ICM
.
The ICM API has a standard method for accessing command-line options --
as a convenience for the C programmer. The icmGetOptions
function (see section icmGetOptions
) offers a high-level method to process
our echo
program's command-line options.
We will process the command line options to allow the user of
echo
to override the port and host computer to
find the CS on.
To use icmGetOptions
, we provide a C string which specifies the
legal command line options, and a callback procedure -- pickOpt
--which is invoked for each commmand line option found by
icmGetOptions
.
This fragment is part of the pickOpt
callback procedure in
echo
:
... case 'n': /* override agent name for the echo server */ name = optarg; return; ...
When we invoke icmGetOptions
we specify what the legal options
are, and display an error message if something went wrong at this stage:
if(icmGetOptions(argc,argv,"n:P:",pickOpt,&errflag)==-1){ if(icmLogFile!=NULL) icmLogMsg("Usage: %s [-n name] [-P port]",argv[0]); else fprintf(stderr,"Usage: %s [-n name] [-P port]",argv[0]); exit(1); } if(icmLogFile == NULL) icmInitLogfile(LOGFILE); /* Default log file */
The API supports a file logging function -- reflecting the fact that many
servers are not intended to be connected to a console but may still need to
record special events in a log file. We will use this to log the messages
that the echo
server receives.
By default, logging messages generated using icmLogMsg
are thrown
away, but we want to be able to keep them. So we determine the
correct log file from the command line options, or if the user does not
specify one we use a default log file.
Notice how we used icmLogMsg
if we managed to specify at least the
log file. A true daemon would generally have no opportunity to display
messages on the console.
The icmInitLogfile
function is an ICM API function that creates an
output file. By default the API does not have a log file, and all calls
to icmLogMsg
are discarded. But we generally want to record
these messages, so icmInitLogfile
makes sure that there is
somewhere for the logging messages to go to.
ICM
API
Before we can send any messages, or receive any messages from the
commserver, we must initialise the API using the function icmInitComms
which establishes contact with the CS and sets up the raw
communications ports.
The initialization sequence in our application with the code fragment is:
icmConn conn; ... ourHandle = icmParseHandle(name); if(icmInitComms(portNo,NULL,&conn)==icmOk){ signal(SIGINT, sig_int); /* set up signal handling */ signal(SIGBUS, sig_fatal); signal(SIGSEGV, sig_fatal); signal(SIGFPE, sig_fatal); /* register ourselves */ if(icmRegisterAgent(conn,ourHandle,NULL,&ourHandle)==icmOk){ icmLogMsg("Echo server %h started",ourHandle); icmEventLoop(conn,HandleQuery,NULL); } else icmLogMsg("Failed to register %h",ourHandle); }
The call to icmParseHandle
constructs a handle from the name that
we decided to call our echo
server.
Once we have initialized the communications, we register this handle
with the CS. Notice that ourHandle
is potentially overwritten by
the call to icmRegisterAgent
-- see section icmRegisterAgent
. This is
to allow the CS to update us with any proxy services that it is aware
of. The returned handle will be essentially the same as the one we
supplied, except that it may have additional addresses in the locations
field of the handle. These additional addresses refer to proxy services
that the CS has registered with; this in turn allows the echo
server to reside on a mobile computer, or an intermittently connected
computer, without losing messages from other network devices.
After registering ourHandle
, the echo
server enters the
standard event loop -- using icmEventLoop
(see section icmEventLoop
). Once inside this function, the only way that the
loop may be terminated is either through an error in the connection to
the CS, or if the icmTerminateEventLoop
function is called (see section icmTerminateEventLoop
).
icmEventLoop
expects three arguments -- the connection, a
callback function and a `client' parameter that we can pass to the
callback function. Since we are just going to echo any messages we get
we don't really need to pass in a client value. Our main loop then
becomes:
icmEventLoop(conn,HandleQuery,NULL);
The main work goes on in the HandleQuery
function. This is called
every time the event loop receives a message for the echo
application. Since work in the echo
program is simply to echo
back each message received we can implement this easily:
static icmStatus HandleQuery(icmConn conn,icmMsg msg, icmHandle to,icmOption opt, icmHandle sender,void *cl) { icmLogMsg("Message `%M' from %h to %h",msg,from,to); icmFmtSendMsg(conn,sender,ourHandle,NULL,"%(echo%#%)",msg); return icmOk; /* signal that the message was consumed */ }
To be a little more sophisticated, our message handler can be extended
to recognize a termination command, which it responds to by invoking
icmTerminateEventLoop
(see section icmTerminateEventLoop
):
static icmStatus HandleQuery(icmConn conn,icmMsg msg, icmHandle to,icmHandle reply, icmHandle sender,void *cl) { icmLogMsg("Message `%M' from %h to %h",msg,from,to); if(icmScanMsg(msg,"quit")==icmOk){ icmTerminateEventLoop(conn); icmFmtSendMsg(conn,reply,ourHandle,NULL,"%(ok%,quit%)"); } else icmFmtSendMsg(conn,reply,ourHandle,NULL,"%(echo%#%)",msg); return icmOk; /* signal that the message was consumed */ }
This slightly more realistic example displays the typical structure of a message handler -- parsing the message for different types of message and generating different responses depending on the particular message.(24)
For those applications which are not purely designed as servers for
ICM
messages, a more complex main loop is necessary. The precise
form of this code depends on the operating system and other
environmental aspects of the application. Here we assume a Unix
environment.
We can simulate the effect of the icmEventLoop
using a
non-terminating while
statement, in each iteration of the loop we
test to see if there are any messages. If there are messages then we
perform the appropriate actions.
The main function for determining if there is a message for us is
icmGetMsg
. This returns icmOk
if there is a message,
and icmFailed
otherwise.
In order to make sure that our application does not consume too much CPU
resources simply waiting for messages, we use a Unix system call to
pause the application until there is data available on ICM
's
communications port. This is done using the select
system call;
which takes as argument an array of sockets to `listen to' for data.
In our echo
server application, we are only listening to one socket --
ICM
's socket; but in a real application we might well be
listening to several different sources of data, including but not
limited to ICM
's message stream.
At the heart of the event loop is the call to icmGetMsg
and
its response:
while(icmGetMsg(conn,&to,&sender,&opts,&msg,&seqNo)==icmOk){ HandleQuery(msg,to,sender,opts,NULL); icmReleaseMsg(msg); icmReleaseOptions(opts); }
The icmGetMsg
function assigns the sender
to the handle of
the sender of the message, msg
is set to the message itself and
opts
is set to an icmOption
structure (see section icmOption
)
which contains information about any message options selected. The
seqNo variable must first be initialized with the sequence number
of the first acceptable message, typically this is set to -LONG_MAX
:
seqNo = -LONG_MAX; while(icmGetMsg(conn,&to,&sender,&opts,&msg,&seqNo)==icmOk){ HandleQuery(msg,to,sender,opts,NULL); icmReleaseMsg(msg); icmReleaseOptions(opts); seqNo = -LONG_MAX; }
The call to icmReleaseOptions
removes any message options
structure built as part of the icmGetMsg
call.
There are many situations where an agent is not in a position to process just any message, but is looking for a response to a message it sent out earlier. The agent needs to be able to process particular messages, leaving other messages to be processed later.
The ICM
API library provides for this with the
icmReplaceMsg
call (see section icmReplaceMsg
). The general technique
when an agent is looking for particular messages is to enter a loop
which reads any messages that are available but replaces those messages
it cannot immediately handle back in the message queue.
For example, the icmRegisterAgent
API function is a case in
point: this function requires that a message be sent to the CS, and then
wait for the reply. However, due to the nature of communication, other
messages may be received in between sending the original request and the
CS's reply.(25)
The body of the icmRegisterAgent
function shows a typical
template for this kind of message manipulation:
seqNo = -LONG_MAX; ... while((ret=icmGetMsg(conn,&to,&sndr,&opts,&msg,&seqNo))==icmOk){ char res[MAXSTRING]; icmHandle hh; if(icmSameAgentHandle(sndr,conn->server)==icmOk && icmScanMsg(msg,"%(icmRegisterAgent%s%h%)",&res,&hh)==icmOk && icmSameAgentHandle(hh,agent)==icmOk){ ... /* process the reply from the icm */ } else /* message doesnt interest us now */ icmReplaceMsg(conn,to,sndr,opts,msg,seqNo); }
The technique relies on the specific semantics of the seqNo
variable as it relates to icmGetMsg
and
icmReplaceMsg
. This variable represents a message sequence
number; icmGetMsg
interprets its initial value as the number of
the last message that should be ignored before returning a
message. For example, if there are 5 messages in the message queue, with
sequence numbers 5, 10, 11, 12 and 13 respectively, and seqNo
has
value 10 on entry to icmGetMsg
, then messages 5 and 10 will not
be returned by the function. Instead, message number 11 will be
returned, and seqNo
will be updated to 11.
icmReplaceMsg
replaces a message back in the message queue with a
specific sequence number; normally the same sequence number that was
returned by the prior call to icmGetMsg
.
Before deciding whether to accept the message, we apply a test to the
message. In this case, the test involves a combination of a scan of the
message -- using icmScanMsg
-- and a test on the handle of the
sender -- using icmSameAgentHandle
. This kind of test is typical
of message processing; and may involve quite complex processing before
deciding whether or not to accept the message.
In order to terminate an agent properly which has initialized
contact with the ICM
system it is necessary to perform two main
functions -- to deregister all agents which have been registered and to
terminate the communications.
Note that this is not necessary in normal circumstances for applications
using the icmEventLoop
as this is done when
icmTerminateEventLoop
is called
(see section icmTerminateEventLoop
). However for abnormal termination --
when the user presses control-C for example -- we should still follow
these guidelines.
The first is done using the icmDeregisterAgent
function
(see section icmDeregisterAgent
) and the latter is done using the API
function icmCloseComms
(see section icmCloseComms
).
In common with many realistic server applications, our server does not have a natural termination condition. Our server will only be terminated on an error condition or when the user terminates it by pressing control-C on the keyboard.
In order to handle this we establish 'signal handlers' for the main termination situations which are on a `Control-C' and when a major run-time error such as floating point violation or memory violation occurs.
In our example, it is within these functions that we call the API
functions to terminate the April
communications sessions;
although other applications may have more graceful methods of
termination.
The functions that perform these exits are:
/* * cleanup after CTRL-C and other major errors */ void sig_int() { icmLogMsg("Control-C exit"); icmDeregisterAgent(conn,ourHandle); icmCloseComms(conn); exit(0); } void sig_fatal() { icmLogMsg("bus error or segmentation fault"); icmDeregisterAgent(conn,ourHandle); icmCloseComms(conn); exit(0); }
In order to inform Unix that we want these functions to be called, we add the following lines to the initialization sequence:
signal(SIGINT, sig_int); /* set up termination routines */ signal(SIGBUS, sig_fatal); signal(SIGSEGV, sig_fatal); signal(SIGFPE, sig_fatal);
Note that icmDeregisterAgent
and icmCloseComms
should
only be invoked if contact with the CS has been successfully
established.
There are occasions when an agent needs to temporarily remove itself
from a CS without losing any future messages to
it. For this purpose, the icmDetachAgent
function
(see section icmDetachAgent
) can be used.
This prevents further messages to an agent from being sent through the connection, but the messages are not lost: they are held on the CS until the agent re-attaches itself to the CS.
In order to access the ICM
API, it is necessary to link in to
the application code the library file(26) --
libicm
.
The sample Makefile
listed below show how our server application
might be built.
# # Make sample program to use April's API # # define an ANSI C compiler here CC = gcc # Where the installation directory of the ICM is ICM = /opt/icm CFLAGS = -g -I$(ICM)/include LIBS = -rpath $(ICM)/lib -L$(ICM)/lib -licm # Main make target for the sample server apecho: apecho.o $(CC) -o $@ $(EOBJS) $(LDOPTIONS) $(LOCAL_LIBRARIES) $(LIBS)
echo
serverThe simplest way of invoking our application is simply to run it on a Unix command line:
% apecho
However, our complete application is configurable, so that we can change
the name that we use when registering with the CS. So, we might
call our echo
server as follows:
% apecho -n MyEcho -L-
which would result in the name MyEcho@host
being
registered with the local CS and logging messages to
be displayed on stderr
.
We can test our application using the simple April
`pinger'
program listed below. This simply sends a message built from the command
line to the apecho
server, and waits for the reply:
program{ main(who,msg){ "echo: "++msg^0++" to "++who^0++"\n">>stdout; msg >> who; receive{ X ->> "response was "++string%%X++"\n" >> stdout } } } execute main;
The complete echo
application is listed in section Listing of echo
server.
Messages in the ICM are sent between agents and CSs in a structured
representation. The underlying message format is `self-parsing' and is
able to represent symbols, numbers, arbitrary strings, program code,
lists and tuples of objects. Each of these types of structures have
analogues in most programming languages: for example, the symbol type
can be represented in Prolog as an atom, in April
as a symbol, and in C
or Java as a string. The message format is even able to represent
circular or graph structured values.
The self-parsing nature of the message format is important as it allows
a great deal of flexibility: it is not necessary for the receiving agent
of a message to be able to predict accurately the complete structure of
a message prior to receiving it. Instead, the receiving agent can
consume the message, and then test it -- using the icmScanMsg
API
function -- see section icmScanMsg
-- to see if the message matches a given
pattern.
The representation of values is network neutral, and quite efficient. For certain common cases, the low-level representation is optimized to minimize the length of the encoded message structure.
In this section we show how each type of object is represented in the encoded message.
The low-level structure of a message consists of a sequence of bytes -- in network order. Each object is preceded by a tag byte, followed by zero-or-more data value bytes. Some structures are nested -- such as lists and tuples -- in which case the elements of the nested structure follow the lead tag information.
An integer is represented as the byte 0x1k
, where k is in
the range 0x1
...0xf
, followed by k bytes
defining the integer's value. The bytes making the value of the integer
are in network -- that is most significant byte first -- order. The
value is represented in standard 2's complement form.
For example, the integers 3
and 100,000
are represented by
the sequences:
0x11 0x03
and
0x13 0x01 0x86 0xa0
respectively. Note that the value 0 is represented using the sequence
0x10 0x00
.
If the tag code is 0x10
then the length of the integer is itself
expressed as an integer following the tag code, and the integer data itself
follows the length. This allows arbitrary sized integers.
The following C program shows how an integer can be converted into the appropriate stream of bytes:
unsigned char *icmEncodeInt(int tag,long val,unsigned char *buff) { int len=0; unsigned char bytes[16]; unsigned long v = val; if(v==0) /* special case for 0 */ bytes[len++]=0; else while(v>0){ bytes[len++]=v&0xff; v >>= 8; } *buff++=tag|len; /* 0x1n ... */ while(len-->0) *buff++=bytes[len]; return buff; }
Note this program does not allow for encoding large numbers.
A floating point number is represented using the tag byte
0x2n
, or 0x3n
depending on whether the
floating point number is positive or negative, followed by the exponent
of the number -- in unbaissed form -- encoded in the same format as an
integer and then n bytes of mantissae.
The following C program shows how a floating point number can be converted into the appropriate stream of bytes:
/* Convert a double into a string buffer */ unsigned char *icmEncodeFlt(double f, unsigned char *buff) { unsigned char bytes[16], *bf = bytes; int i,exp,len,sign=0x20; /* positive float */ if(f<0.0){ sign = 0x30; /* negative float */ f = -f; } f = frexp(f, &exp); for(len=0;f!=0.0;len++){ double ip; f = modf(f*256,&ip); *bf++ = (int)ip; } *buff++=sign|len; /* the lead character of the number */ buff = icmEncodeInt(exp,0x10,buff); /* the exponent */ for(i=0;i<len;i++) *buff++=bytes[i]; return buff; }
A symbol is represented by a leading 0x4k
byte,
where k is a number in the range 0x0
...0xf
followed by a length number N -- itself encoded in k bytes --
followed by N characters of the symbol's name.
For example, the symbol apple
is represented by the sequence:
0x41 0x05 0x61 0x70 0x70 0x6c 0x6e
A handle is represented using a lead 0x50
byte, followed by four
values -- the decorations on the handle, the name of the agent, its home
location and a list of `current' or actual locations. These values are either
of the form of a symbol or the nil tag -- (0x80
) -- in the case that
they are to be omitted from the handle.
For example, the handle:
foo:bar@home.com/[123.456.789.012, 127.0.0.1]
is represented in the encoding as:
0x50 0x41 0x03 0x66 0x6f 0x6f 0x41 0x03 0x62 0x61 0x72 0x41 0x08 0x68 0x6d 0x6e 0x2e 0x63 0x68 0x6d 0x81 0x0f 0x31 0x32 0x33 0x2e 0x34 0x35 0x36 0x2e 0x37 0x38 0x39 0x2e 0x30 0x31 0x32 0x81 0x31 0x32 0x37 0x2e 0x30 0x2e 0x30 0x2e 0x31 0x80
A string or uninterpreted data block contains bytes that are not
interpreted as having an internal structure. Their type is analogous to
a string in Java or April
.
A string is represented with a leading 0x6k
byte, where
k is a number in the range 0x0
...0xf
followed by
a length number N -- itself encoded in k bytes -- followed
by N bytes of data.
For example, the string: "apple
" might be encoded as an uninterpreted
byte sequence:
0x61 0x05 0x61 0x70 0x70 0x6c 0x6e
A program code is necessarily quite platform specific. In our representation of program codes, we assume that there is an internal method for verifying that a piece of program code is of the correct structure. From the point of view of the ICM message encoding, program codes are encoded in a similar manner to uninterpreted strings or uninterpreted data blocks.
A program code is represented with a leading 0x7k
byte,
where k is a number in the range 0x0
...0xf
followed by a length number N -- itself encoded in k bytes --
followed by N bytes of data.
A list is a sequence of values, intended to be held either in a Prolog,
LISP or April
style list, or as an array of values in languages such as
C, or Java.
Lists come in two `sizes': the empty list and the non-empty list. An
empty list is represented by the 0x80
byte -- with no data following it
-- and a non-empty list consists of the 0x81
byte, followed by the
encoding of the head of the list, followed by the encoding of the tail
of the list.
For example, the list of integer:
[1,2,3]
is encoded using the sequence:
0x81 0x11 0x01 0x81 0x11 0x02 0x81 0x11 0x03 0x80
A tuple is similar to a list except that the elements of the tuple will often be of different type. Tuples are analogous to struct records in C, object instance in Java, and -- in a particular form -- functors in Prolog.
A tuple is encoded using the lead byte 0x9k
-- where
k is a number in the range 0x0
...0xf
-- followed
by the length N of the tuple -- itself encoded using k bytes --
followed by the N elements of the tuple.
For example, the April
tuple
(fred, 23, [])
would be represented using the sequence:
0x91 0x03 0x41 0x04 0x66 0x72 0x65 0x64 0x11 0x17 0x80
Some structures must be represented using some kind of circular or graphical structure combination of lists and tuples. The ICM message encoding permits this through the use of tags and references. A tag is a marker that identifies a particular structure -- giving it a kind of label. A reference is a reference to a structure that is marked elsewhere in the message: the value denoted by a reference is the value marked by the corresponding tag.
With this, we can embed circular and arbitrary graphical structures in messages and reproduce their structure when the message is decoded.
Clearly, the exact manner in which circular and graphical structures are decoded depends on specific language platforms and data structure choices. In the case of Prolog, it is not legal to have a circular structure, but certain graph structures -- arising from the use of variable/variable bindings -- can be represented and decoded. In the case of C, while it is possible to represent circular structures, the lack of a standard C data type makes general decoding of circular messages difficult without reference to the application.
A tag is represented as the byte 0x9n
-- where n is
a number in the range 0x0
...0xf
-- followed by n
bytes which encode a tag number N; followed by the encoding of the
tagged structure itself.
A reference is represented using the byte 0xBn
-- where
n is a number in the range 0x0
...0xf
-- followed
by n bytes which encode a tag number N. The `value' of the encoded
structure is determined by the previously decoded tagged object for the
same N.
For example, the following circular structure:
L0: (foo, 23, R0)
where R0
is a reference to the complete tuple, can be encoded using:
0xA1 0x00 0x91 0x03 0x41 0x03 0x66 0x6f 0x6f 0x11 0x17 0xB1 0x00
A shorthand reference allows significant message compression by avoiding repetition of elements of the message. The basic form of a shorthand reference consists of a definition followed by a structure which may (or may not) embed a reference to the definiens.
A shorthard definition is encoded using the lead byte 0xCn
-- where n is a number in the range 0x0
...0xf
--
followed by a number N which is the key, followed by an
encoded term -- which is the definiens -- followed by a second encoded
term -- which is the `value' of the entire sequence.
For example, the structure
(foo, (bar, foo), foo)
would normally be represented using the sequence:
0x91 0x03 0x41 0x03 0x66 0x6f 0x6f 0x91 0x02 0x41 0x03 0x62 0x61 0x72 0x41 0x03 0x66 0x6f 0x6f 0x41 0x03 0x66 0x6f 0x6f
There are three references to the symbol foo
. Normally, each
reference would result in the symbol's name being represented in full;
however, using a shorthand reference we can instead represent the
structure using:
0xC1 0x00 0x41 0x03 0x66 0x6f 0x6f 0x91 0x03 0xB1 0x00 0x91 0x02 0x41 0x03 0x62 0x61 0x72 0xB1 0x00 0xB1 0x00
Note that references to the shorthand object are made using the same encoded sequence as references to values in a circular structure.
A type signed value or encapsulated value has a type signature
associated with it as well as the value. Encapsulated values allow a
greater security than un-adorned values since it is possible to be much
more specific about the type of a value. For example, an empty list can
-- in principle -- be consistent with any kind of list; however
embedding the empty list in an encapsulated structure would allow
programs to send an empty list of number
s (say) and have that be
represented differently to a list of handle
s (say).
An encapsulated value is encoded using the code 0xd0
, followed by
a string (itself represented using the uninterpreted data format)
representing the type signature of the value, and this is followed by
the value itself.
For example, an empty list of number can be encoded as:
0xd0 0x61 0x02 0x4c 0x4e 0x80
where 0xd0
introduces the encapsulated value, 0x61 0x02
introduces a string which will contain a the type signature as a two
byte string, 0x4c 0x4e
('LN')
signifies a number list and
0c80
is the empty list itself.
The format of type signatures is presented below.
An opaque value is a value which is wrapped by the application. From the point of view of the ICM, an opaque value has no internal structure; however, it does have an `owner'. The owner can be used by the intending applications (and other applications) to determine if the value `belongs to them'.
The message format of an opaque value does not mandate the form of either the owner or the data; however, the convention we recommend (and enforced in parts of the ICM API), is that the owner is in the form of a symbol and the data is in the form of an uninterpreted data block.
An opaque value is encoded using the code 0xE0
, followed by the
encoding of the owner and then the encoding of the data itself.
One application of opaque values is to embed ICM messages inside an ICM message, or to embed a message encoded in another encoding scheme.
A type signature is a string that describes a type. The type signature string follows a prefix format: a lead character denotes the top-level type (such as list or tuple) and this is optionally followed by type signatures of the elements of the list or tuple.
N
'N'
type signature denotes a number value. Note that no
distinction is made between integers or floating point values.
s
's'
type signature denotes a symbol value. Note that this is
different to the `literal signature' described below.
S
'S'
type signature denotes a string or uninterpreted data
value.
h
'h'
type signature denotes a handle value
l
'l'
type signature denotes a logical value. Legal values of
this type are the symbols false
and true
.
L
'L'
type signature denotes a list value. The type of the
elements of the list follows the L
character.
T
'T'
type signature denotes a tuple value. It is followed by a
byte giving the number of elements of the tuple and then the types of
the elements of the tuple in order.
|
'|'
type signature denotes an alternative type. It is
followed by two type signatures representing the either/or branches.
Note that it is not a good idea to use `predefined' types (such as
N
or Lh
in the branches of a alternative type. Instead the
arms of the alternatives should either be literal types or labelled
types.
#
'#'
type signature denotes a particular symbol. It is
followed by the symbol itself enclosed in delimiter characters (which
may be any character not itself included in the symbol.
For exmaple, the literal value false
is represented with the type
signature:
#'false'Literal types are mostly used in conjunction with alternate types.
R
'R'
type signature denotes a labelled type. It is followed by
the label -- which is itself enclosed in delimiter characters as with
the literal type -- and the labelled type itself.
For example, to denote a time
record where the labelled type is a
triple of numbers, we use:
R'time'T\3NNN
?
'?'
type signature denotes a fielded type. I.e., an elements
of a record. The ?
is followed by the delimited field name and
the type itself.
$
'$'
introduces a type variable. Where the type variable is
`free' in the type signature it means that the type is left unspecified
and may be any legal type. This is only valid for values such as the
empty list which are the same for all possible lists.
The '$'
character is followed by a byte giving an index which
represents the variable. Tyep variables with the same index in a type
signature represent the same variable.
.
'.'
character denotes a bound or fix-point type. The form of
a fixpoint type is:
X = type-expressionwhere type-expression may refer to
X
. The meaning of such a
type is the smallest type for which the equation holds -- otherwis
referred to as recursive types.
For example, a type for the tree
type:
tree ::= empty | node(tree,tree)would be represented as a type signature:
.\0|'empty'R'node'$\0$\0The byte following the
'.'
character is an index. If a type
variable of the same index appears in the following type expression then
the type variable refers to the `entire' type.
':'
character denotes a universally quantified type. A
universally quantified type refers to all possible instantiations of the
bound variable. The bound variable is represented by an index -- whcih
is represented in the byte following the ':'
character.
For example, to represent the type:
tree(%A) ::= empty | node(tree(%A),%A,tree(%A))we can use the type signature:
:\0.\1|#'empty'R'node'$\0$\1$\0note that the fixpoint variable and universally bound variable must be distinguished within the main type expression since they are both `in scope'. In practice, universally quantified type expressions do not apply to literal data values; however they may apply in the case of functions or procedures.
F
'F'
type signature denotes a function. It is followed by two
types: the type of the argument vector of the function and the type of
the returned value.
The ICM
system allows functions (and procedures) to be sent in
messages. However, the code format used to represent code fragments is
beyond the scope of the ICM itself.
P
'P'
type signature denotes a procedure. It is followed by
the type of the argument vector of the procedure.
A
'A'
type signature denotes an encapsulated (or any
)
value.
echo
serverThis program is the complete listing of a simple but functional server that uses the ICM's api.
/* * This is a simple `echo' server implemented as an example to demonstrate * The ICM API */ #include <stdio.h> #include <stdlib.h> #include <signal.h> #include "icm.h" #include "icmFile.h" char *name = "echo"; int portNo = 0; #define LOGFILE "echo.log" icmHandle ourHandle = NULL; icmConn conn=NULL; long delayTime = 0; /* delay before sending replies */ #ifdef ALLTRACE #ifndef COMMTRACE #define COMMTRACE #endif #endif #ifdef COMMTRACE extern icmTruth traceMsg; #endif void sig_int(int sig); void sig_fatal(int sig); static icmStatus HandleQuery(icmConn con,icmHandle recip,icmHandle snder, icmOption opts,icmMsg msg,void *cl); icmStatus pickOpt(char opt,char *optarg,void *cl) { switch(opt){ case 'P': portNo = atoi(optarg); return icmOk; case 'n': name = optarg; return icmOk; case 'd': delayTime = atoi(optarg); /* allows a delay between msg and response */ return icmOk; case 'D':{ /* Debug options */ char *d = optarg; while(*d!='\0') switch(*d++){ case 'm': /* Tracing TCP-level messages? */ #ifdef COMMTRACE traceMsg = icmYes; continue; #else fprintf(stderr,"Message tracing not enabled\n"); return icmError; #endif default: fprintf(stderr,"Unknown debug option [%c]\n",*(d-1)); return icmError; } return icmOk; } default: return icmError; } } int main(int argc, char **argv) { if(icmGetOptions(argc,argv,"n:d:P:L:D:",pickOpt,NULL)==-1){ if(icmLogFile!=NULL) icmLogMsg("Usage: %s [-n name] [-n delay] [-P port] [-L log]",argv[0]); else fprintf(stderr,"Usage: %s [-n name] [-n delay] [-P port] [-L log]",argv[0]); exit(1); } if(icmLogFile == NULL) icmInitLogfile(LOGFILE); /* Default log file */ ourHandle = icmParseHandle(name); if(icmInitComms(portNo,NULL,&conn)==icmOk){ signal(SIGINT, sig_int); /* set up termination routines */ signal(SIGBUS, sig_fatal); signal(SIGSEGV, sig_fatal); signal(SIGFPE, sig_fatal); /* register ourselves */ if(icmRegisterAgent(conn,ourHandle,NULL,&ourHandle)==icmOk){ icmLogMsg("Echo server %h started",ourHandle); icmEventLoop(conn,HandleQuery,NULL); } else icmLogMsg("Failed to register %h",ourHandle); } else icmLogMsg("Failed to initialize ICM API"); return 0; } static icmStatus HandleQuery(icmConn con,icmHandle recip, icmHandle snder,icmOption opts,icmMsg msg,void *cl) { icmHandle replyto = snder; icmIsOption(opts,icmReplyto,&replyto); icmLogMsg("Message `%M' from %h",msg,snder); if(icmScanMsg(msg,"quit")==icmOk){ icmTerminateEventLoop(conn); icmFmtSendMsg(conn,replyto,ourHandle,NULL,"%(ok%,quit%)"); } else{ sleep(delayTime); icmFmtSendMsg(conn,replyto,ourHandle,NULL,"%(echo%#%)",msg); } return icmOk; } /* * cleanup after CTRL-C and other major errors */ void sig_int(int sig) { icmLogMsg("Control-C exit"); icmDeregisterAgent(conn,ourHandle); icmCloseComms(conn); exit(0); } void sig_fatal(int sig) { icmLogMsg("bus error or segmentation fault"); icmDeregisterAgent(conn,ourHandle); icmCloseComms(conn); exit(0); }
Jump to: a - b - c - d - e - f - g - h - i - l - m - o - p - r - s - t - w
handle
s
echo
main loop, echo
main loop
ICM
communications libraries
icmPoolBase
-- opaque pool base pointer type
ICM
API
echo
server
select
system call
echo
cleanly
echo
server
Jump to: i
icmInitPool
API function
This document was generated on 10 October 2002 using texi2html 1.56k.