[San-Diego-pm] A few questions of Perl

Tkil tkil at scrye.com
Thu Dec 10 12:59:48 PST 2009


(Alex -- it's generally better to keep conversations on the list; that
way, you get the benefit of more eyes, and others can find the
information later.  Thanks!)

> >>>>> "Alex" == Alex (Yu) Hu <foxele at gmail.com> writes:  

Alex> Thanks Tkil. I am pretty much using a similar way to build my
Alex> socket interface. Regarding the two sockets, I want both socket
Alex> to be readable and writable. Here is my code, it is working but
Alex> sometimes gives me strange behavior:

I don't know if it got munged by your mailer, but the indentation was
pretty messed up.  After cleaning up whitespace, I got:

Alex> | $swe_sock = new IO::Socket::INET->new( PeerAddr=>'192.168.0.2',
Alex> |                                        PeerPort=>1850,
Alex> |                                        Proto=>'tcp',
Alex> |                                        Type=>SOCK_STREAM,
Alex> |                                        Reuse=>1 )
Alex> |   || die "Can't connect to 192.168.0.2:1850 : $!\n";
Alex> | 
Alex> | $swe_sock->autoflush(1);
Alex> | 
Alex> | $dsp_sock = new IO::Socket::INET->new( PeerAddr=>'192.168.0.2',
Alex> |                                        PeerPort=>1852,
Alex> |                                        Proto=>'tcp',
Alex> |                                        Type=>SOCK_STREAM,
Alex> |                                        Reuse=>1 )
Alex> |   || die "Can't connect to 192.168.0.2:1852 : $!\n";
Alex> | 
Alex> | $dsp_sock->autoflush(1);
Alex> | 
Alex> | $sel = IO::Select->new;
Alex> | $sel->add($swe_sock);
Alex> | $sel->add($dsp_sock);
Alex> | 
Alex> | while ( @ready = $sel->can_read )
Alex> | {
Alex> |     foreach $fh (@ready)
Alex> |     {
Alex> | 
Alex> |         #read your data
Alex> |         my $count = sysread($fh, $bytes, 1024);
Alex> | 
Alex> |         $string .= $bytes;
Alex> | 
Alex> |         if ($fh == $dsp_sock)
Alex> |         {
Alex> |             print "Message from DSP_Sock.\n";
Alex> |         }
Alex> | 
Alex> |         if ($fh == $swe_sock)
Alex> |         {
Alex> |             print "Message From SWE_Sock.\n";
Alex> |         }
Alex> | 
Alex> |         # do something with $line
Alex> |         #print the results on a third socket
Alex> |         &msg_proc($string);
Alex> |         $string="";
Alex> |     }
Alex> | }

A substantial problem is that you're not using "strict" nor
"warnings".  Those would have told you at least one problem, which is
that you can't use "==" on IO::Socket objects...

Huh.  Turns out you can.  That surprises me:

| $ perl -Mstrict -MIO::Socket::INET= -lwe \
|   'my $s = IO::Socket::INET->new( PeerAddr => "www.google.com:80" ) or die $!;
|   print 0+$s;' 
| 21700712 

So much for that theory.  Anyway, a few comments:

