Пишем потом читаем через сокеты в проблемах PHP - PullRequest
3 голосов
/ 05 января 2012

Я пишу команду и затем читаю обратно с сервера через сокеты в PHP. У нас есть 20 серверов, на которых все работают сценарии Node JS, которые могут получать эти команды и выполнять их. Сценарий Node JS вернет "ok", который PHP считывает, чтобы подтвердить выполнение команды.

Сценарий Node JS прослушивает порт 9000 и имеет значение разрешить полуоткрытое .

В большинстве случаев это работает нормально, но когда отправляется большой объем команд, мы иногда получаем ошибки, которые говорят следующее:

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

1 Ответ

1 голос
/ 05 января 2012

Для простого клиента сокетов, такого как этот, я очень предпочитаю fsockopen() - он значительно уменьшает сложность клиентского кода и не требует расширения сокетов, которое не доступно везде.Основным недостатком этого является то, что вы теряете сообщения об ошибках строки, но в любом случае они редко бывают полезными - вы все равно получаете строку ошибки при создании сокета, а если операции чтения / записи не выполняются, это происходит потому, что сокет отключился.

Я также не уверен, насколько полезно «разрешить полуоткрытое» в этом контексте - я думаю, что это может создать больше проблем, чем решить.Звонки на socket_shutdown() здесь не делают ничего полезного (и могут быть источником ваших проблем) - вы можете использовать это, только если вы работаете с сокетом, который не будет закрыт и может быть задействован на более позднем этапевремя другим кодом.Поскольку вы создаете новый сокет и уничтожаете его в той же процедуре, это не так.

Здесь ваш код переписан для использования fsockopen() - как вы можете видеть, он несколько короче и проще.Я также немного изменил его, чтобы он всегда возвращал bool - ваш код возвращает либо bool FALSE, либо строку ok, и, поскольку есть только эти две опции, для функции имеет больше смысла всегда возвращать bool.

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

}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...