[php] PHP에서 웹 소켓 서버를 만드는 방법

PHP로 간단한 웹 소켓 서버를 작성하는 방법을 보여주는 튜토리얼이나 가이드가 있습니까? 나는 그것을 구글에서 찾아 보았지만 많이 찾지 못했습니다. 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 can't establish a connection to the server at ws://localhost:12345/.

편집
이 질문에 많은 관심이 있기 때문에 마침내 내가 생각 해낸 것을 제공하기로 결정했습니다. 여기 내 전체 코드가 있습니다.



답변

나는 최근에 당신과 같은 배에 있었고 여기에 내가 한 일이 있습니다.

1) 서버 측 코드를 구성하는 방법에 대한 참조로 phpwebsockets 코드를 사용했습니다. (이미이 작업을 수행하고있는 것으로 보이며 언급했듯이 코드가 실제로 여러 가지 이유로 작동하지 않습니다.)

2) PHP.net을 사용하여 phpwebsockets 코드에서 사용되는 모든 소켓 함수에 대한 세부 정보를 읽었습니다. 이를 통해 마침내 전체 시스템이 개념적으로 어떻게 작동하는지 이해할 수있었습니다. 이것은 꽤 큰 장애물이었습니다.

3) 실제 WebSocket 초안을 읽었습니다 (게시물 당 두 개 이상의 링크를 게시 할 수 없으므로 웹 검색을 수행하십시오). 이 문서가 드디어 시작되기 전에 여러 번 읽어야했습니다.이 문서는 올바른 최신 정보를 가진 하나의 결정적인 리소스이기 때문에 프로세스 전반에 걸쳐이 문서를 반복해서 읽어야 할 것입니다. 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 후보 정보 끝에있는이 데이터는 무엇입니까?”에 대한 웹 검색을 수행하십시오. (인용 부호 제외).

8)이 시점에서 거의 모든 것이 작동했습니다. 연결 종료를 처리하기위한 추가 논리를 추가해야했고 완료되었습니다.

그 과정에 총 2 주가 걸렸습니다. 좋은 소식은 지금 WebSocket을 정말 잘 이해하고 있으며 처음부터 훌륭하게 작동하는 클라이언트 및 서버 스크립트를 만들 수 있다는 것입니다. 모든 정보의 정점을 통해 WebSocket PHP 스크립트를 코딩 할 수있는 충분한 지침과 정보를 얻을 수 있기를 바랍니다. 행운을 빕니다!

편집 :이 편집은 원래 답변 후 2 년이 지났으며 아직 작동하는 솔루션이 있지만 실제로 공유 할 준비가되지 않았습니다. 운 좋게도 GitHub의 다른 누군가가 저와 거의 동일한 코드 (하지만 훨씬 더 깔끔함)를 가지고 있으므로 작동하는 PHP WebSocket 솔루션에 대해 다음 코드를 사용하는 것이 좋습니다.
https://github.com/ghedipunk/PHP-Websockets/blob/master/ websockets.php

편집 # 2 : 나는 여전히 많은 서버 측 관련 일에 PHP를 사용하는 것을 좋아하지만 최근 Node.js로 워밍업을 많이했고 주된 이유는 더 나은 디자인이기 때문입니다. PHP (또는 다른 서버 측 언어)보다 WebSocket을 처리하기 위해 기초를 두었습니다. 따라서 최근에 서버에 Apache / PHP와 Node.js를 모두 설정하고 WebSocket 서버를 실행하는 데 Node.js를 사용하고 다른 모든 작업에 Apache / PHP를 사용하는 것이 훨씬 쉽다는 것을 알게되었습니다. 그리고 WebSocket 용 Node.js를 설치 / 사용할 수없는 공유 호스팅 환경에있는 경우 Heroku와 같은 무료 서비스를 사용하여 Node.js WebSocket 서버를 설정하고 교차 도메인을 만들 수 있습니다. 서버에서 요청합니다.


답변

내가 아는 한 Ratchet 은 현재 사용 가능한 최고의 PHP WebSocket 솔루션입니다. 오픈 소스 이므로 작성자가 PHP를 사용하여이 WebSocket 솔루션을 구축 한 방법을 볼 수 있습니다.


답변

소켓 http://uk1.php.net/manual/en/book.sockets.php 를 사용하지 않는 이유는 무엇 입니까? 잘 문서화되어 있으며 (PHP 컨텍스트에서만이 아님) 좋은 예가 있습니다. http://uk1.php.net/manual/en/sockets.examples.php


답변

base64_encoding 전에 키를 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";

이것이 도움이되는지 알려주세요.


답변

나는 한동안 당신의 입장에 있었고 마침내 node.js를 사용하게되었습니다. 웹과 소켓 서버를 하나로 통합하는 것과 같은 하이브리드 솔루션을 할 수 있기 때문입니다. 따라서 PHP 백엔드는 http를 통해 노드 웹 서버에 요청을 제출 한 다음 websocket으로 브로드 캐스트 할 수 있습니다. 매우 효율적인 방법입니다.


답변

<?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


답변