Sophie

Sophie

distrib > Fedora > 14 > x86_64 > by-pkgid > 1f0b21aa88f3c2c3f7d3ecd7ad1b78e0 > files > 449

festival-speechtools-devel-1.2.96-16.fc13.i686.rpm


<chapter>
    <title>Client-Server Mechanisms</title>

    <para>
      The &cpp; class <classname>EST_Server</classname> provides the core
      mechanisms required for simple client-server applications. It is
      currently used to implement the 
      <literal role=program>fringe_client</literal> 
      program and client-server mechanisms for SIOD. It is planned to
      use it to re-implement the festival client-server mechanism.
    </para>

    <para>
      Servers have types and names. When a server is started it
      records it's type, name and location in a `services' file. When
      a client wishes to connect to a server it looks for it's
      location in that file by giving a name and type. 
    </para>

    <para>
      Once connected a client must present a magic cookie to the
      server as a simple form of authentication. Once authenticated
      the client sends requests, consisting of a package name,
      operation name and a set of arguments, to the server. The server
      responds with an error report or a sequence of values.
    </para>

    <para>
      An instance of <classname>EST_Server</classname> embodies each
      side of the client-server relationship. In the server an
      instance of <classname>EST_Server</classname> is created and
      told how to process requests from clients, a call to the
      <function role=method>run()</function> method then starts the
      server. In a client an instance of <classname>EST_Server</classname>
      represents the server, and calls to the 
      <function role=method>execute(...)</function> method send
      requests to the server.
    </para>

    <sect1>
      <title>The Services Table</title>

      <para>
	The first problem which needs to be addressed by any
	client-server system is how the client finds the
	server. Servers based on <classname>EST_Server</classname>
	handle this problem by writing a record into a file giving
	their name, type and location. Clients can then look servers
	up by namd and type.
      </para>

      <para>
	By default the file <filename>.estServices</filename> is used
	for this purpose, meaning that each user has their own list of
	servers. An alternative file could be specified to record
	public services.
      </para>

      <para>
	The services table also provides a simple authorisation
	mechanism. Each server records a random string in the table,
	and clients must send this string before making any
	requests. Thus people who can't read the services table can't
	make requests of the server, and the file permissions on the
	services table can be used to control access to the server.
	<important>
	  <para>
	    This `magic cookie' authorisation scheme is not very
	    secure. The cookie is sent as plain text over the
	    network and so anyone who can snoop on the network can
	    break the security. 
	  </para>

	  <para>
	    A more secure `challange-responce' authorisation scheme
	    should be implemented.
	  </para>
	</important>
      </para>

      <para>
	The in-file format of the services table is based on the
	&java; properties file format. A typical file might look as
	follows:
	<programlisting>
	  #Services
	  fringe.type=fringe
	  fringe.host=foo.bar.com
	  fringe.cookie=511341634
	  fringe.port=56362
	  fringe.address=123.456.789.654
	  siod.type=siod
	  siod.cookie=492588950
	  siod.host=foo.bar.com
	  siod.address=123.456.789.654
	  siod.port=56382
	  labeling.type=fringe
	  labeling.host=foo.bar.com
	  labeling.cookie=511341634
	  labeling.port=56362
	  labeling.address=123.456.789.654
	</programlisting>
	This file lists three services, a
	<literal role=program>fringe</literal> server with the default
	name of <literal>fringe</literal>, a scheme interpreter
	running as a server, also with the default name, and a second
	<literal role=program>fringe</literal> server named
	<literal>labeling</literal>.
      </para>

      <para>
	The programing interface to the services table is provided by
	the 
	<link linkend='est-servicetable'><classname>EST_ServiceTable</classname></link>
	class. 
      </para>

      &docpp-EST-ServiceTable-2;
    </sect1>

    <sect1>
      <title>Writing Clients and Servers</title>

      <para>
	If a service type (that is a sub-class of
	<classname>EST_Server</classname>) has already been defined
	for the job you need to do, creating clients and servers is
	quite straight forward. For this section I will use the
	<classname>EST_SiodServer</classname> class, which defines a
	simple &scheme; execution service service, as an example.
      </para>

      <sect2>
	<title>A Simple Server</title>

	<para>
	  To run a siod server we have to read the server table,
	  create the server object and update the table, then start the
	  service running.
	</para>

	<para>
	  First we read the default service table.

	  <programlisting arch='cpp'>
	    EST_ServiceTable::read();
	  </programlisting>

	  Now we create the new scheme service called "mySiod". The
	  <literal>EST_Server::sm_sequential</literal> parameter to the
	  server constructor tells the server todeal with one client
	  at a time. The <literal>NULL</literal> turns off trace
	  output, replace this with <literal>&amp;cout</literal> to see
	  what the server is doing.

	  <programlisting arch='cpp'>
	    EST_SiodServer *server 
		= new EST_SiodServer(EST_Server::sm_sequential,
				     "mySiod",
				     NULL);
	  </programlisting>

	  Write the table back out so clients can find us.

	  <programlisting arch='cpp'>
	    EST_ServiceTable::write();
	  </programlisting>

	  Create the object which handles the client requests. The
	  <parameter>handler</parameter> object actually does the work
	  the client requests.  <classname>EST_SiodServer</classname>
	  provides the obvious default handler (it executes the scheme
	  code and returns the results), so we use that.
	  
	  <programlisting arch='cpp'>
	    EST_SiodServer::RequestHandler handler;
	  </programlisting>

	  Finally, start the service. This call never returns.

	  <programlisting arch='cpp'>
	    server->run(handler);
	  </programlisting>

	</para>

      </sect2>

      <sect2 id='simple-client'>
	  <title>A Simple Client</title>

	<para>
	  A client is created by reading the service table, and then
	  asking for a server by name. Again the
	  <literal>NULL</literal> means `no trace output'.

	  <programlisting arch='cpp'>
	    EST_ServiceTable::read();
	    
	     EST_SiodServer *server 
		= new EST_SiodServer("mySiod", NULL);
	  </programlisting>

	  Now we have a representation of the server we must connect
	  before we can do anything. We can connect and dissconnect a
	  server object any number of times over it's life. This may
	  or may not have some meaning to the server. The return value
	  of the connect operation tells us if we managed to connect.

	  <programlisting arch='cpp'>
	    if (server->connect() != connect_ok)
		EST_sys_error("Error Connecting");
	  </programlisting>

	  Once we are connected we can send requests to the
	  server. The siod server executes scheme for us, assume that
	  the function <literal>get_sexp()</literal> returns something
	  we want evaluated.

	  <programlisting arch='cpp'>
	    LISP expression = get_sexp();
	  </programlisting>

	  We pass arguments to requests in an <classname>Args</classname>
	  structure, a special type of
	  <classname>EST_Features</classname>. The sod server wants
	  the expression to execute as the value of
	  <literal>"sexp"</literal>.

	  <programlisting arch='cpp'>
	    EST_SiodServer::Args args;
	    args.set_val("sexp", est_val(expression));
	  </programlisting>

	  As in the server, the behaviour of the client is defined by
	  a `handler' object. The handler
	  <classname>EST_SiodServer</classname> defines for us does
	  nothing with the result, leaving it for us to deal with in
	  the <classname>EST_Features</classname> structure
	  <literal>handler.res</literal>. Again this is good enough
	  for us.

	  <programlisting arch='cpp'>
	    EST_SiodServer::ResultHandler handler;
	  </programlisting>

	  Finally we are ready to send the request to the server. The
	  siod server provides only one operation, called
	  <literal>"eval"</literal> in package
	  <literal>"scheme"</literal>, this is the evaluate-expression
	  operation we want. The return value of
	  <function>execute(...)</function> is true of everything goes
	  OK, false for an error. For an error the message is the
	  value of <literal>"ERROR"</literal>.
	  
	  <programlisting arch='cpp'>
	    if (!server->execute("scheme", "eval", args, handler))
		EST_error("error from siod server '%s'",
			  (const char *)handler.res.String("ERROR"));
	  </programlisting>

	  Now we can get the result of the evaluation, it is returned
	  as the value of <literal>"sexp"</literal>. 

	  <programlisting arch='cpp'>
	    LISP result = scheme(handler.res.Val("sexp"));
	  </programlisting>

	  Although this may seem a lot of work just to evaluate one
	  expression, once a connection is established, only the three
	  steps set arguments, execute, extract results need to be
	  done for each request. So the following would be the code
	  for a single request:

	  <programlisting arch='cpp'>
	    args.set_val("sexp", est_val(expression));
	    if (!server->execute("scheme", "eval", args, handler))
		    [handle error]
	    LISP result = scheme(handler.res.Val("sexp"));
	  </programlisting>
	</para>
      </sect2>

      <sect2>
	<title>A Specialised Server</title>

	<para>
	  If you need to create a server similar to an existing one
	  but which handles requests slightly differently, all you
	  need to do is define your own
	  <classname>RequestHandler</classname> class. This class has
	  a member function called 
	  <function role=method>process()</function> which deos the
	  work.
	</para>

	<para>
	  Here is a varient on the siod server which handles a new
	  operation <literal>"print"</literal> which evaluates an
	  expression and prints the result to standard output as well
	  as retruning it. (In this example some details of error
	  catching and so on necessary for dealing with scheme are
	  omitted so as not to obscure the main points).
	</para>

	<para>
	  First we define the handler class. It is a sub-class of the
	  default handler for siod servers.

	  <programlisting arch='cpp'>
	    class MyRequestHandler : public EST_SiodServer::RequestHandler
	    {
	    public:
	    virtual EST_String process(void);
	    };
	  </programlisting>

	  Now, we define the processing method. For any operation
	  other than <literal>"print"</literal> we call the default
	  siod handler. (<function>leval</function> and
	  <function>lprint</function> are functions provided by the
	  siod interpreter).

	  <programlisting arch='cpp'>
	    EST_String MyRequestHandler::process(void)
	    {
	    if (operation == "print")
	        {
	        // Get the expression.
	        LISP sexp = scheme(args.Val("sexp"));
	        
	        // Evaluate it.
	        LISP result = leval(sexp, current_env);

	        // Print it.
	        lprint(result);
	        
	        // Return it.
	        res.set_val("sexp", est_val(result));
	        return "";
	        }
	    else
	        // Let the default handler deal with other operations.
	        return EST_SiodServer::RequestHandler::process();
	    }
	  </programlisting>

	  And now we can start a server which understands the new
	  operation.

	  <programlisting arch='cpp'>
	    MyRequestHandler handler;
	    server->run(handler);
	  </programlisting>
	</para>
      </sect2>

      <sect2>
	<title>A Client Which Handles Multiple Results</title>

	<para>
	  Servers have the option to return more than one value for a
	  single request. This can be used to return the results of a
	  request a piece at a time as they become available, for
	  instance &festival; returns a waveform for each sentence in
	  a piece of text it is given to synthesise.
	</para>

	<para>
	  Clearly a simple client of the kind described
	  <link linkend='simple-client'>above</link> which gets the
	  result of a request as a result of the call to
	  <function role=method>execute(...)</function> can't handle
	  multiple results of this kind. This is what the handler
	  object is for.
	</para>

	<para>
	  I'll asuume we need a client to del with a varient on the
	  normal siod sever which returns multiple values, say it
	  evaluates the expression in each of a number of environments
	  and returns each result separately. I'll also assume that
	  the work to be done for each result is defined by the fucntion
	  <function>deal_with_result()</function>.
	</para>

	<para>
	  Most of the client will be the same as for
	  <link linkend='simple-client'>the simple client</link>,
	  the exception is that we use our own result handler rather
	  than the default one.

	  <programlisting arch='cpp'>
	    class MyResultHandler : public EST_SiodServer::ResultHandler
	    {
	    public:
	    virtual void process(void);
	    };
	  </programlisting>

	  As for the server's request handler, the beahviour of the
	  result handler is defeined by the 
	  <function role=method>process()</function> method of the
	  handler.

	  <programlisting arch='cpp'>
	    EST_String MyResultHandler::process(void)
	    {
	    // Get the result.
	    LISP result = scheme(handler.res.Val("sexp"));

	    // And deal with it.
	    deal_with_result(result);
	    }
	  </programlisting>

	  With this definition in place we can make requests to the
	  server as follows.

	  <programlisting arch='cpp'>
	    MyResultHandler handler;
	    if (!server->execute("scheme", "multi-eval", args, handler))
		[handle errors]
	  </programlisting>

	  The <function>deal_with_result()</function> function will be
	  called on each result which is returned. If anything special
	  needs to be done with the final value, it can be done after
	  the call to <function role=method>execute(...)</function>
	  as in the simple client example.
	</para>
      </sect2>
    </sect1>

    <sect1>
      <title>Creating a new Service</title>
      <para>
      </para>

      <sect2>
	<title>Commands</title>
	<para>
	</para>
      </sect2>

      <sect2>
	<title>Results</title>
	<para>
	</para>
      </sect2>
    </sect1>

    <sect1>
      <title>The Network Protocol</title>
      <para>
      </para>
    </sect1>
</chapter>


<!--
Local Variables:
mode: sgml
sgml-doctype:"speechtools.sgml"
sgml-parent-document:("speechtools.sgml" "chapter" "book")
sgml-omittag:nil
sgml-shorttag:t
sgml-minimize-attributes:nil
sgml-always-quote-attributes:t
sgml-indent-step:2
sgml-indent-data:t
sgml-exposed-tags:nil
sgml-local-catalogs:nil
sgml-local-ecat-files:nil
End:
-->