2012-11-03 3 views
11

간단한 클라이언트/서버 응용 프로그램을 만들려고하고 있으므로 PHP로 소켓을 실험하고 있습니다.PHP 소켓 - 다중 연결 허용

이제 서버에 잘 연결되는 간단한 클라이언트가 있지만이 서버에 한 번에 하나의 클라이언트 만 연결할 수 있습니다.이 코드 예제는 온라인에서 찾았으며 테스트 목적으로 약간 수정했습니다. , https://stackoverflow.com/questions/10318023/php-socket-connections-cant-handle-multiple-connection

나는 그것의 모든 부분을 이해하기 위해 노력하고 나는 그것이 구체적으로 어떻게 작동하는지보고에 가까운 해요,하지만 몇 가지 이유 :

재미는 충분히 여기 같은 예에 따라, 같은 질문을 발견 두 번째 클라이언트에 연결할 때 첫 번째 클라이언트가 연결이 끊어 지거나 충돌이 발생합니다.

누구나 내가 봐야 할 야생 아이디어 또는 포인터를 줄 수 있습니까?

<?php 
// Set time limit to indefinite execution 
set_time_limit (0); 
// Set the ip and port we will listen on 
$address = '127.0.0.1'; 
$port = 9000; 
$max_clients = 10; 
// Array that will hold client information 
$client = array(); 
// Create a TCP Stream socket 
$sock = socket_create(AF_INET, SOCK_STREAM, 0); 
// Bind the socket to an address/port 
socket_bind($sock, $address, $port) or die('Could not bind to address'); 
// Start listening for connections 
socket_listen($sock); 
// Loop continuously 
while (true) { 
    // Setup clients listen socket for reading 
    $read[0] = $sock; 
    for ($i = 0; $i < $max_clients; $i++) 
    { 
     if (isset($client[$i])) 
     if ($client[$i]['sock'] != null) 
      $read[$i + 1] = $client[$i]['sock'] ; 
    } 
    // Set up a blocking call to socket_select() 
    $ready = socket_select($read, $write = NULL, $except = NULL, $tv_sec = NULL); 
    /* if a new connection is being made add it to the client array */ 
    if (in_array($sock, $read)) { 
     for ($i = 0; $i < $max_clients; $i++) 
     { 
      if (!isset($client[$i])) { 
       $client[$i] = array(); 
       $client[$i]['sock'] = socket_accept($sock); 
       echo("Accepting incomming connection...\n"); 
       break; 
      } 
      elseif ($i == $max_clients - 1) 
       print ("too many clients"); 
     } 
     if (--$ready <= 0) 
      continue; 
    } // end if in_array 

    // If a client is trying to write - handle it now 
    for ($i = 0; $i < $max_clients; $i++) // for each client 
    { 
     if (isset($client[$i])) 
     if (in_array($client[$i]['sock'] , $read)) 
     { 
      $input = socket_read($client[$i]['sock'] , 1024); 
      if ($input == null) { 
       // Zero length string meaning disconnected 
       echo("Client disconnected\n"); 
       unset($client[$i]); 
      } 
      $n = trim($input); 
      if ($n == 'exit') { 
       echo("Client requested disconnect\n"); 
       // requested disconnect 
       socket_close($client[$i]['sock']); 
      } 
      if(substr($n,0,3) == 'say') { 
       //broadcast 
       echo("Broadcast received\n"); 
       for ($j = 0; $j < $max_clients; $j++) // for each client 
       { 
        if (isset($client[$j])) 
        if ($client[$j]['sock']) { 
         socket_write($client[$j]['sock'], substr($n, 4, strlen($n)-4).chr(0)); 
        } 
       } 
      } elseif ($input) { 
       echo("Returning stripped input\n"); 
       // strip white spaces and write back to user 
       $output = ereg_replace("[ \t\n\r]","",$input).chr(0); 
       socket_write($client[$i]['sock'],$output); 
      } 
     } else { 
      // Close the socket 
      if (isset($client[$i])) 
      echo("Client disconnected\n"); 
      if ($client[$i]['sock'] != null){ 
       socket_close($client[$i]['sock']); 
       unset($client[$i]); 
      } 
     } 
    } 
} // end while 
// Close the master sockets 
echo("Shutting down\n"); 
socket_close($sock); 
?> 
+0

