What is wrong with this IO::Socket::UNIX example?

6.5k views Asked by At

I am trying to implement a simple echo client/server, over a Unix socket. (My ultimate goal is to exchange JSON data, but this example is for simplicity). I have no idea why the client process disappears into a black hole when it tries to print to the socket the second time.

server.pl :

use IO::Socket::UNIX;

my $socket_path = '/tmp/mysocket';
unlink $socket_path if -e $socket_path;

my $socket = IO::Socket::UNIX->new(
    Local  => $socket_path,
    Type   => SOCK_STREAM,
    Listen => SOMAXCONN,
);

die "Can't create socket: $!" unless $socket;

while (1) {
    next unless my $connection = $socket->accept;
    chomp( my $line = <$connection> );
    print $connection "$line\n";
}

client.pl :

use IO::Socket::UNIX;

my $socket = IO::Socket::UNIX->new(
    Type => SOCK_STREAM,
    Peer => '/tmp/mysocket',
);

die "Can't create socket: $!" unless $socket;

my $line;

print $socket "one\n";
chomp( $line = <$socket> );
say $line;

print $socket "two\n";
chomp( $line = <$socket> );
say $line;

say "three";

Expected output:

> ./client.pl
> one
> two
> three

Actual output:

> ./client.pl
> one
2

There are 2 answers

1
mob On BEST ANSWER

You put the $socket->accept call inside your while loop. After your server establishes a connection and receives some input from the client, the next thing it wants to do is establish a new connection.

Move the accept call outside the while loop

my $connection = $socket->accept;
$connection->autoflush(1);
while (my $line = <$connection> ) {
    chomp($line);
    print $connection "$line\n";
}

or, if you do want to accept more than one connection,

while (1) {
    next unless my $connection = $socket->accept;
    $connection->autoflush(1);
    while (my $line = <$connection>) {
        chomp($line);
        print $connection "$line\n";
    }
}

Your current solution will also likely be "suffering from buffering", so both the server and the client should set autoflush(1) on their socket handlers.

Now to handle simultaneous connections, the server would usually call fork after getting a connection, and handling that connection in a child process.

while (1) {
    my $connection = $socket->accept;
    if (fork() == 0) {
        $connection->autoflush(1);
        while (my $line = <$connection>) {
            chomp($line);
            print $connection "$line\n";
        }
        close $connection;
        exit;
    }
}
0
internet-og On

A complete bi-directional example for anyone looking:

Server:

#! /usr/bin/perl -w

use strict;

use IO::Socket::UNIX qw( SOCK_STREAM SOMAXCONN );

my $SOCK_PATH = '/tmp/test.sock';
unlink($SOCK_PATH) if -e $SOCK_PATH;

my $server = IO::Socket::UNIX->new(
   Type => SOCK_STREAM(),
    Local => $SOCK_PATH,
   Listen => SOMAXCONN,
)
   or die("Can't create server socket: $!\n");

while (1) {
    my $connection = $server->accept;
    if (fork() == 0) {
        print "** New connection received **\n";
        $connection->autoflush(1);
        my $count = 1;
        while (my $line = <$connection>) {
            if ($line){
                chomp($line);
                $connection->print($count . ' -> ' . $line . "\n"); # Sent response back to client, \n terminates
                print "Received and replied to $count '$line'\n";
                $count++;
            }
        }
        close $connection;
        exit;
    }
}

Client:

#!/usr/bin/perl -w

use strict;

use IO::Socket::UNIX qw( SOCK_STREAM );

my $SOCK_PATH = '/tmp/test.sock';

my $client = IO::Socket::UNIX->new(
   Type => SOCK_STREAM(),
   Peer => $SOCK_PATH
)
   or die("Can't connect to server: $!\n");

$client->autoflush(1);


## Listen for replies
if (fork() == 0) {
    while (my $line = <$client>) {
        if ($line){
            chomp($line);
            print("Recv: '" . $line . "'\n");
        }
    }
}

## Send something
for my $itm ('Alpha','Beta','Gamma','Delta'){
    print("Send: " . $itm . "\n");
    print($client $itm . "\n") or warn("Can't send: $!\n"); # send to server, \n terminates
}

print "** Client Finished **\n";