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


88

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에 문제가 있었다 그들이 예 SRC 코드에서 변경 한 내용을 포함이 페이지에는 net.tutsplus.com/tutorials/javascript-ajax/...
scrappedcola

1
WebSockets 애플리케이션에 사용할 수있는 유용한 라이브러리입니다. 클라이언트와 PHP 측을 모두 포함합니다. techzonemind.com/…
Jithin Jose

1
나는 그것을 C ++로 구현하는 것이 더 낫다고 생각합니다.
Michael Chourdakis

답변:


113

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

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 서버를 설정하고 교차 도메인을 만들 수 있습니다. 서버에서 요청합니다.


Thx, 나는 그것을 당신의 방식으로 시도 할 것입니다. 이 PHP 서버의 성능을 어떻게 평가하십니까?
Dharman

@Dharman, 내 로컬 호스트에서만 실행할 수 있었기 때문에 말하기가 어렵습니다. 물론 거기에서 잘 작동하지만 부하가 많은 실제 서버에서는 모르겠습니다. 내 코드에 부풀어 오르지 않기 때문에 꽤 잘 실행될 것이라고 생각합니다.
HartleySan 2013-01-26

1
지금은 가지고 있지 않지만 가까운 장래에 모든 코드로 전체 프로세스에 대한 자습서를 작성할 계획입니다. 그렇게하면 링크를 게시하겠습니다.
HartleySan 2013-06-24

1
@HartleySan : 안녕하세요! 나는 당신의 코드를 보는 데 매우 관심이 있습니다. 온라인에 올리거나 개인적으로 보내 주시겠습니까?
forthrin

예, 곧 온라인 상태가됩니다. 요청하신 모든 분들께 죄송합니다. 최근에 너무 바빴어요. 곧 시작하겠습니다.
HartleySan

26

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


2
나는 래칫과 렉스를 사용하여 내 솔루션 여기에 추가 github.com/eole-io/sandstone는 당신이 찾을 수 있다면 나도 몰라 그것은 유용한
Alcalyn

8

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


2
예, 일반 PHP 소켓과 웹 페이지간에 지속적으로 연결할 수 있습니다. 여러 번 테스트했습니다.
WiMantis 2013

@WiMantis : 안녕하세요! 이 작업을 수행하는 코드 예제를 온라인에 넣거나 선택적으로 나에게 개인적으로 보낼 수 있습니까?
forwardrin

일반 HTTP 연결과 함께 사용할 수 있습니까? 나는 DDD 프레임 워크를 구축하고 있었고 이것 위에 래퍼 클래스처럼 만들고 소켓 기능을 제공하고 싶습니다. 코어 확장을 사용하여 바닐라 PHP에서 가능합니다

1

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

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


1

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


그래서 우리는 http 클라이언트를 사용하여 PHP에서 노드 서버로 http 요청을해야합니까?
Kiren Siva 2019

Kiren Siva, 맞습니다. Curl 또는 유사, 노드는 websocket을 통해 메시지를 브로드 캐스트
MZ

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


1
그게 무슨 뜻입니까?
Dharman 2013

8
이것은 WebSocket이 아니라 소켓입니다. 큰 차이가 있습니다.
Chris

@techexpander, 질문에 따라 전 대신 처음부터 설명해야합니다. 작동하지 않습니다.
Jaymin
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.