Home / Alt manpages / perlipc(1)

  • perlipc(1)
  • User command
  • linux

Use Perl IPC Safely: Signals, Pipes and a Local TCP Socket

You will finish with three small, testable Perl interprocess communication patterns: a deferred signal handler, a parent-child pipe, and a local TCP client/server exchange. The examples use Perl v5.38.2 from Ubuntu's perl-doc package and change no persistent system configuration.

Allow about 25 minutes. You need Perl and a shell. Check the installed versions first:

$ perl -v | sed -n '1,5p'
$ dpkg-query -W -f='${Package} ${Version}\n' perl perl-doc
perl 5.38.2-3.2ubuntu0.6
perl-doc 5.38.2-3.2ubuntu0.6

The exact package revision may differ on your machine. The local perlipc(1) page is the authority for the examples below. Most IPC functions return a false value or undef on failure, so every useful call needs an explicit check.

1. Handle a signal without doing work inside the handler

Perl puts installed signal handlers in the %SIG hash. A robust handler records that something happened, then lets the main loop perform the real work. This avoids hiding file I/O or other complex operations in asynchronous signal code.

use strict;
use warnings;

my $stop = 0;
$SIG{INT} = sub { $stop = 1 };

print "PID $$: press Ctrl-C\n";
while (!$stop) {
    sleep 1;
}
print "stopping cleanly\n";

Save that as /tmp/perl-signal.pl and run it in the foreground. Press Ctrl-C once. The handler only changes a scalar; the loop notices it and prints the final line.

$ perl /tmp/perl-signal.pl
PID 12345: press Ctrl-C
^Cstopping cleanly

The PID is different on every run. This process has no persistent state to undo: stop it with Ctrl-C, or use kill -INT PID from another shell. Do not use kill -KILL as a normal cleanup method because it cannot be handled and skips orderly cleanup.

2. Start a child and read its output through a pipe

Perl's open can create a pipe to an external command, but an explicit pipe, fork and exec make the direction and ownership visible. The parent reads from one filehandle while the child writes to the other. The child calls exec with a list, so the example does not invoke a shell to interpret the command.

use strict;
use warnings;

pipe(my $read, my $write) or die "pipe: $!";
my $pid = fork // die "fork: $!";

if ($pid == 0) {
    close $read or die "close read end: $!";
    print {$write} "child says hello\n" or die "write: $!";
    close $write or die "close write end: $!";
    exit 0;
}

close $write or die "close write end: $!";
while (my $line = <$read>) {
    print "parent received: $line";
}
close $read or die "close read end: $!";
waitpid($pid, 0);
die "child failed\n" if $? != 0;

Run it with perl /tmp/perl-pipe.pl. Expected output is:

parent received: child says hello

Closing the unused end in each process is essential. If a reader still has a write end open, end-of-file may never arrive. If you adapt this to several children, track their PIDs and reap them with waitpid; a SIGCHLD handler must loop with WNOHANG because several children can finish before one handler invocation.

Checkpoint: if the program hangs after printing the line, inspect which process still owns the pipe's write end. Stop a test process with Ctrl-C. There is no file or service to remove.

3. Exchange one line over a local TCP socket

Use TCP when you need an ordered, reliable byte stream. Keep the first test on loopback and choose an unprivileged port such as 19000. Ports below 1024 normally require elevated privileges; this example does not, and it must not be run with sudo.

Create the server as /tmp/perl-server.pl:

use strict;
use warnings;
use IO::Socket::INET;

my $server = IO::Socket::INET->new(
    LocalAddr => '127.0.0.1',
    LocalPort => 19000,
    Listen    => 1,
    Reuse     => 1,
    Proto     => 'tcp',
) or die "server: $!";

my $client = $server->accept() or die "accept: $!";
$client->autoflush(1);
my $line = <$client>;
print {$client} "reply: $line" if defined $line;
close $client or die "client close: $!";
close $server or die "server close: $!";

Start it in one shell:

$ perl /tmp/perl-server.pl

It waits in accept, which is normal. In a second shell, run the client:

$ perl -MIO::Socket::INET -e 'my $s = IO::Socket::INET->new(PeerAddr => "127.0.0.1", PeerPort => 19000, Proto => "tcp") or die "connect: $!"; $s->autoflush(1); print {$s} "local IPC test\n"; print while <$s>; close $s or die "close: $!";'
reply: local IPC test

The server exits after one connection. If the client reports Connection refused, confirm that the server is still waiting and that both commands use port 19000. If the server reports Address already in use, find the process owning that port before stopping anything. The server's Reuse option helps with quick restarts, but it does not make an occupied port safe to take over.

On a shared host, binding to loopback limits this test to local clients. A server bound to a non-loopback address becomes reachable from the network and needs authentication, input validation and an explicit exposure decision. Do not turn this smoke test into a production listener by changing the address casually.

4. Keep the failure boundaries clear

Signals, pipes and sockets report different failures. A signal handler does not prove a child exited; reap the child and inspect $?. A successful connect only proves that a peer accepted the connection, not that your application protocol is correct. A readable pipe can still carry an application-level error. Check return values, close handles deliberately and print useful diagnostics containing $! where the system call provides it.

For a long-running server, use the taint-checking flag in the shebang and treat every client byte as untrusted. Never pass client input to a shell command without a strict allow-list. The examples above avoid external commands in the network path and use only loopback.

Done means

  • The installed Perl and perl-doc versions are known.
  • Ctrl-C causes the signal example to leave its loop cleanly.
  • The pipe example closes unused ends, reads to EOF and reaps its child.
  • The TCP server accepts one loopback connection and returns the test line.
  • No command required elevated privileges, changed a service or opened a network-facing port.
  • Temporary files under /tmp can be removed with rm -- /tmp/perl-signal.pl /tmp/perl-pipe.pl /tmp/perl-server.pl after the tests.