Pomoc - Szukaj - Użytkownicy - Kalendarz
Pełna wersja: [php] pobieranie pliku w tle + buforowanie
Forum PHP.pl > Forum > PHP
Sztef89
Chciałbym zrobić pobieranie plików z innych serwerów, mam kilka wątpliwości:
- Mój obecny serwer ma mało pamięci dlatego chce użyć buforowanego zapisu (kod poniżej), czy takie rozwiązanie jest wydajne, może jest jakieś lepsze ?
- Gdy plik się pobiera wisi wątek w przeglądarce i dopiero po pobraniu pliku skrypt się odwiesza. Zauważyłem, że jak wyłączę przeglądarkę to plik się dalej pobiera. Czy takie działanie skryptu podczas pobierania dużej ilości plików (używając tego samego skryptu) będzie wydajne ? kiedyś czytałem, że lepiej żeby skrypt działał w tle ale nie wiem jak to się robi,
- Chciałbym poniższy skrypt rozbudować o możliwość wznawiania pobierania gdy wystąpi błąd podczas pobierania (max 3 próby). Wiem, że trzeba użyć fseek ale nie za bardzo wiem gdzie i jak w tym kodzie go umieścić.

  1. <?php
  2.  
  3. $url = 'http://strona.pl/plik.zip';
  4. getUrlContents($url);
  5.  
  6. function getUrlContents($url)
  7. {
  8. $url_parsed = parse_url($url);
  9.  
  10. $host = $url_parsed["host"];
  11. if ($url == '' || $host == '') {
  12. return false;
  13. }
  14. $port = 80;
  15. $path = (empty($url_parsed["path"]) ? '/' : $url_parsed["path"]);
  16. $path.= (!empty($url_parsed["query"]) ? '?'.$url_parsed["query"] : '');
  17. $out = "GET $path HTTP/1.0\r\nHost: $host\r\nConnection: Close\r\n\r\n";
  18. $fp = fsockopen($host, $port, $errno, $errstr, 30);
  19. fwrite($fp, $out);
  20. $headers = '';
  21. $content = '';
  22. $buf = '';
  23. $isBody = false;
  24. while (!feof($fp) and !$isBody) {
  25. $buf = fgets($fp, 1024);
  26. if ($buf == "\r\n" ) {$isBody = true;}
  27. else{$headers .= $buf;}
  28. }
  29. $file1 = fopen(basename($url_parsed["path"]), 'w');
  30. $bytes=stream_copy_to_stream($fp,$file1);
  31. fclose($fp);
  32. return $bytes;
  33. }
  34.  
  35. ?>
Sephirus
ad 1. Co do wydajności to cięższa sprawa - wszystko zależy od ustawień nie tylko PHP ale i serwera, wielkości buforów i prędkości połączenie - im szybsze tym gorzej (szybciej docierają dane i zapychają pamięć) - może kod zamienić na CURL z CURLOPT_WRITEFUNCTION i spróbować z tego - to duże ułatwienie i łatwo można kontrolować pobieranie i zapisywanie pliku.

ad 2. Zapoznaj się z register_shutdown_function oraz ignore_user_abort ich odpowiednia kombinacja powinna pozwolić Ci na odpalenie skryptu w przeglądarce, zwrócenie przez niego wyniku i dalszej pracy. Pamiętaj tylko o zamykaniu sesji - aby sesja nie wisiała.

ad 3. Jeśli użyłbyś CURL'a to przy pomocy WRITE_FUNCTION i CURL_GETINFO (z tego co pamiętam) łatwo można wykryć czy plik pobrał się dobrze i zadziałać odpowiednio wink.gif
Sztef89
Cytat(Sephirus @ 28.02.2012, 14:33:25 ) *
ad 1. Co do wydajności to cięższa sprawa - wszystko zależy od ustawień nie tylko PHP ale i serwera, wielkości buforów i prędkości połączenie - im szybsze tym gorzej (szybciej docierają dane i zapychają pamięć) - może kod zamienić na CURL z CURLOPT_WRITEFUNCTION i spróbować z tego - to duże ułatwienie i łatwo można kontrolować pobieranie i zapisywanie pliku.

