Pomoc - Szukaj - Użytkownicy - Kalendarz
Pełna wersja: Komunikacja z protokolem UDP
Forum PHP.pl > Forum > PHP
azerty
Czesc.

Mam dokumentacje protokolu opartego o UDP:
http://xbtt.sourceforge.net/udp_tracker_protocol.html

... i nie bardzo wiem jak to ugryzc. Chodzi mi o to, jak mam podac dane. Nie moge uzmyslowic sobie co to jest offset - zakladam, ze jest to n-ty bajt w ciagu ?

  1. $transId = 1234567;
  2. $binString = pack('I@8I@12I@16', 0x41727101980, 0, $transId);
  3.  
  4. $fp = stream_socket_client($uri, $errNo, $errStr, $timeout);
  5. stream_set_timeout($fp, $timeout);
  6.  
  7. if (!$fp) {
  8. echo "ERROR: $errNo - $errStr<br />\n";
  9. }else{
  10. fwrite($fp, $binString);
  11. $r = fgets($fp, 1024);
  12. var_dump($r);
  13. echo 'ok';
  14. fclose($fp);
  15. }


Gdyby ktos byl tak mily, i wytlumaczyl mi krok 1 - "obtain connection id" oraz jak odebrane dane przekonwertowac z postaci binarnej do np. tablicy.
Crozin
Cytat
Chodzi mi o to, jak mam podac dane.
Struktura pakietu jest dosyć jasno wytłumaczona. Jako treść wiadomości musisz podać zmienną zawierającą w sobie zlepek różnych danych (do zlepienia danych musisz wykorzystać pack).
Pamiętaj, że w PHP liczby całkowite (int) mogą być 32 lub 64 bitowe, tak więc powinieneś to mieć na uwadze. W przypadku, gdy masz 64-bitowy PHP, a protokół wymaga podania danych 32-bitowych musisz pozbyć się początkowych 32 bitów. W przypadku gdy masz 32-bitowy PHP, a protokół wymaga podania 64-biotwej liczby musisz spakować dwie zmienne w jedną - ponownie: pack.

Cytat
wytlumaczyl mi krok 1 - "obtain connection id"
Jest to losowa 32-biotwa liczba, która pozwala Ci identyfikować kolejne pakiety, w skrócie robisz coś takiego:

  1. $id1 = mt_rand(0, 0xFFFFFFFF);
  2.  
  3. // wysyłasz pakiet (zawiera on pow. id)
  4.  
  5. // ....
  6.  
  7. $packet = // odbierasz pakiet
  8. if ($packet->getId() == $id1) {
  9. // odebrany pakiet jest odpowiedzią na ten wysłany tuż wcześniej
  10. }
  11.  
  12.  
  13. // Ponownie generujesz identyfikator
  14. $id2 = mt_rand(0, 0xFFFFFFFF);
  15.  
  16. // wysyłasz pakiet (zawiera on pow. id)
  17.  
  18. // ....
  19.  
  20. $packet = // odbierasz pakiet
  21. if ($packet->getId() == $id2) {
  22. // odebrany pakiet jest odpowiedzią na ten wysłany tuż wcześniej, a nie ten z samego początku
  23. }
Pamiętaj, że UDP to protokół który nie gwarantuje praktycznie niczego - dotyczy to również kolejności w jakiej dochodzą pakiety (pakiet wysłany później może dojść wcześniej).
Cytat
oraz jak odebrane dane przekonwertowac z postaci binarnej do np. tablicy.
Działanie odwrotne od pakowania danych do binarnego stringu - czyli unpack

EDIT:
Cytat
Nie moge uzmyslowic sobie co to jest offset - zakladam, ze jest to n-ty bajt w ciagu ?
Offset jest tam podany w bajtach (czyli offset = 8 oznacza "od 64 bitu"). Tutaj masz przykład struktury pakietu wysyłanego w protokole TCP: http://en.wikipedia.org/wiki/Transmission_...gment_structure - myślę, że to dobrze obrazuje o co chodzi.
azerty
Dzieki, pomoglo.
Pogoooglowalem i ... :

32 bitowa liczba posiada 4 oktety (4*8 = 32).
64 bitowa liczba ma owych oktetów 8 (8*8 = 64).

