NAME

RMI::Node - base class for RMI::Client and RMI::Server

VERSION

This document describes RMI::Node v0.10.

SYNOPSIS

# applications should use B<RMI::Client> and B<RMI::Server>
# this example is for new client/server implementors

pipe($client_reader, $server_writer);  
pipe($server_reader,  $client_writer);     
$server_writer->autoflush(1);
$client_writer->autoflush(1);

$c = RMI::Node->new(
    reader => $client_reader,
    writer => $client_writer,
);

$s = RMI::Node->new(
    writer => $server_reader,
    reader => $server_writer,
);

sub main::add { return $_[0] + $_[1] }

if (fork()) {
    # service one request and exit
    require IO::File;
    $s->receive_request_and_send_response();
    exit;
}

# send one request and get the result
$sum = $c->send_request_and_receive_response('call_function', 'main::add', 5, 6);

# we might have also done..
$robj = $c->send_request_and_receive_response('call_class_method', 'IO::File', 'new', '/my/file');

# this only works on objects which are remote proxies:
$txt = $c->send_request_and_receive_response('call_object_method', $robj, 'getline');

DESCRIPTION

This is the base class for RMI::Client and RMI::Server. RMI::Client and RMI::Server both implement a wrapper around the RMI::Node interface, with convenience methods around initiating the sending or receiving of messages.

An RMI::Node object embeds the core methods for bi-directional communication. Because the server often has to make counter requests of the client, the pair will often switch functional roles several times in the process of servicing a particular call. This class is not technically abstract, as it is fully functional in either the client or server role without subclassing. Most direct coding against this API, however, should be done by implementors of new types of clients/servers.

See RMI::Client and RMI::Server for the API against which application code should be written. See RMI for an overview of how clients and servers interact. The documentation in this module will describe the general piping system between clients and servers.

An RMI::Node requires that the reader/writer handles be explicitly specified at construction time. It also requires and that the code which uses it is be wise about calling methods to send and recieve data which do not cause it to block indefinitely. :)

METHODS

new()

$n = RMI::Node->new(reader => $fh1, writer => $fh2);

The constructor for RMI::Node objects requires that a reader and writer handle be provided. They can be the same handle if the handle is bi-directional (as with TCP sockets, see RMI::Client::Tcp).

close()

$n->close();

Closes handles, and does any additional required bookeeping.

send_request_and_recieve_response()

@result = $n->send_request_and_recieve_response($call_type,@data);

@result = $n->send_request_and_recieve_response($opts_hashref, $call_type, @data);

This is the method behind all of the call_* methods on RMI::Client objects.
It is also the method behind the proxied objects themselves (in AUTOLOAD).

The optional initial hashref allows special serialization control.  It is currently
only used to force serializing instead of proxying in some cases where this is
helpful and safe.

The call_type maps to the client request, and is one of:
   call_function
   call_class_method
   call_object_method
   call_eval
   call_use
   call_use_lib

The interpretation of the @data parameters is dependent on the particular call_type, and is handled entirely on the remote side.

receive_request_and_send_response()

This method waits for a single request to be received from its reader handle, services the request, and sends the results through the writer handle.

It is possible that, while servicing the request, it will make counter requests, and those counter requests, may yield counter-counter-requests which call this method recursively.

virtual_lib()

This method returns an anonymous subroutine which can be used in a "use lib $mysub" call, to cause subsequent "use" statements to go through this node to its partner.

e.x.:
   use lib RMI::Client::Tcp->new(host=>'myserver',port=>1234)->virtual_lib;

If a client is constructed for other purposes in the application, the above can also be accomplished with: $client->use_lib_remote(). (See RMI::Client)

INTERNALS: MESSAGE TYPES

The RMI internals are built around sending a "message", which has a type, and an array of data. The interpretation of the message data array is based on the message type.

The following message types are passed within the current implementation:

query

A request that logic execute on the remote side on behalf of the sender. This includes object method calls, class method calls, function calls, remote calls to eval(), and requests that the remote side load modules, add library paths, etc.

This is the type for standard remote method invocatons.

The message data contains, in order:

- wantarray    1, '', or undef, depending on the requestor's calling context.
               This is passed to the remote side, and also used on the
               local side to control how results are returned.

- object/class A class name, or an object which is a proxy for something on the remote side.
               This value is not present for plain function calls, or evals.

- method_name  This is the name of the method to call.
               This is a fully-qualified function name for plain function calls.

- param1       The first parameter to the function/method call.
               Note that parameters are "passed" to eval as well by exposing @_.

- ...          The next parameter to the function/method call, etc.

result

The return value from a succesful "query" which does not result in an exception being thrown on the remote side.

The message data contains the return value or values of that query.

exception

The response to a query which resulted in an exception on the remote side.

The message data contains the value thrown via die() on the remote side.

close

Indicatees that the remote side has closed the connection. This is actually constructed on the receiver end when it fails to read from the input stream.

The message data is undefined in this case.

INTERNALS: WIRE PROTOCOL

The _send() and _receive() methods are symmetrical. These two methods are used by the public API to encapsulate message transmission and reception. The _send() method takes a message_type and a message_data arrayref, and transmits them to the other side of the RMI connection. The _receive() method returns a message type and message data array.

Internal to _send() and _receive() the message type and data are passed through _serialize and _deserialize and then transmitted along the writer and reader handles.

The _serialize method turns a message_type and message_data into a string value suitable for transmission. Conversely, the _deserialize method turns a string value in the same format into a message_type and message_data array.

The serialization process has two stages:

replacing references with identifiers used for remoting

An array of message_data of length n to is converted to have a length of n*2. Each value is preceded by an integer which categorizes the value.

 0    a primitive, non-reference value
      
      The value itself follows, it is not a reference, and it is passed by-copy.
      
 1    an object reference originating on the sender's side

      A unique identifier for the object follows instead of the object.
      The remote side should construct a transparent proxy which uses that ID.
      
 2    a non-object (unblessed) reference originating on the sender's side
      
      A unique identifier for the reference follows, instead of the reference.
      The remote side should construct a transparent proxy which uses that ID.
      
 3    passing-back a proxy: a reference which originated on the receiver's side
      
      The following value is the identifier the remote side sent previously.
      The remote side should substitue the original object when deserializing

 4    a serialized object

      This is the result of serializing the reference.  This happens only
      when explicitly requested.  (DBI has some issues with proxies, for instance
      and has customizations in RMI::Proxy::DBI::db to force serialization of
      some connection attributes.)

      See B<RMI::ProxyObject> for more details on forcing serialization.

      Note that, because the current wire protocol is to use newline as a record 
      separator, we use double-quoted strings to ensure all newlines are escaped.

Note that all references are turned into primitives by the above process.

stringification

The "wire protocol" for sending and receiving messages is to pass an array via Data::Dumper in such a way that it does not contain newlines. The receiving side uses eval to reconstruct the original message. This is terribly inefficient because the structure does not contain objects of arbitrary depth, and is parsable without tremendous complexity.

Details on how proxy objects and references function, and pose as the real item in question, are in RMI, and RMI::ProxyObject and RMI::ProxyReference

BUGS AND CAVEATS

See general bugs in RMI for general system limitations

the serialization mechanism needs to be made more robust and efficient

It's really just enough to "work".

The current implementation uses Data::Dumper with options which should remove newlines. Since we do not flatten arbitrary data structures, a simpler parser would be more efficient.

The message type is currently a text string. This could be made smaller.

The data type before each paramter or return value is an integer, which could also be abbreviated futher, or we could go the other way and be more clear. :)

This should switch to sysread and pass the message length instead of relying on buffers, since the non-blocking IO might not have issues.

SEE ALSO

RMI, RMI::Server, RMI::Client, RMI::ProxyObject, RMI::ProxyReference

IO::Socket, Tie::Handle, Tie::Array, Tie:Hash, Tie::Scalar

AUTHORS

Scott Smith <sakoht@cpan.org>

COPYRIGHT

Copyright (c) 2008 - 2009 Scott Smith <sakoht@cpan.org> All rights reserved.

LICENSE

This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself.

The full text of the license can be found in the LICENSE file included with this module.