ad 2. Zapoznaj się z register_shutdown_function oraz ignore_user_abort ich odpowiednia kombinacja powinna pozwolić Ci na odpalenie skryptu w przeglądarce, zwrócenie przez niego wyniku i dalszej pracy. Pamiętaj tylko o zamykaniu sesji - aby sesja nie wisiała.

ad 3. Jeśli użyłbyś CURL'a to przy pomocy WRITE_FUNCTION i CURL_GETINFO (z tego co pamiętam) łatwo można wykryć czy plik pobrał się dobrze i zadziałać odpowiednio wink.gif


1. Coś czułem ze ta funkcja nie jest najlepsza smile.gif Ale testowałem i działa bardzo dobrze, pobrałem nią paręnaście GB i nie było żadnych problemów tylko niepotrzebnie skrypt wisi w przeglądarce w trakcie pobierania

2. Ok zapoznam się - mam nadzieje że dam rade, dzięki ! smile.gif

3. Czyli najlepszym rozwiązaniem byłoby użycie cURLa do pobierania pliku ? Robię w cURL logowanie aby pobrać plik więc by było niemal 2 w jednym: logowanie i pobieranie przez cURL. Czyli dzięki tym dwóm funkcjom WRITE_FUNCTION i CURL_GETINFO można sprawdzić czy dobrze się pobrał plik, a jeśli nie to wznowić pobieranie - od tego miejsca co się zdążyło pobrać ?

A jeszcze jedno: czy curl ładuje pobierany plik do pamięci czy na dysk serwera ? To jest bardzo dla mnie ważne bo pamięci serwer mój ma bardzo mało, a transfer ok 5MB/s na pobieranie
Sephirus
Jeszcze raz tongue.gif

ad 1. Tu mi chodzilo o to że możesz to sobie uprościć tylko wink.gif

ad 2. wink.gif

ad 3. W praktyce jest tak jak mówisz o ile serwer z którego pobierasz obsługuje pobieranie "kawałków" (CONTENT-RANGE - Http). Jeśli tak to po pobraniu sprawdzasz czy pobrane bajty równają się założonym (pobranym z nagłówka Content-Type poprzez Curl_getinfo) - jeśli się okażę że tak nie jest możesz spróbować pobierać od nowa lub wywołać plik od miejsca w którym się skończyło - właśnie poprzez odpowiednie ustawienie content-range w nagłówkach - jest o tym sporo w Internecie.

Co do dodatkowego pytania to nie jestem pewny więc musiałbyś to sprawdzić. Dodatkowo też zamiast CURL i FSOCKOPEN możesz pop orstu użyć FOPEN wink.gif co może w niektórych aspektach być wygodniejsze a jest podobne do fsockopen wink.gif Tam możesz dać odpowiedni nagłówek/nagłówki poprzez tzw context jako ostatni argument fopen wink.gif Tu masz manual: stream_context_create.

Ze względu na to że masz mało pamieci - przetestowałbym na twoim miejscu wszystkie 3 opcje i wybrał najbardziej wydajną - na pewno curl sam w sobie dorzuca coś od siebie ale lepiej to sprawdzić smile.gif

Zawsze też możesz ewentualnie posłużyć się komendami linuxa - wget itd... to powinno też nieźle działać wink.gif
Sztef89
OK, poradziłem sobie z logowaniem + pobieraniem pliku + praca skryptu w tle + wykrywaniem błędów podczas pobierania. Została jedna rzecz - wznawianie pobierania.

Specjalnie pogorszyłem sygnał sieci aby wystąpiły błędy podczas pobierania i w rezultacie pobiera się więcej niż plik powinien zajmować (dałem warunek w while że kończy pracę dopiero gdy plik zajmuje tyle samo co wielkośc pliku pobrana z serwera). Natomiast bez pętli while pobiera się do pewnego miejsca i kończy pracę. Przy silnym sygnale sieci plik pobiera się w całości - bez i z pętlą while, więc jest ok.

