Потоковая передача некоторых данных из PHP в C ++ - PullRequest
0 голосов
/ 30 мая 2018

Моя цель - отправить из приложения C ++ простой запрос GET в PHP, такой как "do re mi fa sol", и получить некоторые данные, которые я могу воспроизвести.

PHP-скрипт вернетпоток байтов, которые я буду интерпретировать в клиенте C ++.Я прочитал один байт, чтобы узнать природу следующего сегмента (текстовый или WAV-аудиофайл).Я прочитал 2 байта для размера следующего сегмента.Я читаю данные сегмента и соответственно с ними обращаюсь.

Я не эксперт ни в C ++, ни в PHP, но мне удалось, используя библиотеку boost.Это работает нормально, пока данные, которые я посылаю, невелики.Если данные достигают определенного размера, они перепутаны: я получаю недопустимый байт для характера сегмента (первый, который я прочитал).

Я думаю, что предел, после которого они испорчены, составляет 8192 байт, и яЯ предполагаю, что это просто запись в начале буфера?

Я попробовал несколько вещей: - изменение размера буфера, в котором я читал сокет.- заставить мой скрипт PHP спать, но клиент c ++ начинает ждать завершения запроса, поэтому у меня просто задержка, прежде чем я получу ту же ошибку.- попробуйте выполнить сброс в моем PHP-сценарии, и ob-flush ... он не работает лучше, и он использует эти методы, добавляя несколько байтов к выводу.

Вот мой код: C ++

    std::string host = "192.168.1.232";
    std::string port = "80";
    std::string path = "/praat/play.php";
    int httpVersion = 11;

    boost::asio::io_context ioc;
    boost::asio::ip::tcp::resolver resolver{ ioc };
    boost::asio::ip::tcp::socket socket{ ioc };

    auto const results = resolver.resolve( host, port );

    boost::asio::connect( socket, results.begin(), results.end() );

    boost::beast::http::request<boost::beast::http::string_body> req{ boost::beast::http::verb::get, path, httpVersion };
    req.set( boost::beast::http::field::host, host );
    req.set( boost::beast::http::field::user_agent, BOOST_BEAST_VERSION_STRING );

    boost::beast::http::write( socket, req );


    OutputDebugString( L"████ Let's read the request:\n" );

    std::array< byte, 32 > buf;
    bool parsingHeader = true;
    std::string header = "";
    std::deque< byte > data;
    unsigned long cumul = 0;

    while ( true ) {

        boost::system::error_code error;

        int len = socket.read_some( boost::asio::buffer( buf ), error );

        cumul += len;

        OutputDebugString((L"████ Error value: " + std::to_wstring(error.value()) + L"\n").c_str());

        OutputDebugString( ( L"████ Bytes received: " + std::to_wstring( len ) + L" for a total of " + std::to_wstring( cumul ) + L"\n" ).c_str() );

        if ( error == boost::asio::error::eof ) {

            OutputDebugString(L"████ End of file\n");
            break;
        }

        std::string s = (char *)buf.data();
        s = s.substr( 0, len );

        if ( parsingHeader ) {

            std::size_t index = s.find( "\r\n\r\n" );

            if ( index != std::string::npos ) {

                header += s.substr( 0, index );

                std::wstring ws( header.begin(), header.end() );

                OutputDebugString( L"████ We got a header:\n" );
                OutputDebugString( ( ws + L"\n" ).c_str() );

                int dataLen = len - index - 4;

                data.resize( dataLen );

                std::copy( std::begin( buf ) + index + 4, std::begin( buf ) + len, std::begin( data ) );

                parsingHeader = false;
            }
            else {

                header += s;
            }
        }
        else {

            int currentSize;

            currentSize = data.size();

            data.resize( currentSize + len );

            std::copy( std::begin(buf), std::begin(buf) + len, std::begin(data) + currentSize );
        }

        if ( !parsingHeader ) {

            OutputDebugString( ( L"████ Data size: " + std::to_wstring( data.size() ) + L"\n" ).c_str() );

            while ( data.size() != 0 ) {

                int typeFlag = ( unsigned int )data[0];

                OutputDebugString( ( L"████ First byte: " + std::to_wstring( typeFlag ) + L"\n" ).c_str() );

                switch ( typeFlag ) {

                    case 1:
                        OutputDebugString(L"████ We have text");
                        break;
                    case 2:
                        OutputDebugString(L"████ We have sound");
                        break;
                    default:
                        OutputDebugString( L"████ Unhandled byte\n" );
                        goto exitloop;
                }

                if ( data.size() < 3 ) {

                    OutputDebugString( L" but not enough bytes to read its length.\n" );
                    break;
                }

                int segmentSize = int( (unsigned char)data[1] << 8 | (unsigned char)data[2] );

                OutputDebugString( ( L" for " + std::to_wstring( segmentSize )  + L" bytes" ).c_str() );

                if ( data.size() < 3 + segmentSize ) {

                    OutputDebugString( L" but they are not ready yet.\n" );
                    break;
                }

                OutputDebugString( L".\n" );

                if ( typeFlag == 1 ) {

                    std::wstring encoded( data.begin() + 3, data.begin() + 3 + segmentSize );

                    std::wstring text = base64_decode( encoded );

                    OutputDebugString( ( text + L"\n" ).c_str() );
                }
                else if (typeFlag == 2) {


                }

                data.erase( data.begin(), data.begin() + 3 + segmentSize );
            }
        }
    }

