mirror of
https://github.com/berkeleydb/libdb.git
synced 2024-11-16 17:16:25 +00:00
663 lines
24 KiB
HTML
663 lines
24 KiB
HTML
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||
<head>
|
||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
||
<title>Example Processing Loop</title>
|
||
<link rel="stylesheet" href="gettingStarted.css" type="text/css" />
|
||
<meta name="generator" content="DocBook XSL Stylesheets V1.73.2" />
|
||
<link rel="start" href="index.html" title="Getting Started with Replicated Berkeley DB Applications" />
|
||
<link rel="up" href="fwrkmasterreplica.html" title="Chapter 4. Replica versus Master Processes" />
|
||
<link rel="prev" href="processingloop.html" title="Processing Loop" />
|
||
<link rel="next" href="addfeatures.html" title="Chapter 5. Additional Features" />
|
||
</head>
|
||
<body>
|
||
<div xmlns="" class="navheader">
|
||
<div class="libver">
|
||
<p>Library Version 11.2.5.3</p>
|
||
</div>
|
||
<table width="100%" summary="Navigation header">
|
||
<tr>
|
||
<th colspan="3" align="center">Example Processing Loop</th>
|
||
</tr>
|
||
<tr>
|
||
<td width="20%" align="left"><a accesskey="p" href="processingloop.html">Prev</a> </td>
|
||
<th width="60%" align="center">Chapter 4. Replica versus Master Processes</th>
|
||
<td width="20%" align="right"> <a accesskey="n" href="addfeatures.html">Next</a></td>
|
||
</tr>
|
||
</table>
|
||
<hr />
|
||
</div>
|
||
<div class="sect1" lang="en" xml:lang="en">
|
||
<div class="titlepage">
|
||
<div>
|
||
<div>
|
||
<h2 class="title" style="clear: both"><a id="exampledoloop"></a>Example Processing Loop</h2>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="toc">
|
||
<dl>
|
||
<dt>
|
||
<span class="sect2">
|
||
<a href="exampledoloop.html#runningit">Running It</a>
|
||
</span>
|
||
</dt>
|
||
</dl>
|
||
</div>
|
||
<p>
|
||
In this section we take the example
|
||
processing loop that we presented in the
|
||
previous section and we flesh it out to
|
||
provide a more complete example. We do this
|
||
by updating the
|
||
<code class="function">doloop()</code>
|
||
function that our original transaction
|
||
application used
|
||
<span>(see <a class="xref" href="simpleprogramlisting.html#doloop_c" title="Function: doloop()">Function: doloop()</a>)</span>
|
||
|
||
|
||
to fully support our replicated application.
|
||
</p>
|
||
<p>
|
||
In the following example code, code that we
|
||
add to the original example is presented in
|
||
<strong class="userinput"><code>bold</code></strong>.
|
||
</p>
|
||
<p>
|
||
To begin, we include a new header file into
|
||
our application so that we can check for the
|
||
<code class="literal">ENOENT</code> return value later
|
||
in our processing loop. We also define our
|
||
<code class="literal">APP_DATA</code>
|
||
structure, and we define a
|
||
<code class="literal">sleeptime</code> value.
|
||
|
||
<span>
|
||
Finally, we
|
||
add a new forward declaration for our event
|
||
callback.
|
||
</span>
|
||
|
||
|
||
</p>
|
||
<pre class="programlisting">/*
|
||
* File: ex_rep_gsg_repmgr.c
|
||
*/
|
||
|
||
#include <stdlib.h>
|
||
#include <string.h>
|
||
<strong class="userinput"><code>#include <errno.h></code></strong>
|
||
#ifndef _WIN32
|
||
#include <unistd.h>
|
||
#endif
|
||
|
||
#include <db.h>
|
||
|
||
#ifdef _WIN32
|
||
extern int getopt(int, char * const *, const char *);
|
||
#endif
|
||
|
||
|
||
#define CACHESIZE (10 * 1024 * 1024)
|
||
#define DATABASE "quote.db"
|
||
<strong class="userinput"><code>#define SLEEPTIME 3</code></strong>
|
||
|
||
const char *progname = "ex_rep_gsg_repmgr";
|
||
|
||
<strong class="userinput"><code>typedef struct {
|
||
int is_master;
|
||
} APP_DATA;</code></strong>
|
||
|
||
int create_env(const char *, DB_ENV **);
|
||
int env_init(DB_ENV *, const char *);
|
||
int doloop (DB_ENV *);
|
||
static int print_stocks(DBC *);
|
||
<strong class="userinput"><code>void *event_callback(DB_ENV *, u_int32_t, void *);</code></strong> </pre>
|
||
<p>
|
||
In our <code class="function">main()</code> function, most of what we
|
||
have to add to it is some new variable declarations and
|
||
initializations:
|
||
</p>
|
||
<pre class="programlisting">int
|
||
main(int argc, char *argv[])
|
||
{
|
||
DB_ENV *dbenv;
|
||
DB_SITE *dbsite;
|
||
extern char *optarg;
|
||
const char *home;
|
||
char ch, *host, *portstr;
|
||
int ret, local_is_set, is_group_creator;
|
||
u_int32_t port;
|
||
<strong class="userinput"><code>/* Used to track whether this is a replica or a master */
|
||
APP_DATA my_app_data;
|
||
|
||
my_app_data.is_master = 0; /* Assume that we start as a replica */</code></strong>
|
||
dbenv = NULL;
|
||
|
||
ret = local_is_set = is_group_creator = 0;
|
||
home = NULL;</pre>
|
||
<p>
|
||
The rest of our <code class="function">main()</code> function
|
||
is unchanged, except that we make our
|
||
<code class="literal">APP_DATA</code> structure available through our
|
||
environment handle's <code class="methodname">app_private</code>
|
||
field:
|
||
</p>
|
||
<pre class="programlisting">
|
||
if ((ret = create_env(progname, &dbenv)) != 0)
|
||
goto err;
|
||
|
||
<strong class="userinput"><code>/* Make APP_DATA available through the environment handle */
|
||
dbenv->app_private = &my_app_data;</code></strong>
|
||
|
||
/* Default priority is 100 */
|
||
dbenv->rep_set_priority(dbenv, 100);
|
||
/* Permanent messages require at least one ack */
|
||
dbenv->repmgr_set_ack_policy(dbenv, DB_REPMGR_ACKS_ONE);
|
||
/* Give 500 microseconds to receive the ack */
|
||
dbenv->rep_set_timeout(dbenv, DB_REP_ACK_TIMEOUT, 500);
|
||
|
||
while ((ch = getopt(argc, argv, "h:l:L:p:r:")) != EOF)
|
||
switch (ch) {
|
||
case 'h':
|
||
home = optarg;
|
||
break;
|
||
/* Set the host and port used by this environment */
|
||
case 'l':
|
||
host = strtok(optarg, ":");
|
||
if ((portstr = strtok(NULL, ":")) == NULL) {
|
||
fprintf(stderr, "Bad host specification.\n");
|
||
goto err;
|
||
}
|
||
port = (unsigned short)atoi(portstr);
|
||
if ((ret = dbenv->repmgr_site(dbenv, host, port, &dbsite
|
||
0)) != 0 ) {
|
||
fprintf(stderr,
|
||
"Could not set local address %s.\n", host);
|
||
goto err;
|
||
}
|
||
dbsite->set_config(dbsite, DB_LOCAL_SITE, 1);
|
||
if (is_group_creator)
|
||
dbsite->set_config(dbsite, DB_GROUP_CREATOR, 1);
|
||
|
||
if ((ret = dbsite->close(dbsite)) != 0) {
|
||
dbenv->(dbenv, ret, "DB_SITE->close");
|
||
goto err;
|
||
}
|
||
local_is_set = 1;
|
||
break;
|
||
/* Set this replica's election priority */
|
||
case 'p':
|
||
dbenv->rep_set_priority(dbenv, atoi(optarg));
|
||
break;
|
||
/* Identify another site in the replication group */
|
||
case 'r':
|
||
host = strtok(optarg, ":");
|
||
if ((portstr = strtok(NULL, ":")) == NULL) {
|
||
fprintf(stderr, "Bad host specification.\n");
|
||
goto err;
|
||
}
|
||
port = (unsigned short)atoi(portstr);
|
||
if ((dbenv->repmgr_site(dbenv, host, port, &dbsite,
|
||
0)) != 0) {
|
||
fprintf(stderr,
|
||
"Could not add site %s.\n", host);
|
||
goto err;
|
||
}
|
||
dbenv->set_config(dbsite, DB_BOOTSTRAP_HELPER, 1);
|
||
if ((dbenv->close(dbsite)) != 0) {
|
||
dbenv->err(dbenv, ret, "DB_SITE->close");
|
||
goto err;
|
||
}
|
||
break;
|
||
case '?':
|
||
default:
|
||
usage();
|
||
}
|
||
|
||
/* Error check command line. */
|
||
if (home == NULL || !local_is_set)
|
||
usage();
|
||
|
||
if ((ret = env_init(dbenv, home)) != 0)
|
||
goto err;
|
||
|
||
if ((ret = dbenv->repmgr_start(dbenv, 3, DB_REP_ELECTION)) != 0)
|
||
goto err;
|
||
|
||
/* Sleep to give ourselves time to find a master. */
|
||
sleep(5);
|
||
|
||
if ((ret = doloop(dbenv)) != 0) {
|
||
dbenv->err(dbenv, ret, "Application failed");
|
||
goto err;
|
||
}
|
||
|
||
err: if (dbenv != NULL)
|
||
(void)dbenv->close(dbenv, 0);
|
||
|
||
return (ret);
|
||
} </pre>
|
||
<p>
|
||
Having updated our <code class="function">main()</code>, we must also
|
||
update our <code class="function">create_env()</code> function to
|
||
register our <code class="function">event_callback</code> callback.
|
||
Notice that our <code class="function">env_init()</code> function, which is
|
||
responsible for actually opening our environment handle, is
|
||
unchanged:
|
||
</p>
|
||
<pre class="programlisting">int
|
||
create_env(const char *progname, DB_ENV **dbenvp)
|
||
{
|
||
DB_ENV *dbenv;
|
||
int ret;
|
||
|
||
if ((ret = db_env_create(&dbenv, 0)) != 0) {
|
||
fprintf(stderr, "can't create env handle: %s\n",
|
||
db_strerror(ret));
|
||
return (ret);
|
||
}
|
||
|
||
dbenv->set_errfile(dbenv, stderr);
|
||
dbenv->set_errpfx(dbenv, progname);
|
||
<strong class="userinput"><code>(void)dbenv->set_event_notify(dbenv, event_callback);</code></strong>
|
||
|
||
*dbenvp = dbenv;
|
||
return (0);
|
||
}
|
||
|
||
int
|
||
env_init(DB_ENV *dbenv, const char *home)
|
||
{
|
||
u_int32_t flags;
|
||
int ret;
|
||
|
||
(void)dbenv->set_cachesize(dbenv, 0, CACHESIZE, 0);
|
||
(void)dbenv->set_flags(dbenv, DB_TXN_NOSYNC, 1);
|
||
|
||
flags = DB_CREATE |
|
||
DB_INIT_LOCK |
|
||
DB_INIT_LOG |
|
||
DB_INIT_MPOOL |
|
||
DB_INIT_REP |
|
||
DB_INIT_TXN |
|
||
DB_RECOVER |
|
||
DB_THREAD;
|
||
if ((ret = dbenv->open(dbenv, home, flags, 0)) != 0)
|
||
dbenv->err(dbenv, ret, "can't open environment");
|
||
return (ret);
|
||
}</pre>
|
||
<p>
|
||
That done, we need to implement our
|
||
<code class="function">event_callback()</code> callback. Note that what we use
|
||
here is no different from the callback that we described in
|
||
the previous section. However, for the sake of completeness we
|
||
provide the implementation here again.
|
||
</p>
|
||
<pre class="programlisting">
|
||
<strong class="userinput">
|
||
<code>/*
|
||
* A callback used to determine whether the local environment is a
|
||
* replica or a master. This is called by the Replication Manager
|
||
* when the local replication environment changes state.
|
||
*/
|
||
void *
|
||
event_callback(DB_ENV *dbenv, u_int32_t which, void *info)
|
||
{
|
||
APP_DATA *app = dbenv->app_private;
|
||
|
||
info = NULL; /* Currently unused. */
|
||
|
||
switch (which) {
|
||
case DB_EVENT_REP_MASTER:
|
||
app->is_master = 1;
|
||
break;
|
||
|
||
case DB_EVENT_REP_CLIENT:
|
||
app->is_master = 0;
|
||
break;
|
||
|
||
case DB_EVENT_REP_STARTUPDONE: /* fallthrough */
|
||
case DB_EVENT_REP_NEWMASTER:
|
||
/* Ignore. */
|
||
break;
|
||
|
||
default:
|
||
dbenv->errx(dbenv, "ignoring event %d", which);
|
||
}
|
||
}</code>
|
||
</strong>
|
||
</pre>
|
||
<p>
|
||
That done, we need to update our
|
||
<code class="function">doloop()</code>
|
||
<span>function. This is the place where we most
|
||
heavily modify our application.</span>
|
||
|
||
|
||
</p>
|
||
<p>
|
||
We begin by introducing <code class="literal">APP_DATA</code> to the
|
||
function:
|
||
</p>
|
||
<pre class="programlisting">/*
|
||
* Provides the main data processing function for our application.
|
||
* This function provides a command line prompt to which the user
|
||
* can provide a ticker string and a stock price. Once a value is
|
||
* entered to the application, the application writes the value to
|
||
* the database and then displays the entire database.
|
||
*/
|
||
#define BUFSIZE 1024
|
||
int
|
||
doloop(DB_ENV *dbenv)
|
||
{
|
||
DB *dbp;
|
||
<strong class="userinput"><code>APP_DATA *app_data;</code></strong>
|
||
DBT key, data;
|
||
char buf[BUFSIZE], *rbuf;
|
||
int ret;
|
||
u_int32_t flags;
|
||
|
||
dbp = NULL;
|
||
ret = 0;
|
||
memset(&key, 0, sizeof(key));
|
||
memset(&data, 0, sizeof(data));
|
||
<strong class="userinput"><code>app_data = dbenv->app_private;</code></strong></pre>
|
||
<p>
|
||
Next we begin to modify our main loop.
|
||
To start, upon entering the loop we create the database handle and configure it as
|
||
normal. But we also have to decide what flags we will use for
|
||
the open. Again, it depends on whether we are a replica or a
|
||
master.
|
||
</p>
|
||
<pre class="programlisting"> for (;;) {
|
||
if (dbp == NULL) {
|
||
if ((ret = db_create(&dbp, dbenv, 0)) != 0)
|
||
return (ret);
|
||
|
||
<strong class="userinput"><code>flags = DB_AUTO_COMMIT;
|
||
if (app_data->is_master)
|
||
flags |= DB_CREATE;</code></strong> </pre>
|
||
<p>
|
||
When we open the database, we modify our error handling to
|
||
account for the case where the database does not yet exist. This can
|
||
happen if our code is running as a replica and the Replication Manager has not
|
||
yet had a chance to create the databases for us. Recall that replicas never
|
||
write to their own databases directly, and so they cannot
|
||
create databases on their own.
|
||
</p>
|
||
<p>
|
||
If we detect that the database does not yet exist, we simply
|
||
close the database handle, sleep for a short period of time
|
||
and then continue processing. This gives the Replication Manager a chance to
|
||
create the database so that our replica can continue
|
||
operations.
|
||
</p>
|
||
<pre class="programlisting"> if ((ret = dbp->open(dbp,
|
||
NULL, DATABASE, NULL, DB_BTREE, flags, 0)) != 0) {
|
||
<strong class="userinput"><code>if (ret == ENOENT) {
|
||
printf(
|
||
"No stock database yet available.\n");
|
||
if ((ret = dbp->close(dbp, 0)) != 0) {
|
||
dbenv->err(dbenv, ret,
|
||
"DB->close");
|
||
goto err;
|
||
}
|
||
dbp = NULL;
|
||
sleep(SLEEPTIME);
|
||
continue;
|
||
}</code></strong>
|
||
dbenv->err(dbenv, ret, "DB->open");
|
||
goto err;
|
||
}
|
||
} </pre>
|
||
<p>
|
||
Next we modify our prompt, so that if the local process is running
|
||
as a replica, we can tell from the shell that the prompt is for a
|
||
read-only process.
|
||
</p>
|
||
<pre class="programlisting"> printf("QUOTESERVER<strong class="userinput"><code>%s</code></strong>> ",
|
||
<strong class="userinput"><code>app_data->is_master ? "" : " (read-only)");</code></strong>
|
||
fflush(stdout); </pre>
|
||
<p>
|
||
When we collect data from the prompt, there is a case that says
|
||
if no data is entered then show the entire stocks database.
|
||
This display is performed by our
|
||
<code class="function">print_stocks()</code>
|
||
<span>function</span>
|
||
|
||
(which has not
|
||
required a modification since we first introduced it in
|
||
<a class="xref" href="simpleprogramlisting.html#printstocks_c" title="Function: print_stocks()">
|
||
<span>Function: print_stocks()</span>
|
||
|
||
|
||
</a>).
|
||
</p>
|
||
<p>
|
||
When we call
|
||
<span><code class="function">print_stocks()</code>, </span>
|
||
|
||
we check for a dead replication handle. Dead
|
||
replication handles happen whenever a replication election
|
||
results in a previously committed transaction becoming
|
||
invalid. This is an error scenario caused by a new master having a
|
||
slightly older version of the data than the original
|
||
master and so all replicas must modify their database(s) to
|
||
reflect that of the new master. In this situation, some
|
||
number of previously committed transactions may have to be
|
||
unrolled. From the replica's perspective, the database
|
||
handles should all be closed and then opened again.
|
||
</p>
|
||
<pre class="programlisting"> if (fgets(buf, sizeof(buf), stdin) == NULL)
|
||
break;
|
||
if (strtok(&buf[0], " \t\n") == NULL) {
|
||
switch ((ret = print_stocks(dbp))) {
|
||
case 0:
|
||
continue;
|
||
<strong class="userinput"><code>case DB_REP_HANDLE_DEAD:
|
||
(void)dbp->close(dbp, DB_NOSYNC);
|
||
dbp = NULL;
|
||
dbenv->errx(dbenv, "Got a dead replication handle");
|
||
continue; </code></strong>
|
||
default:
|
||
dbp->err(dbp, ret, "Error traversing data");
|
||
goto err;
|
||
}
|
||
}
|
||
rbuf = strtok(NULL, " \t\n");
|
||
if (rbuf == NULL || rbuf[0] == '\0') {
|
||
if (strncmp(buf, "exit", 4) == 0 ||
|
||
strncmp(buf, "quit", 4) == 0)
|
||
break;
|
||
dbenv->errx(dbenv, "Format: TICKER VALUE");
|
||
continue;
|
||
}</pre>
|
||
<p>
|
||
That done, we need to add a little error checking to our
|
||
command prompt to make sure the user is not attempting to
|
||
modify the database at a replica. Remember, replicas must never
|
||
modify their local databases on their own. This guards against
|
||
that happening due to user input at the prompt.
|
||
</p>
|
||
<pre class="programlisting"> <strong class="userinput"><code>if (!app_data->is_master) {
|
||
dbenv->errx(dbenv, "Can't update at client");
|
||
continue;
|
||
}</code></strong>
|
||
key.data = buf;
|
||
key.size = (u_int32_t)strlen(buf);
|
||
|
||
data.data = rbuf;
|
||
data.size = (u_int32_t)strlen(rbuf);
|
||
|
||
if ((ret = dbp->put(dbp,
|
||
NULL, &key, &data, 0)) != 0) {
|
||
dbp->err(dbp, ret, "DB->put");
|
||
goto err;
|
||
}
|
||
}
|
||
|
||
err: if (dbp != NULL)
|
||
(void)dbp->close(dbp, DB_NOSYNC);
|
||
|
||
return (ret);
|
||
} </pre>
|
||
<p>
|
||
With that completed, we are all done updating our application
|
||
for replication.
|
||
|
||
The only remaining
|
||
<span>function, <code class="function">print_stocks()</code>,</span>
|
||
|
||
|
||
|
||
is unmodified from when we
|
||
originally introduced it. For details on that function, see
|
||
<a class="xref" href="simpleprogramlisting.html#printstocks_c" title="Function: print_stocks()">
|
||
<span>Function: print_stocks()</span>
|
||
|
||
|
||
</a>.
|
||
</p>
|
||
<div class="sect2" lang="en" xml:lang="en">
|
||
<div class="titlepage">
|
||
<div>
|
||
<div>
|
||
<h3 class="title"><a id="runningit"></a>Running It</h3>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<p>
|
||
To run our replicated application, we need to make
|
||
sure each participating environment has its own unique
|
||
home directory. We can do this by running
|
||
each site on a separate networked machine, but that
|
||
is not strictly necessary; multiple instances of this
|
||
code can run on the same machine provided the
|
||
environment home restriction is observed.
|
||
</p>
|
||
<p>
|
||
To run a process, make sure the environment home exists and
|
||
then start the process using the <code class="literal">-h</code>
|
||
option to specify that directory. You must also use the
|
||
<code class="literal">-l</code> or <code class="literal">-L</code> option to
|
||
identify the local host and port that this process will use
|
||
to listen for replication messages (-L means that this is a
|
||
group creator), and the <code class="literal">-r</code> option to
|
||
identify the other processes in the replication group.
|
||
Finally, use the <code class="literal">-p</code> option to specify a
|
||
priority. The process that you designate to have the
|
||
highest priority will become the master.
|
||
</p>
|
||
<pre class="programlisting">> mkdir env1
|
||
> ./ex_rep_gsg_repmgr -h env1 -L localhost:8080 -p 10
|
||
No stock database yet available.
|
||
No stock database yet available. </pre>
|
||
<p>
|
||
Now, start another process. This time, change the environment
|
||
home to something else, use the <code class="literal">-l</code> flag to at
|
||
least change the port number the process is listening on, and
|
||
use the <code class="literal">-r</code> option to identify the host and
|
||
port of the other replication process:
|
||
</p>
|
||
<pre class="programlisting">> mkdir env2
|
||
> ./ex_rep_gsg_repmgr -h env2 -l localhost:8081 \
|
||
-r localhost:8080 -p 20</pre>
|
||
<p>
|
||
After a short pause, the second process should display the master
|
||
prompt:
|
||
</p>
|
||
<pre class="programlisting">
|
||
QUOTESERVER > </pre>
|
||
<p>
|
||
And the first process should
|
||
display the read-only prompt:
|
||
</p>
|
||
<pre class="programlisting">
|
||
QUOTESERVER (read-only)> </pre>
|
||
<p>
|
||
Now go to the master process and give it a couple of stocks and stock
|
||
prices:
|
||
</p>
|
||
<pre class="programlisting">QUOTESERVER> FAKECO 9.87
|
||
QUOTESERVER> NOINC .23
|
||
QUOTESERVER> </pre>
|
||
<p>
|
||
Then, go to the replica and hit <strong class="userinput"><code>return</code></strong> at the prompt to
|
||
see the new values:
|
||
</p>
|
||
<pre class="programlisting">QUOTESERVER (read-only)>
|
||
Symbol Price
|
||
====== =====
|
||
FAKECO 9.87
|
||
NOINC .23
|
||
QUOTESERVER (read-only)> </pre>
|
||
<p>
|
||
Doing the same at the master results in the same thing:
|
||
</p>
|
||
<pre class="programlisting">QUOTESERVER>
|
||
Symbol Price
|
||
====== =====
|
||
FAKECO 9.87
|
||
NOINC .23
|
||
QUOTESERVER> </pre>
|
||
<p>
|
||
You can change a stock by simply entering the stock value and
|
||
new price at the master's prompt:
|
||
</p>
|
||
<pre class="programlisting">QUOTESERVER> FAKECO 10.01
|
||
QUOTESERVER> </pre>
|
||
<p>
|
||
Then, go to either the master or the replica to see the updated
|
||
database. On the master:
|
||
</p>
|
||
<pre class="programlisting">QUOTESERVER>
|
||
Symbol Price
|
||
====== =====
|
||
FAKECO 10.01
|
||
NOINC .23
|
||
QUOTESERVER> </pre>
|
||
<p>
|
||
And on the replica:
|
||
</p>
|
||
<pre class="programlisting">QUOTESERVER (read-only)>
|
||
Symbol Price
|
||
====== =====
|
||
FAKECO 10.01
|
||
NOINC .23
|
||
QUOTESERVER (read-only)> </pre>
|
||
<p>
|
||
Finally, to quit the applications, simply type
|
||
<code class="literal">quit</code> at both prompts. On the replica:
|
||
</p>
|
||
<pre class="programlisting">QUOTESERVER (read-only)> quit
|
||
> </pre>
|
||
<p>
|
||
And on the master as well:
|
||
</p>
|
||
<pre class="programlisting">QUOTESERVER> quit
|
||
> </pre>
|
||
</div>
|
||
</div>
|
||
<div class="navfooter">
|
||
<hr />
|
||
<table width="100%" summary="Navigation footer">
|
||
<tr>
|
||
<td width="40%" align="left"><a accesskey="p" href="processingloop.html">Prev</a> </td>
|
||
<td width="20%" align="center">
|
||
<a accesskey="u" href="fwrkmasterreplica.html">Up</a>
|
||
</td>
|
||
<td width="40%" align="right"> <a accesskey="n" href="addfeatures.html">Next</a></td>
|
||
</tr>
|
||
<tr>
|
||
<td width="40%" align="left" valign="top">Processing Loop </td>
|
||
<td width="20%" align="center">
|
||
<a accesskey="h" href="index.html">Home</a>
|
||
</td>
|
||
<td width="40%" align="right" valign="top"> Chapter 5. Additional Features</td>
|
||
</tr>
|
||
</table>
|
||
</div>
|
||
</body>
|
||
</html>
|