определение размера не доотданного файла

alan4ick

Guest
определение размера не доотданного файла, выгрузка файлов

Нужно узнать , какой размер от файла был передан клиенту, если клиент оборвал передачу.

Можно использовать : readfile , fread или fpassthru

Также стоит ignore_user_abort(true)

readfile и fpassthru являются предпочтительными, так как не нагружают apache , но так как нужно сделать возможность докачки , то readfile не подходит.


fread я использовать не хочу, так как очень сильно грузит apache, хотя им можно корректно отловить обрыв соединения.Использую след. код:


PHP:
$dl_size_block=1024*2
// код  вырезан
while( !feof($fl) && (connection_status()==0) ) {
     			print(fread($fl,$dl_size_block));
//$dl_total+=$dl_size_block;
flush();

}

$dl['total']=ftell($fl);
fclose($fl);
если использовать fpassthru , то в случае обрыва по ftell или return значению возвращается полный размер файла

PHP:
$dl_total=fpassthru($file);
//$dl_total=ftell($file);
fclose($file);
Как можно отловить реально отданный размер файла при использовании fpassthru или readfile?

или заставить fread меньше кушать ресурсов apache?
 

white phoenix

Новичок
alan4ick
> Как можно отловить реально отданный размер файла при использовании fpassthru или readfile?
Наверно реально принятый ты имел ввиду. Никак. А вообще зачем тебе это нужно?
> в случае обрыва по ftell или return значению возвращается полный размер файла
Это естественно. Допустим файл скачивается минуту, на половине ты отключил, $dl_size_block будет равен размеру файла или больше (на 1-2048 байт), т.к. PHP-скрипт закончит свою работу через ~0.1 сек, а Apache сам по себе будет передавать данные.
 

alan4ick

Guest
white phoenix
>> Наверно реально принятый ты имел ввиду. Никак. А вообще зачем тебе это нужно?
чтобы точно знать сколько было полных закачек и какой суммарный траффик

> в случае обрыва по ftell или return значению возвращается полный размер файла
>>Это естественно. Допустим файл скачивается минуту, на
>>половине ты отключил, $dl_size_block будет равен размеру
>>файла или больше (на 1-2048 байт), т.к. PHP-скрипт закончит
>>свою работу через ~0.1 сек, а Apache сам по себе будет >>передавать данные.

я имел ввиду это для fpassthru и fileread, а не для fread

Я предпологал , что если стоит ignore_user_abort(true) , то php завершит выполнение фунций fpassthru, fileread (так как передавать некому) и вернет значении неполностью переданного файла и продолжит выполнение скрипта.

А получается что fpassthru, fileread будут продолжать передачу, поэтому и возвращается полный размер файла.
 

white phoenix

Новичок
alan4ick
> чтобы точно знать сколько было полных закачек и какой суммарный траффик
Проще посчитать на более низком уровне.
> А получается что fpassthru, fileread будут продолжать передачу, поэтому и возвращается полный размер файла.
Именно так. ignore_user_abort(TRUE) указывает на то что скрипт должен продолжать работу после отключения пользователя. fileread что за зверь?
 

alan4ick

Guest
readfile конечно :)

>Проще посчитать на более низком уровне.
парсировать логи?

почему бы и не добавлять информацию в базу сразу, после завершения закачки ( счетчик загрузок, трафик и т.д. ), если это не требует никаких дополнительных операций и вычислений?!?

посмотрел ман, и нашел register_shutdown_function.
Вот если попробывать ее при отключенном ignore_user_abort с fpassthru ... вернет ли ftell верное кол-во отданных байт или нет?
 

white phoenix

Новичок
alan4ick
> парсировать логи?
Можно и так... можно файрваллом.
> вернет ли ftell верное кол-во отданных байт или нет?
Нет.
 

alan4ick

Guest
>Можно и так... можно файрваллом.
это уже изобретение велосипеда :))

>Нет.

вот и ошибся, все работает как я и предпологал, задача решена.

