Как создать сервер websockets в PHP

Существуют ли какие-либо руководства или руководства, показывающие, как написать себе простой сервер веб-серверов в PHP? Я пробовал искать его на google, но я не нашел много. Я нашел phpwebsockets, но он устарел и не поддерживает новейший протокол. Я попробовал обновить его сам, но он не работает.

#!/php -q <?php /* >php -q server.php */ error_reporting(E_ALL); set_time_limit(0); ob_implicit_flush(); $master = WebSocket("localhost",12345); $sockets = array($master); $users = array(); $debug = false; while(true){ $changed = $sockets; socket_select($changed,$write=NULL,$except=NULL,NULL); foreach($changed as $socket){ if($socket==$master){ $client=socket_accept($master); if($client<0){ console("socket_accept() failed"); continue; } else{ connect($client); } } else{ $bytes = @socket_recv($socket,$buffer,2048,0); if($bytes==0){ disconnect($socket); } else{ $user = getuserbysocket($socket); if(!$user->handshake){ dohandshake($user,$buffer); } else{ process($user,$buffer); } } } } } //--------------------------------------------------------------- function process($user,$msg){ $action = unwrap($msg); say("< ".$action); switch($action){ case "hello" : send($user->socket,"hello human"); break; case "hi" : send($user->socket,"zup human"); break; case "name" : send($user->socket,"my name is Multivac, silly I know"); break; case "age" : send($user->socket,"I am older than time itself"); break; case "date" : send($user->socket,"today is ".date("Ymd")); break; case "time" : send($user->socket,"server time is ".date("H:i:s")); break; case "thanks": send($user->socket,"you're welcome"); break; case "bye" : send($user->socket,"bye"); break; default : send($user->socket,$action." not understood"); break; } } function send($client,$msg){ say("> ".$msg); $msg = wrap($msg); socket_write($client,$msg,strlen($msg)); } function WebSocket($address,$port){ $master=socket_create(AF_INET, SOCK_STREAM, SOL_TCP) or die("socket_create() failed"); socket_set_option($master, SOL_SOCKET, SO_REUSEADDR, 1) or die("socket_option() failed"); socket_bind($master, $address, $port) or die("socket_bind() failed"); socket_listen($master,20) or die("socket_listen() failed"); echo "Server Started : ".date('Ymd H:i:s')."\n"; echo "Master socket : ".$master."\n"; echo "Listening on : ".$address." port ".$port."\n\n"; return $master; } function connect($socket){ global $sockets,$users; $user = new User(); $user->id = uniqid(); $user->socket = $socket; array_push($users,$user); array_push($sockets,$socket); console($socket." CONNECTED!"); } function disconnect($socket){ global $sockets,$users; $found=null; $n=count($users); for($i=0;$i<$n;$i++){ if($users[$i]->socket==$socket){ $found=$i; break; } } if(!is_null($found)){ array_splice($users,$found,1); } $index = array_search($socket,$sockets); socket_close($socket); console($socket." DISCONNECTED!"); if($index>=0){ array_splice($sockets,$index,1); } } function dohandshake($user,$buffer){ console("\nRequesting handshake..."); console($buffer); //list($resource,$host,$origin,$strkey1,$strkey2,$data) list($resource,$host,$u,$c,$key,$protocol,$version,$origin,$data) = getheaders($buffer); console("Handshaking..."); $acceptkey = base64_encode(sha1($key . "258EAFA5-E914-47DA-95CA-C5AB0DC85B11",true)); $upgrade = "HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Accept: $acceptkey\r\n"; socket_write($user->socket,$upgrade,strlen($upgrade)); $user->handshake=true; console($upgrade); console("Done handshaking..."); return true; } function getheaders($req){ $r=$h=$u=$c=$key=$protocol=$version=$o=$data=null; if(preg_match("/GET (.*) HTTP/" ,$req,$match)){ $r=$match[1]; } if(preg_match("/Host: (.*)\r\n/" ,$req,$match)){ $h=$match[1]; } if(preg_match("/Upgrade: (.*)\r\n/",$req,$match)){ $u=$match[1]; } if(preg_match("/Connection: (.*)\r\n/",$req,$match)){ $c=$match[1]; } if(preg_match("/Sec-WebSocket-Key: (.*)\r\n/",$req,$match)){ $key=$match[1]; } if(preg_match("/Sec-WebSocket-Protocol: (.*)\r\n/",$req,$match)){ $protocol=$match[1]; } if(preg_match("/Sec-WebSocket-Version: (.*)\r\n/",$req,$match)){ $version=$match[1]; } if(preg_match("/Origin: (.*)\r\n/",$req,$match)){ $o=$match[1]; } if(preg_match("/\r\n(.*?)\$/",$req,$match)){ $data=$match[1]; } return array($r,$h,$u,$c,$key,$protocol,$version,$o,$data); } function getuserbysocket($socket){ global $users; $found=null; foreach($users as $user){ if($user->socket==$socket){ $found=$user; break; } } return $found; } function say($msg=""){ echo $msg."\n"; } function wrap($msg=""){ return chr(0).$msg.chr(255); } function unwrap($msg=""){ return substr($msg,1,strlen($msg)-2); } function console($msg=""){ global $debug; if($debug){ echo $msg."\n"; } } class User{ var $id; var $socket; var $handshake; } ?> 

