Back


Peer-to-peer Networking

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).

Return to top


Back