请教一个 nginx 做下载服务器统计下载信息的问题。

2017-01-11 15:32:59 +08:00
 xuexixuexi2

需求:需要知道有人开始下载,是否下载完成。
思路:通过 php 的 fpassthru 发送实际的文件数据,并在开始前和结束后写数据库。
代码:

$db = new DownloadDB();
$db->exec("INSERT INTO tblDownloads(info) VALUES('下载开始');");
$db->close();

$filepath = '../files/file1';
@header('Content-type: application/octet-stream');
@header('Content-Disposition: attachment; filename=' . $filename);
@header('Accept-Ranges: bytes');
@header('Content-Length: ' . filesize($filepath));
@header('Cache-control: no-cache,no-store,must-revalidate');
@header('Pragma: no-cache');
@header('Expires: 0');
$file = @fopen($filepath, "rb");
@fpassthru($file);
@fclose($file);

$db = new DownloadDB();
$db->exec("INSERT INTO tblDownloads(info) VALUES('下载完成');");
$db->close();

问题:还没有下载完,就写了“下载完成”到数据库中了,甚至是和“下载开始”同时写入数据库的。
tblDownloads 表有时间字段,可以看到下载完成和下载开始是同时写入数据库的,也就是中间的 fopen-fpassthru-fclose 这些耗时为 0 。
而我的理解是 fpassthru 应该将数据全部推送给用户后才返回,是不是有缓存机制? 要怎么能按我预想的进行?
环境: Linux+nginx+fastCGI

4510 次点击
所在节点    程序员
37 条回复
aru
2017-01-11 16:16:35 +08:00
网页上通过 js 来实现:下载完成后触发一次 js 请求,服务器做记录
xuexixuexi2
2017-01-11 18:14:44 +08:00
@aru 谢谢,但还是希望能在服务端实现。 js 不考虑。
xfspace
2017-01-11 18:16:38 +08:00
不判断文件就写数据库 hhh
xuexixuexi2
2017-01-11 19:44:33 +08:00
@xfspace 你好,是我的代码哪里有问题吗?我确实不清楚,请指教。
但是文件../files/file1 是存在的,我在自己的电脑上测试,可以下载,下载后文件也是正确的。
但是下载需要几分钟,而一开始浏览器弹出选择保存文件位置的时候,“下载完成”就写到数据库中了。
aru
2017-01-11 20:11:04 +08:00
@xuexixuexi2
php 将文件发给了 nginx ,完成
nginx 慢慢的再传给客户端
xuexixuexi2
2017-01-11 20:19:28 +08:00
@aru 跟我猜想的一样。那有什么办法可以达到我的目的吗?比如修改 PHP.ini 或者 fastCGI 或者 nginx 的配置文件。
why1
2017-01-11 20:26:42 +08:00
找下 Nginx 的模块
xuexixuexi2
2017-01-11 20:50:46 +08:00
@why1
--prefix=/etc/nginx
--sbin-path=/usr/sbin/nginx
--conf-path=/etc/nginx/nginx.conf
--error-log-path=/var/log/nginx/error.log
--http-log-path=/var/log/nginx/access.log
--pid-path=/var/run/nginx.pid
--lock-path=/var/run/nginx.lock
--http-client-body-temp-path=/var/cache/nginx/client_temp
--http-proxy-temp-path=/var/cache/nginx/proxy_temp
--http-fastcgi-temp-path=/var/cache/nginx/fastcgi_temp
--http-uwsgi-temp-path=/var/cache/nginx/uwsgi_temp
--http-scgi-temp-path=/var/cache/nginx/scgi_temp
--user=nginx
--group=nginx
--with-http_ssl_module
--with-http_dav_module
--with-http_flv_module
--with-http_mp4_module
--with-http_gunzip_module
--with-http_gzip_static_module
--with-http_random_index_module
--with-http_secure_link_module
--with-http_stub_status_module
--with-http_auth_request_module
--with-mail
--with-mail_ssl_module
--with-file-aio
--with-ipv6
--with-http_spdy_module
--with-cc-opt='-O2 -g -pipe -Wp,-D_FORTIFY_SOURCE=2
-fexceptions
-fstack-protector
--param=ssp-buffer-size=4
-m32
-march=i386
-mtune=generic-fasynchronous-unwind-tables
xuexixuexi2
2017-01-11 20:53:34 +08:00
--with-http_realip_module
--with-http_addition_module
--with-http_sub_module
skydiver
2017-01-11 20:55:48 +08:00
不知道为什么用这么奇怪的方法来解决问题…然后用更奇怪的方法来修正奇怪的方法导致的问题……
xuexixuexi2
2017-01-11 20:57:41 +08:00
@skydiver 哦,还有什么更好的办法吗?
ryd994
2017-01-11 22:29:59 +08:00
说实话,我也觉得用 JS 是最简单的
开始前统计很简单,用 auth_request 配合 PHP 就行
但是结束后统计很难
主要是 Nginx 之类的会有 buffer ,还有 sendfile 这类
如果只是为了统计开始结束时间和下载的区段的话,你可以直接写 Nginx log 事后分析
xuexixuexi2
2017-01-11 22:42:54 +08:00
sendfile 设为 off ,试过了,没用。
代码在 apache 下是没问题的,下载成功才写入“下载完成”。
我在这里也是问问,多学点知识。实在不行就换 apache 服务器算了。
xuexixuexi2
2017-01-11 22:44:15 +08:00
上网查了一下,貌似和 with-file-aio 模块有关,但是 nginx 模块好像都是编译的,不能关闭?
lslqtz
2017-01-12 01:46:47 +08:00
@aru 其实我也这么做过,不过大部分都是 php 在卡着而不是 nginx 。

fpassthru() 函数输出文件指针处的所有剩余数据。
该函数将给定的文件指针从当前的位置读取到 EOF ,并把结果写到输出缓冲区。
摘自: http://www.w3school.com.cn/php/func_filesystem_fpassthru.asp
所以在这之前先清除缓冲并禁止。
#设置执行时间不限时 。
set_time_limit(0);
#发送内部缓冲区的内容到浏览器,删除缓冲区的内容,不关闭缓冲区。
ob_flush();
#发送内部缓冲区的内容到浏览器,删除缓冲区的内容,关闭缓冲区。
ob_end_flush();
#将 ob_flush 释放出来的内容,以及不在 PHP 缓冲区中的内容,全部输出至浏览器;刷新内部缓冲区的内容,并输出。
flush();
所以实际上前面加几句:
set_time_limit(0);
ob_end_flush();
flush();
感觉就好了。。
lslqtz
2017-01-12 01:52:26 +08:00
顺便说一下,可以改用 readfile 而不用 fopen
msg7086
2017-01-12 01:54:53 +08:00
nginx 负责发送数据,要统计开始和完成,当然依赖 nginx 了。
manhere
2017-01-12 02:53:46 +08:00
流输出+限速 就能统计到了吧
xuexixuexi2
2017-01-12 03:47:07 +08:00
@lslqtz 将代码改为:
set_time_limit(0);
ob_end_flush();
flush();
$file = @fopen($filepath, "rb");
@fpassthru($file);
@fclose($file);
和:
set_time_limit(0);
ob_end_flush();
flush();
@readfile($filepath);
都试过了,还是一样。
xuexixuexi2
2017-01-12 03:47:50 +08:00
@msg7086 那要怎么做呢?

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/333885

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX