# = HISTORY SECTION ===================================================================== # --------------------------------------------------------------------------------------- # version | date | author | changes # --------------------------------------------------------------------------------------- # 2.03 |29.02.00| JSTENZEL | corrected perl version demand; # | | JSTENZEL | slight POD and comment improvements; # 2.02 |06.01.00| JSTENZEL | started translation of POD; # | | JSTENZEL | replaced bug() by Carp::confess(); # | | JSTENZEL | integrated inhouse modules and funtions (like filters); # |15.01.00| JSTENZEL | improved source filters; # 2.01 | | JSTENZEL | ; # 2.00 |25.10.99| JSTENZEL | added the delayed sending feature; # |26.10.99| JSTENZEL | formatted POD to be better prepared for pod2text; # | | JSTENZEL | added traces; # --------------------------------------------------------------------------------------- # 1.05 |30.09.99| JSTENZEL | extended traces; # 1.04 |15.09.99| JSTENZEL | documented noAssert; # 1.03 |10.09.99| JSTENZEL | improvements to avoid warnings; # | | JSTENZEL | constructor now checks if the passed handle is open; # | | JSTENZEL | all methods now check if the the handle is still opened; # | | JSTENZEL | improved POD formatting; # | | JSTENZEL | message fix; # 1.02 |09.09.99| JSTENZEL | comment fixes; # | | JSTENZEL | better fcntl() calls (saving original flags really), # | | | inspired by code pieces in "Advanced Perl Programming"; # | | JSTENZEL | non blocking mode is now activated only during transfers; # | | JSTENZEL | modified some traces; # 1.01 |09.09.99| JSTENZEL | adding initial select() calls to avoid obstipation trouble; # | | JSTENZEL | using IO::Select now; # | | JSTENZEL | the waiting select() call to complete incomplete transfers # | | | now waits SPECIFICALLY on the socket AND with timeout - # | | | so if the socket is ready before the timeout period ends, # | | | the process is accelerated now (select() returns earlier); # | | JSTENZEL | SIGPIPE is now temporarily disabled in send() and receive() # | | | to avoid trouble with sockets closing during a transfer; # 1.00 |07.09.99| JSTENZEL | new, derived from Admin::IO::common; # --------------------------------------------------------------------------------------- # = POD SECTION ========================================================================= =head1 NAME B - implements a length based IPC protocol =head1 SCRIPT DATA This manual describes version B<2.03>. =head1 DESCRIPTION Interprocess communication often uses line (or record) oriented protocols. FTP, for example, usually is such a protocol: a client sends a command (e.g. "LS") which is completed by a carriage return. This carriage return is included in the command which is sent to the server process (FTP deamon) which could implement its reading in a way like this: while ($cmd=) { chomp($cmd); performCommand($cmd); } Well, such record oriented, blocked protocols are very useful and simply to implement, but sometimes there is a need to transfer more complex data which has no trailing carriage return, or data which may include more carriage returns inside the message which should not cause the reciepient to think the message is already complete while it is really not. Even if you choose to replace carriage returns by some obscure delimiters, the same could happen again until you switch to a protocol which does not flag the end of a message by special strings. On the other hand, if there is no final carriage return (or whatever flag string) within a message, the end of the message has to be marked another way to avoid blocking by endless waiting for more message parts. A simple way to provide this is to precede a message by a prefix which includes the length of the remaining (real) message. A reciepient reads this prefix, decodes the length information and continues reading until the announced number of bytes came in. B provides a class to build objects which transparently perform such "Iength Iriven Iransfer". A user sends and receives messages by simple method calls, while the LDT objects perform the complete translation into and from LDT messages (with prefix) and all the necessary low level IO handling to transfer stream messages on non blocked handles. B objects can be configured to transfer simle string messages as well as complex data structures. Additionally, they allow to delay the transfer of certain messages in a user defined way. =head1 SYNOPSIS Load the module as usual: use IPC::LDT; Make an LDT object for every handle that should be used in an LDT communication: my $asciiClient=new IPC::LDT(handle=>HANDLE); my $objectClient=new IPC::LDT(handle=>HANDLE, objectMode=>1); Now you can send and receive data: $data=$asciiClient->receive; @objects=$objectClient->receive; B<> $asciiClient=$client->send("This is", " a message."); $objectClient=$client->send("These are data:", [qw(a b c)]); =cut # check perl version require 5.00503; # = PACKAGE SECTION (internal helper packages) ========================================== # declare package package IPC::LDT::Filter::MeTrace; # declare package version $VERSION=$VERSION=1.00; # set pragmas use strict; # load CPAN modules use Filter::Util::Call; # The main function - see the Filter::Util::Call manual for details. # I'm using the closure variant here. It's shorter. sub import { # get parameter my ($self)=@_; # define and register the filter filter_add( sub { # get parameter my ($self)=@_; # declare variable my ($status); # remove trace code ... s/\$me->trace\(.+?\);//g if ($status=filter_read())>0; # reply state $status; } ); } # reply a true value to flag successfull init 1; # reset pragmas; no strict; # declare package package IPC::LDT::Filter::Assert; # declare package version $VERSION=$VERSION=1.00; # set pragmas use strict; # load CPAN modules use Filter::Util::Call; # The main function - see the Filter::Util::Call manual for details. # I'm using the closure variant here. It's shorter. sub import { # get parameter my ($self, $noAssert)=@_; # define and register the filter filter_add( sub { # get parameter my ($self)=@_; # declare variable my ($status); # remove trace code ... if (($status=filter_read())>0) { if ($noAssert) {s/bug\(.+?\)[^;]*?;//g;} else {s/bug\((['"])/confess\($1\[BUG\] /g;} } # reply state $status; } ); } # reply a true value to flag successfull init 1; # reset pragmas no strict; # = PACKAGE SECTION ====================================================================== # declare package package IPC::LDT; # filters BEGIN { # deactivate compiler checks no strict 'refs'; # trace filter (first line to avoid useless warnings) defined ${join('::', __PACKAGE__, 'Trace')} ? 1 : 1; IPC::LDT::Filter::MeTrace::import() unless ${join('::', __PACKAGE__, 'Trace')}; # assertion filter (first line to avoid useless warnings) defined ${join('::', __PACKAGE__, 'noAssert')} ? 1 : 1; IPC::LDT::Filter::Assert::import(${join('::', __PACKAGE__, 'noAssert')}); } use Exporter (); @ISA=qw(Exporter); # declare package version $VERSION=2.03; # declare fields use fields qw( delayFilter delayQueue fileno handle msg objectMode rc select startblockLength traceMode ); =pod =head2 Exports No symbol is exported by default. You can explicitly import LDT_CLOSED, LDT_READ_INCOMPLETE, LDT_WRITE_INCOMPLETE, LDT_OK and LDT_INFO_LENGTH which are described in section I. =cut # declare exporter modules @EXPORT=qw(); @EXPORT_OK=qw( LDT_CLOSED LDT_INFO_LENGTH LDT_OK LDT_READ_INCOMPLETE LDT_WRITE_INCOMPLETE ); # = PRAGMA SECTION ======================================================================= # set pragmas use strict; # = LIBRARY SECTION ====================================================================== # load modules use Carp; # message handling; use POSIX; use Storable; # data serialization; use IO::Select; # a select() wrapper; # = CODE SECTION ========================================================================= # exportable constants use constant LDT_INFO_LENGTH=>8; # length of a handle message length string; # internal constants use constant HANDLE_RETRY_COUNT=>100; # number of trials to complete a message from a handle; use constant HANDLE_RETRY_DELAY=>0.2; # number of seconds until a new attempt to complete a reading; =pod =head1 Global Variables =head2 Settings =over 4 =item Traces You may set the module variable B<$IPC::LDT::Trace> I the module is loaded (that means in a I block before the "use" statement) to activate the built in trace code. If not prepared this way, all runtime trace settings (e.g. via the constructor parameter I) will take I because the trace code will have been filtered out at compile time for reasons of performance. (This means that no trace message will appear.) I B<$IPC::LDT::Trace> is set before the module is loaded, te builtin trace code is active and can be deactivated or reactivated at runtime globally (for all objects of this class) by unsetting or resetting of this module variable. Alternatively, you may choose to control traces for certain objects by using the constructor parameter I. So, if you want to trace every object, set B<$IPC::LDT::Trace> initially and load the module. If you want to trace only certain objects, additionally unset B<$IPC::LDT::Trace> after the module is loaded and construct these certain objects with constructor flag I. =item Assertions It is a good tradition to build self checks into a code. This makes code execution more secure and simplifies bug searching after a failure. On the other hand, self checks decrease code performance. That's why you can filter out the self checking code (which is built in and activated by default) by setting the module variable B<$IPC::LDT::noAssert> I the module is loaded. The checks will be removed from the code before they reach the compiler. Setting or unsetting this variable after the module was loaded takes I. =back =head1 CONSTANTS =head2 Error codes =over 4 =item LDT_CLOSED a handle related to an LDT object was closed when reading or writing should be performed on it; =item LDT_READ_INCOMPLETE a message could not be (completely) read within the set number of trials; =item LDT_WRITE_INCOMPLETE a message could not be (completely) written within the set number of trials; =back =cut # error constants - these are made public (but not exported by default) use constant LDT_OK =>100; # all right; use constant LDT_CLOSED =>-1; # the handle was closed while it should be read; use constant LDT_READ_INCOMPLETE =>-2; # a handle message could not be read completely; use constant LDT_WRITE_INCOMPLETE=>-3; # a handle message could not be read completely; =pod =head1 METHODS =cut # ------------------------------------------------------------------- =pod =head2 new() The constructor builds a new object for data transfers. All parameters except of the class name are passed named (this means, by a hash). B =over 4 =item Class name the first parameter as usual - passed implicitly by Perl: my $asciiClient=new IPC::LDT(...); The method form of construtor calls is not supported. =item handle The handle to be used to perform the communication. It has to be opened already and will not be closed if the object will be destroyed. Example: handle => SERVER A closed handle is I accepted. You can use whatever type of handle meets your needs. Usually it is a socket or anything derived from a socket. For example, if you want to perform secure IPC, the handle could be made by Net::SSL. There is only one precondition: the handle has to provide a B method. (You can enorce this even for Perls default handles by simply using B.) =item objectMode Pass a true value if you want to transfer data structures. If this setting is missed or a "false" value is passed, the object will transfer strings. Data structures will be serialized via I for transfer. Because of this, such a communication is usually restricted to partners which could use I methods as well to reconstruct the data structures (which means that they are written in Perl). String transfer objects, on the other hand, can be used to cimmunicate with any partner who speaks the LDT protocol. We use Java and C clients as well as Perl ones, for example. Example: objectMode => 1 The transfer mode may be changed while the object is alive by using the methods I and I. =item startblockLength sets the length of the initial info block which preceds every LDT message coding the length of the remaining message. This setting is done in bytes. If no value is provided, the builtin default value I is used. (This value can be imported in your own code, see section "I" for details.) I is designed to meet usual needs. Example: startblockLength => 4 =item traceMode Set this flag to a true value if you want to trace to actions of the module. If set, messages will be displayed on STDERR reporting what is going on. Traces for objects of this class can be activated (regardless of this constructor parameter) via I<$IPC::LDT::Trace>. This is described more detailed in section "I". Example: traceMode => 1 =back B A successfull constructor call replies the new object. A failed call replies an undefined value. B my $asciiClient=new IPC::LDT(handle=>HANDLE); my $objectClient=new IPC::LDT(handle=>HANDLE, objectMode=>1); =cut # ------------------------------------------------------------------- sub new { # get parameters bug("Number of parameters should be even") unless @_ % 2; my ($class, %switches)=@_; # and check them bug("Missing class name parameter") unless $class; bug("Constructor called as method, use copy() method instead") if ref($class); bug("Missing handle parameter") unless exists $switches{'handle'} and $switches{'handle'}; # declare function variables my ($me); # make new object { no strict 'refs'; $me=bless([\%{"$class\::FIELDS"}], $class); } # check the handle for being valid and open if (defined $switches{'handle'}->fileno) { # build and init the object $me->{'handle'}=$switches{'handle'}; $me->{'fileno'}=$me->{'handle'}->fileno; $me->{'msg'}=$me->{'rc'}=''; $me->{'objectMode'}=(exists $switches{'objectMode'} and $switches{'objectMode'}) ? 1 : 0; $me->{'startblockLength'}=(exists $switches{'startblockLength'} and $switches{'startblockLength'}>0) ? $switches{'startblockLength'} : LDT_INFO_LENGTH; $me->{'traceMode'}=(exists $switches{'trace'} and $switches{'trace'}) ? 1: 0; $me->{'select'}=new IO::Select($me->{'handle'}); # trace, if necessary $me->trace("LDT $me->{'fileno'}: object is made."); # reply the new object return $me; } else { # invalid or closed handle passed return undef; } } # internal method sub DESTROY { # get and check parameters my ($me)=@_; bug("Missed object parameter") unless $me; bug("Object parameter is no ${\(__PACKAGE__)} object") unless ref($me) eq __PACKAGE__; # get fileno (and handle status this way) my $fileno=$me->{'handle'}->fileno; # trace, if necessary $me->trace("LDT ${\($fileno?$fileno:qq(with closed handle, was $me->{'fileno'}))}: object dies. Queue is", (defined $me->{'delayQueue'} and @{$me->{'delayQueue'}}) ? 'filled.' : 'empty.'); } # ------------------------------------------------------------------- =pod =head2 setObjectMode() Switches the LDT object to "object trasnfer mode" which means that is can send and receive Perl data structures now. Runtime changes of the transfer mode have to be exactly synchronized with the partner the object is talking with. See the constructor (I) description for details. B =over 4 =item object An LDT object made by I. =back B $asciiClient->setObjectMode; =cut # ------------------------------------------------------------------- sub setObjectMode { # get and check parameters my ($me)=@_; bug("Missed object parameter") unless $me; bug("Object parameter is no ${\(__PACKAGE__)} object") unless ref($me) eq __PACKAGE__; # trace, if necessary $me->trace("LDT $me->{'fileno'}: object switches into object mode."); # modify mode $me->{'objectMode'}=1; } # ------------------------------------------------------------------- =pod =head2 setAsciiMode() Switches the LDT object to "ASCII trasnfer mode" which means that is sends and receives strings now. Runtime changes of the transfer mode have to be exactly synchronized with the partner the object is talking with. See the constructor (I) description for details. B =over 4 =item object An LDT object made by I. =back B $objectClient->setAsciiMode; =cut # ------------------------------------------------------------------- sub setAsciiMode { # get and check parameters my ($me)=@_; bug("Missed object parameter") unless $me; bug("Object parameter is no ${\(__PACKAGE__)} object") unless ref($me) eq __PACKAGE__; # trace, if necessary $me->trace("LDT $me->{'fileno'}: objekt switches into ASCII mode."); # modify mode $me->{'objectMode'}=0; } # ------------------------------------------------------------------- =pod =head2 delay() Sometimes you do not want to send messages immediatly but buffer them for later delivery, e.g. to set up a certain send order. You can use I to install a filter which enforces the LDT object to delay the delivery of all matching messages until the next call of I. The filter is implemented as a callback of I. As long as it is set, I calls it to check a message for sending or buffering it. You can overwrite a set filter by a subsequent call of I. Messages already collected will remain collected. To send delayed messages you have to call I. If the object is detroyed while messages are still buffered, they will not be delivered but lost. B =over 4 =item object An LDT object made by I. =item filter A code reference. It should await a reference to an array which will contain the message (possibly in parts). It should reply a true or false value to flag if the passed message has to be delayed. It is recommended to provide a I function because it will be called everytime I will be invoked. =back B $ldt->delay(\&filter); with filter() defined as sub filter { # get and check parameters my ($msg)=@_; confess "Missed message parameter" unless $msg; confess "Message parameter is no array reference" unless ref($msg) and ref($msg) eq 'ARRAY'; C<> # check something $msg->[0] eq 'delay me'; } See I for a complete example. =cut # ------------------------------------------------------------------- sub delay { # get and check parameters my ($me, $filter)=@_; bug("Missed object parameter") unless $me; bug("Object parameter is no ${\(__PACKAGE__)} object") unless ref($me) eq __PACKAGE__; bug("Missed filter parameter") unless $filter; bug("Filter parameter is no code reference ($filter)") unless ref($filter) and ref($filter) eq 'CODE'; # trace, if necessary $me->trace("LDT $me->{'fileno'}: object is setting a new delay filter."); # store filter $me->{'delayFilter'}=$filter; $me->{'delayQueue'}=[] unless defined $me->{'delayQueue'} and @{$me->{'delayQueue'}}; # keep messages possibly delayed by another filter; } # ------------------------------------------------------------------- =pod =head2 undelay() Sends all messages collected by a filter which was set by I. The filter is I, so that every message will be sent by I immediatly afterwards again. In case of no buffered message and no set filter, a call of this message takes no effect. B =over 4 =item object An LDT object made by I. =back B $ldt->undelay; Here comes a complete example to illustrate how delays can be used. filter definition: sub filter { # check something $msg->[0] eq 'delay me'; } usage: # send messages $ldt->send('send me', 1); # sent; $ldt->send('delay me', 2); # sent; # activate filter $ldt->delay(\&filter); # send messages $ldt->send('send me', 3); # sent; $ldt->send('delay me', 4); # delayed; $ldt->send('send me', 5); # sent; $ldt->send('delay me', 6); # delayed; # send collected messages, uninstall filter $ldt->undelay; # sends messages 4 and 6; # send messages $ldt->send('send me', 7); # sent; $ldt->send('delay me', 8); # sent; =cut # ------------------------------------------------------------------- sub undelay { # get and check parameters my ($me)=@_; bug("Missed object parameter") unless $me; bug("Object parameter is no ${\(__PACKAGE__)} object") unless ref($me) eq __PACKAGE__; # check for a set filter if (defined $me->{'delayFilter'}) { # trace, if necessary $me->trace("LDT $me->{'fileno'}: object stops delay and sends", scalar(@{$me->{'delayQueue'}}), "stored message(s)."); # remove filter $me->{'delayFilter'}=undef; # send all delayed messages $me->send(@$_) foreach @{$me->{'delayQueue'}}; # empty queue $me->{'delayQueue'}=undef; } else { # trace, if necessary $me->trace("LDT $me->{'fileno'}: object was enforced to stop delay, but there was no delay set before."); } } # ------------------------------------------------------------------- =pod =head2 send() Sends the passed message via the related handle (which was passed to I). The message, which could be passed as a list of parts, is sent as a (concatenated) string or as serialized Perl data depending on the settings made by the constructor flag I and calls of I or I, respectively. In case of an error, the method replies an undefined value and stores both an error code and an error message inside the object which could be accessed via the object variables "rc" and "msg". (See I for a list of error codes.) An error will occur, for example, if the handle related to the LDT object was closed (possibly outside the module). An error is detected as well if a I call of I or I already detected an error. This behaviour is implemented for reasons of security, however, if you want to try it again regardless of the objects history, you can reset the internal error state by I. For reasons of efficiency, sent messages may be splitted up into parts by the underlaying (operating or network) system. The reciepient will get the message part by part. On the other hand, the sender might only be able to I them part by part as well. That is why this I method retries writing attempts to the associated handle until the complete message could be sent. Well, in fact it stops retries earlier if an inacceptable long period of time passed by without being successfull. If that happens, the method replies I and provides an error code in the object variable "rc". I I I B =over 4 =item object An LDT object made by I. =item message (a list) All list elements will be combined to the resulting message as done by I or I (that means, I separating parts by additional whitespaces). =back B $asciiClient->send('Silence?', 'Maybe.') or die $asciiClient->{'msg'}; B<> $objectClient->send({oops=>1, beep=>[qw(7)]}, $scalar, \@array); B If the connection is closed while the message is sent, the signal I might arrive and terminate the complete program. To avoid this, I is ignored while this method is running. The handle associated with the LDT object is made I during data transmission. The original mode is restored before the method returns. =cut # ------------------------------------------------------------------- sub send { # get and check parameters my ($me, @msg)=@_; bug("Missed object parameter") unless $me; bug("Object parameter is no ${\(__PACKAGE__)} object") unless ref($me) eq __PACKAGE__; bug("Missed message parameter(s)") unless @msg; # trace, if necessary $me->trace("LDT $me->{'fileno'}: starting send."); # check state if ($me->{'rc'} and $me->{'rc'}!=LDT_OK) { # trace, if necessary $me->trace("LDT $me->{'fileno'}: message unsent: object is in state $me->{'rc'}."); # flag error undef; } elsif (not defined $me->{'handle'}->fileno) { # trace, if necessary $me->trace("LDT $me->{'fileno'}: message unsent: related handle was closed."); # set internal flags $me->{'rc'}=LDT_CLOSED; $me->{'msg'}='Related handle was closed.'; # flag error undef; } elsif (defined $me->{'delayFilter'} and &{$me->{'delayFilter'}}(\@msg)) { # messages should be delayed, queue the new one push(@{$me->{'delayQueue'}}, \@msg); # trace, if necessary $me->trace("LDT $me->{'fileno'}: message unsent: handle was closed."); } else { # temporarily disable SIGPIPE local($SIG{'PIPE'})='IGNORE'; # build the message as necessary my $msg=join('', @msg); $msg=Storable::nfreeze([@msg]) if $me->{'objectMode'}; # store original handle access flags my $handleFlags=fcntl($me->{'handle'}, F_GETFL, 0); # activate non blocking mode fcntl($me->{'handle'}, F_SETFL, $handleFlags | O_NONBLOCK); # trace, if necessary $me->trace("LDT $me->{'fileno'}: new message on the way ..."); # send my $rc=$me->writeHandle(\(join('', sprintf(join('', '%.', $me->{'startblockLength'}, 'd'), length($msg)), $msg))); # trace, if necessary $me->trace("LDT $me->{'fileno'}: sent message: $msg."); # reset file handle access flags fcntl($me->{'handle'}, F_SETFL, $handleFlags); # reply result state $rc; } } # ------------------------------------------------------------------- =pod =head2 reset If an error occurs while data are transmitted, further usage of the associated handle is usually critical. That is why I and I stop operation after a transmission error, even if you repeat their calls. This should I your program and make it more stable (e.g. writing to a closed handle migth cause a fatal error and even terminate your program). Nevertheless, if you really want to retry after an error, here is the I method which resets the internal error flags - unless the associated handle was not already closed. B =over 4 =item object An LDT object made by I. =back B $ldtObject->reset; =cut sub reset { # get and check parameters my ($me)=@_; bug("Missed object parameter") unless $me; bug("Object parameter is no ${\(__PACKAGE__)} object") unless ref($me) eq __PACKAGE__; # trace, if necessary $me->trace("LDT $me->{'fileno'}: object resets error state."); # reset state buffer $me->{'msg'}=$me->{'rc'}='' unless $me->{'rc'}==LDT_CLOSED; } # ------------------------------------------------------------------- =pod =head2 receive() reads a message from the associated handle and replies it. In case of an error, the method replies an undefined value and provides both a return code (see I) and a complete message in the object variables "rc" and "msg", respectively, where you can read them. An error will occur, for example, if the handle related to the LDT object was closed (possibly outside the module). An error is detected as well if a I call of I or I already detected an error. This behaviour is implemented for reasons of security, however, if you want to try it again regardless of the objects history, you can reset the internal error state by I. For reasons of efficiency, sent messages may be splitted up into parts by the underlaying (operating or network) system. The reciepient will get the message part by part. That is why this I method retries reading attempts to the associated handle until the complete message could be read. Well, in fact it stops retries earlier if an inacceptable long period of time passed by without being successfull. If that happens, the method replies I and provides an error code in the object variable "rc". I I I B =over 4 =item object An LDT object made by I. =back The received message is replied as a string in ASCII mode, and as a list in object mode. B $msg=$asciiClient->receive or die $asciiClient->{'msg'}; B<> @objects=$objectClient->receive or die $objectClient->{'msg'}; B If the connection is closed while the message is read, the signal I might arrive and terminate the complete program. To avoid this, I is ignored while this method is running. The handle associated with the LDT object is made I during data transmission. The original mode is restored before the method returns. =cut # ------------------------------------------------------------------- sub receive { # declare function variables my ($buffer, $mlen)=('', ''); # get and check parameters my ($me)=@_; bug("Missed object parameter") unless $me; bug("Object parameter is no ${\(__PACKAGE__)} object") unless ref($me) eq __PACKAGE__; # trace, if necessary $me->trace("LDT $me->{'fileno'}: startet receiving."); # check state if ($me->{'rc'} and $me->{'rc'}!=LDT_OK) { # trace, if necessary $me->trace("LDT $me->{'fileno'}: stopped receiving: object is in state $me->{'rc'}."); # flag error undef; } elsif (not defined $me->{'handle'}->fileno) { # trace, if necessary $me->trace("LDT $me->{'fileno'}: stopped receiving: object is in state $me->{'rc'}."); # set internal flags $me->{'rc'}=LDT_CLOSED; $me->{'msg'}='Related handle was closed.'; # flag error undef; } else { # temporarily disable SIGPIPE local($SIG{'PIPE'})='IGNORE'; # store original handle access flags my $handleFlags=fcntl($me->{'handle'}, F_GETFL, 0); # activate non blocking mode fcntl($me->{'handle'}, F_SETFL, $handleFlags | O_NONBLOCK); # read message, start with length info my $rc=($me->readHandle(\$mlen) and $me->readHandle(\$buffer, $mlen)); # reset file handle access flags fcntl($me->{'handle'}, F_SETFL, $handleFlags); # check transfer success unless ($rc) { # failed: reply state return undef; } else { # thaw result list, if necessary my @buffer=@{Storable::thaw($buffer)} if $buffer and $me->{'objectMode'}; # reply result in correct form $me->{'objectMode'} ? @buffer : $buffer; } } } # ------------------------------------------------------------------- # # Internal method: Reads a number of bytes from the object handle. # # ------------------------------------------------------------------- sub readHandle { # declare function variables my ($readBytes, $trials); # get and check parameters my ($me, $targetBufferRef, $targetLength)=@_; bug("Missed object parameter") unless $me; bug("Object parameter is no ${\(__PACKAGE__)} object") unless ref($me) eq __PACKAGE__; bug("Missed target buffer parameter") unless $targetBufferRef; bug("Target buffer parameter is no scalar reference") unless ref $targetBufferRef eq 'SCALAR'; # set default length, if necessary $targetLength=$me->{'startblockLength'} unless defined $targetLength; # read! my $length=$targetLength; while ($length) { # perform reading $readBytes=sysread($me->{'handle'}, $$targetBufferRef, $length, $targetLength-$length); # all right? if (defined $readBytes) { # connection closed? unless ($readBytes) { # the handle closed! $me->{'msg'}="Related handle was closed (while reading was performed)."; $me->{'rc'}=LDT_CLOSED; $me->trace("LDT $me->{'fileno'}: $me->{'msg'}"); return undef; } # If here, we read a little bit more - and this bit was already added # to our buffer. All we still have to do is to update our length # counter and to reset the trial one. $length-=$readBytes; $trials=0; $me->trace("LDT $me->{'fileno'}: read $readBytes bytes gelesen, still waiting for $length."); } else { if ($!==EAGAIN and ++$trialstrace("LDT $me->{'fileno'}: waitig for a new chance to read remaining $length bytes ($trials. trial)."); $me->{'select'}->can_read(HANDLE_RETRY_DELAY); } else { # anything is wrong here $me->{'msg'}="Cannot read the message completely."; $me->{'rc'}=LDT_READ_INCOMPLETE; $me->trace("LDT $me->{'fileno'}: $me->{'msg'}"); return undef; } } } # trace, if necessary $me->trace("LDT $me->{'fileno'}: message received: \"$$targetBufferRef\"."); # if we are here, we were successfull $me->{'rc'}=LDT_OK; 1; } # ------------------------------------------------------------------- # # Internal method: Writes a number of bytes to the object handle. # # ------------------------------------------------------------------- sub writeHandle { # declare function variables my ($writtenBytes, $trials, $length, $srcLength); # get and check parameters my ($me, $srcBufferRef)=@_; bug("Missed object parameter") unless $me; bug("Object parameter is no ${\(__PACKAGE__)} object") unless ref($me) eq __PACKAGE__; bug("Missed source buffer parameter") unless $srcBufferRef; bug("Source buffer parameter is no scalar reference") unless ref $srcBufferRef eq 'SCALAR'; # write! $length=$srcLength=length($$srcBufferRef); while ($length) { # perform writing $writtenBytes=syswrite($me->{'handle'}, $$srcBufferRef, $length, $srcLength-$length); # all right? if (defined $writtenBytes) { # connection closed? unless ($writtenBytes) { # the handle closed! $me->{'msg'}="Related handle was closed (while writing to it)."; $me->{'rc'}=LDT_CLOSED; $me->trace("LDT $me->{'fileno'}: $me->{'msg'}"); return undef; } # If here, we wrote a little bit more. All we still # have to do is to update our length counter and to reset the trial one. $length-=$writtenBytes; $trials=0; $me->trace("LDT $me->{'fileno'}: wrote $writtenBytes bytes, $length bytes still waiting."); } else { if ($!==EAGAIN and ++$trialstrace("LDT $me->{'fileno'}: waiting for a new chance to write remaining $length bytes ($trials. trial)."); $me->{'select'}->can_write(HANDLE_RETRY_DELAY); } else { # anything is wrong here $me->{'msg'}="Cannot write the message completely."; $me->{'rc'}=LDT_WRITE_INCOMPLETE; $me->trace("LDT $me->{'fileno'}: $me->{'msg'}"); return undef; } } } # trace, if necessary $me->trace("LDT $me->{'fileno'}: message sent completely: \"$$srcBufferRef\"."); # if we are here, we were successfull $me->{'rc'}=LDT_OK; 1; } # ------------------------------------------------------------------- # Internal trace method. # ------------------------------------------------------------------- sub trace { # get and check parameters my ($me, @msg)=@_; bug("Missed object reference parameter") unless $me; bug("Object parameter is no ${\(__PACKAGE__)} object") unless ref($me) eq __PACKAGE__; bug("Missed message parameter(s)") unless @msg; # deactivate compiler checks no strict 'refs'; # display trace (use print() instead of warn() because the message may contain freezed data) print STDERR "[Trace] ", time, ": @msg\n" if ${join('::', __PACKAGE__, 'Trace')} or $me->{'traceMode'}; } # ---------------------------------------------------------------------------------------------- =pod =head2 version() replies the modules version. It simply replies $IPC::LDT::VERSION and is implemented only to provide compatibility to other object modules. Example: # get version warn "[Info] IPC is performed by IPC::LDT ", IPC::LDT::version, ".\n"; =cut # ---------------------------------------------------------------------------------------------- sub version { # reply module version $IPC::LDT::VERSION; } # = MODULE TRAILER SECTION =============================================================== # mark a completely read module 1; # = POD TRAILER SECTION ================================================================== =pod =head1 ENVIRONMENT =head1 FILES =head1 SEE ALSO =head1 NOTES =head1 EXAMPLE To share data between processes, you could embed a socket into an LDT object. my $ipc=new IO::Socket(...); my $ldt=new IPC::LDT(handle=>$ipc, objectMode=>1); Now you are able to send data: my $dataRef=[{o=>1, lal=>2, a=>3}, [[qw(4 5 6)], [{oo=>'ps'}, 7, 8, 9]]]; $ldt->send($dataRef) or die $ldt->{'msg'}; or receive them: @data=$ldt->receive or die $ldt->{'msg'}; =head1 AUTHOR Jochen Stenzel (perl@jochen-stenzel.de) =head1 COPYRIGHT Copyright (c) 1998-2000 Jochen Stenzel. All rights reserved. This program is free software, you can redistribute it and/or modify it under the terms of the Artistic License distributed with Perl version 5.003 or (at your option) any later version. Please refer to the Artistic License that came with your Perl distribution for more details. =cut