Socket_recv не получает полные данные

Я отправляю из браузера через Websocket данные изображения около 5000 байт, но эта строка получает всего 1394 байт:

while ($bytes = socket_recv($socket, $r_data, 4000, MSG_DONTWAIT)) {
    $data .= $r_data;
}

Это происходит после того, как рукопожатие сделано правильно. Данные json обрезаются после 1394 байт. В чем может быть причина?

В интерфейсе браузера он отправляет изображение как JSON:

websocket.send(JSON.stringify(request));

Интерфейс браузера прекрасен, так как он работает с другими программами PHP, которые я тестировал бесплатно.

Вот полный исходный код.

Ответ 1

У вас есть сокет, настроенный как неблокирующий, указав MSG_DONTWAIT, поэтому он вернет EAGAIN после того, как он прочитает первый фрагмент данных, а не ждет больше данных. Удалите флаг MSG_DONTWAIT и вместо этого используйте MSG_WAITALL, чтобы он ожидал получения всех данных.

Есть несколько способов узнать, получили ли вы все данные, которые вы ожидаете:

  • Отправьте длину данных. Это полезно, если вы хотите отправить несколько блоков содержимого переменной длины. Например, если я хочу отправить три строки, я могу сначала отправить "3", чтобы сообщить получателю, сколько строк ожидать, а затем для каждого из них я бы отправил длину строки, за которой следуют строковые данные.
  • Использовать сообщения с фиксированной длиной. Если вы ожидаете нескольких сообщений, но каждый из них имеет одинаковый размер, вы можете просто прочитать его из сокета до тех пор, пока у вас не будет хотя бы столько байтов, а затем обработайте сообщение. Обратите внимание, что вы можете получить более одного сообщения (включая частичные сообщения) в одном вызове recv().
  • Закройте соединение. Если вы отправляете только одно сообщение, вы можете закрыть соединение. Это работает, потому что TCP-соединения поддерживают отдельные состояния для отправки и получения, поэтому сервер и закрытие отправляющего соединения еще остаются открытыми для ответа клиента. В этом случае сервер отправляет все свои данные клиенту, а затем вызывает socket_shutdown (1)

1 и 2 полезны, если вы хотите обрабатывать данные во время их получения - например, если вы пишете игру, приложение для чата или что-то еще, где сокет остается открытым, а несколько сообщений передаются туда и обратно. # 3 - самый простой и полезен, когда вы просто хотите получать все данные за один раз, например, загружать файлы.

Ответ 2

1394 находится вокруг общего размера MTU, особенно если вы туннелируете через VPN (вы?).

Вы не можете рассчитывать на чтение всех байтов в одном вызове, пакеты могут быть фрагментированы в соответствии с сетевым MTU.

Ответ 3

Только мои 2 цента на этом. socket_recv может вернуть false при ошибке. Где он также может принимать ноль (0) байтов в неблокирующем IO.

Ваша проверка в вашем цикле должна быть:

while(($bytes = socket_recv($resource, $r_data, 4000, MSG_DONTWAIT)) !== false) {}

Наверху я также проверил бы сокет на наличие ошибок и добавлю какой-нибудь служебный вызов, чтобы предотвратить "ожог процессора".

$data = '';
$done = false;
while(!$done) {
    socket_clear_error($resource);
    $bytes = @socket_recv($resource, $r_data, 4000, MSG_DONTWAIT);

    $lastError = socket_last_error($resource);

    if ($lastError != 11 && $lastError > 0) {
        // something went wrong! do something
        $done = true;
    }
    else if ($bytes === false) {
        // something went wrong also! do something else
        $done = true;
    }
    else if (intval($bytes) > 0) {
        $data .= $r_data;
    }
    else {
        usleep(2000); // prevent "CPU burn"
    }
}

Ответ 4

Мне интересно, есть ли у вас проблемы с подключением к веб-окнам. Цикл while, который вы цитируете выше, смотрит на меня, чтобы он находился в части кода, где было выполнено квитирование клиента, в else от if($client->getHandshake()) { ... } else { ... }.

Насколько я могу сказать, что $client - отдельный класс, поэтому я не вижу, как выглядит класс или что делает Client:: getHandshake(), но я предполагаю, что он является получателем логического что подтверждает успех или неудачу рукопожатия по обновлению websocket.

Если я прав, квитирование не выполняется, и соединение закрывается клиентом. Из кода я вижу, что используемый серверный код требует версии 13 спецификации. Вы не упомянули, какую клиентскую библиотеку вы используете, но другие серверы будут принимать другие версии, чем этот сервер.

Пожалуйста, убедитесь, что ваша клиентская библиотека поддерживает последнюю версию.

Проводка подробного вывода с сервера при получении входящего соединения и сбой передачи будет полезной, если то, что я предлагаю, неверно.

Ответ 5

НО, это не часть кода, который вы вставили в блок else? Блок else, который для меня похож на дрожание руки, не прошел?

Не могли бы вы напечатать полученные байты как строку?

Ответ 6

Я не думаю, что ваш вопрос правильный. Согласно исходному коду, если рукопожатие преуспело, выполняется этот раздел кода:

$data = '';

while (true) {
    $ret = socket_recv($socket, $r_data, 4000, MSG_DONTWAIT);
    if ($ret === false) {
        $this->console("$myidentity socket_recv error");
        exit(0);
    }
    $data .= $r_data;
    if (strlen($data) > 4000) {
        print "breaking as data len is more than 4000\n";
        break;
    } else {
        print "curr datalen=" . strlen($data) . "\n";
    }
}

Если программа действительно переходит к разделу кода, который вы предоставили, стоит посмотреть, почему не удалось выполнить рукопожатие.

Класс сервера имеет третий параметр verboseMode, который при установке в true предоставит вам подробные журналы отладки о том, что именно происходит.

Мы просто будем размышлять без журнала отладки, но если предоставлен журнал отладки, мы можем предложить лучшее предложение.