Stream FTP download to output
我试图通过HTTP从ftp将文件流/管道传输到用户的浏览器。也就是说,我正在尝试在FTP服务器上打印文件的内容。
这就是我目前为止所拥有的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
1 2 3 | public function get($path, $stream) { ftp_fget($this->ftp, $stream, $path, FTP_BINARY); // Line 200 } |
使用这种方法,我只能将小文件发送到用户的浏览器。对于较大的文件,不会打印任何内容,我会得到一个致命的错误(可以从Apache日志中读取):
PHP Fatal error: Allowed memory size of 16777216 bytes exhausted (tried to allocate 15994881 bytes) in /xxx/ftpconnection.php on line 200
我试图用
如何在将数据同时发送到浏览器时从ftp高效下载?
注:我不想使用
找到了解决方案!
创建一个套接字对(匿名管道?).使用非阻塞
测试速度很快(在100Mbps连接上很容易达到10MB/s),因此没有太多的I/O开销。
确保清除所有输出缓冲区。框架通常会缓冲您的输出。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 | public function echo_contents() { /* FTP writes to [0]. Data passed through from [1]. */ $sockets = stream_socket_pair(STREAM_PF_UNIX, STREAM_SOCK_STREAM, STREAM_IPPROTO_IP); if($sockets === FALSE) { throw new Exception('Unable to create socket pair'); } stream_set_write_buffer($sockets[0], 0); stream_set_timeout($sockets[1], 0); try { // $this->ftp is an FtpConnection $get = $this->ftp->get_non_blocking($this->path, $sockets[0]); while(!$get->is_finished()) { $contents = stream_get_contents($sockets[1]); if($contents !== false) { echo $contents; flush(); } $get->resume(); } $contents = stream_get_contents($sockets[1]); if($contents !== false) { echo $contents; flush(); } } catch(Exception $e) { fclose($sockets[0]); // wtb finally fclose($sockets[1]); throw $e; } fclose($sockets[0]); fclose($sockets[1]); } // class FtpConnection public function get_non_blocking($path, $stream) { // $this->ftp is the FTP resource returned by ftp_connect return new FtpNonBlockingRequest($this->ftp, $path, $stream); } /* TODO Error handling. */ class FtpNonBlockingRequest { protected $ftp = NULL; protected $status = NULL; public function __construct($ftp, $path, $stream) { $this->ftp = $ftp; $this->status = ftp_nb_fget($this->ftp, $stream, $path, FTP_BINARY); } public function is_finished() { return $this->status !== FTP_MOREDATA; } public function resume() { if($this->is_finished()) { throw BadMethodCallException('Cannot continue download; already finished'); } $this->status = ftp_nb_continue($this->ftp); } } |
尝试:
1 |
我发现,通过大量的文件操作,让底层操作系统功能为您处理它是值得的。
我知道这是旧的,但有些人可能仍然认为它是有用的。
我在Windows环境中尝试过您的解决方案,它几乎完美地工作:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | $conn_id = ftp_connect($host); ftp_login($conn_id, $user, $pass) or die(); $sockets = stream_socket_pair(STREAM_PF_INET, STREAM_SOCK_STREAM, STREAM_IPPROTO_IP) or die(); stream_set_write_buffer($sockets[0], 0); stream_set_timeout($sockets[1], 0); set_time_limit(0); $status = ftp_nb_fget($conn_id, $sockets[0], $filename, FTP_BINARY); while ($status === FTP_MOREDATA) { echo stream_get_contents($sockets[1]); flush(); $status = ftp_nb_continue($conn_id); } echo stream_get_contents($sockets[1]); flush(); fclose($sockets[0]); fclose($sockets[1]); |
我用了
所以我决定使用另一种方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | $ctx = stream_context_create(); stream_context_set_params($ctx, array('notification' => function($code, $sev, $message, $msgcode, $bytes, $length) { switch ($code) { case STREAM_NOTIFY_CONNECT: // Connection estabilished break; case STREAM_NOTIFY_FILE_SIZE_IS: // Getting file size break; case STREAM_NOTIFY_PROGRESS: // Some bytes were transferred break; default: break; } })); @readfile("ftp://$user:$pass@$host/$filename", false, $ctx); |
这对php 5.4.5很有吸引力。不好的部分是,您不能捕获传输的数据,只能捕获块大小。
听起来您需要关闭该页面的输出缓冲,否则PHP将尝试在所有内存中容纳它。
一个简单的方法是:
1 2 3 |
把它放在您调用->get()之前,我认为这将解决您的问题。
一个快速的搜索带来了PHP的刷新。
这篇文章可能也很有趣:http://www.net2ftp.org/forums/viewtopic.php?ID=3774
(我自己从来没有遇到过这个问题,所以这只是一个疯狂的猜测;但是,也许……)
也许更改正在写入的"文件"的输出缓冲区的大小会有所帮助?
关于这一点,请参见
例如:
1 2 |
有了这个,您的代码应该使用非缓冲流——这可能有助于…