1. "new Class->new( ... )" is ... odd.  It might actually work (and
   apparently it does, since you're getting something sometimes), but
   you really only need one of those "new"s.  I personally prefer the
   latter, so you end up with just "Class->new( ... )".

2. Most of the arguments you are giving to the IO::Socket::INET
   constructor are the defaults, so you can omit them.

3. IO::Socket has had 'autoflush' turned on for years now; if you're
   in control of your perl install, and if this script is just for
   limited internal testing, you can probably omit those calls as
   well.

4. I suspect that you cut out some of the processing, but you don't
   really need a separate $string variable; you can operate directly
   on the $bytes in the main loop.

Anyway, a cleaned-up version of the code.  First, always use 'strict'
and 'warnings':

| #!/usr/bin/perl
| 
| # paranoia
| use strict;
| use warnings;

Out of habit, I try to avoid bringing anything into my namespace that
I don't need.  This is probably overkill, but better that you miss
something now (and have perl tell you that you need it), than to
unexpectedly get a value or function brought in.

| # limit imports
| use IO::Socket::INET qw();
| use IO::Select qw();

And I like to put all the "magic values" into one place up top:

| # constants
| my $SWE_ADDR = '192.168.0.2:1850';
| my $DSP_ADDR = '192.168.0.2:1852';

I almost made a subroutine for these two, but I managed to restrain
myself.  (Did I mention something about how I dislike repetition?)

| # set up sockets
| my $swe_sock = IO::Socket::INET->new( PeerAddr => $SWE_ADDR,
|                                       Reuse => 1 )
|   or die "Can't connect to $SWE_ADDR: $!\n";
| 
| my $dsp_sock = IO::Socket::INET->new( PeerAddr => $DSP_ADDR,
|                                       Reuse => 1 )
|   or die "Can't connect to $DSP_ADDR: $!\n";
|
| # listen to both sockets
| my $sel = IO::Select->new();
| $sel->add( $swe_sock );
| $sel->add( $dsp_sock );
|
| while ( my @ready = $sel->can_read() )
| {
|     foreach my $fh ( @ready )
|     {
|         my $bytes;
|         my $count = sysread( $fh, $bytes, 1024 );
| 
|         if ( $fh == $dsp_sock )
|         {
|             print "Message from DSP_Sock.\n";
|         }
|         elsif ( $fh == $swe_sock )
|         {
|             print "Message From SWE_Sock.\n";
|         }
| 
|         &msg_proc( $bytes );
|     }
| }

That compiled and ran... but as you said, it didn't do anything.  Next
we try some debugging prints:

| while ( my @ready = $sel->can_read() )
| {
|     print STDERR "select: " . @ready . " handles ready\n";
|     foreach my $fh ( @ready )
|     {
|         my $bytes;
|         my $count = read( $fh, $bytes, 1024 );
|         print STDERR "read: $fh: $count bytes\n";
| 
|         if ( $fh == $dsp_sock )
|         {
|             print STDERR "Message from DSP_Sock.\n";
|         }
|         elsif ( $fh == $swe_sock )
|         {
|             print STDERR "Message From SWE_Sock.\n";
|         }
| 
|         # &msg_proc( $bytes );
|     }
| }

And running it gave me just this:

| $ ./alex-sockets-2.plx
| select: 1 handles ready

For comparison, adding debugging to the code I posted yesterday
evening gives me output something like this:

| $ ./alex-sockets.plx
| server: IO::Socket::INET=GLOB(0x19ef3f8)
| output: IO::Socket::INET=GLOB(0x1939388)
| select: IO::Select=ARRAY(0x19ef260)
| IO::Select=ARRAY(0x19ef260): 1 sockets ready
| new client: IO::Socket::INET=GLOB(0x19ef578)
| IO::Select=ARRAY(0x19ef260): 1 sockets ready
| IO::Socket::INET=GLOB(0x19ef578): 4 bytes 'bar\n'
| IO::Select=ARRAY(0x19ef260): 1 sockets ready
| IO::Socket::INET=GLOB(0x19ef578): goodbye
| IO::Select=ARRAY(0x19ef260): 1 sockets ready
| new client: IO::Socket::INET=GLOB(0x19ef500)
| IO::Select=ARRAY(0x19ef260): 1 sockets ready
| IO::Socket::INET=GLOB(0x19ef500): 5 bytes 'quit\n'
| $

So it looks like it's blocking on the read... Oh, which I screwed up.
You had "sysread" in there, and it works fine.  (There's even a
warning in 'perlfunc' about mixing "select" with "read".)  Now the
output looks like this:

| $ ./alex-sockets-2.plx
| select: 1 handles ready
| read: IO::Socket::INET=GLOB(0xdb9200): 4 bytes
| Message From SWE_Sock.
| select: 1 handles ready
| read: IO::Socket::INET=GLOB(0xdb9200): 4 bytes
| Message From SWE_Sock.
| select: 1 handles ready
| read: IO::Socket::INET=GLOB(0xdb9200): 4 bytes
| Message From SWE_Sock.
| select: 1 handles ready
| read: IO::Socket::INET=GLOB(0xdb9080): 6 bytes
| Message from DSP_Sock.
| select: 1 handles ready
| read: IO::Socket::INET=GLOB(0xdb9080): 5 bytes
| Message from DSP_Sock.

I also set it up so that it sends the message to the other socket, and
that works fine.

(Those of strong constitution might be amused by the development
environment I was using to test this:

  http://scrye.com/~tkil/perl/emacs-nerd.png
  http://scrye.com/~tkil/perl/emacs-nerd-2.png

That's what I get for still doing all my mailing lists and newsgroups
on a remote system -- my local emacs is much prettier.  :)

Anyway, Here's the final code:

------------------------------------------------------------------------
#!/usr/bin/perl

# paranoia
use strict;
use warnings;

# limit imports
use IO::Socket::INET qw();
use IO::Select qw();

# constants
# my $SWE_ADDR = '192.168.0.2:1850';
# my $DSP_ADDR = '192.168.0.2:1852';
my $SWE_ADDR = 'localhost:1850';
my $DSP_ADDR = 'localhost:1852';

# set up sockets
my $swe_sock = IO::Socket::INET->new( PeerAddr => $SWE_ADDR,
                                      Reuse => 1 )
  or die "Can't connect to $SWE_ADDR: $!\n";

my $dsp_sock = IO::Socket::INET->new( PeerAddr => $DSP_ADDR,
                                      Reuse => 1 )
  or die "Can't connect to $DSP_ADDR: $!\n";

# listen to both sockets
my $sel = IO::Select->new();
$sel->add( $swe_sock );
$sel->add( $dsp_sock );

while ( my @ready = $sel->can_read() )
{
    print STDERR "select: " . @ready . " handles ready\n";
    foreach my $fh ( @ready )
    {
        my $bytes;
        my $count = sysread $fh, $bytes, 1024;
        print STDERR "read: $fh: $count bytes\n";

        if ( $fh == $dsp_sock )
        {
            print STDERR "Message from DSP_Sock.\n";
            syswrite $swe_sock, $bytes;
        }
        elsif ( $fh == $swe_sock )
        {
            print STDERR "Message From SWE_Sock.\n";
            syswrite $dsp_sock, $bytes;
        }

        # &msg_proc( $bytes );
    }
}
------------------------------------------------------------------------


More information about the San-Diego-pm mailing list