Komputery Okna Internet

gniazda javascript i serwer php. Gniazda w PHP. Używanie protokołu WebSocket z phpws. Kiedy unikać korzystania z gniazd WebSocket

Lub rozpoczęcie pracy z WebSocket PHP bez phpDaemona

Cześć! Przepraszam za tak długi tytuł, ale mam nadzieję, że ten artykuł będzie łatwiejszy do znalezienia dla nowicjuszy takich jak ja, ponieważ nie mogłem znaleźć niczego podobnego. Kilka tygodni temu zdecydowałem się przerobić klienta gry i serwer mojej gry Growing Crystals z AJAX na WebSocket, ale wszystko okazało się nie tylko trudne, ale bardzo trudne. Dlatego zdecydowałem się napisać artykuł, który pomoże najbardziej początkującym programistom WebSocket + PHP zaoszczędzić kilka dni czasu, wyjaśniając możliwie szczegółowo każdy krok w konfiguracji i uruchomieniu pierwszego skryptu WebSocket w PHP.

Nie podam kodu serwera gniazd w PHP, bo... znajduje się w archiwum i jest opatrzony komentarzami. Sztucznie ograniczyłem czas działania akceptowania połączeń do 100 sekund, aby skrypt nie pozostawał w pamięci, nie zamykał wszystkich połączeń i nie musiał ciągle restartować Denver podczas wprowadzania w nim zmian. Co więcej, po 100 sekundach skrypt nie przestanie działać, ale będzie czekał na połączenie, po czym jego praca zostanie zakończona, a wszystkie gniazda zostaną bezpiecznie zamknięte. Ponadto do testów używam localhost i portu 889.

Socket_bind($socket, "127.0.0.1", 889);//powiąż go z określonym adresem IP i portem

Pliki z archiwum można umieścić w głównym folderze localhost Denver. Algorytm testowania:

  1. Uruchamiamy http://localhost/socket.html, automatycznie połączy się z serwerem ws echo ws://echo.websocket.org, możesz wysłać kilka wiadomości, które serwer musi odesłać, ponieważ jest to serwer echo. Jeśli to zadziała, świetnie, wszystko jest w porządku z klientem, jeśli nie, sprawdź, czy umieściłeś wszystkie pliki w jednym katalogu, czy masz uruchomione Denver i czy masz połączenie z Internetem.
  2. Otwórz konsolę JavaScript na tej samej karcie, na której masz otwartego klienta ws (w GoogleChrome 34.8.1847.137 m i FireFox robi się to prawie tak samo menu->narzędzia->konsola Java Script lub Ctrl+Shift+J, ale osobiście ja używać dla tej konsoli FireBug). Wyślij jeszcze kilka wiadomości. Zobaczysz, jakie wiadomości przychodzą z serwera. Możesz kliknąć rozłącz, a następnie wysłać kilka wiadomości, upewnisz się, że wiadomości nie znikną, ponieważ. połączenie z serwerem ws://echo.websocket.org zostało zerwane.
  3. Uruchamiamy nasz serwer gniazd http://localhost/simpleworking.php w nowej karcie przeglądarki. Pożądane jest, aby od razu można było zobaczyć zarówno kartę klienta z konsolą, jak i kartę serwera. GO() ... gniazdo_utwórz ...OK gniazdo_bind ...OK Gniazdo nasłuchujące... OK

    Oznacza to, że serwer jest gotowy na połączenia przychodzące.

  4. W zakładce klienta w polu Adres serwera wpisz ws://127.0.0.1:889 i kliknij połącz ponownie, widzimy, że na kliencie nic się nie dzieje, ale komunikaty typu Klient „Identyfikator zasobu nr 3” połączył się Wyślij do klienta „ Witaj” pojawia się na serwerze, Kliencie!”... OK Czekam... OK

    Co nam mówi, że technicznie połączenie z gniazdem na serwerze jest nawiązywane, ale po kliencie oczekuje się połączenia za pośrednictwem protokołu gniazda sieciowego, a ono nie jest nawiązywane, ponieważ Przeglądarka nie otrzymała odpowiednich nagłówków protokołu ws. Przy próbie wysłania wiadomości od klienta w konsoli powinien pojawić się błąd informujący, że połączenie z ws nie zostało nawiązane. Niestety, skrypt w GoolgeChrome zatrzyma się i w przypadku nowych prób połączenia konieczne będzie ponowne załadowanie strony za pomocą klienta internetowego. W FireFox skrypt kontynuuje działanie. Pamiętaj, aby ponownie połączyć się 100 sekund po uruchomieniu skryptu serwera, aby umożliwić jego pomyślne zakończenie.

    Klient „Identyfikator zasobu nr 4” połączył się. Wyślij do klienta „Witaj, Kliencie!”... OK czas = 309,8900001049return go() zakończony Zamykanie połączenia... OK

  5. Aby w końcu upewnić się, że serwer odpowiada i że jego wiadomości nie są blokowane przez zaporę ogniową, uruchom skrypt serwera http://localhost/simpleworking.php, uruchom telnet i spróbuj połączyć się z adresem 127.0.0.1:889, ale musi to być wykonane nie później niż 100 sekund od momentu uruchomienia serwera do momentu zamknięcia połączeń i zakończenia skryptu.

Odpowiedź „Witaj, Kliencie!” powinna nadejść przez telnet, co oznacza, że ​​wszystko działa normalnie, a połączenie z serwerem jest dwukierunkowe.

Podczas testowania na komputerze lokalnym przy użyciu Denver zawsze upewnij się, że skrypt PHP jest ukończony, w przeciwnym razie uruchom ponownie Denver, aby uniknąć kolizji i zajętych portów.

Protokół WebSocket

Teraz pozostaje tylko nauczyć nasz serwer gniazd komunikować się jak serwer gniazd sieciowych, poprawnie działać w tle (demon) i co najważniejsze, na tanim hostingu. Trochę teorii: umożliwienie serwerowi wyświetlenia wiadomości, którą otrzymujemy od klienta przy próbie połączenia, dodanie przed linią $msg = „Witaj, Kliencie!”; kod

