web-dev-qa-db-ja.com

PHPでwebsocketsサーバーを作成する方法

PHPで簡単なWebSocketサーバーを作成する方法を示すチュートリアルやガイドはありますか?私はグーグルでそれを探してみましたが、私は多くを見つけませんでした。 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);
};

コードに何か問題がある場合は、修正するのを手伝ってもらえますか? FirefoxのConcoleはFirefox can't establish a connection to the server at ws://localhost:12345/.と言います

編集
この質問には多くの関心があるため、最終的に思いついたものを提供することにしました。 これが私の完全なコードです。

74
Dharman

私は最近あなたと同じ船に乗っていました。

1)サーバー側のコードを構成する方法のリファレンスとして、phpwebsocketsコードを使用しました。 (あなたはすでにこれをしているようです、そしてあなたが述べたように、コードは実際にはさまざまな理由で機能しません。)

2)PHP.netを使用して、phpwebsocketsコードで使用されるすべてのソケット関数の詳細を読み取りました。これを行うことにより、システム全体が概念的にどのように機能するかをようやく理解することができました。これはかなり大きなハードルでした。

3)実際のWebSocketドラフトを読みます(投稿ごとに3つ以上のリンクを投稿することはできないため、Web検索を行ってください)。最終的に沈み始める前に、このことを何度も読まなければなりませんでした。正しい、最新の1つの決定的なリソースであるため、プロセス全体で何度もこのドキュメントに戻る必要があります。 WebSocket APIに関する情報。

4)#3のドラフトの指示に基づいて、適切なハンドシェイク手順をコーディングしました。これはそれほど悪くはありませんでした。

5)ハンドシェイク後にクライアントからサーバーに送信された文字化けしたテキストの束を取得し続け、データがエンコードされており、マスクを解除する必要があることに気付くまで、理由を理解できませんでした。次のリンクはここで非常に役立ちました: http://srchea.com/blog/2011/12/build-a-real-time-application-using-html5-websockets/

このリンクで利用可能なコードには多くの問題があり、さらに変更しないと適切に動作しないことに注意してください。

6)次に、次のSOスレッドに遭遇しました。これは、送受信されるメッセージを適切にエンコードおよびデコードする方法を明確に説明しています。 サーバー側でWebSocketメッセージを送受信するにはどうすればよいですか?

このリンクは本当に役に立ちました。 WebSocketドラフトを見ながら相談することをお勧めします。ドラフトが言っていることをより理解するのに役立ちます。

7)私はこの時点でほぼ完了しましたが、WebSocketを使用して作成しているWebRTCアプリにいくつかの問題があったため、最終的にSOで自分の質問をすることになり、最終的に解決しました。質問と回答を参照するには、「SO WebRTC候補者情報の最後にあるこのデータは何ですか?」のWeb検索を行ってください。 (引用符なし)。

8)この時点で、私はほとんどすべてが機能していました。接続のクローズを処理するためのロジックをいくつか追加するだけで、完了しました。

そのプロセスには合計で約2週間かかりました。幸いなことに、今ではWebSocketを非常によく理解しているので、自分のクライアントおよびサーバースクリプトをゼロから作成して、うまく機能させることができました。すべての情報が集まることで、WebSocket PHPスクリプトをコーディングするための十分なガイダンスと情報が得られることを願っています。幸運を!

Edit:この編集は元の回答から数年後であり、有効なソリューションはまだありますが、共有する準備ができていません。幸いなことに、GitHubの他の誰かが私のコードとほとんど同じコードを持っています(ただし、ずっときれいです)。したがって、動作するPHP WebSocketソリューションには次のコードを使用することをお勧めします。
https://github.com/ghedipunk/PHP-Websockets/blob/master/websockets.php

#2を編集:PHPをサーバー側の関連する多くの事柄に使用することを今でも楽しんでいますが、私はそれを認めなければなりません最近、Node.jsを実際にウォームアップしました。その主な理由は、PHP(または他のサーバー側言語)よりもWebSocketを処理するようにゼロから設計されているためです。そのため、最近、サーバーでApache/PHPとNode.jsの両方をセットアップし、Node.jsを使用してWebSocketサーバーを実行し、Apache/PHPを他のすべてに使用する方がはるかに簡単であることがわかりました。また、Node.js for WebSocketをインストール/使用できない共有ホスティング環境を使用している場合は、Herokuなどの無料サービスを使用してNode.js WebSocketサーバーをセットアップし、クロスドメインを作成できますサーバーからのリクエスト。そのようにする場合は、WebSocketサーバーを設定して、クロスオリジンリクエストを処理できるようにしてください。

95
HartleySan

私の知る限り、 Ratchet は現時点で利用可能なPHP WebSocketソリューションの中で最高です。 オープンソース なので、作成者がPHPを使用してこのWebSocketソリューションをどのように構築したかを確認できます。

21
leggetter

ソケットを使用しない理由 http://uk1.php.net/manual/en/book.sockets.php ?よく文書化されており(PHPコンテキストだけでなく)、良い例があります http://uk1.php.net/manual/en/sockets.examples.php

6
Lukasz Kujawa

私はしばらくの間あなたの靴の中にいましたが、最終的にnode.jsを使用することになりました。なぜなら、Webサーバーとソケットサーバーを1つに持つなどのハイブリッドソリューションを実行できるからです。そのため、PHPバックエンドは、httpを介してノードWebサーバーにリクエストを送信し、それをwebsocketでブロードキャストできます。非常に効率的な方法です。

1
M.Z.

Base64エンコードの前にキーを16進数からdecに変換してから、ハンドシェイクのために送信する必要があります。

$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";

これが役立つかどうか教えてください。

0
user2288650