2012-11-03 54 views
11

Tôi đang cố gắng tạo một ứng dụng khách/chủ đơn giản và do đó tôi đang thử nghiệm với các socket trong PHP.Ổ cắm PHP - Chấp nhận nhiều kết nối

Bây giờ tôi có một máy khách đơn giản trong C# kết nối với máy chủ, nhưng tôi chỉ có thể kết nối một máy khách cùng một lúc với máy chủ này (tôi tìm thấy mẫu mã này trực tuyến và tinh chỉnh nó một chút cho mục đích thử nghiệm).

vui đủ tôi thấy cùng một câu hỏi, dựa trên cùng một ví dụ ở đây: https://stackoverflow.com/questions/10318023/php-socket-connections-cant-handle-multiple-connection

Tôi cố gắng để hiểu được tất cả các phần của nó và tôi gần nhìn thấy như thế nào nó hoạt động một cách chi tiết, nhưng đối với một số lý do, khi tôi kết nối với máy khách thứ 2, máy khách thứ nhất bị ngắt kết nối/bị treo.

Có ai có thể cho tôi một số ý tưởng hoang dã hoặc con trỏ đến nơi tôi nên xem không?

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

bạn đã bao giờ có may mắn với điều này chưa? –

+0

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

Trả lời

1

Máy chủ socket thông thường cần phải có nhiều luồng nếu bạn muốn xử lý> 1 khách hàng. Bạn sẽ tạo một chuỗi 'nghe' và sinh ra một chuỗi 'trả lời' mới cho mỗi yêu cầu của khách hàng. Im không chắc chắn làm thế nào PHP sẽ xử lý một tình huống như thế này mặc dù. Có lẽ nó có một cơ chế ngã ba?

CHỈNH SỬA: Không xuất hiện rằng PHP cung cấp luồng cho mỗi lần (http://stackoverflow.com/questions/70855/how-can-one-use-multi-threading-in-php-applications) Nếu bạn muốn để làm theo mô hình điển hình cho một máy chủ socket, bạn có thể lấy đi bằng cách sử dụng 'popen' để sinh ra một quy trình để xử lý yêu cầu con. Hãy tắt id ổ cắm và để nó tự đóng khi ổ cắm con đóng. Bạn sẽ cần phải giữ trên đầu danh sách này để tránh mồ côi các quá trình này nếu quá trình máy chủ của bạn đóng.

FWIW: đây là một số ví dụ về máy chủ đa khách hàng: http://php.net/manual/en/function.socket-accept.php

+0

Xin chào, cảm ơn bạn đã trả lời. Bạn có thực sự đọc mã của tôi không? Tôi đang đi máng một loạt các kết nối để xử lý tất cả những điều đó. Giống như trong nhiều ví dụ bạn đã liên kết. Vẫn cảm ơn! :) – JapyDooge

+0

Phải - nhưng có vẻ như bạn đang chặn cả kết nối và đọc. Nó sẽ không nhận được để phục vụ bất kỳ yêu cầu đang chờ xử lý trừ khi một kết nối mới được thực hiện. – ethrbunny

+0

Ahh có thể là trường hợp, cảm ơn vì điều đó :) Tôi sẽ xem làm thế nào tôi có thể làm cho nó đi! – JapyDooge

1

Kịch bản này đang làm việc một cách hoàn hảo đối với tôi

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

script không có nhiều tài liệu, bạn có thể đưa ra cái nhìn tổng quan về cách bạn khởi tạo client và sever connection không? Cảm ơn. –

0

Câu trả lời hàng đầu hiện tại ở đây là sai, bạn không cần nhiều luồng để xử lý nhiều ứng dụng khách. Bạn có thể sử dụng I/O không chặn và stream_select/socket_select để xử lý thư từ các ứng dụng khách có thể thao tác. Tôi khuyên bạn nên sử dụng các chức năng stream_socket_* trên socket_*.

Mặc dù I/O không chặn hoạt động khá tốt, bạn không thể thực hiện bất kỳ cuộc gọi chức năng nào liên quan đến chặn I/O, nếu không chặn I/O chặn quy trình hoàn tất và tất cả khách hàng treo, không chỉ một.

Điều đó có nghĩa là tất cả I/O phải không bị chặn hoặc được bảo đảm rất nhanh (không hoàn hảo, nhưng có thể chấp nhận được). Vì không chỉ ổ cắm của bạn cần sử dụng stream_select, nhưng bạn cần phải chọn trên tất cả luồng mở, tôi khuyên bạn nên sử dụng thư viện cung cấp để đăng ký người đọc đã đọc và ghi sau khi luồng có thể đọc/ghi được.

Có nhiều khung như vậy, các khung phổ biến nhất là ReactPHPAmp.Các vòng lặp sự kiện cơ bản là khá giống nhau, nhưng Amp cung cấp thêm một vài tính năng ở bên đó.

Sự khác biệt chính giữa hai phương pháp này là cách tiếp cận API. Trong khi ReactPHP sử dụng callback ở khắp mọi nơi, Amp cố gắng tránh chúng bằng cách sử dụng coroutines và tối ưu hóa các API của nó để sử dụng như vậy.

Hướng dẫn sử dụng Amp "Hướng dẫn" về cơ bản chính xác về chủ đề này. Bạn có thể đọc hướng dẫn đầy đủ here. Tôi sẽ bao gồm một ví dụ làm việc dưới đây.

<?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() chạy vòng lặp sự kiện và giám sát mọi sự kiện hẹn giờ, tín hiệu và dòng hành động, có thể được đăng ký với Loop::on*() phương pháp. Ổ cắm máy chủ được tạo bằng cách sử dụng Amp\Socket\listen(). Server::accept() trả về số Promise có thể được sử dụng để chờ kết nối khách hàng mới. Nó thực hiện một coroutine một khi một khách hàng được chấp nhận đọc từ khách hàng và của echo cùng một dữ liệu trở lại với nó. Để biết thêm chi tiết, hãy tham khảo tài liệu của Amp.

Các vấn đề liên quan