Определение факта выкачки вариант для partial content

whirlwind

TDD infected, paranoid
Определение факта выкачки вариант для partial content

Скрипт выдает файлы, поддерживает докачку в том числе пределы X-Y в заголовках Range. Проблема в том, что некоторые (не знаю все не все но FlashGet, например) менеджеры закачек используют спецификаторы вида Y-, т.е. от определенной позиции и до конца файла. Итерация цикла выдачи выглядит примерно так
PHP:
$buffer = fread($fh,$read_buffer_size);
echo $buffer;
Буффер, допустим, 4кб. Копировать побайтно не устраивает. Можно инкрементировать некие счетчики-указатели после отдачи клиенту, но в этом случае счетчик будет меньше актуального, если будет выкачано меньше 4кб. (менеджер разрывает соединение, апач пришибает процесс, соотв. никаких подсчетов не состоится). Если читать до выдачи, получается наоборот - счетчик будет больше актуального, если клиент оборвет не выкачав 4кб. Меня интересует наиболее точный метод определения факта полной выдачи файла.

Какие варианты были опробованы, рассмотрены и по каким то причинам не подходят.

1. Подсчет по логам вебсервера
2. Лимит по времени
3. Определения факта выкачки по выдаче последнего куска (байта)

Еще какие нибудь идеи есть? Ведь как то же считают люди все это дело.

Спасибо
 

ksnk

прохожий
Imho, вопрос в частности, звучит так: как проверить - получил клиент отосланый контент? Ответ - никак.

Можно еще вычислять логическое объединение всех выданных кусков. Если оно стало "без дырок" и граничные размеры совпадают - файл отдан.
Как пример реализации - завести map-массив, отображающий прогресс закачки. Хранить где-нибудь в юзерных данных. Каждая закачка добавляет в map-массив новую пару Начало- конец. После чего происходит "склейка" пересекающихся пар. Если map стал из одного элемента (0, длина файла) - файл отдан.
После этого, конечно, неплохо бы подождать немного, вдруг клиент потерял пару пакетов по дороге :)
 

whirlwind

TDD infected, paranoid
Imho, вопрос в частности, звучит так: как проверить - получил клиент отосланый контент? Ответ - никак.
Я бы не был таким категоричным. Если бы в логах писался не только выданный объем, но еще и позиция, с которой была осуществлена выдача, то вопрос отпал бы сам собой.

Насчет логического объединения я сейчас как раз думаю. Но тут вопрос - где хранить. Куки отпадают. Если БД, то сильно смущает количество апдейтов, которые для точности придется делать на каждой итерации. Ну пусть даже не апдейты, а проверка появления нового потока, с целью последующей ориентации (т.е. берем ближний-минимальный по позиции и считаем что клиент по достижении этой позиции отвалится). Но ведь опять таки, делать это придется довольно часто.

В общем не знаю даже что делать. Файлы большие - без докачки нельзя. И не закрывать потом то же нельзя, потому как начинают наглеть и по несколько раз вычкачивать. Когда юзеров сотня, это еще терпимо, но тут речь о тысячах и общий траф получается недетский. Меня устроит так-же вариант колдовства над вебсервером (например добавление в лог нового поля, ограничение кол-ва потоков, если к этому можно как то привязаться).

PS. Только что осенило! А что, если сделать в два этапа для фиксации начала запрошенного куска и для выдачи? Во-первых редиректим еррорлог на программу, которая будет вылавливать наши линки. Подозреваю, что запись в лог валится в случае если клиент отвалился, либо если он получил eof. Т.е. в момент появления в логе новой записи, программа-монитор будет выцеплять длину. Здесь задача - определить с какого байта начали скачивать "эту длину". Тут можно схитрить с редиректами. Главное что бы в лог попала какая нибудь дата, по которой монитор сможет найти в БД запись в которой указана позиция начала из RANGE. Например идентификатор записи, который пихается в качестве переменной запроса. Программа монитор будет выцеплять не только длину и идентификатор файла, но так же и идентификатор записи, в которой указана позиция. Ну, а дальше я думаю, понятно...

Как вам такая идея?
 

SiMM

Новичок
> Если оно стало "без дырок" и граничные размеры совпадают - файл отдан.
Если граничные размеры совпадают - это ещё не значит, что файл до клиента дошёл полностью и без "дыр".
 

ksnk

прохожий
Я бы не был таким категоричным.
Имелся ввиду не факт посылки, а факт получения контента пользоваетлем.
Если бы в логах писался ...
Никто не мешает завести собственный лог [m]ref.errorfunc[/m].
Если же хранить и изменять "map", то только в базе данных, так как в файлах при многопоточном массовом скачивании неизбежны накладки одновременного доступа к файлу. Где-нибудь ближе к значению счетчика скачиваний "этого" файла "этим" юзером. Можно - в виде строки - serialize от map-массива. Один запрос - получить ячейку и один - изменить... Работу с ним можно написать и на PHP.
 