exitloop:



    boost::system::error_code ec;
    socket.shutdown( boost::asio::ip::tcp::socket::shutdown_both, ec );

    if ( ec && ec != boost::system::errc::not_connected )
        throw boost::system::system_error{ ec };

PHP:

<?php


set_time_limit( 0 );

// header or not, it doesn't seam to change anything.
/*header( 'Content-Type: application/octet-stream' );
header( 'Content-Transfer-Encoding: binary' );
header( 'Expires: 0' );
header( 'Cache-Control: must-revalidate' );
header( 'Pragma: public' );*/

//$partition = $_GET[ "partition" ];
$partition = "do"; //,re,mi,fa,sol! la:si?";
$separators = " ,.;:!?";

chdir( ".." );

function echoObject( $object ) {

    $json = json_encode( $object );

    $encoded = base64_encode( $json );

    echo pack( "Cn", 1, strlen( $encoded ) );
    echo $encoded;
}

function play( $partition, $start, $length ) {

    $note = substr( $partition, $start, $length );

    $object = new stdClass();
    $object->type = "highlight";
    $object->start = $start;
    $object->length = $length;
    $object->note = $note;

    echoObject( $object );

    $wav = "notes/" . $note . ".wav";

    $fileHandle = fopen( $wav, 'rb' );

    fseek( $fileHandle, 32 );

    $infos = unpack( 'Vsize', fread( $fileHandle, 4 ) );

    $size = intval( $infos[ 'size' ] );

    fread( $fileHandle, 4 ); //remove the 4 bytes for "data";

    $total = 0;

    do {

    $len = min( $size - $total, 256 );

    echo pack( "Cn", 2, $len );
    echo fread( $fileHandle, $len );

    $total += $len;

    //usleep( 100000 );

    echoObject( array( "total" => $total, "size" => $size ) );
    }
    while( $total < $size );

    fclose( $fileHandle );
}

$start = 0;
$length = 0;

echoObject( array( "type"=> "start" ) );

do {

    if ( strpos( $separators, substr( $partition, $start + $length, 1 ) ) ) {

    if ( $length == 0 ) {

        $start++;
    }
    else {

        play( $partition, $start, $length );

        $start += $length + 1;
        $length = 0;
    }
    }
    else {

    $length++;

    if ( $start + $length == strlen( $partition ) ) {

        play( $partition, $start, $length );
    }
    }
}
while( $start + $length < strlen( $partition ) );

echoObject( array( "type"=> "over" ) );

?>

На данный момент я застрял.Любая помощь приветствуется.Спасибо.

ОБНОВЛЕНИЕ 1

После перехода на другой метод, следуя примеру elarmando, у меня на клиенте следующий код:

