Как создать сервер 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("Y.m.d")); 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('Y-m-d 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 в firefox говорит Firefox can't establish a connection to the server at ws://localhost:12345/.
ИЗМЕНИТЬ
Поскольку в этом вопросе есть большой интерес, я решил предоставить вам то, что я наконец придумал. Вот мой полный код.
Ответы
Ответ 1
Я был в той же лодке, что и вы недавно, и вот что я сделал:
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, и мне удалось создать собственный скрипт клиента и сервера с нуля, который отлично работает.
Надеемся, что кульминация всей этой информации даст вам достаточное руководство и информацию для кодирования вашего собственного WebSocket PHP скрипт.
Удачи!
Изменить. Это редактирование через пару лет после моего первоначального ответа, и пока у меня все еще есть рабочее решение, оно действительно не готово для совместного использования. К счастью, у кого-то еще у 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, чтобы он мог обрабатывать запросы перекрестного происхождения.
Ответ 2
Насколько я знаю Ratchet - лучшее решение для PHP WebSocket, доступное на данный момент. И поскольку он с открытым исходным кодом, вы можете увидеть, как автор построил это решение WebSocket с помощью PHP.
Ответ 3
Почему бы не использовать сокеты http://uk1.php.net/manual/en/book.sockets.php? Он хорошо документирован (не только в контексте PHP) и имеет хорошие примеры http://uk1.php.net/manual/en/sockets.examples.php
Ответ 4
Необходимо преобразовать ключ из шестнадцатеричного значения в 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";
Сообщите мне, если это поможет.
Ответ 5
<?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