alexhemp

Новичок
whirlwind

Факт загрузки файла пользователем - не установишь никак.
Ведь для человека загрузка - это не только байтики, это как минимум файл на диске. Можно случайно нажать Cancel при копировании файла в IE и лишиться полностью загруженного файла.

Самый оптимальный вариант - это уникальный линк каждому качающему, зависящий от его ip и некий срок жизни конкретного линка (например неделя). Так-же нужно дать клиенту возможность в любой момент скачать заново файл (ну или например в течении месяца - если у него месячная подписка).
 

whirlwind

TDD infected, paranoid
Никто не мешает завести собственный лог
Это вроде не то. Надо что бы вебсервер писал, а не скрипт. В апаче так срабатывает
PHP:
ErrorLog "| myprog"
Самый оптимальный вариант - это уникальный линк каждому качающему
Так вот он уникальный как раз. А я в последнем постинге предлагаю добавлять еще и уникальный хеш части.

Если граничные размеры совпадают - это ещё не значит, что файл до клиента дошёл полностью и без "дыр".
Хм... там по моему зависит от таймаута, сколько висеть и ждать будем. Но если уж оборвался, так оборвался. Все таки TCP, а не UDP. Если еще вспомнить про контрольные суммы и всякое прочее... Сильно сомневаюсь, что дыры именно по вине TCP возникают.
 

SiMM

Новичок
> Если еще вспомнить про контрольные суммы и всякое прочее...
И каким это, интересно, образом наличие контрольной суммы может гарантировать доставку пакета к клиенту?
 

whirlwind

TDD infected, paranoid
Никто не говорил про доставку, имелось в виду соответствие отправленного и принятого. И не надо забывать, что отправитель ожидает подтверждения приема пакета. Это на уровне протокола TCP вообще-то, если мне память не отшибает. Я не говорю, что в принципе невозможно принять битый файл или потерять кусок, но в данном случае шансы настолько незначительны, что на эту проблему особо смотреть не стоит.
 

RushHourRider

Новичок
А почему бы не завести длинную битовую маску где один бит равен одному байту. Когда все биты будут равны единице - значит все байты были скачены, то есть файл отдан.
 

DiTHER

bang bang
allofmp3.com решили проблему проще...
просто смотрят - если скачан последний байт - то все. И плевать на куски которые там в середине качались-докачивались.

Прокатит только для файлов которые даже без всего одного байта трудновосстановимы (а-ля 7z).

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

ksnk

прохожий
RushHourRider Вау! На 8 метровый файл держать 1 мб маску ? :)
 

RushHourRider

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

whirlwind

