我正在尝试通过FTP从HTTP流传输/管道文件到用户的浏览器.也就是说,我试图在FTP服务器上打印一个文件的内容.
这是我到目前为止
public function echo_contents() { $file = fopen('PHP://output','w+'); if(!$file) { throw new Exception('Unable to open output'); } try { $this->ftp->get($this->path,$file); } catch(Exception $e) { fclose($file); // wtb finally throw $e; } fclose($file); }
$this-> ftp->得到如下所示:
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
我尝试用PHP:// stdout替换PHP://输出,没有成功(似乎没有发送到浏览器).
同时将数据发送到浏览器时,如何有效地从FTP下载?
注意:我不想使用file_get_contents(‘ftp:// user:pass @ host:port / path / to / file’);或类似.
找到解决方案!
创建套接字对(匿名管道?).使用非阻塞ftp_nb_fget函数写入管道的一端,并回显管道的另一端.
测试速度很快(在100Mbps连接上容易10MB / s),所以没有太多的I / O开销.
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,$stream); } /* TODO Error handling. */ class FtpNonBlockingRequest { protected $ftp = NULL; protected $status = NULL; public function __construct($ftp,$stream) { $this->ftp = $ftp; $this->status = ftp_nb_fget($this->ftp,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); } }