NAME
Linux::Event::Framer - Define message boundaries for ordered byte streams
SYNOPSIS
package LineStream;
use parent 'Linux::Event::IO::Sock::Stream';
use Linux::Event::Framer 'Delimiter', "\n";
sub on_message ($self, $message) {
say "received: $message";
$self->send($message);
}
DESCRIPTION
Stream sockets, pipes, and terminals carry bytes.
They do not inherently know where one application message ends and the next begins.
Linux::Event::Framer lets an ordered-byte subclass declare that message boundary once.
For example:
use Linux::Event::Framer 'Delimiter', "\n";
means:
each message ends at "\n"
while:
use Linux::Event::Framer 'Fixed', 32;
means:
every message is exactly 32 bytes
and:
use Linux::Event::Framer 'U32BE';
means:
each message starts with a four-byte big-endian payload length
Linux::Event performs built-in framing in native code.
FRAMING IS CLASS POLICY
A framer is declared on a subclass:
package LineStream;
use parent 'Linux::Event::IO::Sock::Stream';
use Linux::Event::Framer 'Delimiter', "\n";
The declaration must come after use parent.
The framer belongs to the class.
Linux::Event does not create a separate Perl framer object for every connection.
Therefore every LineStream object uses the same wire format.
This is intentional: framing describes a protocol, not per-connection application state.
CALLBACKS CAN STILL BE PER OBJECT
Although framing is class policy, application callbacks may still be supplied at construction time.
For example:
my $stream = LineStream->new(
fh => $fh,
on_message => sub ($self, $message) {
handle_message($application, $message);
},
);
The framing remains:
Delimiter "\n"
for every LineStream.
Only that object's application callback changes.
A constructor callback overrides a same-named subclass callback for that object.
FRAMERS WORK WITH MORE THAN SOCKETS
Framing belongs to ordered bytes, not specifically TCP.
The same framer system works with subclasses of:
Linux::Event::IO::Sock::Stream
Linux::Event::IO::Pipe
Linux::Event::IO::TTY
For example, terminal lines can be framed exactly like socket lines:
package Console;
use parent 'Linux::Event::IO::TTY';
use Linux::Event::Framer 'Delimiter', "\n";
sub on_message ($self, $line) {
$self->write("You typed: $line\n");
}
A pipe can use the same policy:
package LinePipe;
use parent 'Linux::Event::IO::Pipe';
use Linux::Event::Framer 'Delimiter', "\n";
CHOOSING A FRAMER
Linux::Event currently provides these built-in framing families:
Delimiter-
A byte sequence terminates each message.
Typical examples are newline or CRLF protocols.
Fixed-
Every message has exactly the same byte length.
LengthPrefix-
A one-, two-, or four-byte unsigned integer gives the payload length.
U32BE-
A convenient four-byte big-endian payload-length prefix.
Netstring-
Canonical:
length:payload,framing.
Varint-
An unsigned LEB128 integer gives the payload length.
DecimalLength-
ASCII decimal digits followed by one separator byte give the payload length.
This includes RFC 6587 octet-counted syslog style.
If none of these matches the protocol, do not force the protocol into an incorrect framing model.
Use raw on_data parsing instead.
DELIMITER
Use Delimiter when a specific byte sequence ends each message:
package LineStream;
use parent 'Linux::Event::IO::Sock::Stream';
use Linux::Event::Framer 'Delimiter', "\n";
The delimiter can contain more than one byte:
use Linux::Event::Framer 'Delimiter', "\r\n";
Linux::Event correctly handles delimiters that cross kernel read boundaries.
For example, one read may end with:
"\r"
and the next may begin with:
"\n"
without confusing the framing parser.
include_delimiter
By default, the delimiter is consumed but not included in the message delivered to on_message.
To include it:
use Linux::Event::Framer 'Delimiter', "\r\n",
include_delimiter => 1;
max_frame
Optionally limit the payload size:
use Linux::Event::Framer 'Delimiter', "\n",
max_frame => 1_048_576;
The limit is measured in bytes before the delimiter.
Sending
send appends the configured delimiter automatically:
$self->send("hello");
With a newline delimiter, the wire bytes become:
hello\n
Use write instead when raw bytes should be sent without framing.
FIXED-SIZE MESSAGES
Use Fixed when every message has exactly the same byte length:
package RecordStream;
use parent 'Linux::Event::IO::Sock::Stream';
use Linux::Event::Framer 'Fixed', 32;
The equivalent explicit form is:
use Linux::Event::Framer 'Fixed',
size => 32;
Each on_message callback receives exactly 32 bytes.
send requires exactly 32 payload bytes.
For example:
$self->send($record);
fails if $record is not exactly the configured size.
BINARY LENGTH PREFIX
Use LengthPrefix when a binary integer before each message states the payload length:
package MessageStream;
use parent 'Linux::Event::IO::Sock::Stream';
use Linux::Event::Framer 'LengthPrefix',
bytes => 2,
endian => 'big',
max_frame => 1_048_576;
bytes
bytes may be:
1
2
4
The default is:
4
endian
endian may be:
big
little
The default is:
big
include_prefix
By default, the length prefix is not included in the delivered message.
To include it:
include_prefix => 1
max_frame
Optionally limit the payload byte length:
max_frame => 1_048_576
Sending
send($payload) calculates the payload length and prepends the correctly encoded prefix.
The encoded length is the payload length, not the combined prefix-plus-payload length.
U32BE
U32BE is a convenience framer for a common binary format:
package BinaryStream;
use parent 'Linux::Event::IO::Sock::Stream';
use Linux::Event::Framer 'U32BE',
max_frame => 16 * 1024 * 1024;
It means exactly:
four-byte unsigned big-endian payload length
It is equivalent on the wire to:
use Linux::Event::Framer 'LengthPrefix',
bytes => 4,
endian => 'big';
U32BE also supports include_prefix and max_frame.
The width and byte order cannot be changed.
NETSTRING
Use Netstring for canonical netstrings:
package NetstringStream;
use parent 'Linux::Event::IO::Sock::Stream';
use Linux::Event::Framer 'Netstring',
max_frame => 1_048_576;
Sending:
$self->send("hello");
produces:
5:hello,
Linux::Event validates the netstring format while parsing.
Malformed or noncanonical netstrings are framing errors.
VARINT LENGTH PREFIX
Use Varint when the payload length is encoded as unsigned LEB128:
package CompactStream;
use parent 'Linux::Event::IO::Sock::Stream';
use Linux::Event::Framer 'Varint',
max_frame => 1_048_576;
Small payload lengths require fewer prefix bytes.
Varint supports:
include_prefix
max_frame
send produces the canonical variable-width prefix automatically.
Malformed, overlong, or overflowing prefixes are rejected.
DECIMAL LENGTH
Use DecimalLength when the wire format begins with ASCII decimal payload length:
package SyslogStream;
use parent 'Linux::Event::IO::Sock::Stream';
use Linux::Event::Framer 'DecimalLength',
separator => ' ',
max_frame => 1_048_576;
For example:
$self->send("HELLO");
produces:
5 HELLO
The default separator is one space.
The separator must be exactly one byte and must not be an ASCII digit.
This framing style matches RFC 6587 octet-counted syslog when the default space separator is used.
DecimalLength also supports include_prefix.
RECEIVING MESSAGES
A framed class normally supplies on_message:
sub on_message ($self, $message) {
...
}
or receives one during construction:
my $stream = MessageStream->new(
fh => $fh,
on_message => sub ($self, $message) {
...
},
);
$message is one complete framed application message.
Linux::Event retains incomplete input until enough bytes arrive to form a complete message.
One kernel read may produce:
no complete messages
one complete message
many complete messages
Application code does not need to reconstruct frames across read boundaries.
SENDING MESSAGES
For a framed resource:
$self->send($payload);
applies that class's outbound framing rule.
For example:
Delimiter
payload + delimiter
LengthPrefix
encoded length + payload
Netstring
decimal length + ":" + payload + ","
write remains the raw-byte operation:
$self->write($bytes);
write does not apply framing.
This distinction is useful for protocol handshakes, debugging, or cases where the application intentionally needs direct wire control.
RAW INPUT WITHOUT A FRAMER
A readable ordered-byte object does not have to use a built-in framer.
Without a framer, use on_data:
my $buffer = '';
my $stream = Linux::Event::IO::Sock::Stream->new(
fh => $fh,
on_data => sub ($self, $bytes) {
$buffer .= $bytes;
while (my $record = extract_record(\$buffer)) {
process_record($record);
}
},
);
on_data receives read chunks, not application messages.
A chunk may contain:
part of one protocol message
exactly one message
several messages
The application parser must retain partial state itself.
Use raw mode when the protocol does not match a built-in framing family.
FRAME SIZE LIMITS
For untrusted input, use max_frame where the chosen framer supports it.
For example:
use Linux::Event::Framer 'U32BE',
max_frame => 1_048_576;
This prevents a peer from declaring an unexpectedly large application frame.
The ordered-byte resource also has its independent max_buffer input-storage limit.
max_frame and max_buffer solve different problems:
max_frame-
Maximum allowed protocol message payload.
max_buffer-
Maximum allowed ordered-byte input storage.
A framing violation produces a Linux::Event::Error with type framing and closes through the normal ordered-byte error lifecycle.
MESSAGE BATCHING
Ordinary framed delivery calls:
on_message($self, $message)
once for each complete message.
A high-throughput pipelined protocol may instead explicitly enable message batching through the ordered-byte class tuning:
sub stream_tuning ($class) {
return message_batch_size => 32;
}
and receive:
sub on_messages ($self, $messages) {
process_message($self, $_) for @$messages;
}
$messages is an array reference of complete framed messages.
on_message and on_messages are mutually exclusive for one effective descriptor.
A positive message_batch_size requires on_messages.
Batching changes callback delivery shape.
It does not change the wire format.
Linux::Event does not wait for a later readiness event merely to fill a batch. A partial batch is flushed when the current native read drain finishes.
INHERITANCE
A subclass inherits the nearest framer declaration in its Perl inheritance chain.
For example:
package MessageStream;
use parent 'Linux::Event::IO::Sock::Stream';
use Linux::Event::Framer 'U32BE';
package LoggedMessageStream;
use parent 'MessageStream';
sub on_message ($self, $message) {
log_message($message);
}
LoggedMessageStream still uses U32BE framing.
A concrete class has one effective framing policy.
A class cannot combine framed delivery with raw on_data delivery.
CHANGING PROTOCOLS
Some protocols change their wire format after a handshake.
Linux::Event supports changing between loaded ordered-byte subclasses with transition_to.
For example:
$self->transition_to('BinaryProtocol');
The underlying Linux resource does not change.
A stream socket remains the same connected socket.
Only the protocol descriptor changes.
Unread native input is preserved and interpreted using the target class's policy.
This is useful for transitions such as:
negotiation protocol
->
framed binary protocol
or higher-level protocol upgrades implemented above the reactor.
Queued output that already exists is not reframed.
Future send calls use the new target framing rule.
See the ordered-byte resource documentation for the complete transition_to contract.
FRAMER NAMES
The declaration:
use Linux::Event::Framer 'Delimiter', "\n";
loads:
Linux::Event::Framer::Delimiter
The name is case-sensitive.
There is no separate alias table.
A misspelled or unknown framer name fails while the class is being compiled.
THERE IS NO PER-CONNECTION FRAMER OBJECT
A declaration such as:
use Linux::Event::Framer 'Delimiter', "\n";
records immutable class-level framing configuration.
Linux::Event does not perform:
my $framer = Delimiter->new(...);
for every connection.
Per-object state still exists where required, such as:
partial input bytes
current length-prefix parser progress
output queues
lifecycle state
but the framing definition itself is shared class policy.
This avoids unnecessary Perl objects and repeated dynamic dispatch in the message path.
NATIVE CONSUMERS
This section is for XS extension authors.
Most application code should not use this interface.
A native extension may register a consumer for an ordered-byte subclass:
Linux::Event::Framer->declare_native_consumer(
'My::ProtocolStream',
{
provider => $provider,
abi_version => $abi_version,
operations_address => $native_table_address,
},
);
Native consumers can integrate protocol parsers that should consume input before ordinary Perl message callbacks.
Depending on the provider contract, they may receive complete framed messages or use the raw native-input ABI to inspect the ordered-byte input buffer directly before those bytes are converted into Perl scalars.
This is the extension boundary used for specialized high-performance protocol engines.
It is not a second public Perl framing API.
See docs/ORDERED-BYTE-CONSUMER-ABI.md for the ABI and provider-lifetime contract.
PERFORMANCE MODEL
Built-in inbound framing runs in native code.
Linux::Event keeps parser configuration as cached class policy and maintains only changing parser state per resource.
Complete messages cross into Perl only when semantic application delivery is required.
Outbound send applies the selected built-in framing rule and places the resulting bytes into the normal native write queue.
Optional message batching can reduce Perl callback crossings further for suitable pipelined protocols.
SEE ALSO
Linux::Event, Linux::Event::IO::Sock::Stream, Linux::Event::IO::Pipe, Linux::Event::IO::TTY, Linux::Event::Framer::Delimiter, Linux::Event::Framer::Fixed, Linux::Event::Framer::LengthPrefix, Linux::Event::Framer::U32BE, Linux::Event::Framer::Netstring, Linux::Event::Framer::Varint, Linux::Event::Framer::DecimalLength, docs/FRAMING.md, docs/CHOOSING-A-FRAMER.md.