您需要清除statcache以获取实际文件大小。固定了一些其他位之后,您的stream.php可能如下所示:
<?phpheader("Content-Type: text/event-stream");header("Cache-Control: no-cache");header("Connection: keep-alive");// Check if the header's been sent to avoid `PHP Notice: Undefined index: HTTP_LAST_EVENT_ID in stream.php on line `// php 7+//$lastId = $_SERVER["HTTP_LAST_EVENT_ID"] ?? 0;// php < 7$lastId = isset($_SERVER["HTTP_LAST_EVENT_ID"]) ? intval($_SERVER["HTTP_LAST_EVENT_ID"]) : 0;$upload = $_GET["filename"];$data = 0;// if file already exists, its initial size can be bigger than the new one, so we need to ignore it$wasLess = $lastId != 0;while ($data < $_GET["filesize"] || !$wasLess) { // system calls are expensive and are being cached with assumption that in most cases file stats do not change often // so we clear cache to get most up to date data clearstatcache(true, $upload); $data = filesize($upload); $wasLess |= $data < $_GET["filesize"]; // don't send stale filesize if ($wasLess) { sendMessage($lastId, $data); $lastId++; } // not necessary here, though without thousands of `message` events will be dispatched //sleep(1); // millions on poor connection and large files. 1 second might be too much, but 50 messages a second must be okay usleep(20000);}function sendMessage($id, $data){ echo "id: $idn"; echo "data: $datann"; ob_flush(); // no need to flush(). It adds content length of the chunk to the stream // flush();}
注意事项:
安全。我是说运气据我了解,这是概念的证明,而安全是最不用担心的,但免责声明应该存在。这种方法从根本上来说是有缺陷的,仅当您不关心DOS攻击或有关文件的信息消失时才应使用此方法。
中央处理器。没有
usleep该脚本将消耗100%的单个内核。长时间睡眠会使您有在单个迭代中上载整个文件的风险,并且永远不会满足退出条件。如果您在本地进行测试,
usleep则应将其完全删除,因为在本地上传MB的时间约为毫秒。
打开连接。apache和nginx /
fpm都具有可以满足请求的php进程数量有限。上载单个文件所需的时间为2。在带宽缓慢或伪造请求的情况下,此时间可能会很长,并且Web服务器可能开始拒绝请求。
客户端部分。文件完全上载后,您需要分析响应并最终停止侦听事件。
编辑:
为了使它或多或少对生产友好,您将需要内存中的存储(例如redis或memcache)来存储文件元数据。
发出发布请求,添加一个唯一的令牌,该令牌标识文件和文件大小。
在您的Javascript中:
const fileId = Math.random().toString(36).substr(2); // or anything more unique...const [request, source] = [ new Request(`${url}?fileId=${fileId}&size=${filesize}`, { method:"POST", headers:headers, body:file }) , new EventSource(`${stream}?fileId=${fileId}`)];....
在data.php中注册令牌并按块报告进度:
....$fileId = $_GET['fileId'];$fileSize = $_GET['size'];setUnique($fileId, 0, $fileSize);while ($uploaded = stream_copy_to_stream($input, $file, 1024)) { updateProgress($id, $uploaded);}....function setUnique($id, $size) { // implement with your storage of choice}function updateProgress($id, $processed) { // implement with your storage of choice}
因此,您的stream.php根本不需要打入磁盘,并且只要UX可以接受就可以hibernate:
....list($progress, $size) = getProgress('non_existing_key_to_init_default_values');$lastId = 0;while ($progress < $size) { list($progress, $size) = getProgress($_GET["fileId"]); sendMessage($lastId, $progress); $lastId++; sleep(1);}.....function getProgress($id) { // implement with your storage of choice}
除非您放弃EventSource进行旧的良好拉拔,否则无法解决2个开放连接的问题。没有循环的stream.php的响应时间只有几毫秒,并且保持连接一直处于打开状态非常浪费,除非您每秒需要数百次更新。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)