TDD infected, paranoid
PS. Только что осенило! А что, если сделать в два этапа для фиксации начала запрошенного куска и для выдачи? Во-первых редиректим еррорлог на программу, которая будет вылавливать наши линки. Подозреваю, что запись в лог валится в случае если клиент отвалился, либо если он получил eof. Т.е. в момент появления в логе новой записи, программа-монитор будет выцеплять длину. Здесь задача - определить с какого байта начали скачивать "эту длину". Тут можно схитрить с редиректами. Главное что бы в лог попала какая нибудь дата, по которой монитор сможет найти в БД запись в которой указана позиция начала из RANGE. Например идентификатор записи, который пихается в качестве переменной запроса. Программа монитор будет выцеплять не только длину и идентификатор файла, но так же и идентификатор записи, в которой указана позиция. Ну, а дальше я думаю, понятно...
Не работает так :(
Менеджер не хочет брать partial с другого урла. Тыркается на тот, на который был первый редирект. Хотя первый поток качает. Вот как это выглядит

Первый
PHP:
Tue Sep 20 16:51:45 2005 GET /dlwr.php?id=6810787 HTTP/1.1
Tue Sep 20 16:51:45 2005 HOST: a.s.net
Tue Sep 20 16:51:45 2005 ACCEPT: */*
Tue Sep 20 16:51:45 2005 Referer: [url]http://a.s.net[/url]
Tue Sep 20 16:51:45 2005 User-Agent: FlashGet
Tue Sep 20 16:51:45 2005 RANGE: bytes=1436708-
Tue Sep 20 16:51:45 2005 Pragma: no-cache
Tue Sep 20 16:51:45 2005 Cache-Control: no-cache
Tue Sep 20 16:51:45 2005 Connection: close
Tue Sep 20 16:51:45 2005 HTTP/1.1 302 Moved temporarily
Tue Sep 20 16:51:45 2005 Date: Tue, 20 Sep 2005 10:51:45 GMT
Tue Sep 20 16:51:45 2005 Server: Apache/2.0.52 (Win32) PHP/5.1.0b3
Tue Sep 20 16:51:45 2005 X-Powered-By: PHP/5.1.0b3
Tue Sep 20 16:51:45 2005 Location: /dlwr.php?id=6810787&part_id=92e874360ad287cf12031a470430459b
Tue Sep 20 16:51:45 2005 ETag: blablabla-gagaga
Tue Sep 20 16:51:45 2005 Content-Length: 0
Tue Sep 20 16:51:45 2005 Connection: close
Tue Sep 20 16:51:45 2005 Content-Type: text/html

Tue Sep 20 16:51:45 2005 GET /dlwr.php?id=6810787&part_id=92e874360ad287cf12031a470430459b HTTP/1.1
Tue Sep 20 16:51:45 2005 HOST: a.s.net
Tue Sep 20 16:51:45 2005 ACCEPT: */*
Tue Sep 20 16:51:45 2005 Referer: [url]http://a.s.net[/url]
Tue Sep 20 16:51:45 2005 User-Agent: FlashGet
Tue Sep 20 16:51:45 2005 RANGE: bytes=1436708-
Tue Sep 20 16:51:45 2005 Pragma: no-cache
Tue Sep 20 16:51:45 2005 Cache-Control: no-cache
Tue Sep 20 16:51:45 2005 Connection: close
Tue Sep 20 16:51:45 2005 HTTP/1.1 206 Partial Content
Tue Sep 20 16:51:46 2005 Date: Tue, 20 Sep 2005 10:51:45 GMT
Tue Sep 20 16:51:46 2005 Server: Apache/2.0.52 (Win32) PHP/5.1.0b3
Tue Sep 20 16:51:46 2005 X-Powered-By: PHP/5.1.0b3
Tue Sep 20 16:51:46 2005 ETag: blablabla-gagaga
Tue Sep 20 16:51:46 2005 Content-Range: bytes 1436708-77413335/77413336
Tue Sep 20 16:51:46 2005 Content-Length: 75976628
Tue Sep 20 16:51:46 2005 Content-Disposition: attachment; filename=MFv7.0.zip
Tue Sep 20 16:51:46 2005 Connection: close
Tue Sep 20 16:51:46 2005 Content-Type: application/force-download
Второй
PHP:
Tue Sep 20 16:51:46 2005 GET /dlwr.php?id=6810787&part_id=92e874360ad287cf12031a470430459b HTTP/1.1
Tue Sep 20 16:51:46 2005 HOST: a.s.net
Tue Sep 20 16:51:46 2005 ACCEPT: */*
Tue Sep 20 16:51:46 2005 Referer: [url]http://a.s.net[/url]
Tue Sep 20 16:51:46 2005 User-Agent: FlashGet
Tue Sep 20 16:51:46 2005 RANGE: bytes=15654931-
Tue Sep 20 16:51:46 2005 Pragma: no-cache
Tue Sep 20 16:51:46 2005 Cache-Control: no-cache
Tue Sep 20 16:51:46 2005 Connection: close

Tue Sep 20 16:51:46 2005 HTTP/1.1 302 Moved temporarily
Tue Sep 20 16:51:46 2005 Date: Tue, 20 Sep 2005 10:51:46 GMT
Tue Sep 20 16:51:46 2005 Server: Apache/2.0.52 (Win32) PHP/5.1.0b3
Tue Sep 20 16:51:46 2005 X-Powered-By: PHP/5.1.0b3
Tue Sep 20 16:51:46 2005 Location: /dlwr.php?id=6810787&part_id=abe4319345e81e704c0ffbf331ecbe19
Tue Sep 20 16:51:46 2005 ETag: blablabla-gagaga
Tue Sep 20 16:51:46 2005 Content-Length: 0
Tue Sep 20 16:51:46 2005 Connection: close
Tue Sep 20 16:51:46 2005 Content-Type: text/html

Tue Sep 20 16:51:46 2005 GET /dlwr.php?id=6810787&part_id=92e874360ad287cf12031a470430459b HTTP/1.1
Tue Sep 20 16:51:46 2005 HOST: a.s.net
Tue Sep 20 16:51:46 2005 ACCEPT: */*
Tue Sep 20 16:51:46 2005 Referer: [url]http://a.s.net[/url]
Tue Sep 20 16:51:46 2005 User-Agent: FlashGet
Tue Sep 20 16:51:46 2005 RANGE: bytes=15654931-
Tue Sep 20 16:51:46 2005 Pragma: no-cache
Tue Sep 20 16:51:46 2005 Cache-Control: no-cache
Tue Sep 20 16:51:46 2005 Connection: close
 
Сверху