When using PHP's non-blocking FTP function ftp_nb_continue(), many developers encounter a frustrating issue: the program appears to "hang" — it neither throws an error nor continues execution. This often happens because the FTP session has entered an unexpected wait state. This article explores the common causes behind ftp_nb_continue() hanging and offers practical solutions to help you avoid blocking and timeout problems.
ftp_nb_continue() is used in conjunction with non-blocking transfer functions such as ftp_nb_get() and ftp_nb_put(). Its purpose is to continue an incomplete FTP operation. The benefit of non-blocking transfers is that you can handle other logic while waiting for a transfer to complete, improving program responsiveness.
A typical non-blocking download process looks like this:
$ftp = ftp_connect('gitbox.net');
ftp_login($ftp, 'username', 'password');
$ret = ftp_nb_get($ftp, 'local_file.txt', 'remote_file.txt', FTP_BINARY);
while ($ret == FTP_MOREDATA) {
// Perform other tasks
do_something_else();
// Continue transfer
$ret = ftp_nb_continue($ftp);
}
Looks pretty straightforward, right? But in practice, many users find themselves stuck in this loop.
Some FTP servers respond slowly under certain conditions, especially when large files are requested or network quality is poor. This can cause ftp_nb_continue() to remain in the FTP_MOREDATA state indefinitely.
Solution:
Set a reasonable timeout:
ftp_set_option($ftp, FTP_TIMEOUT_SEC, 30);
Use stream_set_timeout() for more granular control (requires access to the underlying stream of the FTP connection).
Many developers forget to include a delay in their non-blocking loop, leading to high CPU usage and overwhelming the FTP server, which cannot respond in time.
Solution:
while ($ret == FTP_MOREDATA) {
do_something_else();
usleep(100000); // Delay for 100ms
$ret = ftp_nb_continue($ftp);
}
Some FTP servers do not return the correct status code after completing a transfer, causing ftp_nb_continue() to think the transfer is still in progress.
Solution:
Check if the local file has reached the expected size;
Set a maximum number of retries:
$maxTries = 100;
$tries = 0;
while ($ret == FTP_MOREDATA && $tries < $maxTries) {
usleep(100000);
$ret = ftp_nb_continue($ftp);
$tries++;
}
if ($tries == $maxTries) {
throw new Exception('FTP transfer timed out');
}
Different servers handle active (PORT) and passive (PASV) modes differently. Incorrect configuration can result in a connection being established but data transfer failing.
Solution:
ftp_pasv($ftp, true); // Enable passive mode
Try switching between active and passive modes to see which works better with your FTP server.
Always implement an exit mechanism in non-blocking loops;
Ensure appropriate delays are included in loops;
Set timeouts and maximum retry limits;
If transfer hangs for too long, try switching FTP modes;
Don't forget to release resources:
ftp_close($ftp);
ftp_nb_continue() can significantly improve the flexibility of FTP operations in PHP, but it’s also easy to run into trouble. With the techniques outlined above, you can avoid common pitfalls like blocking, infinite loops, and excessive CPU usage, making your PHP FTP programs more robust and efficient. If you're experiencing similar issues, try diagnosing them one by one and apply the appropriate fix accordingly.