UNIX-domain and UDP sockets#

Not every socket reaches across a network. This page covers two lighter-weight relatives of the TCP socket from the previous page: a UNIX-domain socket pair for two related processes on one machine, and a UDP socket for connectionless datagrams.

Both use the same socket family of built-ins and the same Socket address helpers. The high-level IO::Socket::* wrappers are not available in PetaPerl, so the recipes stay at the built-in layer.

A socket pair for parent and child#

socketpair creates two connected sockets in one call, with no addresses, no bind, and no connect. It is the ideal channel between a parent and a child it is about to fork: full-duplex (both ends can read and write), local, and already connected the moment it returns.

use Socket;

socketpair(my $child_end, my $parent_end, AF_UNIX, SOCK_STREAM, PF_UNSPEC)
    or die "socketpair: $!";

my $kid = fork() // die "fork: $!";

if ($kid == 0) {
    # Child keeps its end, closes the parent's.
    close $parent_end;
    $child_end->autoflush(1);

    print $child_end "child-says-hi\n";
    my $back = <$child_end>;
    print "child got: $back";        # child got: ack
    exit 0;
}

# Parent keeps its end, closes the child's.
close $child_end;
$parent_end->autoflush(1);

my $msg = <$parent_end>;
print "parent got: $msg";            # parent got: child-says-hi
print $parent_end "ack\n";

waitpid($kid, 0);

Why reach for socketpair over a plain pipe? A pipe is one-directional - the reader reads, the writer writes, and that is fixed at creation. A socket pair is bidirectional: each end both reads and writes, so a request-and-reply exchange needs one socketpair call instead of two pipes wired in opposite directions.

The autoflush rule from TCP applies unchanged: set it on each end before the first write, or a buffered request never reaches the other side and both processes block.

A UDP datagram exchange#

UDP is connectionless. There is no connect, no accept, no stream - just discrete messages, each addressed to a destination and each preserving its own boundaries. A receiver binds to a port; a sender fires a datagram at it with send; the receiver collects it with recv, which also reports who sent it.

use Socket;

# --- receiver on an ephemeral port ---
socket(my $server, PF_INET, SOCK_DGRAM, getprotobyname("udp"))
    or die "socket: $!";
bind($server, sockaddr_in(0, INADDR_LOOPBACK)) or die "bind: $!";
my ($port) = sockaddr_in(getsockname($server));

my $kid = fork() // die "fork: $!";
if ($kid == 0) {
    # Sender fires one datagram at the receiver's port.
    socket(my $client, PF_INET, SOCK_DGRAM, getprotobyname("udp")) or die;
    my $to = sockaddr_in($port, INADDR_LOOPBACK);
    send($client, "hello-udp", 0, $to) or die "send: $!";
    exit 0;
}

# recv fills $buf and returns the sender's packed address.
my $from = recv($server, my $buf, 65535, 0);
my ($sender_port, $sender_ip) = sockaddr_in($from);
print "got '$buf' from ", inet_ntoa($sender_ip), "\n";
                                     # got 'hello-udp' from 127.0.0.1

waitpid($kid, 0);

What UDP gives you and what it does not:

  • Message boundaries are preserved. One send is one recv. A recv returns exactly one datagram, never two concatenated and never half of one. The 65535 is the maximum length you are willing to accept; a shorter datagram returns its real length.

  • There is no connect and no handshake. The sender does not open a connection; it addresses each datagram with send’s fourth argument. recv reports the sender’s address so a reply can go back the same way.

  • Delivery is not guaranteed. UDP datagrams can be dropped, duplicated, or reordered by the network. Over the loopback interface, as in this recipe, that effectively never happens; over a real network you must handle loss yourself if it matters.

Next#

For shared memory and semaphores - channels the kernel owns rather than streams between processes - see System V IPC.