혹시 이걸 가지고 행운이 있었나요? –

+0

참조 : [SocketServer.class.php] (https://gist.github.com/navarr/459321) – kenorb

답변

1

일반적으로 소켓 서버는 클라이언트를 1 대 이상 처리하려는 경우 멀티 스레드 여야합니다. '듣기'스레드를 만들고 각 클라이언트 요청에 대해 새로운 '응답'스레드를 생성합니다. PHP는 이런 상황을 어떻게 처리 할 지 모르겠습니다. 아마도 그것은 포크 메커니즘을 가지고 있을까요?

EDIT : PHP가 스레딩을 제공하지 않는다 (http://stackoverflow.com/questions/70855/how-can-one-use-multi-threading-in-php-applications) 원한다면 소켓 서버에 대한 일반적인 패러다임을 따르려면 'popen'을 사용하여 자식 요청을 처리하는 프로세스를 생성해야합니다. 소켓 ID를 건네주고 자식 소켓이 닫히면 소켓 ID를 닫는다. 서버 프로세스가 닫히는 경우 이러한 프로세스가 분리되지 않도록하려면이 목록의 맨 위에 있어야합니다.

FWIW : 여기에 멀티 클라이언트 서버의 몇 가지 예입니다 http://php.net/manual/en/function.socket-accept.php

+0

안녕하세요, 답장을 보내 주셔서 감사합니다. 내 코드를 실제로 읽었습니까? 나는 그 모든 것을 처리하기 위해 일련의 연결을 걸으며 걷고있다. 당신이 링크 한 많은 예제들과 같습니다. 아직도 고마워! :) – JapyDooge

+0

오른쪽 -하지만 당신이 연결과 읽기 모두를 차단하고있는 것처럼 보입니다. 새로운 연결이 만들어지지 않으면 보류중인 요청을 처리하지 않습니다. – ethrbunny

+0

아, 그건 사실일지도 몰라, 고마워. :) 나는 내가 그것을 어떻게 얻을 수 있는지 보러 갈거야! – JapyDooge

1

이 스크립트는 나를

