http://www.perlmonks.org?node_id=573770

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

Hi,
I have a Microsoft Windows server which runs a service named UniverSQL (http://www.sidespace.com) that allows to interact with the installed MSSQL Server from a remote computer, in my case running Linux. The Perl application on the remote computer is based on Socket.pm. It basically creates a socket and sends to the server an XML string containing the SQL query. The Server responds with an XML string:
my $oSocket = IO::Socket::INET->new(PeerAddr => $HostName, + PeerPort => $Port, + Proto => "tcp", Timeout => $SocketTimeout, Type => 'SOCK_STREAM'); my $sSend = "<?xml version=\"1.0\"?> <request>" . "<connectionstring>" . $ConnectionString . "</connectionstring>" . "<sql><![CDATA[" . $sSQL . "]]></sql></request>"; + my $sRet=""; eval { local *FH = $oSocket; print FH $sSend; my $data_read; #read the response while (recv(FH, $data_read, 4096,0)) { + $sRet .= $data_read; last if ($data_read =~ /<\/xml>/i); + } }; die if (length($sRet) == 0)
In old installations this did work very well. My problem is that on two recent installations of Linux (kernel 2..6.16) I don't get any data back ($sRet == 0). But with tcpdump I can see that the query results are coming back to the Linux box. No firewall is installed. I've tried with Socket.pm versions 1.25 and 1.30.
I was wondering if it has anything to do with perl compile options or kernel compile options.
Thank you for any hint.

Replies are listed 'Best First'.
Re: No data received on client socket
by ikegami (Patriarch) on Sep 19, 2006 at 17:11 UTC

    recv is for datagram protocols (like UDP). You need to use sysread when using a stream protocol (like TCP).

    # Send request. print $oSocket $sSend or die("Unable to write to the socket: $!\n"); # Receive response. my $data_read = ""; my $rv; do { $rv = sysread($oSocket, $data_read, 4096, length($data_read)); die("Unable to read from the socket: $!\n") if not defined $rv; } while $rv;

    Tested.

    Update: Added code.

      I've tried it, but it does not work.
      By the way, on old installations it works with recv().
      I've read that recv() shrinks to a smaller size if the last chunk of bytes is shorter than the specivfied size (4096 in this case), while sysread tries to read the specified size.

      Update I've tested your implementation and - as with the original code - I can see with tcpdump the query results coming back as network traffic but I get nothing in Perl.
        Does the server close the socket after the response is sent? My code assumed so. The following looks for </xml> instead.
        # Receive response. my $read_buf = ""; my $response; for (;;) { my $rv = sysread($oSocket, $read_buf, 4096, length($read_buf)); die("Unable to read from the socket: $!\n") if not defined $rv; die("Premature end of data\n") if not $rv; if ($read_buf =~ s{(.*</xml>)}{}si) { $response = $1; last; } }

        Tested. Both version work fine for me (although the first requires that the server closes the socket after sending the response).

        I've read that recv shrinks to a smaller size if the last chunk of bytes is shorter than the specivfied size (4096 in this case), while sysread tries to read the specified size.

        sysread is never guaranteed to return the number of bytes requested. When reading from a socket, it returns when LENGTH bytes are available and after each (non-empty?) packet received. And left-over bytes are returned by the next call to sysread.

        By the way, Type => 'SOCK_STREAM' should be Type => SOCK_STREAM, and specifying both Type and Proto is redundant.

Re: No data received on client socket
by caelifer (Scribe) on Sep 19, 2006 at 19:51 UTC

    Just a wild guess here. I notice that your code is executed within eval block. However, you do not check for its status, which means that you silence any errors and/or exceptions. Try to check for $@ in your last statement.

    Also, depending on the socket option, i.e. if your socket is nonblocking, you should check for EAGAIN system error to see if the socket is ready to be read. Better yet use select or its OO counterpart IO::Select to determine if socket is readable.

    Update: Fixed mixup between $! and $@, pointed out by ikegami

    use IO::Select; my $sock_read_hdl = IO::Select->new(); $sock_read_hdl->add($oSocket); # Block here until message arrives my ($read_ready) = IO::Select->select($sock_read_hdl, undef, undef, un +def); # Now we are ready to read message. Note: $read_ready->[0] == $oSocket + here. eval { ... } or die $@; # As pointed out by ikegami, thanks.

    I usually use a function instead of eval block and return an array containing message, byte count and possible error. Such as in

    sub read_msg { my ($sock, $bcount, $buf, $msg) = (shift, 0, '', ''); # Use sysread for stream-oriented sockets READ_MSG: while (my $bytes_read = sysread($sock, $buf, 1024)) { # Handle partial read $bcount += $bytes_read; $msg .= $buf; } # Just in case redo READ_MSG if $! =~ /Resource temporarily unavailable/; # All other errors trigger unconditional return with error return ($msg, $bcount, $!) if ($!); # Return success return ($msg, $bcount, ''); }

    So now our message reading routine looks like ...

    ... # Instead of eval block use our new function call my ($msg, $bcount, $error) = read_msg($read_ready->[0]); # Check for errors and empty messages die $error, "\n" if ($error); die "Nothing received!\n" if ($bcount == 0); ...

    Hope this helps with your problem here. BR

      eval sets $@ when it catches an exception, yet you check $! in the first snippet.

      In the second snippet, you're relying on $! being false when there has been no error, but $!'s value is undefined (which is not to say that $! is undef) in that circumstance.

        I fixed the $! and $@ mixup as per your suggestion, duh :) . However, I'm not following your point regarding $! in the second snippet. Undefined value should always produce false condition on which I rely in my checks. Could you eleborate more?
      I did try to use your suggested code, but it never gets after:
      # Block here until message arrives my ($read_ready) = IO::Select->select($sock_read_hdl, undef, undef, un +def);
      This is in line with the behavior of the original code, where the message string is empty, but now I know that no message arrives. Even if I can see with tcpdump that the message arrives on the network card of the Linux client.
      Thank you very much in any case.
        This looks more and more like a network / os configuration problem. Check your host configs. Is there a pocket filter active on the host itself? Is it possible that you have some other client binded to the same port? I'm not sure if socket interface on Windows allows that though. As a last suggestion try write a simple echo server and run it on the localhost with the same port number and see if your application can receive a message. Or maybe simulate a network exchage with Telnet session.

        BR

        # Simple echo server using POE framework (from CPAN) #!/usr/bin/perl use warnings; use strict; use POE qw(Component::Server::TCP); POE::Component::Server::TCP->new( Port => 12345, ClientInput => \&client_input, ); POE::Kernel->run(); exit; sub client_input { my ($heap, $input) = @_[ HEAP, ARG0 ]; print "[$$]: $input\n"; $heap->{client}->put($input); }
Re: No data received on client socket
by aufflick (Deacon) on Sep 20, 2006 at 05:13 UTC
    From a different angle - is there any reason that you don't just use DBI with DBD::ODBC to connect to your MSSQL server?
      Thanks for the suggestion, but since all worked fine in the past, I'd like to continue using this kind of connection.
      I don't know if DBD::ODBC would rely on sockets to access a remote Windows computer over the network (intranet/internet). In that case I might have the same problem.
      But as a last resort I might try what you suggest, or use the php client that instead works fine on the same computer connectiong to the same remote server.
Re: No data received on client socket
by rbi (Monk) on Sep 21, 2006 at 14:59 UTC
    I could finally get back the query result by modifying the original script like this:
    my $oSocket = IO::Socket::INET->new(PeerAddr => $HostName, + PeerPort => $Port, + Timeout => $SocketTimeout, Type => SOCK_STREAM); my $sSend = "<?xml version=\"1.0\"?> <request>" . "<connectionstring>" . $ConnectionString . "</connectionstring>" . "<sql><![CDATA[" . $sSQL . "]]></sql></request>"; + local *FH = $oSocket; print FH $sSend; while (<FH>) { print $_ };
    I'd really be grateful if someone can explain me what is not working in the other ways (my original one and the suggested modifications), and how the other ones should be modified in order to work.
    Thanks a lot.

    Update This way it works fine:
    ... local *FH = $oSocket; print FH $sSend; while (1) { sysread($oSocket, $data_read, 4096,length($data_read)); $sRet .= $data_read; last if ($data_read =~ /<\/xml>/i); }; close (FH); print $sRet;

      Which change fixed it? Did using while (<FH>) or fixing the options passed to the constructor fix it?

      SOCK_STREAM is 1, but 'SOCK_STREAM' probably got treated as 0. If that's the problem, you should go back to using sysread/syswrite.

      By the way, what's with local *FH = $oSocket;? Everywhere you use FH, you can put $oSocket. (e.g. print $oSocket ..., while (<$oSocket>), sysread($oSocket, ...), etc)

        It worked when I put while(<FH>) and kept working when I forced the while condition to be true.
        Thanks for the correction on directly using the $oSocket.

        The same code accessing the same SQL server has a different behavior on different Linux boxes.. I hope this fix will work on the old ones :)