FreePastry/html/rawtutorial/tut_scribe.html
ChronosX88 aaa73fe8b3 Pew
2019-05-13 16:45:05 +04:00

411 lines
22 KiB
HTML

<html><head>
<title>FreePastry Tutorial</title>
<link rel="stylesheet" href="tutorial.css" />
</head>
<body>
<div class="content">
<div class="frontmatter">
<h1>The FreePastry Tutorial.</h1>
<div class="abstract">This tutorial is designed to get you cooking quickly with the FreePastry
API and software toolkit.</div>
<h4>Version @tutorial_version@; @tutorial_date@. For <a
href="http://freepastry.org/">FreePastry</a> version @freepastry_version@. Maintained by @maintainer@.</h4>
</div>
<div class="nav">
<span class="nav-left"><a href="tut_timertask.html#timer">Previous (Timer)</a></span>
<span class="nav-center"><a href="index.html">Contents</a></span>
<span class="nav-right"><a href="tut_past.html#past">Next (Past)</a></span>
</div><br/><hr/>
<a name="scribe"><h1>Scribe</h1></a>
<h2>Introducing Scribe.</h2>
<h3>Download the tutorial files:
<a href="./src/scribe/MyScribeClient.java">MyScribeClient.java</a>,
<a href="./src/scribe/MyScribeContent.java">MyScribeContent.java</a>,
<a href="./src/scribe/ScribeTutorial.java">ScribeTutorial.java</a> into a directory called rice/tutorial/scribe/.</h3>
<p/>Scribe is an application that allows you to subscribe to groups and publish messages to that group. This tutorial will show you how to get scribe up and running. You will learn how to do the following:
<ul>
<li><a href="#l6Topic">Create a topic.</a></li>
<li><a href="#l6Client">Create a ScribeClient.</a></li>
<li><a href="#l6Subscribe">Subscribe to a topic.</a></li>
<li><a href="#l6Publish">Publish (Multicast) content.</a></li>
<li><a href="#l6Receive">Receive content.</a></li>
<li><a href="#l6Anycast">Anycast content.</a></li>
<li><a href="#l6Tree">Introspect into the tree.</a></li>
<li>Advanced Scribe Lesson: Policies, will show you how to control tree formation and anycast selection.</li>
</ul>
<h2>Terms:</h2>
<ul>
<li><b>Scribe</b>&mdash;A scalable group communication system for topic-based publish-subscribe applications.<i>Scribe builds an efficient multicast tree for dissemination of events to a topic.</i></li>
<li><b>Topic</b>&mdash;Group. <i>A topic builds a hash of the group name which is used as a unique identifier for the topic, as well used as a rendezvous point in Pastry.</i></li>
<li><b>IdFactory</b>&mdash;A hash function. <i>An IdFactory implements a hash function to build Ids that are compatible with pastry.</i></li>
<li><b>PastryIdFactory</b>&mdash;A commonly used IdFactory in Pastry. <i>The PastryIdFactory uses SHA1 as it's underlieing hash function.</i></li>
<li><b>ScribeContent</b>&mdash;A scribe message.</li>
<li><b>ScribeClient</b>&mdash;An application that receives ScribeContent. <i>The client can subscribe to one or more topics.</i></li>
<li><b>Multicast</b>&mdash;A broadcast received by everyone subscribed to the corresponding topic.</i></li>
<li><b>Anycast</b>&mdash;A message that is received by a single node in a group. <i>Anycast is most commonly used to find a single available service provider. The anycast message will be rejected until it finds a node willing to supply the service that is being requested.</i></li>
<li><b>ScribePolicy</b>&mdash;A policy to determine application specific details of Scribe. <i>This includes formation and anycast selection.</i></li>
</ul>
<a name="l6Topic"></a><h3>Creating a topic.</h3>
This is fairly straightforward, but each node will have to do this for each topic of interest.
<pre>
Topic myTopic = new Topic(new PastryIdFactory(), "example topic");
</pre>
This constructs a topic with the "common name" of "example topic". It uses the PastryIdFactory to generate an
appropriate Id for this topic.
<a name="l6Client"></a><h3>Creating a ScribeClient.</h3>
Let's take a look at MyScribeClient. We are only going to subscribe to a single topic. We are going to publish content every 5 seconds using the FreePastry timer. See <a href="tut_timertask.html#timer">Timer</a> for more details on the timer. The client is also going to implement rice.p2p.commonapi.Application in addition to rice.p2p.scribe.ScribeClient. This will allow us to send and receive non-scribe messages should this be important. Specifically it will allow us to receive messages queued on the timer.<br/><br/>
Here's the constructor and some member variables:
<pre>
Scribe myScribe;
Topic myTopic;
protected Endpoint endpoint;
public MyScribeClient(PastryNode node) {
// you should recognize this from lesson 3
this.endpoint = node.buildEndpoint(this, "myinstance");
// construct Scribe
myScribe = new ScribeImpl(node,"myScribeInstance");
// construct the topic
myTopic = new Topic(new PastryIdFactory(node.getEnvironment()), "example topic");
System.out.println("myTopic = "+myTopic);
// now we can receive messages
endpoint.register();
}
</pre>
The only thing that should be new here is the construction of the ScribeImpl and Topic. The instance name "myScribeInstance" allows you to remain independent of other applications running on the
same ring who also use scribe. They will have their own instance of Scribe that won't be confused with your instance.<br/>
In this example, one of the nodes is going to publish content every 5 seconds. We use the timer pattern described in the Timer tutorial. Each time we are going to send 1
multicast, and 1 anycast.
<pre>
class PublishContent implements Message {
public int getPriority() {
return 0;
}
}
public void startPublishTask() {
publishTask = endpoint.scheduleMessage(new PublishContent(), 5000, 5000);
}
public void deliver(Id id, Message message) {
if (message instanceof PublishContent) {
sendMulticast();
sendAnycast();
}
}
</pre>
In case you forgot, the <code>PublishContent</code> is similar to the Timer tutorial's <code>MessageToSelf</code>.
The <code>startPublishTask()</code> method schedules this to be delivered locally every 5 seconds.
The <code>deliver(id,message)</code> method calls <code>sendMulticast()</code> when the <code>PublishMethod</code> is received.
In your application you will likely have some other event that causes content to be published.
We will look at <code>sendMulticast()</code> and <code>sendAnycast()</code> shortly.
<a name="l6Subscribe"></a><h3>Subscribing to a group.</h3>
Subscribing is very easy. Just call <code>Scribe.subscribe()</code> and provide the topic, and your client.
<pre>
public void subscribe() {
myScribe.subscribe(myTopic, this);
}
</pre>
<a name="l6Publish"></a><h3>Multicasting content.</h3>
First, we need some content to send. MyScribeContent implements ScribeContent and takes
a NodeHandle sender, and an int sequence number. These are just so the output of the
program is more interesting.
<pre>
public class MyScribeContent implements ScribeContent {
NodeHandle from;
int seq;
public MyScribeContent(NodeHandle from, int seq) {
this.from = from;
this.seq = seq;
}
public String toString() {
return "MyScribeContent #"+seq+" from "+from;
}
}
</pre>
To send the content, simply construct the message, then call Scribe.publish(). You give it
the topic and the message. The rest of this function is just to print output and update the
sequence number.
<pre>
public void sendMulticast() {
System.out.println("Node "+endpoint.getLocalNodeHandle()+" broadcasting "+seqNum);
MyScribeContent myMessage = new MyScribeContent(endpoint.getLocalNodeHandle(), seqNum);
myScribe.publish(myTopic, myMessage);
seqNum++;
}
</pre>
<a name="l6Receive"></a><h3>Receiving content.</h3>
Receiving content is as easy as any other p2p application. The method signature is only
slightly different:
<pre>
public void deliver(Topic topic, ScribeContent content) {
System.out.println("MyScribeClient.deliver("+topic+","+content+")");
}
</pre>
All we are doing here is printing output to stdout.
<a name="l6Anycast"></a><h3>Anycasting.</h3>
Anycast will get called on your clients until one returns true. This occurs on
a call to <code>ScribeClient.anycast()</code>. To make this interesting, we're going to only accept the message
1/3 of the time, randomly. Your application will ususally want to do something more interesting, such as see
if a requested resource is available.<br/>
Here is <code>sendAnycast()</code> which is nearly identical to <code>sendMulticast()</code> except for the call to
<code>Scribe.anycast()</code> instead of <code>Scribe.publish()</code>.
<pre>
public void sendAnycast() {
System.out.println("Node "+endpoint.getLocalNodeHandle()+" anycasting "+seqNum);
MyScribeContent myMessage = new MyScribeContent(endpoint.getLocalNodeHandle(), seqNum);
myScribe.anycast(myTopic, myMessage);
seqNum++;
}
</pre>
Here is the code that only accepts the anycast 1/3 of the time. This will allow us to see
that the anycast message can be rejected and sent elsewhere.
<pre>
public boolean anycast(Topic topic, ScribeContent content) {
boolean returnValue = rng.nextInt(3) == 0;
System.out.println("MyScribeClient.anycast("+topic+","+content+"):"+returnValue);
return returnValue;
}
</pre>
<a name="l6Tree"></a><h3>Examining the tree.</h3>
Lastly we have code to introspect the tree. Note that it is only easy to print out the
entire tree because we are running all of the nodes in the same VM and have global
information. It is significantly more difficult to print the scribe tree in an actual
distributed environment, as scribe does not provide this information automatically. Furthermore
this information could change rapidly as nodes join and leave.<br/><br/>
Because we are only going to run the application with a small number of nodes, the tree
will most likely be only 1 level deep. (The root being level 0). However, this will show you how to print out your parent
and children.<br/><br/>
The first thing to note is that we have 3 accessor methods at the bottom of <code>MyScribeContent</code>:
<pre>
public boolean isRoot() {
return myScribe.isRoot(myTopic);
}
public NodeHandle getParent() {
return myScribe.getParent(myTopic);
}
public NodeHandle[] getChildren() {
return myScribe.getChildren(myTopic);
}
</pre>
Note that these simply call through to the same method on myScribe, with the correct topic.<br/><br/>
This code can be found in <code>ScribeTutorial.java</code><br/><br/>
<code>printTree()</code> does the following:
<ol>
<li>Create a table mapping <code>NodeHandle</code> to <code>MyScribeClient</code>.</li>
<li>Recursively traverse the tree to the root, using the helper: <code>getRoot()</code>.</li>
<li>Recursively traverse the tree down from the root, depth first and print the nodes using the helper: <code>recursivelyPrintChildren()</code>.</li>
</ol>
<pre>
public static void printTree(Vector apps) {
// build a hashtable of the apps, keyed by nodehandle
Hashtable appTable = new Hashtable();
Iterator i = apps.iterator();
while (i.hasNext()) {
MyScribeClient app = (MyScribeClient)i.next();
appTable.put(app.endpoint.getLocalNodeHandle(), app);
}
NodeHandle seed = ((MyScribeClient)apps.get(0)).endpoint.getLocalNodeHandle();
// get the root
NodeHandle root = getRoot(seed, appTable);
// print the tree from the root down
recursivelyPrintChildren(root, 0, appTable);
}
</pre>
<ol>
<li><code>getRoot()</code> looks up the client for the seed handle from the appTable.</li>
<li>If the seed is the root, it is returned.</li>
<li>Otherwise, it calls <code>getRoot()</code> on the parent.</li>
</ol>
<pre>
public static NodeHandle getRoot(NodeHandle seed, Hashtable appTable) {
MyScribeClient app = (MyScribeClient)appTable.get(seed);
if (app.isRoot()) return seed;
NodeHandle nextSeed = app.getParent();
return getRoot(nextSeed, appTable);
}
</pre>
<ol>
<li><code>recursivelyPrintChildren()</code> prints the curNode with appropriate whitespace based on the depth in the tree.</li>
<li>Then calls recursivelyPrintChildren() on all children (if it has any)</li>
</ol>
<pre>
public static void recursivelyPrintChildren(NodeHandle curNode, int recursionDepth, Hashtable appTable) {
// print self at appropriate tab level
String s = "";
for (int numTabs = 0; numTabs &lt; recursionDepth; numTabs++) {
s+=" ";
}
s+=curNode.getId().toString();
System.out.println(s);
// recursively print all children
MyScribeClient app = (MyScribeClient)appTable.get(curNode);
NodeHandle[] children = app.getChildren();
for (int curChild = 0; curChild &lt; children.length; curChild++) {
recursivelyPrintChildren(children[curChild], recursionDepth+1, appTable);
}
}
</pre>
<h3>Initializing the apps.</h3>
The majority of the ScribeTutorial is identical to Lesson 4 where we ran multiple nodes within the same JVM. The last part
is listed below. On each app, we call <code>subscribe()</code>, and on the first one we call <code>startPublishTask()</code>.
After that we wait a few seconds then print the tree.
<pre>
Iterator i = apps.iterator();
MyScribeClient app = (MyScribeClient)i.next();
app.subscribe();
app.startPublishTask();
while(i.hasNext()) {
app = (MyScribeClient)i.next();
app.subscribe();
}
env.getTimeSource().sleep(3000);
printTree(apps);
</pre>
<h3>Execution.</h3>
The parameters are identical to those in Lesson 4:
<ol>
<li>Local bind port.</li>
<li>Bootstrap host. (the local host address)</li>
<li>Bootstrap port. (usually whatever you passed in the first arg)</li>
<li>The number of nodes to launch.</li>
</ol>
Your output will resemble:
<pre>
<span class="input">java -cp .:FreePastry-@freepastry_version@.jar rice.tutorial.scribe.ScribeTutorial 9001 10.9.8.7 9001 10</span>
<span class="output">:1122932166578:Error connecting to address /10.9.8.7:9001: java.net.ConnectException: Connection refused: no further information
:1122932166578:No bootstrap node provided, starting a new ring...
Finished creating new node SocketNodeHandle (&lt;0x489BCB..&gt;/FOO/10.9.8.7:9001 [3776408266594462189])
myTopic = [TOPIC &lt;0x19A8F5..&gt;]
Finished creating new node SocketNodeHandle (&lt;0x8904D9..&gt;/FOO/10.9.8.7:9002 [4410881318286179245])
myTopic = [TOPIC &lt;0x19A8F5..&gt;]
Finished creating new node SocketNodeHandle (&lt;0x281817..&gt;/FOO/10.9.8.7:9003 [1144941711194723161])
...
&lt;0x281817..&gt;
&lt;0x8904D9..&gt;
&lt;0x061BB8..&gt;
&lt;0x85DA64..&gt;
&lt;0x489BCB..&gt;
&lt;0x0A9FCC..&gt;
&lt;0x39CE29..&gt;
&lt;0xA20DF8..&gt;
&lt;0x7D350E..&gt;
&lt;0xCF76F1..&gt;
Node [SNH: &lt;0x489BCB..&gt; -&gt; &lt;0x489BCB..&gt;/FOO/10.9.8.7:9001 [3776408266594462189]] broadcasting 0
Node [SNH: &lt;0x489BCB..&gt; -&gt; &lt;0x489BCB..&gt;/FOO/10.9.8.7:9001 [3776408266594462189]] anycasting 1
MyScribeClient.anycast([TOPIC &lt;0x19A8F5..&gt;],MyScribeContent #1 from [SNH: &lt;0x489BCB..&gt; -&gt; &lt;0x489BCB..&gt;/FOO/10.9.8.7:9001 [3776408266594462189]]):false
MyScribeClient.deliver([TOPIC &lt;0x19A8F5..&gt;],MyScribeContent #0 from [SNH: &lt;0x281817..&gt; -&gt; &lt;0x489BCB..&gt;/FOO/10.9.8.7:9001 [3776408266594462189]])
MyScribeClient.anycast([TOPIC &lt;0x19A8F5..&gt;],MyScribeContent #1 from [SNH: &lt;0x281817..&gt; -&gt; &lt;0x489BCB..&gt;/FOO/10.9.8.7:9001 [3776408266594462189]]):true
MyScribeClient.deliver([TOPIC &lt;0x19A8F5..&gt;],MyScribeContent #0 from [SNH: &lt;0x8904D9..&gt; -&gt; &lt;0x489BCB..&gt;/FOO/10.9.8.7:9001 [3776408266594462189]])
MyScribeClient.deliver([TOPIC &lt;0x19A8F5..&gt;],MyScribeContent #0 from [SNH: &lt;0xA20DF8..&gt; -&gt; &lt;0x489BCB..&gt;/FOO/10.9.8.7:9001 [3776408266594462189]])
MyScribeClient.deliver([TOPIC &lt;0x19A8F5..&gt;],MyScribeContent #0 from [SNH: &lt;0xCF76F1..&gt; -&gt; &lt;0x489BCB..&gt;/FOO/10.9.8.7:9001 [3776408266594462189]])
MyScribeClient.deliver([TOPIC &lt;0x19A8F5..&gt;],MyScribeContent #0 from [SNH: &lt;0x39CE29..&gt; -&gt; &lt;0x489BCB..&gt;/FOO/10.9.8.7:9001 [3776408266594462189]])
MyScribeClient.deliver([TOPIC &lt;0x19A8F5..&gt;],MyScribeContent #0 from [SNH: &lt;0x0A9FCC..&gt; -&gt; &lt;0x489BCB..&gt;/FOO/10.9.8.7:9001 [3776408266594462189]])
MyScribeClient.deliver([TOPIC &lt;0x19A8F5..&gt;],MyScribeContent #0 from [SNH: &lt;0x7D350E..&gt; -&gt; &lt;0x489BCB..&gt;/FOO/10.9.8.7:9001 [3776408266594462189]])
MyScribeClient.deliver([TOPIC &lt;0x19A8F5..&gt;],MyScribeContent #0 from [SNH: &lt;0x85DA64..&gt; -&gt; &lt;0x489BCB..&gt;/FOO/10.9.8.7:9001 [3776408266594462189]])
MyScribeClient.deliver([TOPIC &lt;0x19A8F5..&gt;],MyScribeContent #0 from [SNH: &lt;0x489BCB..&gt; -&gt; &lt;0x489BCB..&gt;/FOO/10.9.8.7:9001 [3776408266594462189]])
MyScribeClient.deliver([TOPIC &lt;0x19A8F5..&gt;],MyScribeContent #0 from [SNH: &lt;0x061BB8..&gt; -&gt; &lt;0x489BCB..&gt;/FOO/10.9.8.7:9001 [3776408266594462189]])
Node [SNH: &lt;0x489BCB..&gt; -&gt; &lt;0x489BCB..&gt;/FOO/10.9.8.7:9001 [3776408266594462189]] broadcasting 2
Node [SNH: &lt;0x489BCB..&gt; -&gt; &lt;0x489BCB..&gt;/FOO/10.9.8.7:9001 [3776408266594462189]] anycasting 3
MyScribeClient.anycast([TOPIC &lt;0x19A8F5..&gt;],MyScribeContent #3 from [SNH: &lt;0x489BCB..&gt; -&gt; &lt;0x489BCB..&gt;/FOO/10.9.8.7:9001 [3776408266594462189]]):false
MyScribeClient.deliver([TOPIC &lt;0x19A8F5..&gt;],MyScribeContent #2 from [SNH: &lt;0x281817..&gt; -&gt; &lt;0x489BCB..&gt;/FOO/10.9.8.7:9001 [3776408266594462189]])
MyScribeClient.anycast([TOPIC &lt;0x19A8F5..&gt;],MyScribeContent #3 from [SNH: &lt;0x281817..&gt; -&gt; &lt;0x489BCB..&gt;/FOO/10.9.8.7:9001 [3776408266594462189]]):true
MyScribeClient.deliver([TOPIC &lt;0x19A8F5..&gt;],MyScribeContent #2 from [SNH: &lt;0x8904D9..&gt; -&gt; &lt;0x489BCB..&gt;/FOO/10.9.8.7:9001 [3776408266594462189]])
MyScribeClient.deliver([TOPIC &lt;0x19A8F5..&gt;],MyScribeContent #2 from [SNH: &lt;0xA20DF8..&gt; -&gt; &lt;0x489BCB..&gt;/FOO/10.9.8.7:9001 [3776408266594462189]])
MyScribeClient.deliver([TOPIC &lt;0x19A8F5..&gt;],MyScribeContent #2 from [SNH: &lt;0xCF76F1..&gt; -&gt; &lt;0x489BCB..&gt;/FOO/10.9.8.7:9001 [3776408266594462189]])
MyScribeClient.deliver([TOPIC &lt;0x19A8F5..&gt;],MyScribeContent #2 from [SNH: &lt;0x39CE29..&gt; -&gt; &lt;0x489BCB..&gt;/FOO/10.9.8.7:9001 [3776408266594462189]])
MyScribeClient.deliver([TOPIC &lt;0x19A8F5..&gt;],MyScribeContent #2 from [SNH: &lt;0x0A9FCC..&gt; -&gt; &lt;0x489BCB..&gt;/FOO/10.9.8.7:9001 [3776408266594462189]])
MyScribeClient.deliver([TOPIC &lt;0x19A8F5..&gt;],MyScribeContent #2 from [SNH: &lt;0x7D350E..&gt; -&gt; &lt;0x489BCB..&gt;/FOO/10.9.8.7:9001 [3776408266594462189]])
MyScribeClient.deliver([TOPIC &lt;0x19A8F5..&gt;],MyScribeContent #2 from [SNH: &lt;0x85DA64..&gt; -&gt; &lt;0x489BCB..&gt;/FOO/10.9.8.7:9001 [3776408266594462189]])
MyScribeClient.deliver([TOPIC &lt;0x19A8F5..&gt;],MyScribeContent #2 from [SNH: &lt;0x489BCB..&gt; -&gt; &lt;0x489BCB..&gt;/FOO/10.9.8.7:9001 [3776408266594462189]])
MyScribeClient.deliver([TOPIC &lt;0x19A8F5..&gt;],MyScribeContent #2 from [SNH: &lt;0x061BB8..&gt; -&gt; &lt;0x489BCB..&gt;/FOO/10.9.8.7:9001 [3776408266594462189]])
Node [SNH: &lt;0x489BCB..&gt; -&gt; &lt;0x489BCB..&gt;/FOO/10.9.8.7:9001 [3776408266594462189]] broadcasting 4
Node [SNH: &lt;0x489BCB..&gt; -&gt; &lt;0x489BCB..&gt;/FOO/10.9.8.7:9001 [3776408266594462189]] anycasting 5
MyScribeClient.anycast([TOPIC &lt;0x19A8F5..&gt;],MyScribeContent #5 from [SNH: &lt;0x489BCB..&gt; -&gt; &lt;0x489BCB..&gt;/FOO/10.9.8.7:9001 [3776408266594462189]]):false
MyScribeClient.deliver([TOPIC &lt;0x19A8F5..&gt;],MyScribeContent #4 from [SNH: &lt;0x281817..&gt; -&gt; &lt;0x489BCB..&gt;/FOO/10.9.8.7:9001 [3776408266594462189]])
MyScribeClient.anycast([TOPIC &lt;0x19A8F5..&gt;],MyScribeContent #5 from [SNH: &lt;0x281817..&gt; -&gt; &lt;0x489BCB..&gt;/FOO/10.9.8.7:9001 [3776408266594462189]]):false
MyScribeClient.anycast([TOPIC &lt;0x19A8F5..&gt;],MyScribeContent #5 from [SNH: &lt;0x281817..&gt; -&gt; &lt;0x489BCB..&gt;/FOO/10.9.8.7:9001 [3776408266594462189]]):false
MyScribeClient.deliver([TOPIC &lt;0x19A8F5..&gt;],MyScribeContent #4 from [SNH: &lt;0x8904D9..&gt; -&gt; &lt;0x489BCB..&gt;/FOO/10.9.8.7:9001 [3776408266594462189]])
MyScribeClient.deliver([TOPIC &lt;0x19A8F5..&gt;],MyScribeContent #4 from [SNH: &lt;0xA20DF8..&gt; -&gt; &lt;0x489BCB..&gt;/FOO/10.9.8.7:9001 [3776408266594462189]])
MyScribeClient.deliver([TOPIC &lt;0x19A8F5..&gt;],MyScribeContent #4 from [SNH: &lt;0xCF76F1..&gt; -&gt; &lt;0x489BCB..&gt;/FOO/10.9.8.7:9001 [3776408266594462189]])
MyScribeClient.deliver([TOPIC &lt;0x19A8F5..&gt;],MyScribeContent #4 from [SNH: &lt;0x39CE29..&gt; -&gt; &lt;0x489BCB..&gt;/FOO/10.9.8.7:9001 [3776408266594462189]])
MyScribeClient.deliver([TOPIC &lt;0x19A8F5..&gt;],MyScribeContent #4 from [SNH: &lt;0x0A9FCC..&gt; -&gt; &lt;0x489BCB..&gt;/FOO/10.9.8.7:9001 [3776408266594462189]])
MyScribeClient.deliver([TOPIC &lt;0x19A8F5..&gt;],MyScribeContent #4 from [SNH: &lt;0x7D350E..&gt; -&gt; &lt;0x489BCB..&gt;/FOO/10.9.8.7:9001 [3776408266594462189]])
MyScribeClient.deliver([TOPIC &lt;0x19A8F5..&gt;],MyScribeContent #4 from [SNH: &lt;0x85DA64..&gt; -&gt; &lt;0x489BCB..&gt;/FOO/10.9.8.7:9001 [3776408266594462189]])
MyScribeClient.deliver([TOPIC &lt;0x19A8F5..&gt;],MyScribeContent #4 from [SNH: &lt;0x489BCB..&gt; -&gt; &lt;0x489BCB..&gt;/FOO/10.9.8.7:9001 [3776408266594462189]])
MyScribeClient.deliver([TOPIC &lt;0x19A8F5..&gt;],MyScribeContent #4 from [SNH: &lt;0x061BB8..&gt; -&gt; &lt;0x489BCB..&gt;/FOO/10.9.8.7:9001 [3776408266594462189]])
MyScribeClient.anycast([TOPIC &lt;0x19A8F5..&gt;],MyScribeContent #5 from [SNH: &lt;0x39CE29..&gt; -&gt; &lt;0x489BCB..&gt;/FOO/10.9.8.7:9001 [3776408266594462189]]):false
MyScribeClient.anycast([TOPIC &lt;0x19A8F5..&gt;],MyScribeContent #5 from [SNH: &lt;0x39CE29..&gt; -&gt; &lt;0x489BCB..&gt;/FOO/10.9.8.7:9001 [3776408266594462189]]):false
MyScribeClient.anycast([TOPIC &lt;0x19A8F5..&gt;],MyScribeContent #5 from [SNH: &lt;0x8904D9..&gt; -&gt; &lt;0x489BCB..&gt;/FOO/10.9.8.7:9001 [3776408266594462189]]):true
</span></pre>
Note that the tree is only 1 level deep.<br/>
Note how each publish message is delivered to each node in the group.<br/>
Note that anycast is called on different nodes until a node returns true.<br/>
<h3>Congratulations! You have built and run your first scribe application!<br>
<hr/><div class="nav">
<span class="nav-left"><a href="tut_timertask.html#timer">Previous (Timer)</a></span>
<span class="nav-center"><a href="index.html">Contents</a></span>
<span class="nav-right"><a href="tut_past.html#past">Next (Past)</a></span>
</div><br/>
<div class="footer">
Pastry tutorial version @tutorial_version@. &nbsp;&nbsp;&nbsp; Last updated @tutorial_date@.
&nbsp;&nbsp;&nbsp; For FreePastry @freepastry_version@. &nbsp;&nbsp;&nbsp; Maintained by @maintainer@.
</div>
</div>
</body>
</html>