и клиент:

 var connection = new WebSocket('ws://localhost:12345'); connection.onopen = function () { connection.send('Ping'); // Send the message 'Ping' to the server }; // Log errors connection.onerror = function (error) { console.log('WebSocket Error ' + error); }; // Log messages from the server connection.onmessage = function (e) { console.log('Server: ' + e.data); }; 

Если в моем коде есть что-то не так, вы можете помочь мне исправить это? Concole in firefox говорит, что Firefox can't establish a connection to the server at ws://localhost:12345/.

РЕДАКТИРОВАТЬ
Поскольку в этом вопросе есть большой интерес, я решил предоставить вам то, что я наконец придумал. Вот мой полный код.

Я был в той же лодке, что и вы недавно, и вот что я сделал:

1) Я использовал код phpwebsockets в качестве ссылки для того, как структурировать серверный код. (Кажется, вы уже это делаете, и, как вы заметили, код действительно не работает по разным причинам.)

2) Я использовал PHP.net для чтения сведений о каждой функции сокета, используемой в коде phpwebsockets. Делая это, я наконец смог понять, как вся система работает концептуально. Это было довольно большое препятствие.

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

4) Я закодировал правильную процедуру рукопожатия, основанную на инструкциях в черновике в № 3. Это было не так уж плохо.

5) Я продолжал получать кучу искаженного текста, отправленного с клиентов на сервер после рукопожатия, и я не мог понять, почему, пока не понял, что данные закодированы и должны быть разоблачены. Следующая ссылка мне очень помогла: http://srchea.com/blog/2011/12/build-a-real-time-application-using-html5-websockets/

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

6) Затем я наткнулся на следующий поток SO, в котором четко объясняется, как правильно кодировать и декодировать сообщения, отправляемые туда и обратно: как я могу отправлять и получать сообщения WebSocket на стороне сервера?

Эта ссылка была действительно полезна. Я рекомендую обратиться к нему, глядя на проект WebSocket. Это поможет понять, что говорится в проекте.

7) Я почти закончил на этом этапе, но имел некоторые проблемы с приложением WebRTC, которое я делал с помощью WebSocket, поэтому я в конечном итоге задал свой собственный вопрос о SO, который я в конечном итоге решил. Чтобы ответить на вопрос и ответ, пожалуйста, выполните поиск в Интернете «SO Что это за данные в конце информации о кандидате WebRTC?» (без кавычек).

8) В этот момент я почти все работаю. Мне просто пришлось добавить дополнительную логику для обработки закрытия соединений, и я был сделан.

Этот процесс занял у меня около двух недель. Хорошей новостью является то, что я сейчас хорошо понимаю WebSocket, и мне удалось создать собственный скрипт клиента и сервера с нуля, который отлично работает. Надеемся, что кульминация всей этой информации даст вам достаточное руководство и информацию для написания собственного скрипта PHP WebSocket. Удачи!

Редактировать : это редактирование через пару лет после моего первоначального ответа, и пока у меня все еще есть рабочее решение, оно не готово к совместному использованию. К счастью, у кого-то еще у GitHub почти идентичный код для моего (но гораздо более чистого), поэтому я рекомендую использовать следующий код для рабочего решения PHP WebSocket:
https://github.com/ghedipunk/PHP-Websockets/blob/master/websockets.php

Редактировать # 2 : Хотя мне все еще нравится использовать PHP для многих связанных с сервером вещей, я должен признать, что я действительно сильно разогрелся до Node.js совсем недавно, и основная причина заключается в том, что он лучше разработан из чтобы обрабатывать WebSocket, чем PHP (или любой другой серверный язык). Таким образом, я недавно нашел, что гораздо проще настроить Apache / PHP и Node.js на вашем сервере и использовать Node.js для запуска сервера WebSocket и Apache / PHP для всего остального. И в случае, когда вы находитесь в среде совместного размещения, в которой вы не можете установить / использовать Node.js для WebSocket, вы можете использовать бесплатный сервис, такой как Heroku, для установки сервера Node.js WebSocket и создания междоменного запросы к нему с вашего сервера. Просто убедитесь, что вы делаете это, чтобы настроить сервер WebSocket, чтобы он мог обрабатывать запросы с кросс-началом.

Насколько я знаю, Ratchet – лучшее решение для PHP WebSocket, доступное на данный момент. И поскольку это открытый исходный код, вы можете увидеть, как автор построил это решение WebSocket с помощью PHP.

Почему бы не использовать сокеты http://uk1.php.net/manual/en/book.sockets.php ? Он хорошо документирован (не только в контексте PHP) и имеет хорошие примеры http://uk1.php.net/manual/en/sockets.examples.php

Необходимо преобразовать ключ из шестнадцатеричного значения в dec перед base64_encoding, а затем отправить его для подтверждения.

 $hashedKey = sha1($key. "258EAFA5-E914-47DA-95CA-C5AB0DC85B11",true); $rawToken = ""; for ($i = 0; $i < 20; $i++) { $rawToken .= chr(hexdec(substr($hashedKey,$i*2, 2))); } $handshakeToken = base64_encode($rawToken) . "\r\n"; $handshakeResponse = "HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Accept: $handshakeToken\r\n"; 

Позвольте мне знать, если это помогает.

 <?php // server.php $server = stream_socket_server("tcp://127.0.0.1:8001", $errno, $errorMessage); if($server == false) { throw new Exception("Could not bind to socket: $errorMessage"); } for(;;) { $client = @stream_socket_accept($server); if($client) { stream_copy_to_stream($client, $client); fclose($client); } } 

с одного терминала: php server.php

из другого терминала: echo "hello woerld" | nc 127.0.0.1 8002