을 위해 완벽하게 작동한다
<?php 
    /*! @class  SocketServer 
     @author  Navarr Barnier 
     @abstract A Framework for creating a multi-client server using the PHP language. 
    */ 
    class SocketServer 
    { 
     /*! @var  config 
      @abstract Array - an array of configuration information used by the server. 
     */ 
     protected $config; 

     /*! @var  hooks 
      @abstract Array - a dictionary of hooks and the callbacks attached to them. 
     */ 
     protected $hooks; 

     /*! @var  master_socket 
      @abstract resource - The master socket used by the server. 
     */ 
     protected $master_socket; 

     /*! @var  max_clients 
      @abstract unsigned int - The maximum number of clients allowed to connect. 
     */ 
     public $max_clients = 10; 

     /*! @var  max_read 
      @abstract unsigned int - The maximum number of bytes to read from a socket at a single time. 
     */ 
     public $max_read = 1024; 

     /*! @var  clients 
      @abstract Array - an array of connected clients. 
     */ 
     public $clients; 

     /*! @function __construct 
      @abstract Creates the socket and starts listening to it. 
      @param  string - IP Address to bind to, NULL for default. 
      @param  int - Port to bind to 
      @result  void 
     */ 
     public function __construct($bind_ip,$port) 
     { 
      set_time_limit(0); 
      $this->hooks = array(); 

      $this->config["ip"] = $bind_ip; 
      $this->config["port"] = $port; 

      $this->master_socket = socket_create(AF_INET, SOCK_STREAM, 0); 
      socket_bind($this->master_socket,$this->config["ip"],$this->config["port"]) or die("Issue Binding"); 
      socket_getsockname($this->master_socket,$bind_ip,$port); 
      socket_listen($this->master_socket); 
      SocketServer::debug("Listenting for connections on {$bind_ip}:{$port}"); 
     } 

     /*! @function hook 
      @abstract Adds a function to be called whenever a certain action happens. Can be extended in your implementation. 
      @param  string - Command 
      @param  callback- Function to Call. 
      @see  unhook 
      @see  trigger_hooks 
      @result  void 
     */ 
     public function hook($command,$function) 
     { 
      $command = strtoupper($command); 
      if(!isset($this->hooks[$command])) { $this->hooks[$command] = array(); } 
      $k = array_search($function,$this->hooks[$command]); 
      if($k === FALSE) 
      { 
       $this->hooks[$command][] = $function; 
      } 
     } 

     /*! @function unhook 
      @abstract Deletes a function from the call list for a certain action. Can be extended in your implementation. 
      @param  string - Command 
      @param  callback- Function to Delete from Call List 
      @see  hook 
      @see  trigger_hooks 
      @result  void 
     */ 
     public function unhook($command = NULL,$function) 
     { 
      $command = strtoupper($command); 
      if($command !== NULL) 
      { 
       $k = array_search($function,$this->hooks[$command]); 
       if($k !== FALSE) 
       { 
        unset($this->hooks[$command][$k]); 
       } 
      } else { 
       $k = array_search($this->user_funcs,$function); 
       if($k !== FALSE) 
       { 
        unset($this->user_funcs[$k]); 
       } 
      } 
     } 

     /*! @function loop_once 
      @abstract Runs the class's actions once. 
      @discussion Should only be used if you want to run additional checks during server operation. Otherwise, use infinite_loop() 
      @param  void 
      @see  infinite_loop 
      @result  bool - True 
     */ 
     public function loop_once() 
     { 
      // Setup Clients Listen Socket For Reading 
      $read[0] = $this->master_socket; 
      for($i = 0; $i < $this->max_clients; $i++) 
      { 
       if(isset($this->clients[$i])) 
       { 
        $read[$i + 1] = $this->clients[$i]->socket; 
       } 
      } 

      // Set up a blocking call to socket_select 
      if(socket_select($read,$write = NULL, $except = NULL, $tv_sec = 5) < 1) 
      { 
      // SocketServer::debug("Problem blocking socket_select?"); 
       return true; 
      } 

      // Handle new Connections 
      if(in_array($this->master_socket, $read)) 
      { 
       for($i = 0; $i < $this->max_clients; $i++) 
       { 
        if(empty($this->clients[$i])) 
        { 
         $temp_sock = $this->master_socket; 
         $this->clients[$i] = new SocketServerClient($this->master_socket,$i); 
         $this->trigger_hooks("CONNECT",$this->clients[$i],""); 
         break; 
        } 
        elseif($i == ($this->max_clients-1)) 
        { 
         SocketServer::debug("Too many clients... :("); 
        } 
       } 

      } 

      // Handle Input 
      for($i = 0; $i < $this->max_clients; $i++) // for each client 
      { 
       if(isset($this->clients[$i])) 
       { 
        if(in_array($this->clients[$i]->socket, $read)) 
        { 
         $input = socket_read($this->clients[$i]->socket, $this->max_read); 
         if($input == null) 
         { 
          $this->disconnect($i); 
         } 
         else 
         { 
          SocketServer::debug("{$i}@{$this->clients[$i]->ip} --> {$input}"); 
          $this->trigger_hooks("INPUT",$this->clients[$i],$input); 
         } 
        } 
       } 
      } 
      return true; 
     } 

     /*! @function disconnect 
      @abstract Disconnects a client from the server. 
      @param  int - Index of the client to disconnect. 
      @param  string - Message to send to the hooks 
      @result  void 
     */ 
     public function disconnect($client_index,$message = "") 
     { 
      $i = $client_index; 
      SocketServer::debug("Client {$i} from {$this->clients[$i]->ip} Disconnecting"); 
      $this->trigger_hooks("DISCONNECT",$this->clients[$i],$message); 
      $this->clients[$i]->destroy(); 
      unset($this->clients[$i]);   
     } 

     /*! @function trigger_hooks 
      @abstract Triggers Hooks for a certain command. 
      @param  string - Command who's hooks you want to trigger. 
      @param  object - The client who activated this command. 
      @param  string - The input from the client, or a message to be sent to the hooks. 
      @result  void 
     */ 
     public function trigger_hooks($command,&$client,$input) 
     { 
      if(isset($this->hooks[$command])) 
      { 
       foreach($this->hooks[$command] as $function) 
       { 
        SocketServer::debug("Triggering Hook '{$function}' for '{$command}'"); 
        $continue = call_user_func($function,&$this,&$client,$input); 
        if($continue === FALSE) { break; } 
       } 
      } 
     } 

     /*! @function infinite_loop 
      @abstract Runs the server code until the server is shut down. 
      @see  loop_once 
      @param  void 
      @result  void 
     */ 
     public function infinite_loop() 
     { 
      $test = true; 
      do 
      { 
       $test = $this->loop_once(); 
      } 
      while($test); 
     } 

     /*! @function debug 
      @static 
      @abstract Outputs Text directly. 
      @discussion Yeah, should probably make a way to turn this off. 
      @param  string - Text to Output 
      @result  void 
     */ 
     public static function debug($text) 
     { 
      echo("{$text}\r\n"); 
     } 

     /*! @function socket_write_smart 
      @static 
      @abstract Writes data to the socket, including the length of the data, and ends it with a CRLF unless specified. 
      @discussion It is perfectly valid for socket_write_smart to return zero which means no bytes have been written. Be sure to use the === operator to check for FALSE in case of an error. 
      @param  resource- Socket Instance 
      @param  string - Data to write to the socket. 
      @param  string - Data to end the line with. Specify a "" if you don't want a line end sent. 
      @result  mixed - Returns the number of bytes successfully written to the socket or FALSE on failure. The error code can be retrieved with socket_last_error(). This code may be passed to socket_strerror() to get a textual explanation of the error. 
     */ 
     public static function socket_write_smart(&$sock,$string,$crlf = "\r\n") 
     { 
      SocketServer::debug("<-- {$string}"); 
      if($crlf) { $string = "{$string}{$crlf}"; } 
      return socket_write($sock,$string,strlen($string)); 
     } 

     /*! @function __get 
      @abstract Magic Method used for allowing the reading of protected variables. 
      @discussion You never need to use this method, simply calling $server->variable works because of this method's existence. 
      @param  string - Variable to retrieve 
      @result  mixed - Returns the reference to the variable called. 
     */ 
     function &__get($name) 
     { 
      return $this->{$name}; 
     } 
    } 

    /*! @class  SocketServerClient 
     @author  Navarr Barnier 
     @abstract A Client Instance for use with SocketServer 
    */ 
    class SocketServerClient 
    { 
     /*! @var  socket 
      @abstract resource - The client's socket resource, for sending and receiving data with. 
     */ 
     protected $socket; 

     /*! @var  ip 
      @abstract string - The client's IP address, as seen by the server. 
     */ 
     protected $ip; 

     /*! @var  hostname 
      @abstract string - The client's hostname, as seen by the server. 
      @discussion This variable is only set after calling lookup_hostname, as hostname lookups can take up a decent amount of time. 
      @see  lookup_hostname 
     */ 
     protected $hostname; 

     /*! @var  server_clients_index 
      @abstract int - The index of this client in the SocketServer's client array. 
     */ 
     protected $server_clients_index; 

     /*! @function __construct 
      @param  resource- The resource of the socket the client is connecting by, generally the master socket. 
      @param  int - The Index in the Server's client array. 
      @result  void 
     */ 
     public function __construct(&$socket,$i) 
     { 
      $this->server_clients_index = $i; 
      $this->socket = socket_accept($socket) or die("Failed to Accept"); 
      SocketServer::debug("New Client Connected"); 
      socket_getpeername($this->socket,$ip); 
      $this->ip = $ip; 
     } 

     /*! @function lookup_hostname 
      @abstract Searches for the user's hostname and stores the result to hostname. 
      @see  hostname 
      @param  void 
      @result  string - The hostname on success or the IP address on failure. 
     */ 
     public function lookup_hostname() 
     { 
      $this->hostname = gethostbyaddr($this->ip); 
      return $this->hostname; 
     } 

     /*! @function destroy 
      @abstract Closes the socket. Thats pretty much it. 
      @param  void 
      @result  void 
     */ 
     public function destroy() 
     { 
      socket_close($this->socket); 
     } 

     function &__get($name) 
     { 
      return $this->{$name}; 
     } 

     function __isset($name) 
     { 
      return isset($this->{$name}); 
     } 
    } 

