Back
The Networking package includes a set of features that enable peer-to-peer (P-P) applications to be created
without much difficulty.
To visualize the way P-P applications work, think of Services and Clients. A Service
is an code module that performs tasks under the control of, or of interest to, another code module. A Client is
any code module that requires access to a service, either for information or to request actions. For more information
on the internals of the P-P system implemented for Linguist, see Networker.
As a simple example, imaging that one of your computers has access to an accurate clock, such as from a GPS receiver.
Rather than trust the internal clocks of all the other computers on your network, you can arrange for each to take
the time from this single master and stay in sync. I'll use this example to show how the scripts are built.
First, it should be noted that it makes no difference where services and clients are run. They can be all on the
same computer or distributed around the network, whichever is more appropriate. Even if two or more modules reside
on the same computer it may still be better to structure them as services and clients, because it leaves you the
freedom to move one or more of them at a later date without a major code rewrite. The P-P framework built into
Linguist makes no distinction between modules running locally and those on networked computers as long as you follow
some simple rules.
Creating a service
We start with the code for a clock service:
service ClockService message ClientMessage buffer Request variable GotClient create ClockService name "System Clock Service" type "Clock" set the source of ClientMessage to ClockService on ask ClockService begin put the text of the message into Request if Request is "?" reply the hour cat " " cat the minute cat " " cat the second end on tell ClockService begin put the text of the message into Request if Request is "REG" begin set the destination of ClientMessage to the sender set GotClient end end add ClockService
This starts by creating a variable of type service
and another of type message. The service is created
with a name and type; both of these are strings. The name should be unique, at least on this computer and preferably
on the entire network if you want to avoid mysterious faults later. The type is any string that identifies the
kind of service being offered; several different module types can have the same type code but all must understand
the same messages from their clients. More of this later.
The message variable holds information about
the source and destination of a network message plus the text of the message.
There are two main functions associated with a service; ask
and tell. The former is invoked when a client
asks the service something and expects a reply. As you can see from the code, the service simply sends the current
time to the caller as a reply to the message. The second function, tell,
is used where the client doesn't want a reply. In this example we check to see if the message was REG and take it to mean this client wishes to register with
us for regular time updates. In practice you'd need better code than this in order to service more than one client
simultaneously.
[Note: The client will always know if a message has been delivered, even when using tell, so there's no need to use ask
just for that purpose. There's a command that will tell you if the message didn't get delivered.]
Finally, we add our new service to the system. This is an essential step; until it is done nothing will happen.
Creating a client
The code for a clock client looks like this:
client ClockClient message ClockMessage buffer TheTime variable TheHour variable TheMinute variable TheSecond create ClockClient name "Clock Display" type "Clock" set the source of ClockMessage to ClockClient on notify ClockClient begin set the destination of ClockMessage to service 0 of ClockClient send ClockMessage text "REG" end on tell ClockClient begin put the text of the message into TheTime put the value of the first word of TheTime into TheHour put the value of the second word of TheTime into TheMinute put the value of the third word of TheTime into TheSecond end add ClockClient
The clock client starts in a similar way to the service, by providing a name and type code. Because the type
code is the same as for our service, the framework will tell the client about any Clock service that comes online.
(It also tells the client when the clock isn't there, if you want to make use of that information.) It makes no
difference whether the service or the client starts first, either way the client will receive a notification.
When a notification arrives we register with the service offered. If more than one service is available you may
want to choose one according to some rule or other, or you may simply collect information about all of them and
save it for later use. Having selected a service we then register with it. The service code above isn't really
complete; in a real example it would then start sending the time at regular intervals.
When the service sends the time to the client the on
tell callback is invoked. The example simply
extracts the time from the message and stores it for use elsewhere.
Like services, clients also need to be added to the framework.
Sending messages
To send a message, use a message variable and
set the source and destination of the message as in the above examples. You can also set the destination of a message
using the name and address of the target, as in
set the destination of ClientMessage to "ClockClient1" "192.168.0.162"
Having done that you can send messages using the variable. There are two ways:
set the text of ClientMessage to "This is my message" send ClientMessage
or
send ClientMessage text "This is my message"
To ask a question requiring a reply, use
set the text of ClientMessage to "This is my question" put ask ClientMessage into Reply
where Reply is a buffer. See the first example above (in the description of a service) for the
use of the reply keyword to send a reply back
to the caller.
Once a client and service are in contact with each other, either can send a message to the other. It's up to you
to ensure your code only sends messages that will be understood by their recipients.
Detecting errors
When you send a message, the framework monitors its progress for you. Use the following code to check if it was
delivered:
send ClientMessage if ClientMessage has error begin prompt "SystemClockService: Message failure" clear GotClient end
Here we assume the client has crashed so we deregister him. Depending on the reliability of the network this
may not be the best approach. One useful technique is for the client to regularly poll the service with a simple
(or blank) message; if it fails the client can look for another service or wait for the old one to return.
Handling multiple services or clients
When you need to select from more than one service you have the problem that not all will appear at once. Notifications
to the client are sent whenever the state of the system changes, so be prepared to receive many notifications.
Each notification may announce a different (or the same) set of services depending on how the system is powering
up. Don't invoke a thread that can't afford to be run over and over again. A good technique is to use on notify
to maintain a table of services, and to select one at a different point in the program. Here's an example:
hashtable Services buffer ServiceName buffer ServiceAddress variable N if MyClient has services begin put 0 into N while N is less than the service count of MyClient begin put the name of service N of MyClient into ServiceName put the address of service N of MyClient into ServiceAddress put ServiceAddress into Services as ServiceName increment N end end
Here we have a hashtable variable into which we put the name and address of each service as it appears. Multiple
notifications present no problem because they merely cause an existing entry to be overwritten. Or you may prefer
to clear the table before adding new entries. Services can use a similar technique to build a table of registered
clients.
At some point in the program you may want to iterate through your table of services or clients. Maybe you want
to create a pick list for the user to select one, or maybe you need to send the same message to all registered
clients. To do this, use a table iterator as follows:
hashtable Clients buffer ClientName buffer ClientAddress message ClientMessage reset Clients while Clients has more data begin put Clients into ClientName get ClientAddress from Clients as ClientName set the destination of ClientMessage to ClientName ClientAddress send ClientMessage text "shutdown" end
This code illustrates how to iterate a hashtable and how to reconstruct a message from the data found in it. Here we've just told all our registered clients to shut down (whatever that might mean).