Offset w pakiecie to numer oktetu po ktorym doklejamy kolejne dane.

I tak:

Kod
0    64-bit integer    connection_id    0x41727101980
8    32-bit integer    action    0
12    32-bit integer    transaction_id
16


(Liczymy od zera, tak jak w tablicach)
Od 0 do 7 - osiem oktetow, liczba 64 bitowa
8 - 11 cztery oktety liczby 32 bitowej
12 - 15 kolejne cztery oktety.

Dla pozostalych, ktorzy tez poszerzaja wiedze na ten temat - oktet to np: 1001 1001 1001 1001 1001 1001 1001 1001, razem 32 bity.
Kazda cyfra w HEX (szesnastkowa, 0 - F) reprezentuje 4 bity.
I tak: 1001 = 0x9 a ten dlugi ciag wczesniej: 0x99999999.

Mam 32 bitowa wersje PHP wiec 64-bitowa liczba 0x41727101980 (8 znakow - za duzo na 32 bit - max to 8) nie bedzie dzialac, rozdzielam ja wiec na dwie 32-bitowe:
0x417, 0x27101980 (po prostu rozdzielam, nie robie zadnych dzialan matematycznych).

Nastepnie pakujemy dane. W specyfikacji pisze, ze liczby sa w formacie big endian (najwazniejszy bajt pierwszy (bajt = 8 bitow (oktet))).
Nalezy wziac to pod uwage przy funkcji PACK:

  1. $transId = mt_rand(0, 0xFFFFFF);
  2. $binString = pack('N4', 0x41727, 0x101980, 0, $transId);


Wyjasnienie "N3" - N oznacza, ze pierwszy parametr powinien zostac zakodowany jako:
unsigned long (always 32 bit, big endian byte order)
"4" oznacza, ze nalezy zastosowac to do 4 kolejnych argumentow zaczynajac od aktualnego.
2 pierwsze argumenty to nasz podzielony, 64 bitowy integer, ktorego moje php nie ogarnia :-)

Ok i pozniej lecimy:

  1. $fp = stream_socket_client($uri, $errNo, $errStr, $timeout);
  2. stream_set_timeout($fp, $timeout);
  3.  
  4. if (!$fp) {
  5. echo "ERROR: $errNo - $errStr<br />\n";
  6. }else{
  7. fwrite($fp, $binString);
  8. $r = fread($fp, 255); // oczekiwane 16
  9. var_dump($r);
  10. fclose($fp);
  11. }


... i kupa. fwrite zwraca 16, czyli jest GIT. $fp jest OK - polaczenia nawiazane, var dump z fread natomiast zwraca string o zerowej dlugosci. Nie dostaje zadnej odpowiedzi.

Bede dalej probowal, natomiast tych bardziej doswiadczonych prosil bym o ewentualne sugestie. Moze jest cos o czym nie wiem. Wg. dokumentacji powinienem teraz dostac odpowiedz o dlugosci min. 16 bajtow.
Zyx
Nie stworzysz pakietu UDP, bo w UDP nie ma pakietów. Nie nawiążesz połączenia UDP, bo w UDP nie ma połączeń.

  1. $binString = pack('N4', 0x41727, 0x101980, 0, $transId);


Zainstaluj sobie jakiś dobry kalkulator, ew. weź kartkę papieru w dłoń, zamień sobie 0x41727101980 na zapis binarny, podziel to sobie na dwie 32-bitowe połówki, każdą z nich z powrotem zamień na heksy. Gwarantuję, że nie wyjdą Ci liczby 0x41727 i 0x101980.
Crozin
Cytat
Nie stworzysz pakietu UDP, bo w UDP nie ma pakietów. Nie nawiążesz połączenia UDP, bo w UDP nie ma połączeń.
Połączeń nie ma, bo jest to protokół bezstanowy, ale jeśli nie pakiety to co? Datagramy? - nie mam pojęcia czy istnieje jakieś tłumaczenie. Na dobrą sprawę w kontekście UDP pakiet UDP i datagram UDP można traktować jako synonimy.
Cytat
Zainstaluj sobie jakiś dobry kalkulator [...]
Nawet Windowsowy kalkulator takie coś posiada. wink.gif
erix
Cytat
Połączeń nie ma, bo jest to protokół bezstanowy, ale jeśli nie pakiety to co? Datagramy? - nie mam pojęcia czy istnieje jakieś tłumaczenie. Na dobrą sprawę w kontekście UDP pakiet UDP i datagram UDP można traktować jako synonimy.


