perlipc bidirectional unix domain socket

A

all mail refused

I've been trying to put bidirectional traffic through a
local domain (aka Unix domain) socket by adapting code
from "man perlipc" as seen at
http://perl.active-venture.com/pod/perlipc-sockets.html
where the local stuff comes near the end of the page.

The original code works unidirectional - i.e. the client
reads something from the server but after making what look
to me like suitable changes I've been unable to get the server
to obtain the request from the client. I see the original
server opens STDIN from <&Client but doesn't use it.

I've also simplified it by taking the multithread stuff
out of the server. Tested so far on Linux but aiming to
go on multiple Unix versions.

Can someone show me where I'm falling down?

Client
======

#!/usr/bin/perl -w
use Socket;
use strict;
my ($rendezvous, $line);

$rendezvous = shift || 'catsock';
socket(SOCK, PF_UNIX, SOCK_STREAM, 0) or die();
connect(SOCK, sockaddr_un($rendezvous)) or die();
printf(SOCK "Request something\n");
shutdown(SOCK,1); # finished writing
$line = <SOCK>;
print $line;
exit;


Server
======

#!/usr/bin/perl -Tw
use strict;
use sigtrap;
use Socket;
use Carp;

BEGIN {%ENV=('PATH'=>'/usr/ucb:/bin')}

sub spawn; # forward declaration
sub logmsg { print "$0 $$: @_ at ", scalar localtime, "\n" }

my $NAME = 'catsock';
my $uaddr = sockaddr_un($NAME);
my $proto = getprotobyname('tcp');

socket(Server,PF_UNIX,SOCK_STREAM,0) or die();
unlink($NAME);
bind(Server, $uaddr) or die();
listen(Server,SOMAXCONN) or die();
logmsg "server started on $NAME";

accept(Client,Server) or die("accept $!");
# Is Client now bidirectional?

logmsg "connection on $NAME";
spawn sub {
my $input=shift;
printf("(%s): Reply=XXXX\n", $input);
};

sub spawn {
my $coderef = shift;
my $input;

unless (@_ == 0 && $coderef && ref($coderef) eq 'CODE') {
confess "usage: spawn CODEREF";
}

close(STDIN);
open(STDIN, "<&Client") or die("open STDIN $!");
# lsof here shows we do have fd0 dup from one of the previous fds
open(STDOUT, ">&Client") or die("open STDOUT $!");

$input=<STDIN>;
$input="sample" unless (defined($input));
chomp($input);
printf(STDERR "Now executing coderef(%s)...\n", $input);
exit &$coderef($input);
}
 
M

Mike Pomraning

all said:
I've been trying to put bidirectional traffic through a
local domain (aka Unix domain) socket by adapting code
from "man perlipc" as seen at
http://perl.active-venture.com/pod/perlipc-sockets.html
where the local stuff comes near the end of the page.

The original code works unidirectional - i.e. the client
reads something from the server but after making what look
to me like suitable changes I've been unable to get the server
to obtain the request from the client. I see the original
server opens STDIN from <&Client but doesn't use it.

I've also simplified it by taking the multithread stuff
out of the server. Tested so far on Linux but aiming to
go on multiple Unix versions.

Your sample code seems to work for me without modification:

shhhh$ perl -T server.pl & sleep 2 && perl client.pl
[1] 18983
Name "main::Client" used only once: possible typo at server.pl line 22.
server.pl 18983: server started on catsock at Sat Sep 22 16:10:52 2007
server.pl 18983: connection on catsock at Sat Sep 22 16:10:54 2007
Now executing coderef(sample)...
(sample): Reply=XXXX
[1]+ Exit 1 perl -T server.pl
Can someone show me where I'm falling down?

Can you tell us what specific behavior you are observing, and how it
differs from what you expected?

Regards,
Mik
 
A

all mail refused

Your sample code seems to work for me without modification:

Can you tell us what specific behavior you are observing, and how it
differs from what you expected?

