2012-01-05 82 views
3

我編寫一個命令,然後通過PHP中的套接字從服務器回讀。我們有20臺服務器都運行一個Node JS腳本,它可以接收這些命令並執行它們。 Node JS腳本將返回PHP回讀的「ok」以確認命令已經通過。然後通過在PHP問題中通過套接字回讀

節點JS腳本偵聽端口9000並設置爲allow half open

大多數這工作得很好,但是當命令的大批量發送,我們得到的錯誤回到偶爾會認爲,這一時間:

Contents: Message received back from socket was 'Unexpected token {' 
Transport endpoint is not connected 

傳輸的端點消息建議,我認爲它沒有連接成功。

我不是插座專家,所以我不知道我使用的實現是否「正確」。它大多數時間都有效,但我知道有些功能可能會更好,但我不確定它們的功能,如socket_bindsocket_listen

這是我們正在使用的PHP代碼。任何建議將不勝感激。

public function sendDaemonCommand($address, $template_id, $params = array()) { 

    $hostname = $this->getHostnameFromPrivateIP($address); 
    $port = 9000; 
    $command = array('template_id' => $template_id, 'params' => $params); 
    $command = json_encode($command); 

    // Create a TCP Stream socket 
    if (($sock = socket_create(AF_INET, SOCK_STREAM, SOL_TCP)) === false) { 
     $this->mailError("Command Failed - " . $hostname, "Failed to create socket on " . $address . "\n\n" . socket_strerror(socket_last_error()) . "\n\nCommand:\n\n" . $command . "\n" . $this->functionTraceback()); 
     return false; 
    } 

    // Connect to socket 
    if (socket_connect($sock, $address, $port) === false) { 
     $this->mailError("Command Failed - " . $hostname, "Failed to connect to socket on " . $address . "\n\n" . socket_strerror(socket_last_error($sock)) . "\n\nCommand:\n\n" . $command. "\n" . $this->functionTraceback()); 
     socket_close($sock); 
     return false; 
    } 

    // Write command to socket 
    $_command = $command; 
    $length = strlen($_command); 

    while (true) { 
     $sent = socket_write($sock, $_command, $length); 

     if ($sent === false) { 
      $this->mailError("Command Failed - " . $hostname, "Failed to write command to socket on " . $address . "\n\n" . socket_strerror(socket_last_error($sock)) . "\n\nCommand:\n\n" . $command. "\n" . $this->functionTraceback()); 
      socket_shutdown($sock, 2); 
      socket_close($sock); 
      return false; 
     } 

     if ($sent < $length) { 
      $_command = substr($_command, $sent); 
      $length -= $sent; 
     } 
     else { 
      break; 
     } 
    } 

    socket_shutdown($sock, 1); 

    // Read back from socket 
    if (($out = socket_read($sock, 1024)) !== false) { 
     @socket_shutdown($sock, 0); 
     $out = trim($out); 
     if ($out !== "ok") { 
      $this->mailError("Command Failed - " . $hostname, "Message received back from socket was '" . $out . "' on " . $address . "\n\n" . socket_strerror(socket_last_error($sock)) . "\n\nCommand:\n\n" . $command. "\n" . $this->functionTraceback()); 
      socket_close($sock); 
      return false; 
     } 
    } 
    else { 
     $this->mailError("Command Failed - " . $hostname, "Failed to read from socket on " . $address . "\n\n" . socket_strerror(socket_last_error($sock)) . "\n\nCommand:\n\n" . $command. "\n" . $this->functionTraceback()); 
     socket_shutdown($sock, 0); 
     socket_close($sock); 
     return false; 
    } 

    socket_close($sock); 
    return $out; 
} 
+0

那麼你不應該使用PHP套接字連接到Node.js,這是處理大量打開的連接時真的效率低下。 – alessioalex 2012-01-05 15:01:11

+0

你看過PHP服務器端的netstat ...也許你的本地端口用完了? – 2012-01-05 15:08:05

+0

@alessioalex我還可以在PHP上使用什麼? – fire 2012-01-05 15:17:22

回答

1