Oj chyba nie do końca (coś mi świszczy, że to zależy od warstwy OSI, którą mamy na myśli; pakiety były chyba w warstwie transportowej, a datagramy nieco niżej), ale w tej chwili wyleciało mi to z głowy i mogę się mylić. wink.gif

Jeśli chodzi o skrypt, problem po prostu w logice komunikacji:

  1. fwrite($fp, $binString);
  2. $r = fread($fp, 255); // oczekiwane 16
  3. var_dump($r);
  4. fclose($fp);

W UDP, to tak, jakbyś rzucał granatem na oślep - nie interesuje Cię, czy usłyszysz, że wybuchnie. Jedyna odpowiedź, to kolejny granat od wroga. wink.gif

Summa summarum, w tej sytuacji IMO lepiej przejść na TCP, a jeśli chcesz koniecznie na UDP, to musisz otworzyć gniazdo nasłuchujące, które odbierze pakiety UDP od drugiej strony komunikacji.
azerty
No tak. Jak Wy zaczniecie gadac, to dyskusja wedruje do nrPostu^n warstwy OSI, a nie o to pytam.
Datagramy, czy pakiety, za wiki:
Cytat
UDP (ang. User Datagram Protocol – protokół pakietów użytkownika)
... Pakiety UDP, zwane też datagramami ...


Uznajmy to za temat zamkniety.

Dalej, to co wspomnial Zyx o kalkulatorze itd. - tak, duzo literowek i bledow jest w tym poscie ale za cholere nie moge go edytowac.
Do tej pory nawet odpowiedzi nie moglem napisac, bo w polu edycji bylo pelno znakow html, a przy dodawaniu postu bledy - a dokladnie ich lista ... pusta.

Aktualnie mam taki kod:
  1. // 0 - 0xFFFFFFF // 28 bitow, zeby php nie zwariowalo dajac floaty
  2. $packetId = $this->getRandomPacketId(0);
  3. $packet = pack('N4', 0x417, 0x27101980, 0, $packetId);
  4.  
  5. $handle = $this->getHandle();
  6. $this->write($packet);
  7. $response = $this->read(16);
  8.  
  9. $data = unpack('Naction/Npacket/H*cid', $response);
  10.  
  11. if(!$this->hasPacket($data['packet'], $data['action']))
  12. throw new Scrape_Exception
  13. ('Host returned invalid packet ID: '.$data['packet'].':'.$data['action']);
  14.  
  15. // we have obtained our own connection id - save it for further use
  16. $this->connectionId = $data['cid'];


Problem w tym, ze w odpowiedzi connectionId pobieram jako hex-string a nie jako int unsigned 64 bit. Mozna to zrobic inaczej, lepiej ?
Kolejny problem - jezeli read() podaje bez dlugosci, badz z wielkoscia wieksza niz ma otrzymywany pakiet, dostaje error - timeout po 30 sekundach.
Czy rozwiazanie tu bedzie uzycie non-blocking stream / bez buforowania ?
I jeszcze raz - czy istnieje jakis ogolny sposob na pakowanie do danych binarnych liczb 64 bitowych ?
Ja zrobilem to zamieniajac 1 liczbe: 0x41727101980 na 2: 0x417, 0x27101980 i po zakodowaniu parametrem N dostaje dokladnie
taki ciag binarny jaki powinienem (sprawdzone bin2hex).

Polaczenie inicjalizuje:
  1. stream_socket_client($uri, $errNo, $errStr, $this->timeout)

(zaraz ktos znowu przypomni, ze UDP to bezpolaczeniowy protokol ..... )

Odczyt robie tak (i tu mam problem z dlugoscia i timeoutem grr !):

  1. if(is_null($handle))
  2. $handle = $this->handle;
  3.  
  4. $result = stream_get_contents($handle, $length);
  5. if($this->isTimedOut()){
  6. throw new Scrape_Exception
  7. ('Connection timed out while reading stream.');
  8. }
  9. return $result;


isTimedOut() pobiera dane ze stream_get_metadata - tablica z polem timed_out.

