Bagarre has asked for the wisdom of the Perl Monks concerning the following question:

I've been bouncing my head off the keys for two days now while trying to write a fast proxy. The idea is simple. Listen on a socket, get the data, (do something with the data if you want) connect to server, send data to server, get data from server (do something with the data if you want) and then send data along to client. I'm not using any pre-made modules for certain reasons (different conversation). The problem is waiting for a return from the server. Anyway, here is a clipped version of the code that is giving me issues. If I change line 145 to use _pass_thru(), its much faster :/ Any help would be appreciated. PS This is a module in the real program.
#!/usr/bin/perl use 5.008008; use strict; use Carp; use POSIX 'WNOHANG'; use IO::Socket; use Socket; use Data::Dumper; our $VERSION = '0.01'; # some defaults our %defaults = ( localIP => "127.0.0.1", max_connections => 1, # Not forking port => "80", server => "216.92.118.23", ); __PACKAGE__->make_accessors( qw( localIP max_connections port server rules Alerter kids), keys %defaults ); my @kids; new(); ###################################################################### +########## sub new { my $class = shift; my %params = @_; # non modifiable defaults my $self = bless {}, $class; # get attributes $self->{$_} = exists $params{$_} ? delete( $params{$_} ) : $defaul +ts{$_} for keys %defaults; $self->{Socket} = IO::Socket::INET->new( Listen => 1, Reuse => 1, LocalAddr => $self->localIP, LocalPort => $self->port, Proto => 'tcp' ); die "Could not create socket: $!\n" unless $self->{Socket}; # $self->{Socket}->autoflush(1); while ( my $client = $self->{Socket}->accept() ) { if ( my $pid = fork() ) { #Parent push @kids, $pid; next; } else { #Child handles this conection print "New Connection $$" . time() . "\n"; $self->_handle_connection($client); exit(); # Child exits } } reap_zombies(); } ###################################################################### +########### sub reap_zombies { my $self = shift; while (1) { my $pid = waitpid( -1, WNOHANG ); last if $pid == 0 || $pid == -1; # AS/Win32 returns negativ +e PIDs @kids = grep { $_ != $pid } @kids; print "$pid PM child died\n"; $self->Alerter->log( "debug", "PortManager $$ Socket pid $pid closed @kids remain active +" ); } } ###################################################################### +############################## sub _pass_thru { # Default action when no rules hit or is an unknwon protocol. my $self = shift; my $client = shift; my $first_line = shift; my $client_ip = $client->peerhost(); my $out_handle; # socket to the server $self->_connect_server( \$out_handle ); # $self->Alerter->log( "info", # "Pass_Thru OPEN from $client_ip to " . $self->server ); binmode $out_handle; binmode $client; $client->autoflush(1); $out_handle->autoflush(1); my $c; my $len = 1; if ( my $pid = fork() ) { # parent sends from Server to Client undef $/; undef $|; while ( $len == 1 ) { $len = sysread( $out_handle, $c, 1 ); syswrite( $client, $c, $len ); } } else { # Child sends from Client to Server undef $/; undef $|; syswrite( $out_handle, $first_line, length($first_line) ); while ( $len == 1 ) { $len = sysread( $client, $c, 1 ); syswrite( $out_handle, $c, $len ); } } close $client; close $out_handle; $self->Alerter->log( "info", "Pass_Thru CLOSE from $client_ip to " . $self->server ); exit; } ###################################################################### +#################### ###################################################################### +#################### # Will be broken up later ###################################################################### +#################### ###################################################################### +#################### sub _handle_connection { my $self = shift; my $client = shift; my $REQUEST; my $REPLY; my $out_handle; # Will be a handle to the server side of the so +cket. my $first_line; sysread( $client, $first_line, 4 ); # Change this to use _pass_thru() # if ($first_line =~ /^GET $|^POST$/) if ( $first_line !~ /^GET $|^POST$/ ) { $self->_pass_thru( $client, $first_line ); } else { $REQUEST = $first_line . <$client>; while ( my $line = <$client> ) { $REQUEST .= $line; last if ( $line =~ /^\r\n/ ); } } print "New Connection $$ READ" . time() . "\n"; $REQUEST .= "\r\n\r\n"; # JUST TO BE SURE ITS THERE $REQUEST =~ s/HTTP\/1\.1/HTTP\/1\.0/; # print $REQUEST; $self->_connect_server( \$out_handle ); print "New Connection $$ Connected " . time() . "\n"; # Send select( ( select($out_handle), $| = 1 )[0] ); syswrite( $out_handle, $REQUEST, length($REQUEST) ); select( ( select(STDOUT), $| = 1 )[0] ); print "New Connection $$ REQUEST SENT " . time() . "\n"; # print "URI: " .$object->{Request}->{URI}->{uri} ."\n"; ### SLOW PART HERE select( ( select($out_handle), $| = 1 )[0] ); my $bit; my $len = 1; until ( $len == 0 ) { $len = sysread( $out_handle, $bit, 1 ); $REPLY .= $bit; } ## END SLOW PART print "New Connection $$ GOT DATA " . time() . "\n"; select( ( select($client), $| = 1 )[0] ); # print $client $REPLY; syswrite( $client, $REPLY, length($REPLY) ); print "New Connection $$ RCV SENT" . time() . "\n"; close $client; close $out_handle; print "New Connection $$ DEAD" . time() . "\n"; exit; } ###################################################################### +########## sub _connect_server { my $self = shift; my $server = $self->server; my $port = $self->port; my $sock = shift; #my $proto = getprotobyname('tcp'); #my $paddr = sockaddr_in($port, inet_aton($server)); #socket($$sock, PF_INET, SOCK_STREAM, $proto) || die "socket: $!"; #connect($$sock, $paddr) || die "connect: $!"; $$sock = new IO::Socket::INET( PeerAddr => $server, PeerPort => $port, Proto => 'tcp', Reuse => 1 ); use Socket qw(IPPROTO_TCP TCP_NODELAY); setsockopt( $$sock, IPPROTO_TCP, TCP_NODELAY, 1 ); if ( !$$sock ) { # $self->Alerter->log("info", "Connec to Server failed. + $!"); exit; } $$sock->timeout(.001); #print "Timeout is " .$$sock->Timeout() ."\n"; #$$sock->autoflush(1); return (1); } ###################################################################### +########### # private reaper sub sub reap_zombies { my $self = shift; my $kids = $self->kids; while (1) { my $pid = waitpid( -1, WNOHANG ); last if $pid == 0 || $pid == -1; # AS/Win32 returns negativ +e PIDs @$kids = grep { $_ != $pid } @$kids; $self->{conn}++; # Cannot use the interface for RO attribut +es } } ###################################################################### +########### # class method sub make_accessors { my $class = shift; for my $attr (@_) { no strict 'refs'; *{"$class\::$attr"} = sub { $_[0]{$attr} = $_[1] if defined $_[1]; $_[0]{$attr}; }; } } 1;
A
Entities should not be multiplied unnecessarily.