Powiedźcie mi czy jak chcę wznowić pobieranie to to w opcji CURLOPT_RANGE podaję bajty liczone od 0 czy od 1 ? trzeba zawsze obie wartości podawać, czyli X-Y czy wartość X wystarczy (czyli od którego bajta ma zacząć pobierać). Czytałem manuala ale nic o tym nie napisali :/ Dałem teraz wartości liczone od zera i pliki są pobierane prawidłowo ale chciałbym być pewien na 100%.

pod koniec whila, zaraz przed klamra zamykającą przypisuje nową wielkość pliku $this->file_new_size = filesize($this->file);, a jak widać po działaniu funkcji wygląda jakby przypisało tylko raz:
Próba 0, zakres: 0 - 6004979, w:3800095
Próba 1, zakres: 3800094 - 6004979, w:3800095
Próba 2, zakres: 3800094 - 6004979, w:3800095
Próba 3, zakres: 3800094 - 6004979, w:3800095
Ilość wszystkich prób: 4
Prędkość: 202643
Rozmiar pobieranego pliku: 6004980
Zapisano: 3800095 - powinno być 12442440

Te pogrubione wartości to ilość pobranych danych po wykonaniu zapytania cURL. Wartość "Zapisano" jest obliczana po wyjściu z while'a i jest znowu źle obliczana :/ Dlaczego funkcja filesize() źle oblicza wielkość pliku ?
daniofantasy
a myslales moze o odpaleniu calosci pod cronem?

aaa - moment - to jest w js - file_get_contents() w php i calosc sparsuj jsem - moze pod ajaxem?
Sztef89
to jest PHP a nie js itd biggrin.gif
CRON służy do automatycznego odpalania co pewien okres to mi nic nie da... zresztą błąd jest gdzie indziej jeden już znalazłem:
funkcja filesize() cachuje dane więc kolejne je odpalenia dają ten sam wynik i należało ten cache czyścic po każdym jej wywołaniu funkcją clearstatcache();

Teraz jak skrypt odpalam to rozmiar pliku jest zawsze prawidłowo odczytany ale cały czas pobiera się za dużo tak jakby te przedziały które ustawiam opcją CURLOPT_RANGE nie działały:

Próba 0, zakres: 0 - 6004979, zapisano:3518315
Próba 1, zakres: 3518314 - 6004979, zapisano:7242490
Ilość wszystkich prób: 2
Prędkość: 180443
Rozmiar pliku: 6004980
Zapisano: 7242490

Oto kawałek kodu skryptu, klasy FileWriteHandler nie zamieszczam bo tam błędu nie ma, plik się dobrze zapisuje - funkcja sprawdzona. Błąd musi być w kodzie poniżej i czuje, że w parametrach cURL'a - CURLOPT_RANGE?? hymm

//EDIT
Już sobie poradziłem, odpowiedź znalazłem na zagranicznych forach. cURL'owi należy podać tylko wielkość pliku w bajtach i to mu wystarczy aby plik dokończyć. Czyli nalezy zrobic taki warunek:


  1. #Zapis pliku
  2. while($this->file_new_size != $this->file_size){
  3. //>>>>tutaj ustawienia curla<<<<
  4.  
  5. if(file_exists($this->file)){
  6. curl_setopt($c, CURLOPT_RANGE, $this->file_new_size."-");
  7. }
  8. curl_exec($c);
  9. $this->file_new_size = filesize($this->file);
  10.  
  11. $i++;
  12.  
  13. //Warunek kiedy ma wyjść z while'a
  14. if(($this->file_new_size > $this->file_size)||$i>3)break;
  15. }
  16.  
  17. echo "<br>Ilość wszystkich prób: ".$i;
  18. echo "<br>Prędkość: ".curl_getinfo($c,CURLINFO_SPEED_DOWNLOAD);
  19. echo "<br>Rozmiar: ".$this->file_size;
  20. echo "<br>Zapisano: ".filesize($this->file);
  21. echo "<br>Ścieżka: ".$this->file;
To jest wersja lo-fi głównej zawartości. Aby zobaczyć pełną wersję z większą zawartością, obrazkami i formatowaniem proszę kliknij tutaj.
Invision Power Board © 2001-2025 Invision Power Services, Inc.