Nie rozumiem natomiast o co Erix'owi chodzilo w tym otwieraniu portow UDP ? Huh ?
Crozin
Dochodzi 3:00 więc wybacz za formę i ewentualne błędy. wink.gif

Cytat
Problem w tym, ze w odpowiedzi connectionId pobieram jako hex-string a nie jako int unsigned 64 bit. Mozna to zrobic inaczej, lepiej ?
Upewnij się, że tekst (w formie 16-owej) ma dokładnie 16 znaków. Jeżeli ma mniej dopełnij go zerami, czyli "d50abc0bcaedf" -> "000d50abc0bcaedf". Podziel go na dwa teksty równej długości - teraz każdy reprezentuje liczbę 32-biotwą. Wykorzystując hexdec możesz zamienić to na rzeczywistą liczbę.
PHP nie gwarantuje obsługi liczb 64-bitowych, więc bezpieczniej jest trzymać je jako dwie niezależne 32-bitowe zmienne.

Cytat
Kolejny problem - jezeli read() podaje bez dlugosci, badz z wielkoscia wieksza niz ma otrzymywany pakiet, dostaje error - timeout po 30 sekundach.
Mówisz "chcę otrzymać od Ciebie 50 jabłek", więc oczywistym jest, że nie przyjdę do Ciebie mając ich tylko 40, co nie? smile.gif Przede wszystkim nie powinieneś żądać więcej niż się spodziewasz - przecież masz w tej specyfikacji podane ile konkretnie bajtów zostanie przesłane.


Na Twoim miejscu na początku poszukałbym / napisałbym chociażby najbardziej prymitywnej biblioteki do tworzenia "paczek" o konkretnej (binarnej) zawartości, udostępniającej jakieś sensowne API do odczytu / zapisu danych bo Cie szlag trafi z tym packiem i unpackiem. Nie mówiąc już, że podatność na błędy takiego kodu jest ogromna.
azerty
Ok. Prawie dziala.

Odpowiedz jaka dostaje:

Cytat
ff
00000002
00590572
00000000
00000000
00000000
00000000
00000000
00000000


Nie wiem czym jest 0xFF ktore dostalem.
W specyfikacji protokolu go nie ma. Druga linia 0x2 to akcja i taka powinna byc.
Trzeci parametr to nr pakietu - tez sie zgadza.

Istnieje mozliwosc, ze to zle dzialajaca funkcja w php / albo srodowisko windows ?

Update:
Problem wystepuje tylko w wypadku uzycia funkcji fgets. fread daje dobry rezultat - wtf ...
kiler129
Cytat(azerty @ 5.03.2011, 02:15:43 ) *
Update:
Problem wystepuje tylko w wypadku uzycia funkcji fgets. fread daje dobry rezultat - wtf ...

Tip: Sprawdź znak końca linii (/r | /r/n | /n)
Crozin
Cytat
Istnieje mozliwosc, ze to zle dzialajaca funkcja w php / albo srodowisko windows ?
Zawsze istnieje taka możliwość, ale raczej wątpię by to mogło być tutaj przyczyną.

Dobrze by było jakbyś udostępnił na Twój dokładny kod, bo ciężko powiedzieć skąd Ci się coś wzięło nie mając dostępu do kodu. Jeżeli jest on długi uprość go maksymalnie.
azerty
Mi sie wydaje, ze jednak cos jest nie tak z moim php.
fread do tej pory zawieszal sie jak probowalem odczytac wiecej znakow niz przeslal serwer; prawie tak, jakby nie wykrywal konca strumienia - pobieralo mi wtedy pusty plik html bez informacji o bledzie (timeout) i caly serwer siadal.
Teraz nagle zaczal dzialac i sie juz nie zawiesza. Hmm.

Narazie jest ok, takze dziekuje za czas i cierpliwosc.
Co do fgets, na bugtracku php ktorys z deweloperow pisal, aby nie uzywac go do odczytu z UDP. Nie dociekam, nie chce sie denerwowac biggrin.gif
Fifi209
Cytat(kiler129 @ 5.03.2011, 02:56:11 ) *
Tip: Sprawdź znak końca linii (/r | /r/n | /n)

Raczej:
\r
\r\n
\n


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.