Source on github

+0

스크립트에 많은 문서가 없으므로 클라이언트와 서버 연결을 시작한 방법에 대한 개요를 제공 할 수 있습니까? 감사. –

0

현재 가장 큰 대답은 잘못된 것이므로 여러 클라이언트를 처리하기 위해 다중 스레드가 필요하지 않습니다. 비 차단 I/O 및 stream_select/socket_select을 사용하여 실행 가능한 클라이언트의 메시지를 처리 ​​할 수 ​​있습니다. socket_* 이상의 stream_socket_* 기능을 사용하는 것이 좋습니다.

비 블로킹 I/O는 아주 잘 작동하지만 입출력 차단과 관련된 함수 호출을 만들 수는 없습니다. 그렇지 않으면 입출력 차단으로 인해 전체 프로세스가 중단되고 모든 클라이언트가 중단됩니다.

즉, 모든 I/O가 비 차단이거나 매우 빠르다고 보장해야합니다 (완벽하지는 않지만 받아 들일 수 있음). 소켓이 stream_select을 사용해야 할뿐만 아니라 모두 오픈 스트림을 선택해야하므로 스트림을 읽고 쓸 수있게되면 실행되는 읽기 및 쓰기 관찰자를 등록하는 라이브러리를 권합니다.

이러한 프레임 워크가 여러 개 있습니다. 가장 일반적인 프레임은 ReactPHPAmp입니다.기본 이벤트 루프는 꽤 유사하지만 Amp는 그 측면에서 몇 가지 기능을 추가로 제공합니다.

