This is a really basic non-blocking ftp client. Just put it together for fun to see if it would work. It does but I'm not sure if this is the best way of going about it.

It connects to the server and downloads all the files in the given directory.

<?php
if( $_SERVER['argc'] != 5 ) die( "Expecting 5 parameters, {$_SERVER['argc']} recieved\n" );

define( 'DL_PATH', '/home/bubblenut/ftpdownload/' );
define( 'POOL_SIZE', 10 );

$server = $_SERVER['argv'][1];
$user   = $_SERVER['argv'][2];
$pass   = $_SERVER['argv'][3];
$path   = $_SERVER['argv'][4];

// Create the connection pool
for( $i=0; $i < POOL_SIZE; $i++ ) {
    echo("Creating Connection $i...");
    $ftp_pool[$i] = ftp_connect( $server )   or die( "Failed to connect to server $i) $server\n" );
    ftp_login( $ftp_pool[$i], $user, $pass ) or die("Failed to log in $i) $user:$pass\n");
    echo("DONE\n");
}

// Initialize the active connections array
$ftp_active = array();

// Fetch the file list
$files = ftp_nlist( $ftp_pool[0], $path ) or die( "Failed to fetch files from server" );

print_r($files);

// Set the count of the number of files we're downloading and the number of finished downloads
// This is how we decide to terminate the loop
$files_c = count($files);
$done_c  = 0;

// Used to see if we have to continue dishing out connections
$more_files = true;

// Loop until we've downloaded all the files
while( $done_c < $files_c ) {
    // Loop until the connection pool is empty
    while( count( $ftp_pool ) && $more_files ) {
        // if there's no files left to download then set a complete flag and break
        if( !count( $files ) ) { $more_files=false; break; }

    // fetch an ftp connection and pair it with a file
    $con  = array_pop( $ftp_pool );
    $file = array_pop( $files );

    // Use up one of the pooled connections on one of the files awaiting download.
    $ftp_active[] = array( $con, ftp_nb_get( $con, DL_PATH . $file, $file, FTP_BINARY ) );
}

// While there's no available connection in the pool or while there's no more files to get but still active downloads
while( !count( $ftp_pool ) || ( !$more_files && count( $ftp_active ) ) ) {
    // Loop through the active downloads array
    for($i=0; $i < count($ftp_active); $i++) {
        if( !isset( $ftp_active[$i][1]) ) print_r( $ftp_active );
        if( $ftp_active[$i][1] == FTP_MOREDATA ) {
            // If there's more data then continue the download
            $ftp_active[$i][1] = ftp_nb_continue( $ftp_active[$i][0] );
        } elseif( $ftp_active[$i][1] == FTP_FINISHED ) {
            // If the download is finished then swap the connection reference back to the pool
            // and remove this item from the active downloads array
            $ftp_pool[] = $ftp_active[$i][0];
            unset( $ftp_active[$i] );
            $ftp_active = array_values( $ftp_active );
            $done_c++;
        }
    }
}
}
?>

Cheers

    This seems pretty neat. Seems like a good candidate for OO-ifying (that must be a real word), I can envision a connection object, a pool object etc.

    I wondered what happens to a resource when it is copied in the way you are doing

    $ftp_pool[] = $ftp_active[$i][0]; 
    

    It is first popped off the $ftp_pool array and so the array index is removed, and then copied. I'm assuming that the resource variable is a reference (the manual seems to say this). Perhaps ensuring this by explicity passing by reference may help. Food for thought anyway.

    One improvement I could think of would be an option to only make the FTP connections if they are needed, so a 5 file download wouldn't initiate 10 FTP connections.

      5 days later

      Oooh, cool ideas. I think I'm gonna give some of them a bash this evening. The OO-ifying (blatantly a word!) should be quite easy, I've recently implemented a similar kind of queueing thing Java so hopefully I can just canabalize that. Not sure about the resource refereces, like I say, it was a play to see if it worked so I didn't really investigate it much (other than the API). Looks like I've got a little more reading to do on that point. Only making connections as they're needed is so obvious now you mention it. I guess I still need the limit so that the number of connections doesn't go haywire though.
      Cheers for the input 🙂

        Right, this is a chunky piece of code but see what you think.
        I've tested it now and it seems to work. Note, I've only tested for the case at the bottom of the file.

        Cheers
        Rob

          Write a Reply...