I'm getting the same behaviour as you, but that is not what I call working.

The client is supposed to send "Request something\n" through the
socket where the server should read it on STDIN into the variable
$input. Instead $input is undefined after the read and so replaced
by "sample".

"(Request something): Reply=XXXX" was the hoped-for output from the client.
 
B

Ben Morrow

Quoth all mail refused said:
I've been trying to put bidirectional traffic through a
local domain (aka Unix domain) socket by adapting code
from "man perlipc" as seen at
http://perl.active-venture.com/pod/perlipc-sockets.html
where the local stuff comes near the end of the page.

The original code works unidirectional - i.e. the client
reads something from the server but after making what look
to me like suitable changes I've been unable to get the server
to obtain the request from the client. I see the original
server opens STDIN from <&Client but doesn't use it.

I've also simplified it by taking the multithread stuff
out of the server. Tested so far on Linux but aiming to
go on multiple Unix versions.

Can someone show me where I'm falling down?

(I realise this is mostly copied from the perldoc: the style of the code
is rather out-of-date, and they should probably be updated.)
Client
======

#!/usr/bin/perl -w

use warnings;

is better than -w.
use Socket;

It's usually easier to use IO::Socket than the raw socket calls.
use strict;
my ($rendezvous, $line);

It's better not to declare variables until you need them. Perl is not C
:).
$rendezvous = shift || 'catsock';
socket(SOCK, PF_UNIX, SOCK_STREAM, 0) or die();
connect(SOCK, sockaddr_un($rendezvous)) or die();

Don't use global filehandles. If you *really* want to stick to using
socket() directly, you can use

socket(my $SOCK, PF_UNIX, SOCK_STREAM, 0) or ...;

but it would probably be easier to use

my $SOCK = IO::Socket::UNIX->new($rendevous) or ...;

which will deal with socket/connect/sockaddr_un for you.

You should always give a useful error message when you die, including
the appropriate system error, which for socket functions
printf(SOCK "Request something\n");

Don't use printf when you can use print. In fact, I never use printf at
all in Perl: on the rare occasion I actually want to printf something, I
tend to write it as

print sprintf ...;

Also, while I don't usually have a problem with parens around function
arguments (though I avoid them myself), when the first arg is a
filehandle for print/f the lack of a comma is a little too weird... it
would be all to easy to put the comma in by mistake and write something
perfectly valid but quite different. If you're really attached to the
parens I might even recommend switching to full method syntax:

$SOCK->print("Request something\n");

though this requires a

use IO::Handle;

before it will work on ordinary filehandles. Hmmm, this is very much a
matter of style, and a rare case in which Perl's syntax is less than
ideal, so feel free to ignore this paragraph... :)
shutdown(SOCK,1); # finished writing

It's clearer to use the named constants:

shutdown($SOCK, SHUT_WR);
$line = <SOCK>;
print $line;
exit;

There's no need to exit. 'Falling off the end' is a perfectly valid way
to successfully terminate a Perl program.
Server
======

#!/usr/bin/perl -Tw
use strict;
use sigtrap;
use Socket;
use Carp;

BEGIN {%ENV=('PATH'=>'/usr/ucb:/bin')}

sub spawn; # forward declaration
sub logmsg { print "$0 $$: @_ at ", scalar localtime, "\n" }

my $NAME = 'catsock';
my $uaddr = sockaddr_un($NAME);
my $proto = getprotobyname('tcp');

socket(Server,PF_UNIX,SOCK_STREAM,0) or die();
unlink($NAME);
bind(Server, $uaddr) or die();
listen(Server,SOMAXCONN) or die();

Again, it's probably easier to use

unlink($NAME);
my $Server = IO::Socket::UNIX->new(
Local => $NAME,
Listen => SOMAXCONN,
);

SOCK_STREAM is the default for IO::Socket::UNIX.
logmsg "server started on $NAME";

accept(Client,Server) or die("accept $!");