Replies are listed 'Best First'.
Re: Slow sockets
by weismat (Friar) on Oct 31, 2007 at 15:22 UTC
    Why are you always reading byte by byte from the socket? $len is always 1. If you receive multiple data sets, then you might need to cache the leftovers from the previous read. I did this also once when starting with sockets and saw then the huge speed improvement. One issue is that on multiple platforms different read sizes are recommend, but I would recommend you to use a len of at least 1024.

      I can bump the read up to a bigger size but that doesnt seem to be the issue for now.

      I just added $out_handle->flush(); after writing to it and everything is fast again. Looks like syswrite was being buffered somehow.

      Sorry for the hacked code. The module does a few things more than seen here which is why some of it doesnt make sense. Like the bless and returning nothing. I ripped out the portions that I thought were causing the issue and stuck them here.

      So, $out_handle->flush(); speeds everything up but I dont understand why syswrite($out_handle, $REQUEST, length($REQUEST); would be buffered.

      Entities should not be multiplied unnecessarily.
Re: Slow sockets
by shmem (Chancellor) on Oct 31, 2007 at 15:36 UTC
    PS This is a module in the real program.

    Then it would have been better if you'd posted the code as module. Like this, it doesn't make much sense. No package declaration - __PACKAGE__ is main here.

    I don't see the purpose of blessing and OO-ado in your new() function (or method, whatever), since it doesn't return an object, but the result of reap_zombies(); and it even doesn't return because it loops. It returns only if the accept() call fails, which looks to me rather like a condition to carp, croak or confess when it is encountered.

    --shmem

    _($_=" "x(1<<5)."?\n".q·/)Oo.  G°\        /
                                  /\_¯/(q    /
    ----------------------------  \__(m.====·.(_("always off the crowd"))."·
    ");sub _{s./.($e="'Itrs `mnsgdq Gdbj O`qkdq")=~y/"-y/#-z/;$e.e && print}