需求:需要知道有人开始下载,是否下载完成。
思路:通过 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
1
aru 2017-01-11 16:16:35 +08:00 1
网页上通过 js 来实现:下载完成后触发一次 js 请求,服务器做记录
|
2
xuexixuexi2 OP @aru 谢谢,但还是希望能在服务端实现。 js 不考虑。
|
3
xfspace 2017-01-11 18:16:38 +08:00 via Android
不判断文件就写数据库 hhh
|
4
xuexixuexi2 OP @xfspace 你好,是我的代码哪里有问题吗?我确实不清楚,请指教。
但是文件../files/file1 是存在的,我在自己的电脑上测试,可以下载,下载后文件也是正确的。 但是下载需要几分钟,而一开始浏览器弹出选择保存文件位置的时候,“下载完成”就写到数据库中了。 |
5
aru 2017-01-11 20:11:04 +08:00 1
|
6
xuexixuexi2 OP @aru 跟我猜想的一样。那有什么办法可以达到我的目的吗?比如修改 PHP.ini 或者 fastCGI 或者 nginx 的配置文件。
|
7
why1 2017-01-11 20:26:42 +08:00 via Android 1
找下 Nginx 的模块
|
8
xuexixuexi2 OP @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 |
9
xuexixuexi2 OP --with-http_realip_module
--with-http_addition_module --with-http_sub_module |
10
skydiver 2017-01-11 20:55:48 +08:00 via Android
不知道为什么用这么奇怪的方法来解决问题…然后用更奇怪的方法来修正奇怪的方法导致的问题……
|
11
xuexixuexi2 OP @skydiver 哦,还有什么更好的办法吗?
|
12
ryd994 2017-01-11 22:29:59 +08:00 via Android 1
说实话,我也觉得用 JS 是最简单的
开始前统计很简单,用 auth_request 配合 PHP 就行 但是结束后统计很难 主要是 Nginx 之类的会有 buffer ,还有 sendfile 这类 如果只是为了统计开始结束时间和下载的区段的话,你可以直接写 Nginx log 事后分析 |
13
xuexixuexi2 OP sendfile 设为 off ,试过了,没用。
代码在 apache 下是没问题的,下载成功才写入“下载完成”。 我在这里也是问问,多学点知识。实在不行就换 apache 服务器算了。 |
14
xuexixuexi2 OP 上网查了一下,貌似和 with-file-aio 模块有关,但是 nginx 模块好像都是编译的,不能关闭?
|
15
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(); 感觉就好了。。 |
16
lslqtz 2017-01-12 01:52:26 +08:00 1
顺便说一下,可以改用 readfile 而不用 fopen
|
17
msg7086 2017-01-12 01:54:53 +08:00
nginx 负责发送数据,要统计开始和完成,当然依赖 nginx 了。
|
18
manhere 2017-01-12 02:53:46 +08:00 via iPhone
流输出+限速 就能统计到了吧
|
19
xuexixuexi2 OP |
20
xuexixuexi2 OP @msg7086 那要怎么做呢?
|
21
lslqtz 2017-01-12 04:12:22 +08:00 1
我这边测试是正常的,视频在这里: http://xinchen123.oss-cn-shanghai.aliyuncs.com/o_1b67igqepga1t4g0ahkfsua.mp4 。
set_time_limit(0); ob_end_flush(); flush(); @header('Content-Length:299711208'); @header('Content-Type:application/octet-stream'); @header('Content-Disposition:attachment;filename=WindowsXP_SP2.exe'); @readfile('http://speed.myzone.cn/WindowsXP_SP2.exe'); |
22
msg7086 2017-01-12 04:35:08 +08:00 2
挂个 inotify 在 nginx access log 上,然后写数据库?
|
23
xuexixuexi2 OP @lslqtz 可能还是和 nginx 的模块有关吧。
我又详细测了一下,改代码位置,改 header ,都和你的一样了,还是存在这个问题。 |
24
xuexixuexi2 OP @msg7086 按你说的上网搜了一下,确实是个可行的思路。
明天试试:) |
25
lslqtz 2017-01-12 04:57:48 +08:00
@xuexixuexi2 php/nginx 问题?不清楚,手头是 win 下的,理论可行没错。
|
26
ericls 2017-01-12 07:07:07 +08:00
最简单的做法应该是利用 nginx 的 internel routing
|
29
lslqtz 2017-01-12 09:05:58 +08:00
@aru 楼主的描述是计费后才开始下载,说明这时已经执行完毕并输出缓冲。
而我这个是立即执行的,有缓冲也应该是远程文件下完后才输出。 |
30
aru 2017-01-12 10:17:51 +08:00 1
@lslqtz 不要说那么多。直接试试使用本地的大文件(不用太大,下载方需要 3-10 秒内下完即可),看看能不能记录到准确的下载耗时。我觉得用 apache 的 mod_php 是没问题的, nginx 的 fast_cgi 不行。
|
31
lslqtz 2017-01-12 23:27:02 +08:00
@aru 我试着写了写,不能计算到准确的下载耗时。
不过之前是帮楼主解决下载之前卡住的问题。 现在把代码改成了这样,问题在于如果 readfile 换成 sleep 就可以准确的统计到: <?php set_time_limit(0); ignore_user_abort(1); ob_end_flush(); flush(); $time=time(); @header('Connection:Close'); @header('Content-Length:299711208'); @header('Content-Type:application/octet-stream'); @header('Content-Disposition:attachment;filename=WindowsXP_SP2.exe'); @readfile('1.exe'); while (!connection_aborted()) { file_put_contents('1.txt',time()-$time); die(); } ?> |
32
lslqtz 2017-01-12 23:30:06 +08:00
Reply 31
lslqtz 刚刚 @aru 我试着写了写,不能计算到准确的下载耗时。 不过之前是帮楼主解决下载之前卡住的问题。 现在把代码改成了这样,问题在于如果 readfile 换成 sleep 就可以准确的统计到: <?php set_time_limit(0); ignore_user_abort(1); ob_end_flush(); flush(); $time=time(); @header('Connection:Close'); @header('Content-Length:299711208'); @header('Content-Type:application/octet-stream'); @header('Content-Disposition:attachment;filename=WindowsXP_SP2.exe'); @readfile('1.exe'); while (connection_status() != 0 || connection_aborted()) { file_put_contents('1.txt',time()-$time); die(); } ?> 突然感觉代码写错了... |
33
lslqtz 2017-01-12 23:32:37 +08:00
然后又想了想,改成 while (1) {}里面加 if 会好一点。。
如果在里面的 if 判断到连接被结束了,就断开连接。 Reply 32 lslqtz 1 分钟前 Reply 31 lslqtz 刚刚 @aru 我试着写了写,不能计算到准确的下载耗时。 不过之前是帮楼主解决下载之前卡住的问题。 现在把代码改成了这样,问题在于如果 readfile 换成 sleep 就可以准确的统计到: <?php set_time_limit(0); ignore_user_abort(1); ob_end_flush(); flush(); $time=time(); @header('Connection:Close'); @header('Content-Length:299711208'); @header('Content-Type:application/octet-stream'); @header('Content-Disposition:attachment;filename=WindowsXP_SP2.exe'); @readfile('1.exe'); while (1) { if (connection_status() != 0 || connection_aborted()) { file_put_contents('1.txt',time()-$time); die(); } } ?> |
34
aru 2017-01-13 10:39:57 +08:00
@lslqtz 我感觉,尝试在发送文件的代码做下载耗时统计的做法是歪门邪道(极大降低了 php 的性能)。
js 或者 access log 或者 nginx module 才是正确的做法。 |
36
xuexixuexi2 OP @aru 我本来专业不是做这个的, nginx 用得更少。
有没有现成的 nginx 模块可以实现这个?还有 nginx 模块是不是只能编译进 nginx 里才能用? 我看 nginx 的配置没有像 apache 的配置一样设置模块。 |
37
aru 2017-01-13 21:52:46 +08:00
@xuexixuexi2 似乎没有现成的。日志里面可以记录下载的大小和下载耗时,将这个弄成单独的一个日志文件,然后弄个脚本去做定时处理吧
|