V2EX 首页   注册   登录
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
V2EX  ›  程序员

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

  •  
  •   xuexixuexi2 · 285 天前 · 1800 次点击
    这是一个创建于 285 天前的主题,其中的信息可能已经有所发展或是发生改变。

    需求:需要知道有人开始下载,是否下载完成。
    思路:通过 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

    37 回复  |  直到 2017-01-13 21:52:46 +08:00
        1
    aru   285 天前   ♥ 1
    网页上通过 js 来实现:下载完成后触发一次 js 请求,服务器做记录
        2
    xuexixuexi2   285 天前
    @aru 谢谢,但还是希望能在服务端实现。 js 不考虑。
        3
    xfspace   285 天前 via Android
    不判断文件就写数据库 hhh
        4
    xuexixuexi2   285 天前
    @xfspace 你好,是我的代码哪里有问题吗?我确实不清楚,请指教。
    但是文件../files/file1 是存在的,我在自己的电脑上测试,可以下载,下载后文件也是正确的。
    但是下载需要几分钟,而一开始浏览器弹出选择保存文件位置的时候,“下载完成”就写到数据库中了。
        5
    aru   285 天前   ♥ 1
    @xuexixuexi2
    php 将文件发给了 nginx ,完成
    nginx 慢慢的再传给客户端
        6
    xuexixuexi2   285 天前
    @aru 跟我猜想的一样。那有什么办法可以达到我的目的吗?比如修改 PHP.ini 或者 fastCGI 或者 nginx 的配置文件。
        7
    why1   285 天前 via Android   ♥ 1
    找下 Nginx 的模块
        8
    xuexixuexi2   285 天前
    @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   285 天前
    --with-http_realip_module
    --with-http_addition_module
    --with-http_sub_module
        10
    skydiver   285 天前 via Android
    不知道为什么用这么奇怪的方法来解决问题…然后用更奇怪的方法来修正奇怪的方法导致的问题……
        11
    xuexixuexi2   285 天前
    @skydiver 哦,还有什么更好的办法吗?
        12
    ryd994   285 天前 via Android   ♥ 1
    说实话,我也觉得用 JS 是最简单的
    开始前统计很简单,用 auth_request 配合 PHP 就行
    但是结束后统计很难
    主要是 Nginx 之类的会有 buffer ,还有 sendfile 这类
    如果只是为了统计开始结束时间和下载的区段的话,你可以直接写 Nginx log 事后分析
        13
    xuexixuexi2   285 天前
    sendfile 设为 off ,试过了,没用。
    代码在 apache 下是没问题的,下载成功才写入“下载完成”。
    我在这里也是问问,多学点知识。实在不行就换 apache 服务器算了。
        14
    xuexixuexi2   285 天前
    上网查了一下,貌似和 with-file-aio 模块有关,但是 nginx 模块好像都是编译的,不能关闭?
        15
    lslqtz   285 天前
    @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   285 天前   ♥ 1
    顺便说一下,可以改用 readfile 而不用 fopen
        17
    msg7086   285 天前
    nginx 负责发送数据,要统计开始和完成,当然依赖 nginx 了。
        18
    manhere   285 天前 via iPhone
    流输出+限速 就能统计到了吧
        19
    xuexixuexi2   285 天前
    @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);
    都试过了,还是一样。
        20
    xuexixuexi2   285 天前
    @msg7086 那要怎么做呢?
        21
    lslqtz   285 天前   ♥ 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   285 天前   ♥ 2
    挂个 inotify 在 nginx access log 上,然后写数据库?
        23
    xuexixuexi2   285 天前
    @lslqtz 可能还是和 nginx 的模块有关吧。
    我又详细测了一下,改代码位置,改 header ,都和你的一样了,还是存在这个问题。
        24
    xuexixuexi2   285 天前
    @msg7086 按你说的上网搜了一下,确实是个可行的思路。
    明天试试:)
        25
    lslqtz   285 天前
    @xuexixuexi2 php/nginx 问题?不清楚,手头是 win 下的,理论可行没错。
        26
    ericls   285 天前
    最简单的做法应该是利用 nginx 的 internel routing
        27
    ericls   285 天前
    @ericls *internal
        28
    aru   285 天前 via iPhone
    @lslqtz 你换个本地文件发送试试。
    远程文件读取太耗时了,耗时就可能和客户端下载时间接近了。
        29
    lslqtz   285 天前
    @aru 楼主的描述是计费后才开始下载,说明这时已经执行完毕并输出缓冲。
    而我这个是立即执行的,有缓冲也应该是远程文件下完后才输出。
        30
    aru   285 天前   ♥ 1
    @lslqtz 不要说那么多。直接试试使用本地的大文件(不用太大,下载方需要 3-10 秒内下完即可),看看能不能记录到准确的下载耗时。我觉得用 apache 的 mod_php 是没问题的, nginx 的 fast_cgi 不行。
        31
    lslqtz   284 天前
    @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   284 天前
    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   284 天前
    然后又想了想,改成 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   284 天前
    @lslqtz 我感觉,尝试在发送文件的代码做下载耗时统计的做法是歪门邪道(极大降低了 php 的性能)。
    js 或者 access log 或者 nginx module 才是正确的做法。
        35
    lslqtz   284 天前 via iPhone
    @aru 是的,很费资源。
    但我还是去实现了他 xD 。
        36
    xuexixuexi2   283 天前
    @aru 我本来专业不是做这个的, nginx 用得更少。
    有没有现成的 nginx 模块可以实现这个?还有 nginx 模块是不是只能编译进 nginx 里才能用?
    我看 nginx 的配置没有像 apache 的配置一样设置模块。
        37
    aru   283 天前
    @xuexixuexi2 似乎没有现成的。日志里面可以记录下载的大小和下载耗时,将这个弄成单独的一个日志文件,然后弄个脚本去做定时处理吧
    DigitalOcean
    关于   ·   FAQ   ·   API   ·   我们的愿景   ·   广告投放   ·   鸣谢   ·   2652 人在线   最高记录 3541   ·  
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.0 · 47ms · UTC 05:55 · PVG 13:55 · LAX 22:55 · JFK 01:55
    ♥ Do have faith in what you're doing.
    沪ICP备16043287号-1