【问题标题】:Writing then reading back via sockets in PHP issues在 PHP 问题中通过套接字写入然后读取
【发布时间】:2012-02-03 09:36:06
【问题描述】:

我编写一个命令,然后通过 PHP 中的套接字从服务器读取。我们有 20 台服务器都运行一个 Node JS 脚本,它可以接收这些命令并执行它们。 Node JS 脚本将返回“ok”,PHP 回读该“ok”以确认命令已执行。

Node JS 脚本侦听端口 9000 并设置为 allow half open。

大多数情况下这都能正常工作,但是当发送大量命令时,我们偶尔会收到错误消息:

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

传输端点消息提示我它没有成功连接。

我不是套接字方面的专家,所以我不知道我使用的实现是否“正确”。它在大多数情况下确实有效,但我知道有像 socket_bind 和 socket_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;
}

【问题讨论】:

  • 好吧,你不应该使用 PHP 套接字连接到 Node.js,这在处理大量打开的连接时效率非常低。
  • 您是否查看过 PHP 服务器端的 netstat ......也许您的本地端口用完了?
  • @alessioalex 我还能在 PHP 端使用什么?
  • @EugenRieck 不是节点 JS 应该是非阻塞的并且可以处理同时连接吗?
  • 我说的是 PHP 端,而不是 JS 端:连接使用本地端口,并且 (localIP,localPort,remoteIP,remotePort) 元组必须是唯一的,所以当 NodeJS 处理 remoteIP 时, remotePort 的一部分,您可能会用完本地端口 - 套接字选项可以帮助您,但请通过查看 netstat 来帮助诊断!

标签: php sockets node.js


【解决方案1】:

对于像这样的简单套接字客户端,我更喜欢fsockopen() - 它大大减少了客户端代码的复杂性,并且不需要套接字扩展,这在任何地方都不可用。这样做的主要缺点是您丢失了字符串错误消息,但无论如何这些消息很少有用 - 您仍然会在创建套接字时收到错误字符串,如果读/写操作失败,那是因为套接字已断开连接。

我也不确定“允许半开”在这种情况下有多有用 - 我认为它可能会产生比它解决的问题更多的问题。对socket_shutdown() 的调用在这里没有做任何有用的事情(并且可能是您的问题的根源) - 如果您在一个不会关闭的套接字上操作并且可能在稍后的时间点上操作,您只会使用它时间由其他一些代码。由于您在同一例程中创建了一个新套接字并销毁它,因此情况并非如此。

这是您重写的代码以使用fsockopen() - 如您所见,它更短更简单。我还对其稍作修改,使其始终返回布尔值 - 您的代码返回布尔值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;
    }

}

【讨论】:

  • 谢谢,我已经更改了我的代码,但是在测试中它从立即提交命令变为延迟 30 秒。我认为问题出在 Node JS 端,它不知道数据何时结束,这就是为什么它保持打开我的脚本,更改“允许半开”选项没有任何区别。我的 Node JS 脚本位于 pastebin.com/r9big65J
  • 进一步的测试表明它在fread 处被阻止,如果有帮助的话..
  • @fire 我知道你的问题出在哪里,它确实在节点端。这就是你的socket_shutdown() 所做的,通过关闭套接字来终止命令。没有办法使用fsockopen() 流来模拟这一点,然而 ...我会为这样的事情做的是发出一个永远不会出现在命令中的终止字符,例如换行, 在命令之后。因此,您发送命令后紧跟新行,并让 Node 读取数据,直到它到达新行。许多协议,例如 FTP,都以这种方式工作。我相信这是一种更好/更简单的方法。
  • 好的,所以你说我应该做$command = json_encode($command) . "\n" 并且在节点中当它接收到数据时它应该检查最后一个字符是否是\n,如果是,则调用套接字结束函数?
  • 是的,这就是我会做的——如果你在 PHP 中做这种事情,那是流比套接字更容易使用的另一个原因,因为你可以使用 fgets()——它在返回数据之前将等待 EOL。
猜你喜欢
  • 2015-08-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-09-10
  • 1970-01-01
  • 2021-12-17
  • 2010-11-23
相关资源
最近更新 更多