두 가지 주요 차이점은 API에 대한 접근 방식입니다. ReactPHP는 모든 곳에서 콜백을 사용하지만, Amp는 coroutine을 사용하고 그러한 용도로 API를 최적화함으로써 피싱을 시도합니다.

Amp의 "Getting Started" 안내서는 기본적으로이 주제와 정확히 일치합니다. 전체 가이드 here을 읽을 수 있습니다. 아래에 작업 예제가 포함됩니다.

<?php 

require __DIR__ . "/vendor/autoload.php"; 

// Non-blocking server implementation based on amphp/socket. 

use Amp\Loop; 
use Amp\Socket\ServerSocket; 
use function Amp\asyncCall; 

Loop::run(function() { 
    $uri = "tcp://127.0.0.1:1337"; 

    $clientHandler = function (ServerSocket $socket) { 
     while (null !== $chunk = yield $socket->read()) { 
      yield $socket->write($chunk); 
     } 
    }; 

    $server = Amp\Socket\listen($uri); 

    while ($socket = yield $server->accept()) { 
     asyncCall($clientHandler, $socket); 
    } 
}); 

Loop::run()

이벤트 루프를 실행 Loop::on*() 방법으로 등록 할 수있는 타이머 이벤트 신호 실질적인 흐름을위한 시계. 서버 소켓은 Amp\Socket\listen()을 사용하여 생성됩니다. Server::accept()은 새 클라이언트 연결을 기다리는 데 사용할 수있는 Promise을 반환합니다. 그것은 클라이언트가 클라이언트에서 읽고 받아 들인 동일한 데이터를 다시 받아 들인 후에는 코 루틴을 실행합니다. 자세한 내용은 앰프 설명서를 참조하십시오.