++ed by:

1 non-PAUSE user.

Felipe Gasper
and 1 contributors


IO::Framed - Convenience wrapper for frame-based I/O



    #See below about seed bytes.
    my $reader = IO::Framed::Read->new( $in_fh, 'seed bytes' );

    #This returns undef if the $in_fh doesn’t have at least
    #the given length (5 in this case) of bytes to read.
    $frame = $reader->read(5);

Writing, unqueued (i.e., for blocking writes):

    my $writer = IO::Framed::Write->new( $out_fh );

    #The second parameter (if given) is executed immediately after the final
    #byte of the payload is written. For blocking I/O this happens
    #before the following method returns.
    $writer->write('hoohoo', sub { print 'sent!' } );

Writing, queued (for non-blocking writes):


    #This just adds to a memory queue:
    $writer->write('hoohoo', sub { print 'sent!' } );

    #This will be 1, since we have 1 message/frame queued to send.

    #Returns 1 if it empties out the queue; 0 otherwise.
    #Partial frame writes are accommodated; the callback given as 2nd
    #argument to write() only fires when the queue item is sent completely.
    my $empty = $writer->flush_write_queue();

You can also use IO::Framed::ReadWrite, which combine the features of the read and write modules above.


While writing Net::WAMP I noticed that I was reimplementing some of the same patterns I’d used in Net::WebSocket to parse frames from a stream:

  • Only read() entire frames, with a read queue for any partials.

  • Continuance when a partial frame is delivered.

  • Write queue with callbacks for non-blocking I/O

  • Signal resilience: resume read/write after Perl receives a trapped signal rather than throwing/giving EINTR. (cf. IO::SigGuard)

These are now made available in this distribution.


The premise here is that you expect a given number of bytes at a given time and that a partial read should be continued once it is sensible to do so.

As a result, read() will throw an exception if the number of bytes given for a continuance is not the same number as were originally requested.


    #This reads only 2 bytes, so read() will return undef.

    #… wait for readiness if non-blocking …

    #XXX This die()s because we’re in the middle of trying to read
    #10 bytes, not 4.

    #If this completes the read (i.e., takes in 8 bytes), then it’ll
    #return the full 10 bytes; otherwise, it’ll return undef again.

EINTR prompts a redo of the read operation. EAGAIN and EWOULDBLOCK (the same error generally, but not always) prompt an undef return. Any other failures prompt an instance of IO::Framed::X::ReadError to be thrown.


Writes for blocking I/O are straightforward: the system will always send the entire buffer. The OS’s write() won’t return until everything meant to be written is written. Life is pleasant; life is simple. :)

Non-blocking I/O is trickier. Not only can the OS’s write() only write a portion of the data it’s given, but we also can’t know that the output filehandle is ready right when we want it. This means that we have to queue up our writes then write them once we know (e.g., through select()) that the filehandle is ready. Each write() call, then, enqueues one new buffer to write.

Since it’s often useful to know when a payload has been sent, write() accepts an optional callback that will be executed immediately after the last byte of the payload is written to the output filehandle.

Empty out the write queue by calling flush_write_queue() and looking for a truthy response. (A falsey response means there is still data left in the queue.) get_write_queue_count() gives you the number of queue items left to write. (A partially-written item is treated the same as a fully-unwritten one.)

Note that, while it’s acceptable to activate and deactive the write queue, the write queue must be empty in order to deactivate it. (You’ll get a nasty, untyped exception otherwise!)

write() returns undef on EAGAIN and EWOULDBLOCK. It retries on EINTR, so you should never actually see this error from this module. Other errors prompt a thrown exception.

NB: enable_write_queue() and disable_write_queue() return the object, so you can instantiate thus:

    my $nb_writer = IO::Framed::Write->new($fh)->enable_write_queue();


An empty read or any I/O error besides the ones mentioned previously are indicated via an instance of one of the following exceptions.

All exceptions subclass X::Tiny::Base.


These both have an OS_ERROR property (cf. X::Tiny::Base’s accessor method).


No properties. If this is thrown, your peer has probably closed the connection. You probably should thus always trap this exception.

NOTE: This distribution doesn’t write to $!.


This distribution also includes the following DEPRECATED legacy classes:

  • IO::Frame::Write::Blocking

  • IO::Frame::Write::NonBlocking

  • IO::Frame::ReadWrite::Blocking

  • IO::Frame::ReadWrite::NonBlocking

I’ll keep these in for the time being but eventually WILL remove them. Please adjust any calling code that you might have.




Felipe Gasper (FELIPE)


Copyright 2017 by Gasper Software Consulting, LLC


This distribution is released under the same license as Perl.