Echo „Klient ””.$accept.”” mówi:

";
echo gniazdo_read($accept,512);
Echo "
";

Widać, że próbie nawiązania połączenia przy pomocy protokołu WebSocket towarzyszy zawsze wysłanie przez klienta nagłówka obowiązkowo zgodnego z formatem protokołu WebSocket. Tag wstępny do wyświetlania nagłówków nie został wybrany przypadkowo, ponieważ W nagłówkach dużą rolę odgrywają podziały wierszy. Oto nagłówki, które otrzymuję z przeglądarek, gdy próbuję połączyć się z naszym ws://127.0.0.1:889.

GET / HTTP/1.1 Host: localhost:889 User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:29.0) Gecko/20100101 Firefox/29.0 Akceptuj: tekst/html,application/xhtml+xml,application/xml; q=0,9,*/*;q=0,8 Język akceptacji: ru-RU,ru;q=0,8,en-US;q=0,5,en;q=0,3 Kodowanie akceptacji: gzip, deflate Wersja Sec-WebSocket : 13 Pochodzenie: http://localhost Sec-WebSocket-Key: ndHtpnSPk2H0qOeP6ry46A== Plik cookie: vc=3; __gads=ID=9eabc58c385071c7:T=1400699204:S=ALNI_Ma_g9PZBXpi_MLKDBsao3LQiGx-EA Połączenie: utrzymywanie aktywności, aktualizacja Pragma:

GET / HTTP/1.1 Aktualizacja: połączenie websocket: Aktualizacja Host: localhost:889 Pochodzenie: http://localhost Pragma: no-cache Cache-Control: no-cache Sec-WebSocket-Key: zNMTfmc+C9UK6Ztmv4cE5g== Sec-WebSocket- Wersja: 13 Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits, x-webkit-deflate-frame Agent użytkownika: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, jak Gecko) Chrome/35.0.1916.114 Safari/537.36

Jeszcze zanim zacząłem uczyć się gniazd sieciowych, myślałem, że praca z gniazdami sieciowymi będzie podobna do pracy z AJAX-em, gdzie żądania do serwera wysyłamy za pomocą JavaScript XMLHttpRequest(), jednak w rzeczywistości zadanie okazało się znacznie bardziej skomplikowane i po pierwsze dlatego, że WebSocket jest protokołem na tym samym poziomie co protokół HTTP (ale z pewnymi niuansami) w modelu sieci OSI, co oznacza, że ​​po stronie serwera musimy zaimplementować funkcjonalność zbliżoną do przetwarzania HTTP (co Apache zawsze robił ). Kontynuując porównania z AJAX, w AJAX nie musimy udostępniać protokołu HTTP, ponieważ Wszystko to odbywa się za pomocą przeglądarki Apache i serwera WWW. Po prostu wysyłamy i odbieramy wiadomości. Ale choć korzystanie z ws nakłada na nas dość duży nakład pracy związany z wdrożeniem serwera w PHP, to niesie ze sobą także korzyści: zmniejszenie ilości przesyłanych danych i zwiększenie wydajności oprogramowania serwerowego. Aby pomyślnie komunikować się z klientami poprzez protokół WebSocket, serwer musi ściśle spełniać wymagania normy RFC6455, która szczegółowo opisuje formaty nagłówków odpowiedzi. Wszystkie wiadomości wysyłane za pośrednictwem protokołu WebSocket można podzielić na kilka typów: handsnake (uścisk dłoni podczas nawiązywania połączenia), ping-pong (sprawdzenie połączenia) i transfer danych (transfer danych). Istnieje również krótszy opis protokołu w ujęciu ogólnym w języku rosyjskim. Aby zapewnić pełną, poprawną komunikację pomiędzy serwerem a klientem poprzez protokół web Socket, należy zaimplementować w PHP funkcje umożliwiające odbieranie i analizowanie nagłówków od klienta, kompilowanie nagłówków przez serwer, kompilowanie klucza i liczby innych. Po przestudiowaniu materiałów przedstawionych na Habré i innych zasobach udało nam się znaleźć zaimplementowane odpowiednie funkcje, jedyną mylącą różnicą było to, że większość autorów używa funkcji strumieniowych do pracy z gniazdami, są one uważane za bardziej kompaktowe, ale jednocześnie wymagają więcej zasobów ze względu na użycie bufora. Aby przejść na funkcje przesyłania strumieniowego do pracy z gniazdami, nie trzeba instalować żadnych dodatkowych modułów poza tymi już zainstalowanymi, ponieważ wątki są domyślnie wbudowane w PHP 5. Funkcje gniazd strumieniowych zawsze zaczynają się od stream_socket_*. Podczas korzystania z funkcji strumieniowych zaleca się upewnienie się, że phpinfo() obsługuje wymagany transport strumieni.

Zarejestrowane transporty gniazd strumieniowych: tcp, udp.

Jeśli nie ma takich informacji w phpinfo() lub jeśli pojawią się inne problemy, proszę zapoznać się z dokumentacją, ale problem można rozwiązać po prostu aktualizując Denver do aktualnej wersji.
Kolejnym ważnym faktem jest to, że większość systemów ogranicza możliwość tworzenia gniazda na porcie niższym niż 1024, dlatego we wszystkich dalszych programach dla gniazda będzie używany port 8889.
Bazując na kodach źródłowych funkcji kodowania i dekodowania nagłówków protokołu WebSocket, udało nam się zaimplementować pełnoprawny serwer echa ws. Algorytm jego działania jest równie prosty jak w poprzednim przypadku: tworzymy serwer ws za pomocą funkcji stream_socket_server, uruchamiamy nieskończoną pętlę, w której sprawdzamy nowe połączenia, a gdy otrzymamy nowe połączenie, umieszczamy je w $connects array, uruchom także drugą pętlę, która przebiega przez wszystkie połączenia i zamyka te, które się nie powiodły, oraz odbiera komunikaty z otwartych połączeń. Dodałem także trzy funkcje do skryptu PHP, które zaprojektowałem jako procedury obsługi zdarzeń.

Funkcja onOpen($connect, $info) (
echo "otwórz OK
\N";
}

funkcja onClose($connect) (
echo "zamknij OK
\N";
}

funkcja onMessage($połącz, $dane) (
$f = dekodowanie($dane);
echo "Wiadomość:";
echo $f["ładunek"] . "
\N";
fwrite($connect, encode($f["ładunek"]));
}

Które nie robią nic poza wyświetlaniem komunikatów o zdarzeniach na ekranie. A kiedy otrzymamy wiadomość od klienta, rozszyfrujemy ją i po uprzednim zakodowaniu odsyłamy z powrotem jej tekst. , klient ws nie uległ zmianie. Możesz przetestować serwer ws echo, pobierając archiwum i umieszczając je w folderze głównym lokalnego hosta Denver. Połącz klienta z adresem ws://127.0.0.1:8889.
Omówimy subtelności związane z uruchamianiem serwera ws w tle (demon) i uruchamianiem go na hostingu. Będzie mi miło otrzymać komentarze i opinie.

Zwłaszcza dla tych, którzy szukają hosting, aby uruchomić swój projekt na gniazdach internetowych dyskusja.

Moje artykuły o demonach PHP i websocketach

  • Proste gniazdo internetowe w PHP lub gniazda internetowe z absolutnym 0

Gniazda internetowe

Omówione wcześniej zdarzenia serwerowe są idealnym narzędziem, gdy zachodzi potrzeba odebrania sekwencji komunikatów z serwera WWW. Ale połączenie okazuje się całkowicie jednostronne. Przeglądarka nie może odpowiadać na wiadomości ani prowadzić bardziej złożonego dialogu z serwerem.

Jeśli budujesz aplikację internetową, która wymaga znacznej dwukierunkowej komunikacji między przeglądarką a serwerem WWW, najlepszym podejściem do jej wdrożenia (bez uciekania się do Flasha) jest prawdopodobnie użycie obiektu XMLHttpRequest. W zależności od rodzaju tworzonej aplikacji podejście to może działać zgodnie z oczekiwaniami. Ale tutaj jest sporo możliwych problemów.

Po pierwsze, obiekt XMLHttpRequest nie za bardzo nadaje się do szybkiej wymiany wielu wiadomości (np. na czacie). Nie ma wtedy możliwości powiązania jednego wywołania z drugim, więc przy każdym nowym żądaniu ze strony internetowej serwer musi od początku dowiedzieć się, do kogo należy ta strona. Dlatego poziom złożoności kodu obsługującego serię powiązanych żądań ze strony internetowej może bardzo szybko wzrosnąć do poziomu praktycznie niemożliwego do wdrożenia.

Istnieje rozwiązanie wszystkich tych problemów, chociaż nie jest ono jeszcze całkowicie gotowe. To rozwiązanie to technologia gniazdka internetowe, co pozwala przeglądarce utrzymywać otwarte połączenie z serwerem i wymieniać wiadomości przez dowolną ilość czasu.

Technologia Websocket wywołała wiele emocji wśród twórców stron internetowych, ale wciąż jest w fazie rozwoju, chociaż ma już dobrą kompatybilność z przeglądarkami:

Na razie najlepiej przetestować strony korzystające z websocket w przeglądarce Chrome, która zapewnia dla nich najbardziej spójną obsługę.

Dostęp do gniazd sieciowych

Websockety to wyspecjalizowane narzędzia. Nadają się do zastosowań takich jak czat, masowe gry wieloosobowe lub narzędzie komunikacji peer-to-peer. Websockety umożliwiają tworzenie nowych typów aplikacji, ale prawdopodobnie nie mają one sensu w większości nowoczesnych aplikacji internetowych opartych na JavaScript.

Rozwiązania Websocket mogą być niezwykle złożone. Opracowanie kodu JavaScript dla jednej strony będzie dość prostym zadaniem. Jednak aby stworzyć aplikację serwerową, będziesz potrzebować ogromnej wiedzy i umiejętności programistycznych, w tym zrozumienia koncepcji wielowątkowości i sieci.

Aby móc korzystać z gniazd sieciowych, na serwerze WWW witryny musi działać specjalny program, który będzie nazywany serwerem gniazd sieciowych. Program ten odpowiada za koordynację interakcji wszystkich uczestników, a raz uruchomiony działa bez przerwy.

Wiele firm hostingowych nie pozwala na programy długoterminowe, chyba że płacisz za dedykowany serwer WWW, tj. serwer obsługujący wyłącznie Twoją witrynę internetową. Jeśli regularnie korzystasz z hostingu współdzielonego, najprawdopodobniej nie będziesz w stanie hostować stron korzystających z gniazd sieciowych. Nawet jeśli uda ci się uruchomić serwer websocket i utrzymać go, właściciel hostingu najprawdopodobniej go zidentyfikuje i wyłączy.

Aby dać ci wyobrażenie o zakresie serwera websocket, rozważ niektóre zadania, które musi wykonać serwer gniazd:

    skompiluj „słownik” wiadomości, innymi słowy, zdecyduj, jakie typy wiadomości są dopuszczalne, a jakie nie;

    identyfikuj błędy w wysyłaniu wiadomości do klientów i zaprzestań prób kontaktu z nimi, jeśli wydają się już nieistniejące;

    przetwarzać wszystkie dane w pamięci RAM, tj. danych, do których wszyscy klienci mogą potrzebować dostępu, oraz aby robić to bezpiecznie i niezawodnie. Istnieje wiele możliwych ukrytych problemów, na przykład gdy jeden klient próbuje dołączyć do centrali, podczas gdy drugi jest odłączony, a informacje o obu są przechowywane w tym samym obiekcie w pamięci.

Programiści najprawdopodobniej nigdy nie stworzą programu serwerowego, który samodzielnie korzysta z gniazd sieciowych, ponieważ… po prostu nie jest warte wymaganego znacznego wysiłku. Najłatwiejszym podejściem w tym obszarze byłoby zainstalowanie cudzego serwera Websocket i opracowanie dla niego stron internetowych. Ponieważ korzystanie z JavaScriptowej części standardu websocket jest proste, nie powinno to powodować żadnych problemów.

Innym podejściem byłoby pobranie kodu serwera Websocket innej osoby i dostosowanie go do własnych wymagań. Obecnie istnieje wiele projektów (z których wiele jest bezpłatnych i typu open source), które opracowują serwery gniazd sieciowych w celu rozwiązywania różnych problemów w różnych językach programowania serwerów.

Prosty klient websocket

Z perspektywy strony internetowej funkcjonalność websocket jest łatwa do zrozumienia i użycia. Pierwszym krokiem jest tworzenie Obiekt WebSocket i podaj mu adres URL. Kod tego jest podobny do następującego:

Var gniazdo = nowy WebSocket("ws://localhost/socketServer.php");

Ciąg adresu URL zaczyna się od tekstu ws://, który identyfikuje połączenie z gniazdem internetowym. Ten adres URL wskazuje plik aplikacji internetowej na serwerze (w tym przypadku skrypt SocketServer.php).

Standard Web Sockets obsługuje także adresy URL rozpoczynające się od tekstu wss://, który wskazuje na konieczność korzystania z bezpiecznego, szyfrowanego połączenia (tak samo, jak przy żądaniu strony internetowej należy podać adres URL rozpoczynający się od https:// zamiast http : //).

Websockets mogą łączyć się nie tylko z serwerem internetowym. Strona internetowa może otworzyć połączenie z serwerem gniazd sieciowych działającym na innym serwerze sieciowym bez konieczności wykonywania dodatkowego wysiłku.

Już sama czynność utworzenia obiektu WebSocket wymusza na stronie próbę połączenia się z serwerem. Następnie musisz użyć jednego z czterech zdarzeń obiektu WebSocket: na Otwarte(po nawiązaniu połączenia), naBłąd(kiedy wystąpi błąd) naZamknij(przy zamykaniu połączenia) i na Wiadomość(gdy strona otrzyma wiadomość z serwera):

Socket.onopen = połączenieOpen; gniazdo.onmessage = wiadomośćotrzymana; gniazdo.onerror = wystąpił błąd; gniazdo.onopen = połączeniezamknięte;

Na przykład, jeśli połączenie się powiedzie, dobrze byłoby wysłać odpowiednią wiadomość potwierdzającą. Wiadomość ta jest dostarczana przy użyciu metody wysłać() obiekt WebSocket, do którego jako parametr przekazywany jest zwykły tekst. Poniżej znajduje się funkcja obsługująca zdarzenie onopen i wysyłająca komunikat:

Funkcja ConnectionOpen() ( gniazdo.send("Nazwa użytkownika: [e-mail chroniony]"); }

Prawdopodobnie serwer WWW odbierze tę wiadomość i odpowie na nią.

Do wysyłania powiadomień do osoby odwiedzającej stronę internetową można wykorzystać zdarzenia onError i onClose. Jednak zdecydowanie najważniejsze jest zdarzenie onMessage, które uruchamia się po otrzymaniu nowych danych z serwera. Ponownie, kod JavaScript do obsługi tego zdarzenia jest prosty — po prostu pobieramy tekst komunikatu z właściwości data:

Funkcja wiadomośćOtrzymana(e) ( wiadomośćLog.innerHTML += "

Jeśli strona internetowa uzna, że ​​cała praca została wykonana, może zamknąć połączenie za pomocą tej metody rozłączyć się():

Gniazdo.rozłącz();

Z tego przeglądu websocketów widzimy, że korzystanie z serwera websocket innej firmy jest proste — wystarczy wiedzieć, które wiadomości wysłać, a na które poczekać.

Za kulisami włożono wiele pracy, aby połączenia Websocket działały. Po pierwsze, strona internetowa komunikuje się przy użyciu zwykłego standardu HTTP. To połączenie należy następnie zaktualizować do połączenia typu websocket, umożliwiającego swobodną dwukierunkową komunikację. W tym momencie mogą pojawić się problemy, jeśli pomiędzy komputerem klienta a serwerem WWW znajduje się serwer proxy (jak np. w typowej sieci firmowej). Serwer proxy może odmówić współpracy i zakończyć połączenie. Ten problem można rozwiązać, wykrywając nieudane połączenie (poprzez zdarzenie onError obiektu WebSocket) i stosując jedno z wypełnień gniazd opisanych w witrynie GitHub. Te symbole zastępcze korzystają z metody odpytywania w celu emulacji połączenia z gniazdem internetowym.

Przykłady websocketów w sieci

Jeśli jesteś zainteresowany wypróbowaniem websocketów, w Internecie jest wiele witryn, w których możesz rozpocząć programowanie.

Na początek wypróbuj websocket.org, który udostępnia prosty serwer websocket: strona internetowa wysyła do niego wiadomość, a ona zwraca tę samą wiadomość do strony internetowej:

Chociaż ten serwer websocket nie jest niczym specjalnym, pozwala wypróbować wszystkie funkcje obiektu WebSocket. Co więcej, możesz połączyć się z tym serwerem ze strony znajdującej się zarówno na produkcyjnym serwerze WWW, jak i na testowym serwerze WWW na Twoim komputerze, lub nawet ze strony uruchomionej po prostu z Twojego dysku twardego:

Var gniazdo = nowy WebSocket("ws://echo.websocket.org"); gniazdo.onopen = połączenieOtwórz; gniazdo.onmessage = wiadomośćotrzymana; funkcja połączenieOpen() ( gniazdo.send("Nazwa użytkownika: [e-mail chroniony]"); ) funkcja wiadomośćOtrzymana(e) ( var MessageLog = document.getElementById("messageLog"); MessageLog.innerHTML += "
" + "Odpowiedź serwera: " + e.data; )

Istnieją również serwery WebSocket zapewniające inne możliwości, w tym poniższe.

W tym artykule przyjrzymy się pracy z biblioteką phpws, która jest potrzebna do organizowania aplikacji lub aplikacji WEB opartych na gniazdach i uruchomimy kilka standardowych przykładów, które są prezentowane na stronie repozytorium GitHub tego projektu.

Notatka. Nasze gniazda będą działać zarówno po stronie serwera, jak i po stronie klienta. Po stronie serwera zrobi to standardowy WebSocket, który pojawił się w HTML5, a pracę po stronie serwera, gdzie mamy PHP, wykona biblioteka phpws. Istnieje wiele podobnych bibliotek, być może przede wszystkim Ratchet, co wydawało mi się kłopotliwe w przypadku mojego małego projektu i zdecydowałem się na phpws.

Potrzebujemy Kompozytora

Bardzo przydatna rzecz, która ułatwi pracę z zależnościami i bibliotekami zawartymi w projektach. Zgodnie z uniwersalnym standardem kodowania, czyli prościej, zgodnie z poprawnym pisaniem kodu, wszystkie biblioteki, pakiety, zależności czy projekty są zwykle przechowywane w repozytoriach kodu źródłowego, które następnie są łączone z projektem za pomocą kilku poleceń poprzez pakiet menedżerów lub poprzez menedżerów zależności. Każdy język ma swojego menadżera, albo prawie każdy, więc uzbrójmy się w to narzędzie i zainstalujmy je w systemie poleceniem w Linuksie

$ curl -s https://getcomposer.org/installer | php

Pobraliśmy go, ale polecenia kompozytora nie zostaną wykonane przez PATH, więc przeniesiemy pobrany plik do /usr/local/bin

$ mv Composer.phar /usr/local/bin/composer

Wykonujemy polecenie i otrzymujemy wynik w postaci instrukcji i poleceń Composer, co wskazuje na pomyślną instalację

$kompozytor

W przypadku systemów Windows i Mac instrukcje można wyświetlić poza witryną.

Notatka. Wszystkie zależności, które należy uwzględnić, należy określić w pliku Composer.json w katalogu głównym projektu, który pobierze, zaktualizuje i zbierze wszystkie zależności w jednym folderze dostawcy, z którego można je następnie załadować za pomocą modułu automatycznego ładowania klas. Composer ma własne repozytorium pakietów i bibliotek o nazwie Packagist, które pozwala określić dostawcę/pakiet i zostanie on zainstalowany. Tak, możesz podać konkretne adresy repozytoriów svn/git w pliku Composer.json, ale jest to niewygodne. O wiele wygodniej jest mieć jakiś centralny punkt, w którym znajdują się dopasowania pakietów do ich adresów repozytoriów. To jest Packagist.

Potrzebujemy biblioteki phpws

Aby połączyć się z projektem musimy udać się do katalogu głównego folderu projektu lub do podfolderu jeśli jest to część projektu i tam zainstalować tę bibliotekę, jednak najpierw musimy w tym miejscu utworzyć plik Composer.json, który następnie wykonamy przez konsolę za pomocą poleceń kompozytora i po przeczytaniu wszystkiego zainstalujemy to dla nas. Aby to zrobić, utwórz ten plik o następującej zawartości

( "repozytoria": [ ( "typ": "vcs", "url": "https://github.com/Devristo/phpws" ) ], "wymagaj": ( "devristo/phpws": "dev-master " ) )

W tym przypadku wskazaliśmy, że należy pobrać bezpośrednio z repozytorium GitHub bez pośrednictwa Packagist.

Uruchommy ten plik za pomocą polecenia

Instalacja $kompozytora

Następnie w folderze pojawi się podfolder dostawcy z pobranymi bibliotekami i jedyne, co musimy zrobić, to połączyć się i korzystać z nich.

Potrzebujemy podstawowej wiedzy na temat współpracy WebSocket z PHP

A więc przez chwilę zastanówmy się, co zrobić z pobranymi bibliotekami i jak z nich korzystać, nie będę wnikał w szczegóły, więc jeśli jest na palcach, potrzebujemy 2 plików:

  1. client.html to plik po stronie klienta widoczny dla osoby obsługującej przeglądarkę. Do pracy z gniazdem wykorzystuje JavaScript;
  2. server.php to właściwie nasz serwer gniazd, który przetwarza wszystkie żądania od klienta i odpowiada na nie przetworzonymi odpowiedziami.

Aby się połączyć, musimy określić schemat połączenia lub protokół komunikacyjny, ip - adres serwera. Jeśli jest to serwer zdalny to należy podać adres IP hosta lub VPS, a jeśli jest to lokalny to localhost, który jest równy adresowi 127.0.0.0 i określamy też port na którym będzie świadczona usługa serwera zostanie uruchomiony pod własnym PID. Wszystkie te dane są określane podczas tworzenia instancji połączenia.

Ze strony klienta:

Gniazdo Var = "ws://127.0.0.0:12345/";

W części serwerowej:

$serwer = nowy serwer WebSocket("tcp://127.0.0.0:12345", $loop, $logger);

Standardowy przykład wyświetlania aktualnego czasu serwera, aktualizowanego co do sekundy

Aby ten przykład zadziałał, musisz raz uruchomić plik server.php w konsoli i po wykonaniu tego skryptu uruchomić serwer gniazd z jego PID

Co robi przykład? Przykład pokazuje, jak z dokładnością do ułamka sekundy gniazdo aktualizuje informacje o czasie na serwerze i przekazuje je klientowi

Część klienta:

Timery

Czas serwera

Status:
Czas:


Var gniazdo = nowy WebSocket("ws://localhost:12345"); gniazdo.onopen = funkcja(msg)( document.getElementById("status").innerHTML = "Online"; ); gniazdo.onclose = funkcja(msg)( document.getElementById("status").innerHTML = "Offline"; ) gniazdo.onmessage = funkcja(msg)( document.getElementById("czas").innerHTML = msg.data; ) ;

Część serwerowa:

#!/php -qaddWriter($pisarz); // Utwórz serwer WebSocket przy użyciu protokołu SSL $server = new WebSocketServer("tcp://127.0.0.0:12345", $loop, $logger); // Co 0,5 sekundy wysyłany jest czas do wszystkich podłączonych klientów $loop->addPeriodicTimer(0.5, funkcja() use($server, $logger)( $time = new DateTime(); $string = $time->format(" Y-m-d H:i:s"); $logger->notice("Czas rozgłaszania do wszystkich klientów: $string"); foreach($server->getConnections() jako $klient) $client->sendString($string); )); // Powiąż serwer $server->bind(); // Rozpocznij pętlę zdarzeń $loop->run();

Standardowy przykład prostego czatu

Pokazano przykład prostego czatu. Wizualnie wygląda jak na zdjęciu

Część klienta:

TEST protokołu internetowego

Test protokołu internetowego

Serwer powtórzy Twoją odpowiedź!


gniazdo var; funkcja createSocket(host) (jeśli („WebSocket” w oknie) zwróci nowy WebSocket(host); w przeciwnym razie („MozWebSocket” w oknie) zwróci nowy MozWebSocket(host); wyrzuć nowy błąd („Brak obsługi gniazd internetowych w przeglądarce!” ); ) funkcja init() ( var host = "ws://127.0.0.0:12345/chat"; try ( gniazdo = createSocket(host); log("WebSocket - status " + gniazdo.readyState); gniazdo.onopen = funkcja(msg) ( log("Witamy - status " + this.readyState); ); gniazdo.onmessage = funkcja(msg) ( log(msg.data); ); gniazdo.onclose = funkcja(msg) ( log( "Rozłączony - status " + this.readyState); ); ) catch (ex) ( log(ex); ) document.getElementById("msg").focus(); ) funkcja send() ( var msg = document.getElementById („msg”).wartość; try ( Socket.send(msg); ) catch (ex) ( log(ex); ) ) funkcja Quit() ( log("Do widzenia!"); Socket.close(); gniazdo = null; ) funkcja log(msg) ( document.getElementById("log").innerHTML += "
" + msg; ) funkcja onkey(event) ( if (event.keyCode == 13) ( send(); ) )

Część serwerowa:

#!/php -q php chat.php użyj Devristo\Phpws\Framing\WebSocketFrame; użyj Devristo\Phpws\Framing\WebSocketOpcode; użyj Devristo\Phpws\Messaging\WebSocketMessageInterface; użyj Devristo\Phpws\Protocol\WebSocketTransportInterface; użyj Devristo\Phpws\Server\IWebSocketServerObserver; użyj Devristo\Phpws\Server\UriHandler\WebSocketUriHandler; użyj Devristo\Phpws\Server\WebSocketServer; /** * Poniższa procedura obsługi ChatHandler odpowie na wszystkie wiadomości wysłane do /chat (np. ws://localhost:12345/chat) */ class ChatHandler rozszerza WebSocketUriHandler ( /** * Powiadamia wszystkich, gdy użytkownik dołączy do czatu * * @param WebSocketTransportInterface $user */ funkcja publiczna onConnect(WebSocketTransportInterface $user)( foreach($this->getConnections() jako $client)( $client->sendString("Użytkownik ($user->getId()) dołączył do czat: "); ) ) /** * Wiadomości rozgłoszeniowe wysyłane przez użytkownika do wszystkich w pokoju * * @param WebSocketTransportInterface $user * @param WebSocketMessageInterface $msg */ funkcja publiczna onMessage(WebSocketTransportInterface $user, WebSocketMessageInterface $msg) ( $this->logger->notice("Nadawanie " .strlen($msg->getData()) . " bytes"); foreach($this->getConnections() as $client)( $client->sendString(" Użytkownik ($user->getId()) powiedział: ".$msg->getData()); ) ) ) klasa ChatHandlerForUnroutedUrls rozszerza WebSocketUriHandler ( /** * Ta klasa dotyczy użytkowników, którzy nie są routowani */ funkcja publiczna onConnect( WebSocketTransportInterface $user)( //nic nie rób $this->logger->notice("Użytkownik ($user->getId()) nie dołączył do żadnego pokoju"); ) funkcja publiczna onMessage(WebSocketTransportInterface $user, WebSocketMessageInterface $msg) ( //nic nie rób $this->logger->notice("Użytkownika ($user->getId()) nie ma w pokoju, ale próbował powiedzieć: ($ msg->getData())"); ) ) $loop = \React\EventLoop\Factory::create(); // Utwórz rejestrator, który zapisuje wszystko do STDOUT $logger = new \Zend\Log\Logger(); $writer = nowy Zend\Log\Writer\Stream("php://output"); $logger->addWriter($writer); // Utwórz serwer WebSocket $server = new WebSocketServer("tcp://127.0.0.0:12345", $loop, $logger); // Utwórz router, który przekaże wszystkie połączenia /chat do klasy ChatHandler $router = new \Devristo\Phpws\Server\UriHandler\ClientRouter($server, $logger); // trasa /chat url $router->addRoute("#^/chat$#i", new ChatHandler($logger)); // kieruj niedopasowane adresy URL podczas tej wersji demonstracyjnej, aby uniknąć błędów $router->addRoute("#^(.*)$#i", new ChatHandlerForUnroutedUrls($logger)); // Powiąż serwer $server->bind(); // Rozpocznij pętlę zdarzeń $loop->run();

Musisz uruchomić ten przykład tak jak poprzedni - po przejściu przez konsolę uruchom plik server.php i przez przeglądarkę wejdź do części klienckiej client.html, łącząc się ze skryptem script.js.

Praca z PHP - serwer gniazd z konsoli

Aby zaktualizować kod serwera i ponownie uruchomić, bardzo wygodnie jest użyć poleceń zatrzymujących i restartujących plik serwera PHP za pośrednictwem konsoli, w przeciwnym razie może wystąpić incydent, gdy wydaje się, że edytujesz kod serwera, ale wykonuje on stary.

Najpierw wyświetlamy PID działającego procesu serwera gniazd. Dowiemy się tego przeglądając listę uruchomionych gniazd na ich portach za pomocą polecenia:

Netstat --tcp --słuchanie --program

Znajdując żądany PID z listy, zabijamy go za pomocą polecenia:

Zabij %pid%

W idealnym przypadku zamykamy WebSocket poprzez część JavaScript klienta za pomocą polecenia przed rozpoczęciem „zabijania” PID:

Gniazdo.zamknij(); gniazdo = null;

Satya zajmuje się programowaniem sieciowym, a w szczególności programowaniem gniazd w PHP. Istnieją dwie kategorie funkcji komunikacji sieciowej w PHP:

  1. Funkcja fsockopen(string nazwa hosta, liczba całkowita port, liczba całkowita numer_błędu, ciąg opis_błędu, podwójny limit czasu) - otwiera połączenie sieciowe jako strumień pliku i zwraca deskryptor pliku, z którym działają funkcje fputs, fgets itp.
  2. Funkcje przekazujące informacje bezpośrednio na poziomie protokołu IP. A jest to poziom znacznie niższy niż poziom, na którym działa funkcja fsockopen.

Uwzględnione zostaną tylko funkcje nr 2, ponieważ są ciekawsze.
Najpierw sprawdźmy, czy masz podłączoną bibliotekę gniazd.

Możesz to sprawdzić za pomocą następującego skryptu:

If(extension_loaded("sockets")) echo "załadowano rozszerzenie"; w przeciwnym razie echo „rozszerzenie nie zostało załadowane”; ?>

Jeżeli rozszerzenie nie zostało pobrane, należy je pobrać.

Więc. Najprostszym przykładem w tym artykule jest serwer echa. Serwer Echo - oznacza to, że ciąg znaków wysłany przez klienta do serwera jest zwracany. Oznacza to, że serwer otrzymuje jakąś wiadomość od klienta, robi z nią coś i odsyła mu ją.

Będziemy mieli 2 skrypty:

  1. Serwer lub demon.
  2. Klient.

Skrypt „Klient”.

Do implementacji klienta potrzebujemy następujących funkcji współpracujących z gniazdami:

  1. utworzenie_gniazdka(rodzina liczb całkowitych, typ gniazda typu integer, protokół liczb całkowitych); — funkcja tworzy gniazdo i zwraca zasób gniazda. Pierwszym argumentem jest rodzina protokołów.Jeżeli połączenie odbywa się przez Internet to należy podać wartość - AF_INET; Jeżeli połączenie będzie odbywać się poprzez gniazda UNIX - AF_UNIX; Drugi argument to typ gniazda. Zwykle SOCK_STREAM jest używany do komunikacji TCP, a SOCK_DGRAM do komunikacji UPD. Trzeci argument określa protokół SOL_TCP lub SOL_UPD w zależności od typu.
  2. gniazdo_connect(gniazdo zasobu, adres ciągu, port całkowity); — po utworzeniu gniazda należy się z nim połączyć. Pierwszy argument to zasób utworzonego gniazda, drugi to adres IP gniazda, jeśli rodzina protokołów to AF_INET, lub nazwa ścieżki gniazda domeny uniksowej, jeśli gniazdo należy do rodziny AF_UNIX. Trzecim argumentem jest numer portu, z którym należy nawiązać połączenie.
  3. gniazdo_czytaj(gniazdo zasobu, długość całkowita, typ całkowity); — funkcja odczytuje z podanego gniazda liczbę bajtów podaną w argumencie długość. Domyślnie odczyt odbywa się bez uwzględnienia znaków kontrolnych lub możesz ustawić argument typu na PHP_BINARY_READ; aby uwzględnić znaki sterujące, musisz ustawić wartość PHP_NORMAL_READ.
  4. gniazdo_zapis(gniazdo zasobu, bufor ciągu, długość całkowita); — funkcja zapisuje dane do gniazda.
  5. gniazdo_zamknij(gniazdo zasobu); — zamyka gniazdo i zwalnia pamięć.

Listing 1.0 - Klient

Set_time_limit(0); ob_implicit_flush(); echo "- Klient

"; $adres = "127.0.0.1"; // adres hosta lokalnego. $port = 5555; // port, z którym zostanie nawiązane połączenie. echo "Tworzenie gniazda..."; $socket = gniazdo_create(AF_INET, SOCK_STREAM , SOL_TCP); if ($socket "; ) else ( echo "OK
"; ) echo "Łączenie z gniazdem..."; $connect = gniazdo_connect($socket, $adres, $port); if($connect === false) ( echo "Błąd: ".socket_strerror(socket_last_error() ) "
"; ) else ( echo "OK

"; $msg = "Witaj serwer!"; echo "Powiedz serwerowi \".$msg."\"..."; gniazdo_write($socket, $msg, strlen($msg)); echo "OK
"; echo "Serwer powiedział: "; $awr = gniazdo_read($socket, 1024); echo $awr."
"; $msg = "exit"; echo "Powiedz serwerowi \".$msg."\"..."; gniazdo_write($socket, $msg, strlen($msg)); echo "OK
"; ) if(isset($socket)) ( echo "Zamykanie połączenia..."; Socket_close($socket); echo "OK
"; } ?>

Skrypt serwera.

Do implementacji serwera potrzebujemy następujących funkcji współpracujących z gniazdami:

  1. Wszystkie funkcje opisane powyżej.
  2. gniazdo_bind(gniazdo zasobu, adres ciągu, port całkowity); — funkcja wiąże adres z gniazdem. Argument adresu jest adresem IP gniazda, jeśli rodzina protokołów to AF_INET, lub nazwą ścieżki gniazda domeny uniksowej, jeśli gniazdo należy do rodziny AF_UNIX.
  3. gniazdo_słuchaj(gniazdo zasobu, zaległości całkowite) - funkcja nasłuchuje połączeń przychodzących do gniazda. Opcjonalny drugi argument ustawia maksymalny rozmiar kolejki żądań oczekujących na połączenie.
  4. gniazdo_akcept(gniazdo zasobu); - Po utworzeniu, powiązaniu i rozpoczęciu nasłuchiwania gniazda, właśnie ta funkcja sprawia, że ​​serwer staje się serwerem. Funkcja akceptuje połączenia przychodzące.

Listing 1.1 – Serwer

Set_time_limit(0); ob_implicit_flush(); echo "- Serwer

"; $adres = "127.0.0.1"; $port = 5555; echo "Tworzenie gniazda..."; $socket = gniazdo_create(AF_INET, SOCK_STREAM, SOL_TCP); if($socket "; ) else ( echo "OK
"; ) echo "Wiązanie gniazda..."; $bind = gniazdo_bind($socket, $adres, $port); if($bind "; ) else ( echo "OK
"; ) echo "Słuchanie gniazda..."; $listen = gniazdo_listen($socket, 5); if($słuchaj "; ) else ( echo "OK
"; ) while(true) ( ​​​​echo "Oczekiwanie... "; $accept = Socket_accept($socket); if($accept "; break; ) else ( echo "OK
"; ) $msg = "Witaj, Kliencie!"; echo "Wyślij do klienta \".$msg."\"... "; Socket_write($accept, $msg, strlen($msg)); echo " OK
"; while(true) ( ​​​​$awr = gniazdo_read($accept, 1024); if (false === $awr) ( echo "Błąd: ".socket_strerror(socket_last_error())."
"; break 2; ) else ( if (trim($awr) == "") break; else echo "Klient powiedział: ".$awr."
"; ) if ($awr == "exit") ( Socket_close($accept); break 2; ) echo "Powiedz klientowi \".$msg."\"... "; Socket_write($accept, $awr , strlen($awr)); echo "OK
"; ) ) if (isset($socket)) ( echo "Zamykanie połączenia..."; Socket_close($socket); echo "OK
"; } ?>

To wszystko. Najpierw uruchom skrypt serwera, utworzy, powiąże, rozpocznie nasłuchiwanie na gnieździe i przejdzie w tryb gotowości klienta. Następnie uruchom klienta.

Chcę również zauważyć, że najbardziej użyteczną funkcją jest:
gniazdo_wybierz(odczyt tablicy, zapis tablicy, wyjątkiem tablicy, liczba całkowita limit czasu_sekund, liczba całkowita limit czasu_mikrosekund); — Funkcja kontroluje zmiany zachodzące w węzłach. PHP szuka nowych danych przychodzących do gniazd określonych w tablicy odczytu. PHP szuka gotowości do odbioru danych na gniazdach określonych w tablicy zapisu. PHP sprawdza strumienie określone w argumencie z wyjątkiem pod kątem błędów. Limity czasu określają czas, po jakim funkcja zwróci liczbę gniazd, które zmieniły swój stan lub FALSE.

Funkcja ta jest niezastąpiona przy monitorowaniu klientów wiszących na gniazdku.

W artykule pokazany jest mały wycinek wszystkich funkcji współpracujących z gniazdami, jeśli jesteś zainteresowany, odkop instrukcje i przeczytaj... Powodzenia w eksperymentach...

W poprzednim artykule mówiłem o. Jesteśmy z Tobą za pomocą gniazd stworzyliśmy serwer w PHP. W tym artykule napiszemy z Tobą klient w PHP, który wyśle ​​żądanie do serwera i otrzyma od niego odpowiedź.

przynoszę kod klienta w PHP:

header("Typ zawartości: tekst/zwykły;"); //Wyświetlimy prosty tekst
set_time_limit(0); //Skrypt powinien działać stale
ob_implicit_flush(); //Wszystkie echa powinny zostać natychmiast wyprowadzone
$adres = "localhost"; //Adres służbowy serwera
$port = 1985; //Port operacyjny serwera (najlepiej jakiś rzadko używany)
if (($socket = gniazdo_create(AF_INET, SOCK_STREAM, SOL_TCP))< 0) {
//AF_INET - rodzina protokołów
//SOCK_STREAM – typ gniazda
//SOL_TCP - protokół
echo "Błąd podczas tworzenia gniazda";
}
w przeciwnym razie(
echo "Utworzono gniazdo\n";
}
$result = gniazdo_connect($socket, $adres, $port);
if ($wynik === fałsz) (
echo "Błąd podczas łączenia z gniazdem";
) w przeciwnym razie (
echo "Połączenie z gniazdem powiodło się\n";
}

echo "Wiadomość z serwera: $out.\n";
$msg = "15";

gniazdo_write($gniazdo, $msg, strlen($msg)); //Wyślij wiadomość do serwera
$wyjście = gniazdo_odczyt($gniazdo, 1024); //Odczytaj wiadomość z serwera
echo "Wiadomość z serwera: $out.\n"; //Wyślij wiadomość z serwera
$msg = "wyjście"; //Polecenie zamknięcia
echo "Wiadomość do serwera: $msg\n";
gniazdo_write($gniazdo, $msg, strlen($msg));
echo "Połączenie zakończone\n";
//Przestań pracować z gniazdem
if (isset($socket)) (
gniazdo_zamknij($gniazdo);
echo "Socket został pomyślnie zamknięty";
}
?>

Kod jest dobrze skomentowany, więc myślę, że wszystko tutaj jest wyjątkowo jasne. Algorytm klienta jest banalny: utworzenie gniazda, połączenie się z serwerem, wysłanie żądań, otrzymanie odpowiedzi, zamknięcie połączenia. Wysłaliśmy Ci numer 15 . Jeśli czytałeś poprzedni artykuł, to pamiętaj, że zadaniem serwera jest podniesienie tej liczby do kwadratu i zwrócenie jej. Dlatego jeśli uruchomisz tego klienta, zobaczysz go z serwera 225 (15*15 ). Następnie wydajemy polecenie zamknięcie, co zatrzymuje serwer.

Teraz masz minimalny zestaw wiedzy na temat pracy z gniazdami i ogólnie temat jest bardzo interesujący, więc możesz przestudiować go bardziej szczegółowo. Można tworzyć bardzo złożone aplikacje klient-serwer, z którymi zawsze można się połączyć i wysyłać różnorodne żądania, które serwer będzie przetwarzał.