Я думаю эта тема будет многим интересна, в результате мы имеем:
-- можно сделать докачку
-- малая нагрузка на Apache за счет использования fpassthru
-- контроль отданного трафика
-- конролируем разрыв соединения

вот набросок кода для ваших дальнейших размышлений

PHP:
//  выполнение необходимых действий, даже если клиентоборвал соединение
register_shutdown_function('dl_hook');
// тут еще код

function dl_hook(){

global $dl;
	
   if( ($ft=fopen('log.log','a')) ){
       if ( $dl['dl_fl']  ){
       // считываем кол-во отданных байт и закрываем handle
	    $dl_traffic=ftell($dl['dl_fl']);
	    fclose($dl['dl_fl']);
       }
   
       fputs($ft,date('t.h.Y H:i:s').'|'.$dl['file_name'].'|'.$dl['file_size'].'|'.$dl_traffic."\n");
       fclose($ft);

    }
}

$dl['file_size']=filesize($dl['file_name']);
if ( $dl['dl_fl'] = fopen($dl['file_name'], 'rb')) {
   fpassthru($logger['dl_fl']);
// не закрываем handle, его закроем dl_hook
}
 

alan4ick

Guest
white phoenix
это относительно логов
или
> вернет ли ftell верное кол-во отданных байт или нет?
>>Нет.

Каждый думает и мыслит по своему+куча других проблемм, которые отвлекают и мешают взаимопонимаю, поэтому можно без туманных намеков... а не перечитать ли тебе мануал? ...

Можешь сразу сказать, в чем я ошибаюсь? чтобы не разводить лишний флуд, и не тратить лишнее время и внимание : твое , мое и других форумчан.
 

white phoenix

Новичок
alan4ick
> Каждый думает и мыслит по своему+куча других проблемм, которые отвлекают и мешают взаимопонимаю, поэтому можно
> без туманных намеков... а не перечитать ли тебе мануал?
Намеки вовсе не туманные, а вполне конкретные, указывают на то над чем тебе нужно задуматься, но судя по всему они не помогут. Ладно.
alan> Как можно отловить реально отданный размер файла при использовании fpassthru или readfile?
wp>Наверно реально принятый ты имел ввиду. Никак. А вообще зачем тебе это нужно?
Ты снова перепутал отданный и принятый размер, ftell будет всегда возвращать полный размер файла. Рассмотрим на пальцах: идет запрос http://site/download.php?file=10mb.rar (файл 10mb.rar скачивается например 5 минут), Apache видит что .php должен обрабатываться через PHP (AddType), и передает команду PHP-интерпритатору, затем скрипт открывает файл, fpassthru "печатает" содержимое файла, которое передается в Apache (все эти действия происходят за незначительное время), затем скрипт завершается и Apache втечение оставшихся ~4 минут и 58 секунд передает всё в броузер, если например через минуту происходит обрыв, PHP об этом естественно не "узнает".
 

alan4ick

Guest
Не смотря на вашу огромную активность на форуме, человеку свойственно ошибаться, оссобенно когда он уверен в своей правоте. Тогда он пропускает явные истины, которые и приводят к вот к таким казусам.

Очень жаль, что вы потратили свое время на написание этого эпоса. Увы, но вы постоянно пропускали важные моменты, возможно виноват в этом я, но все же попробую указать ваши или мои ошибки и зафиналить этот топик.

Ваши ошибки :

>>затем скрипт открывает файл, fpassthru "печатает" содержимое файла....
>>затем скрипт завершается и Apache втечение оставшихся ~4 минут
>> и 58 секунд передает всё в броузер
вот уж действительно бред... ваш apache уже давно бы умер от нехватки памяти ...
В php есть буфер, обычно 4к, apache передает пакеты клиенту и если клиент рвет коннект, то apache сообщает об этом php. В результате чего скрипт прекращает свою работу, если только в php скрипте не стоит значение для игнорирования разрыва коннекта ignore_user_abort(true) - именно об этом вы и писали во всех ваших постах.

Мои доводы:

1) трафик мы обычно считаете исходящий. Да он не будет точным, так как пройдет какое-то время пока php поймает разрыв и он будет отличаться от дошедшего до клиента, но это мизер, которым можно пренебречь.

