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

Hi Monks,

I am tranferring a binary file by using a filehandle over a socket connection:

The code for the client sending the file:

open(INPUT, 'file.dat'); binmode(INPUT); while (<INPUT>) { print $sock $_; } close(INPUT);

The code for the server receiving the file:

open(INPUT, '>file2.dat'); binmode(INPUT); while (<$sock>) { print INPUT $_; } close INPUT;
Problem is, I want the client to keep the socket connection open after sending the file, but don't know how to tell the receiving side to stop receiving the file. How can I add a condition so the receiver will know when to stop receiving the file?

Replies are listed 'Best First'.
Re: How to end socket file transfer without closing socket
by ikegami (Patriarch) on Oct 23, 2007 at 16:36 UTC

    You need to expand you protocol. Here are some ideas based on existing protocols:

    HTTP

    The size of the body ("Content-Length") is transmitted before the body itself, so the receiver knows when to stop listening for the body and start listening for another request or response.

    Pro: Trivial to implement.
    Con: Sender needs to know data's length in advance.
    Con: Fails poorly when an error occurs.

    FTP

    A command connecion is maintained. Commands are exchanged to create new connections over which data is transfered.

    Pro: Handles errors easily.
    Con: More complex to implement.
    Con: Harder for firewalls and proxies to handle.

    SLIP

    Packets are terminated with a special character ("\0300") that is not allowed to exist anywhere else in the stream. An encoding mechanism is provided to transmit these special characters as data ("\0300" is encoded as "\0333\0334" and "\0333" is encoded as "\0333\0335").

    Pro: Easy to implement.
    Pro: Sender doesn't need to know data's length in advance.
    Pro: Can handle errors without breaking the connection.

    Update: Added pros and cons.

      I kinda like MIME's choice. XML's CDATA seems rather pointless to me, by contrast (having such a strange syntax that still requires one to escape a 3-char delimiter but whose existance is "justified" because you don't have to escape a 1-char delimiter).

      For MIME, you pick some short, random string (from a restricted set of characters). If that string is in the data to be sent, then look at the character that follows it and append some other random character on the end of your random string. Repeat until you have a string that does not appear anywhere in your data. Then output the delimiter before and after the data. It requires that you scan the data to be sent once before you send it but is robust in the face of character translations in your pipe (important in an e-mail protocol where trailing spaces might not be preserved, for example) and doesn't require any transformation of the data to be sent.

      - tye        

      Thanks for the ideas. If the receiver knows the filesize, how can the following snippet be modified to not read and write more than the filesize?
      while (<$sock>) { print INPUT $_; }
        my $sock_in = ...; my $fh_out = ...; my $bytes_to_read = ...; while ($bytes_to_read > 0) { my $bytes_read = read($sock_in, my $block = '', $bytes_to_read); if (!$bytes_read) { my $msg = defined($bytes_read) ? 'Socket closed unexpectedly' : +$!; die("Unable to read from sender: $msg\n"); } $bytes_to_read -= $bytes_read; print $fh_out $block; }

        Update: It was an infinite loop. Fixed.

        I've repeatedly noted that <$sock> is generally a pretty bad idea. Lots of problems get traced back to that. See read, sysread, and recv for alternatives.

        - tye