對於一個簡單的socket客戶端,如這個,我更喜歡fsockopen() - 它大大降低了客戶端代碼的複雜性和不要求插槽的擴展,這是不是隨處可見。這樣做的主要缺點是你丟失了字符串錯誤信息,但是這些信息很少有用 - 你仍然會從創建套接字中得到一個錯誤字符串,並且如果讀寫操作失敗,那是因爲套接字已經斷開連接。

我也不確定在這種情況下「允許半開放」有多麼有用 - 我認爲它可能會產生比解決問題更多的問題。對socket_shutdown()的調用在這裏沒有做任何有用的事情(並且可能是你的問題的根源) - 只有當你在一個不會關閉的套接字上運行時纔會使用這個套接字,並且可以在稍後的時間點通過一些其他的代碼。由於您創建了一個新的套接字並將其在同一個例程中銷燬,情況並非如此。

這是你的代碼改寫爲使用fsockopen() - 正如你所看到的,它更短,更簡單。我也稍微修改了它,以便它總是返回一個布爾 - 你的代碼返回bool FALSE或字符串ok,並且由於只有這兩個選項,所以函數總是返回一個布爾值更有意義。

public function sendDaemonCommand($address, $template_id, $params = array()) { 

    // Prepare data 
    $hostname = $this->getHostnameFromPrivateIP($address); 
    $port = 9000; 
    $command = array('template_id' => $template_id, 'params' => $params); 
    $_command = $command = json_encode($command); 
    $length = strlen($_command); 

    // Connect to socket 
    if (!$sock = fsockopen($address, $port, $errNo, $errStr)) { 
     $this->mailError("Command Failed - " . $hostname, "Failed to connect to socket on " . $address . "\n\n" . $errStr . "\n\nCommand:\n\n" . $command. "\n" . $this->functionTraceback()); 
     return false; 
    } 

    // Write command to socket 
    while (true) { 

     // Try and write data to socket 
     $sent = fwrite($sock, $_command, $length); 

     // If it failed, error out 
     if ($sent === false) { 
      $this->mailError("Command Failed - " . $hostname, "Failed to write command to socket on " . $address . "\n\nCommand:\n\n" . $command. "\n" . $this->functionTraceback()); 
      fclose($sock); 
      return false; 
     } 

     // If there is data left to send, try again 
     if ($sent < $length) { 
      $_command = substr($_command, $sent); 
      $length -= $sent; 
      continue; 
     } 

     // If we get here the write operation was successful 
     break; 

    } 

    // Read back from socket and close it 
    $out = fread($sock, 1024); 
    fclose($sock); 

    // Test the response from the server 
    if ($out === false) { 
     $this->mailError("Command Failed - " . $hostname, "Failed to read from socket on " . $address . "\n\nCommand:\n\n" . $command. "\n" . $this->functionTraceback()); 
     return false; 
    } else if (($out = trim($out)) !== "ok") { 
     $this->mailError("Command Failed - " . $hostname, "Message received back from socket was '" . $out . "' on " . $address . "\n\nCommand:\n\n" . $command. "\n" . $this->functionTraceback()); 
     return false; 
    } else { 
     return true; 
    } 

} 
+0

謝謝,我已經改變了我的代碼,但是在測試中,它已經從立即提交命令到延遲了30秒。我認爲問題出現在NodeJe上,它不知道數據何時結束,這就是爲什麼它保持打開我的腳本,改變'允許半開'選項沒有任何區別。我的節點的JS腳本在http://pastebin.com/r9big65J – fire 2012-01-06 14:52:25

+0

進一步的測試表明它被阻止在'fread'的位置,如果這有幫助.. – fire 2012-01-06 15:07:49

+0

@fire我看到你的問題在哪裏,它的確在節點結束。這就是你的'socket_shutdown()'所做的事情,通過關閉套接字來終止命令。沒有辦法使用'fsockopen()'流來模仿這個,*然而* ...我會這樣做的是發出一個終止字符,它永遠不會出現在命令中,比如一個新行,在命令。因此,您立即發送命令後緊跟着一個新行,並讓Node讀取數據,直到它碰到新行。許多協議,例如FTP,都以這種方式工作。我相信這是一種更好/更簡單的方法。 – DaveRandom 2012-01-06 16:36:40