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.