my $Client = $Server->accept
or die "accept: $!";
# Is Client now bidirectional?

Yes, it is.
logmsg "connection on $NAME";
spawn sub {
my $input=shift;
printf("(%s): Reply=XXXX\n", $input);

No need for printf even here:

print("($input): Reply=XXXX\n");
};

sub spawn {
my $coderef = shift;
my $input;

unless (@_ == 0 && $coderef && ref($coderef) eq 'CODE') {
confess "usage: spawn CODEREF";
}

close(STDIN);
open(STDIN, "<&Client") or die("open STDIN $!");

This is a really bad idea. The example in the perldoc was forking a new
process dedicated to handling one connection, so attaching the std
handles of the new process only to the client socket was useful. You're
not doing that (yet?), so just print to and read from the client socket
directly.

You're passing the client socket from the main program to this sub as a
global variable. This is ad for all the usual reasons globals are bad:
they all apply just as much to filehandles. Use properly scoped
variables for filehandles, and pass the client socket into spawn as a
parameter. You will probably also need to pass it into the coderef;
unless it is created within the scope of $Client above, in which case
things Just Work (technically, this feature of Perl is called 'lexical
closure', and only works with anonymous subs).

Note that if you switch to using a variable for Client, but still want
to dup it onto STDIN, you have to use the three-arg form of open to do
so:

open(STDIN, '<&', $Client) or ...;
# lsof here shows we do have fd0 dup from one of the previous fds
open(STDOUT, ">&Client") or die("open STDOUT $!");

$input=<STDIN>;
$input="sample" unless (defined($input));
chomp($input);
printf(STDERR "Now executing coderef(%s)...\n", $input);
exit &$coderef($input);

I'm not sure what you meant by this, but this will pass exit() the
return value of the printf that was the last statement in the coderef;
probably not what you want, as it will usually be 1.

IMO, 'don't call subs with &' applies to subrefs as well: I would write
the call as

$coderef->($input)

Ben
 
B

Brian McCauley

socket(SOCK, PF_UNIX, SOCK_STREAM, 0) or die();
connect(SOCK, sockaddr_un($rendezvous)) or die();
printf(SOCK "Request something\n");
shutdown(SOCK,1); # finished writing

I may be off-base here but ISTR that (at least in some older versions
of Perl) you had to explicitly set SOCK to be autoflushed (or
explicitly flush the buffers) when using the low-level socket
operations rather than IO::Socket.
 
M

Mike Pomraning

Brian said:
I may be off-base here but ISTR that (at least in some older versions
of Perl) you had to explicitly set SOCK to be autoflushed (or
explicitly flush the buffers) when using the low-level socket
operations rather than IO::Socket.

That's right. Given the OP's clarification of problem, autoflush is the
issue -- the client doesn't write until after it's read from the server,
and, indeed, the write fails anyway since shutdown has already been
called, leaving the server with EOF.

Something like this:

...
connect(SOCK, sockaddr_un($rendezvous)) or die();
$| = 1, select $_ for select SOCK;
printf(SOCK "Request something\n");
...

does the trick. ``perldoc -q flush'' has other $! idioms, and of course
IO::Socket objects offer ->autoflush().

-Mike
 
A

all mail refused

That's right. Given the OP's clarification of problem, autoflush is the
issue -- the client doesn't write until after it's read from the server,
and, indeed, the write fails anyway since shutdown has already been
called, leaving the server with EOF.

Something like this:
...
connect(SOCK, sockaddr_un($rendezvous)) or die();
$| = 1, select $_ for select SOCK;
printf(SOCK "Request something\n");
...

does the trick. ``perldoc -q flush'' has other $! idioms, and of course
IO::Socket objects offer ->autoflush().

Thanks a lot everybody. I'd imagined that shutdown() forced flushing of
the stream up to that point but obviously not.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,734
Messages
2,569,441
Members
44,832
Latest member
GlennSmall

Latest Threads

Top