Programming with libsockhop.so (the
SockHop shared library)
Overview
Virtual network architecture
Format of a typical SockHop program
Example program source
Compiling SockHop programs
Writing your own SHWorker subclasses
Writing your own SHSorter subclasses
Overview: The environment under SockHop
SockHop is a software kit designed to allow BeOS programs to run easily
on many (many!) machines at once. It is designed mainly for controlled
environments where the programmer has access to and control over one or
more BeOS machines, and knows in advance which machines will be used to
run the parallel program. Using SockHop, the programmer can write
a program on a single BeOS machine, but test and run it on every machine
in the lab, without ever needing to leave his or her chair. SockHop
handles all the network communication, message routing, code propagation,
and so on, leaving the programmer free to concentrate on the actual application.
SockHop's Virtual Network Architecture
In a SockHop program, the "SockHop Virtual Machine" takes the form of
a tree of nodes. For many applications, the tree need not be more
than one or two levels deep, but theoretically the tree could grow to any
size or depth.
Here are some important qualities of the tree:
-
Each node in the tree is given a name, and is addressable via a "path",
much like a node in a hierarchical filesystem. The root node's name
is always "/", but the names of all the other nodes are assigned by the
programmer.
-
The root node of the tree always lives as a thread in the same team as
the BeOS program that created it. Every other node in the tree can
(optionally) live in its own team, or even on its own computer. Or,
it can run as a thread inside the same team as its parent node. Whatever
configuration is chosen, it's totally transparent to user code.
-
Each node may contain one or more SHWorkers.
SHWorkers are really just glorified BLoopers,
and operate in a similar way. The main difference is that SHWorkers
are subclasses of SHDistributableObject,
and thus can be Archive()'d and automatically migrated (executable code
and all) to run on other computers in the tree.
-
Each node also contains one or more SHSorters,
which control how incoming BMessages are routed. When a new node
is created, it is automatically given an SHWi1dPathSorter,
which allows BMessages to be routed using regular expressions and pathnames.
For most applications, this will be the only SHSorter
you need. If you want to get fancy, however, you can define
your own SHSorter subclasses, and thus your
own BMessage-routing protocol. You can specify, for each BMessage
you send, which SHSorter should be used to route
it.
Format of a typical SockHop program.
A typical SockHop program will do the following things:
-
Create or designate a BLooper that SockHop will send its reply BMessages
to.
-
Create the root node of a virtual network by calling SHCreateRootNode().
-
Post BMessages to the root node telling it to create
some child nodes (and possibly after that post messages to some of
the child nodes telling them to create grandchild nodes, and so on)
-
Instantiate some objects of one or more SHWorkers
subclasses, Archive() them, and send them to various nodes.
-
Post BMessages to control the behavior of the SHWorkers,
and receive BMessages from the SHWorkers that
indicate the results.
-
When all is done, call Lock() and Quit() on the root node (since the root
node is a BLooper), and exit. Destroying the root node will cause the rest
of the tree to disappear as well.
A simple example
Here is an example of a simple, do-almost-nothing SockHop program.
It assumes the following conditions are true:
-
There exist computers named beos1.sockhop.com, beos2.sockhop.com, and beos3.sockhop.com,
on the network, and that they are all running BeOS, and that they all have
a SockHop server running on port 2958
(the default SockHop port)
-
An add-on file containing an SHWorker subclass named SHTestWorker has been
placed in the correct location(s) relative to this program's current directory
(the actual place it needs to be is up to the add-on code, but is often
something like "./add-ons/x86/SHTestWorker" for the Intel flavor, and "./add-ons/ppc/SHTestWorker"
for the PowerPC flavor)
-
This program needs to link against the SHTestWorker add-on, in order to
construct an SHTestWorker object to Archive() and send. Thus, the
SHTestWorker add-on must be in a directory specified by the LIBRARY_PATH
variable (otherwise, this program won't execute at all!)
/*----------- begin sample code ----------- */
#include <sockhop/SockHop.h>
#include "SHTestWorker.h"
// We need a BLooper to receive the BMessages that SockHop
// sends back to us. This one will just print out any
// BMessage it receives.
class DebugLooper : public BLooper
{
private:
virtual void MessageReceived(BMessage * msg)
{
printf("DebugLooper: Received
this message from SockHop:\n");
msg->PrintToStream();
}
};
int main(int argc, char ** argv)
{
// Create a target BLooper to receive reply
BMessages
DebugLooper * debugLooper = new DebugLooper;
debugLooper->Run();
// Create the root node ("/") of the SockHop
tree
BLooper * root = SHCreateRootNode(BMessenger(debugLooper));
if (root == NULL)
{
printf("Error, couldn't create root
node!\n");
exit(5);
}
root->Run();
// Tell the root node to add our three computers
as children.
BMessage createKids(SH_COMMAND_ADDCOMPONENTS);
createKids.AddFlat(SH_NAME_CHILDREN, &SHNodeSpec("node-A",
"beos1.sockhop.com"));
createKids.AddFlat(SH_NAME_CHILDREN, &SHNodeSpec("node-B",
"beos2.sockhop.com"));
createKids.AddFlat(SH_NAME_CHILDREN, &SHNodeSpec("node-C",
"beos3.sockhop.com"));
createKids.AddString(SH_NAME_TO, "/");
root->PostMessage(&createKids);
// Create an SHTestWorker, and then capture its
essence into a BMessage.
// SHWorkers (and hence SHTestWorkers), unlike
real BLooper subclasses, can be
// safely created on the stack.
SHTestWorker testWorker("I'm a test worker!");
BMessage testWorkerMsg;
testWorker.Archive(&testWorkerMsg);
// Now send a copy of the SHTestWorker to all
three child nodes.
// Note how wildcarding is used to specify all
three children's
// node paths ("/node-A", "/node-B", and "/node-C")
at once.
BMessage sendWorker(SH_COMMAND_ADDCOMPONENTS);
sendWorker.AddMessage(SH_NAME_COMPONENTS, &sendWorker);
sendWorker.AddString(SH_NAME_TO, "/node-*");
root->PostMessage(&sendWorker);
// Now we will send a BMessage to the SHTestWorkers,
just
// for fun. How they interpret it is up
to them.
BMessage msg('helo');
msg.AddString(SH_NAME_TO, "/node-*");
root->PostMessage(&sendWorker);
// Lastly, we shut down our root node and debugLooper.
// Note that shutting down the root node will
cause
// the three remote nodes to automatically shut
down as well.
if (root->Lock()) root->Quit();
if (debugLooper->Lock()) debugLooper->Quit();
return 0;
}
/* --------- end sample code ------------ */
Tips for compiling SockHop programs
When compiling SockHop programs, here are some useful facts to keep
in mind:
-
SockHop executables should link against libsockhop.so, and also against
any SockHop add-ons that define SHWorker or
SHSorter subclasses that they wish to instantiate
and propagate across the virtual network. (Note: this isn't
quite true--you can use SHCreateDistributableObject()
to dynamically link with add-ons at run time, instead) It is recommended
that add-on files that are to be propagated to other computers be stored
in a subdirectory of the current directory of the initiating SockHop program,
using a relative path such as "adds-ons/x86/AddOnName" (referencing add-on
files with absolute pathnames will work, may cause conflicts with other
programs). The exact required location of the add-on file at run-time
is up to the writer of the add-on, though. The caching mechanism
assumes that all necessary files can be found on the root node's filesystem.
-
The only file you need to #include in your programs is <sockhop/SockHop.h>.
This file pulls in everything that is available in the SockHop API.
-
The "meaty" parts of your code should be written as SHWorker subclass implementations,
because SHWorkers can be propagated to other
machines easily. Your main program will probably just control startup
and shutdown.
-
If you have more than one SHWorker or SHSorter
subclass defined for your project, it is legal to include all of them in
the same add-on file or shared library for convenience. Be sure to
put "#pragma export on ... #pragma export reset" (for PPC) or "_EXPORT"
(for Intel) tags around the class declarations in your add-on's header
files!
-
While SockHop's application signature is available in <sockhop/SockHop.h>,
it is not necessary to set this attribute in your add-on files for SockHop
to work properly. This is because SockHop uses its own mechanism
(the results of the SHDistributableObject::GetAddOnSpec() call) to locate
add-on files by pathname, instead of relying on instantiate_object() to
find them.
Writing your own SHWorker subclasses
Writing SHWorker subclasses is something
every SockHop programmer has to do in order to create code that can be
automatically propagated to multiple machines. Fortunately, SHWorkers
are very similar in concept to BLoopers, so you probably already mostly
know how they work. The rest of what you need to do to implement
an SHWorker can be found by reading the SHWorker
class documentation, and by remembering the tips below:
-
To be propagatable, your SHWorkers need to be
defined in and exported by an add-on or shared library. You may define
multiple SHWorker subclasses in the same add-on,
so for many programs, only one add-on file will be required. Just
set CodeWarrior to generate a "Shared Library", and put _EXPORT tags or
#pragma export ... #pragma reset around your SHWorker
subclass declarations, and you're ready to go!
-
If you define the IsInterested() or GetName() methods for your SHWorker,
be aware that they may be called at any time, from another thread.
You may have to implement some kind of synchronization inside these methods
if the data they access ever changes. Also, you must call the SHWorker::Stop()
method as the first line in your subclass's destructor--otherwise, your
methods might get called after your destructor has freed the data they
access! This could cause a crash.
-
When defining your implementations of the Archive() method and the constructor
that takes a (BMessage *), be sure to call the corresponding methods of
the parent class. If you don't, your SHWorker
objects won't get archived properly. The compiler doesn't give warning
messages about this, so be careful!
Writing your own SHSorter subclasses
Writing an SHSorter subclass is something
that most SockHop programs won't need to do--IMHO, the included SHWi1dPathSorter
class will be sufficient for 99% of the apps that might be written for
SockHop. However, custom message routing can be handy sometimes,
so SockHop lets you write your own BMessage routing algorithms by creating
SHSorter subclasses and distributing your own
custom SHSorter objects to the nodes in your
tree. To find out more about how to do this, read the SHSorter
class documentation, and remember the following tips:
-
SHSorters (unlike SHWorkers)
are "dead" objects that don't have their own thread; they run in
the node's message forwarding thread. So if your SHSorter
is inefficient, it could slow down the entire node!
-
It's possible when making a custom SHSorter
to create routing algorithms that route BMessages in endless loops, or
even create "exponential BMessage explosions" that could eat all of your
systems' virtual memory very quickly! So be careful, and be especially
careful about under what circumstances you route BMessages back up the
tree...
-
If you are going to use your custom SHSorter
subclass objects for most or all of the routing, you can send out an SH_COMMAND_SETPARAMETERS
BMessage to all the nodes in your tree that sets the default SHSorter
to be your sorter. This will save you the trouble of having to explicitely
specify your SHSorter by name in every BMessage
you send.