boost::asio::streambuf response;
    boost::asio::read_until( socket, response, "\r\n" );

    // Check that response is OK.
    std::istream response_stream( &response );
    std::string http_version;
    response_stream >> http_version;
    unsigned int status_code;
    response_stream >> status_code;
    std::string status_message;
    std::getline( response_stream, status_message );

    if ( !response_stream || http_version.substr( 0, 5 ) != "HTTP/" ) {

        OutputDebugString( L"████ Invalid response\n" );
        return S_FALSE;
    }

    if ( status_code != 200 ) {

        OutputDebugString( ( L"████ Response returned with status code " + std::to_wstring( status_code ) + L"\n" ).c_str() );
        return S_FALSE;
    }

    // Read the response headers, which are terminated by a blank line.
    boost::asio::read_until( socket, response, "\r\n\r\n" );

    OutputDebugString( L"████ We have a header:\n" );

    // Process the response headers.
    std::string header;
    while ( std::getline( response_stream, header ) && header != "\r" )
        OutputDebugString( ( std::wstring( header.begin(), header.end() ) + L"\n" ).c_str() );
    OutputDebugString( L"\n" );

    OutputDebugString( L"████ Now let us parse the body:\n" );

    std::deque< byte > data;
    auto handle = [ &data, &response ]() -> bool {

        const BYTE* test = boost::asio::buffer_cast<const BYTE*>(response.data());

        int currentSize;

        currentSize = data.size();

        data.resize( currentSize + response.size() );

        std::copy( test, test + response.size(), std::begin(data) + currentSize );

        response.consume(response.size());

        OutputDebugString((L"████ Data end - begin: " + std::to_wstring( data.end() - data.begin() ) + L"\n").c_str());
        OutputDebugString((L"████ Data size: " + std::to_wstring(data.size()) + L"\n").c_str());

        while ( data.size() != 0 ) {

            int typeFlag = (unsigned int)data[0];

            OutputDebugString( ( L"████ First byte: " + std::to_wstring(typeFlag) + L"\n" ).c_str());

            switch (typeFlag) {

                case 1:
                    OutputDebugString( L"████ We have text" );
                    break;
                case 2:
                    OutputDebugString( L"████ We have sound" );
                    break;
                default:
                    OutputDebugString( L"████ Unhandled byte\n" );
                    return false;
            }

            if ( data.size() < 3 ) {

                OutputDebugString( L" but not enough bytes to read its length.\n" );
                break;
            }

            int segmentSize = int( ( unsigned char)data[1] << 8 | (unsigned char)data[2] );

            OutputDebugString( ( L" for " + std::to_wstring( segmentSize ) + L" bytes" ).c_str());

            if ( data.size() < 3 + segmentSize) {

                OutputDebugString( L" but they are not ready yet.\n" );
                break;
            }

            OutputDebugString( L".\n" );

            if (typeFlag == 1) {

                std::wstring encoded( data.begin() + 3, data.begin() + 3 + segmentSize );

                OutputDebugString( ( encoded + L"\n").c_str() );

                std::wstring text = base64_decode( encoded );

                OutputDebugString( ( text + L"\n" ).c_str() );
            }
            else if (typeFlag == 2) {

                //play the audio here
            }

            OutputDebugString((L"████ Data size before erase: " + std::to_wstring(data.size()) + L"\n").c_str());

            data.erase( data.begin(), data.begin() + 3 + segmentSize );

            OutputDebugString((L"████ Data size after resize: " + std::to_wstring(data.size()) + L"\n").c_str());
        }

        //OutputDebugString( ( L"████ Size: " + std::to_wstring( data.size() ) + L"\n").c_str() );

        return true;
    };

    if ( response.size() > 0 )
        if ( !handle() ) goto parsingerror;

    boost::system::error_code error;

    while (boost::asio::read(socket, response, boost::asio::transfer_at_least(1), error))
        if ( !handle() ) goto parsingerror;

    if ( error != boost::asio::error::eof )
        throw boost::system::system_error( error );

    goto done;

parsingerror:

    int typeFlag = (unsigned int)data[0];
    OutputDebugString( ( L"████ First byte is: " + std::to_wstring( typeFlag ) + L"\n" ).c_str() );

done:

    OutputDebugString( L"████ Done\n" );

    return S_FALSE;

И у меня точно такой же результат.

Но потом я кое-что понял, и это то, что, когда контент больше определенного значения (в моем случае это выглядит как что-то около 8000, я предполагаю 8192) заголовок ответа изменится с:

Date: Thu, 31 May 2018 14:09:52 GMT
Server: Apache/2.4.29 (Ubuntu)
Vary: Accept-Encoding
Content-Length: 7977
Content-Type: text/html; charset=UTF-8

на:

Date: Thu, 31 May 2018 14:09:31 GMT
Server: Apache/2.4.29 (Ubuntu)
Vary: Accept-Encoding
Transfer-Encoding: chunked
Content-Type: text/html; charset=UTF-8

Так что я полагаю, что кодирование по частям требует дополнительного разбора.

...