NAME

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

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', undef, '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,$object,$method,$params,$opts)

$fh = $n->send_request_and_receive_response('call_class_method', 'IO::File', 'new', ['/my/file'], {});

This is the primary method used by nodes acting in a client-like capacity.

$call_type:    one of: call_object_method, call_class_method, or call_function, or one of several internal types
$object:       the object or class on which the method is being called, may be undef for subroutine/function calls
$method:       the method to call on $object (even if $object is a class name), or the fully-qualified sub name
$params:       an optional arrayref of values which should be passed to $method
$opts:         an optional hashref of values which can control/optimize message passing

Return values:

$result|@result: the return value will be either a scalar or list, depending on the value of $wantarray

This method sends a method call request through the writer, and waits on a response from the reader. It will handle a response with the answer, exception messages, and also handle counter-requests from the server, which may occur b/c the server calls methods on objects passed as parameters.

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

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:

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

- 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       A class name, or an object which is a proxy for something on the remote side.
               This value is undef for plain function calls.

- param1       The first parameter to the function/method call

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

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 vaues 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.

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, and 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

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.