Почему вы не правы:

1) fpassthru не загружает весь файл в память apache, иначе бы он сдох от нехватки памяти. fpassthru пакетно передает данные apache.


2) Я четко написал в последних 2 постах, что fpassthru используется при ignore_user_abort(false) т.е. когда клиент рвет коннект, то php экстренно завершает выполнение скрипта, в том числе fpassthru, но за счет того что открыт fopen'ом handle, то в handle хранится позиция, на которой fpassthru прекратил чтение!!!
Далее срабатывает shutdown функция, а так как handle остался открыт , то я считываю ftell'ом текущую позицию.

Вот если бы ignore_user_abort работал, тогда действительно бы
fpassthru передал весь файл и вернул его полный размер.

Финал :
и как любит Фанат, я бы порекомендовал почитать мануал php.net :)

Я не хочу чтобы другие пользователи неправильно поняли мои мысли, как возможно и вы, поэтому и хочу довести до конечной точки, чтобы все было понятно... конечно я в чем-то не прав, я не писал php и не занимаюсь им профессионально, но при решении задачи стараюсь максимально рассмотреть все тонкости функционирования и широту инструментария, дабы найти наивернейший путь.



...было бы неплохо если бы высказался кто-то из авторитетных старожил, а не продолжался ТЕТ-А-ТЕТ. Свежая голова всегда вносит ясность и прозерцание.


Всем Б.Спасибо, если прочитали этот монолог...
 

alan4ick

Guest
white phoenix

и что ты хочешь замерять через microtime ?
сколько выполняется fpassthru? сколько работает скрипт? сколько идет выгрузка ? скорость передачи?

Повторюсь твоими словами :
Подумай хорошенько над тем,что я написал.Без шуток.

или хотя бы запусти приведенный мной выше код в оригинальном виде ,а потом с ignore_user_abort(true)

-~{}~ 15.01.06 22:00:

Не знаю, почему ты на столько упрямый и не хочешь посмотреть истине, правде в глаза.

Если я не прав, то приведи опровергающие доказательства.

Просто смешно, просил помощи, а в итоге приходится доказывать правду.

Выкачиваю 700 Мб и 50 Мб файл.

Вот лог при 4-х возможных вариантах:

size=полный размер файла
return= return значение для fpassthru
ftell = переданный размер из shutdown ф-ции для fpassthru handle

1) разрыв соединения с ignore_user_abort(false)
============================

connection_status=1 | ABORTED
ignore_user_abort=0
time_dl=14.091305017471
size=689346560 | 657.41 Mb
return= | 0 b
ftell=84295680 | 80.39 Mb

2) полное скачивание с ignore_user_abort(false)
============================

connection_status=0 | NORMAL
ignore_user_abort=0
time_dl=72.814349889755
size=689346560 | 657.41 Mb
return=689346560 | 657.41 Mb
ftell=689346560 | 657.41 Mb

3) разрыв соединения с ignore_user_abort(true)
============================

connection_status=1 | ABORTED
ignore_user_abort=1
time_dl=21.175467014313
size=51230720 | 48.86 Mb
return=51230720 | 48.86 Mb
ftell=51230720 | 48.86 Mb

3) полное скачивание с ignore_user_abort(true)
============================

connection_status=0 | NORMAL
ignore_user_abort=1
time_dl=13.990994930267
size=51230720 | 48.86 Mb
return=51230720 | 48.86 Mb
ftell=51230720 | 48.86 Mb

Осталось только завтра провести тестирование скрипта на относительно слабой машине 700 Mhz Celeron под linux при
работе в несколько потоков на предмет нагрузки на сервер.
 

vovanium

Новичок
alan4ick
А чем ты скачиваешь файл при тестировании? Ты бы для большей достоверности, попробовал какой-нибудь download-менеджер с ограничением скорости скажем до 7-32 КБ/с (т.е. имитация модема или выделенки), а также попробуй оборвать скачивание не нажатием кнопки стоп, а вырубив download-менеджер из Дипетчера задач...
 

alan4ick

Guest
Щас пока пробовал Explorer, Firefox , Reget... но это все не серьезно, так как
все работает на локальной машине и реально не оценить... а вот завтра проверим на отдельной машине.

для потоков будем пробовать ab - apache bench, ну и еще что-нибуть посмотрим.. или еще запустим ab и сделаем kill ему для симуляции утери коннекта а не stop.

-~{}~ 16.01.06 20:16:

Под Linux действительно есть проблемы в PHP с определением connection_status().

Ни под Apache 2.0.52 и 2.0.55 с PHP 4.3.9 и 4.4.2 не определялся корректно статус соединения при использовании fpassthru.
Все время возвращался NORMAL.

А вот с использованием fread все работало отлично.

Потом я долго пытался найти истину в changelog и bugreport'ах.
Похоже что данная проблемма встречается под apache2 со многими версиями PHP.
http://bugs.php.net/bug.php?id=30301
http://bugs.php.net/bug.php?id=28195

Так как осталась не проверенной 5-ка, а разработчики рекомендовали обновится до последней, то я поставил 5.1.2 и все ЗАРАБОТАЛО НА УРА!!!

Под Windows у меня все отлично работает на apache 2.0.55 с php 4.3.10 и 5.1.1.

Увы сегодня не хватило времени на нормальное тестирование, но предварительно, при 20 потоках на 100 Мбитной сетке при отдаче тестового 100 метрового файла на 2 x 2.4 Гг HT Xeon , т.е. 4 логических процессора при fpassthru нагрузка на каждый камень была около 2%, а при fread с 2048 буфером чтения 7-9%

при такой грубости разница уже очевидна...
 

vovanium

Новичок
alan4ick
Под Windows у меня все отлично работает на apache 2.0.55 с php 4.3.10 и 5.1.1.
Ты не понимаешь одной вещи, когда ты тестишь на локалке, все что передал апач передается в броузер почти мгновенно, так что время скачивания в основном упирается во время работы скрипта (т.е. банальное чтение файла и отправка его клиенту). Я же тебе говорил проверить что будет если скорость скачивания будет близкой к реальной, т.е. ситуация когда например скрипт прочитал и отдал 5 МБ инфы, а клиенту нужно, например, минут 5 чтобы её получить, что будет если через 2 минуты скачивания клиент разорвал коннект...
А это твоё тестирование актуально только для случая если твой скрипт будет работать в локальной сети, и с него действительно будут тянуть файлы со скоростью близкой к 100MBit.

И еще что касается медлительности fread, ты бы посчитал ради интереса сколько нужно обращений к диску, чтобы прочитать файл 100 МБ при буфере 2КБ. Это все равно что кидать сахар в чай по одной песчинке и жаловаться на медлительность процесса. И почему именно 2 КБ? Если в том же NTFS один кластер обычно занимает 4 КБ.
 

alan4ick

Guest
vovanium
>>что будет если скорость скачивания будет близкой к реальной
тоже самое что и на локалке. Если connection_status работает корректно, то отлавливается на любой скорости соединения... например, создавал 100 коннектов через ab для нагрузки сети и качал reget'ом с ограничением по скорости..

По крайне мере я понял одно, что на любых apache под linux, freeBSD с php 4.x.x connection_status() или не отлавливыается
или работает при включенном ignore_user_abord(true) с fread.
С fpassthru опять же проблеммы :-(

В php 5.1.2 все работает замечательно.

>И еще что касается медлительности fread
это уже был другой интерес, найти оптимальное соотношение между размером буфера чтения и нагрузкой на CPU.

В конечном итоге fread и fpassthru достаточно мало загружают CPU. На тестовом Cel 566 с 256 Мб под CentOS 4.1 с Apache 2.2 и php 5.1.2 против двух запущенных ab на 80 соединений:

fpassthru : ~11-13%
fread 2k : ~33 %
fread 1M : ~7-9 %


Итог : использовать fread при включенном ignore_user_abord(true) с отлавливанием connection_status при очередном fread.... для прекращения передачи.
 
Сверху