1

我的目标是从 C++ 应用程序向 PHP 发送一个简单的 GET 查询,例如“do re mi fa sol”并获取一些我可以播放的数据。

PHP 脚本将返回我将在 C++ 客户端中解释的字节流。我读取一个字节以了解下一段(文本或 WAV 音频文件)的性质。我读取了 2 个字节作为下一段的大小。我读取了段数据并进行了相应的处理。

我不是 C++ 和 PHP 方面的专家,但我使用 boost 库进行了管理。只要我发送的数据很小,它就可以正常工作。如果数据达到一定的大小,它就搞砸了:我得到了一个无效字节,因为该段的性质(我读取的第一个字节)。

我认为它搞砸的限制是8192Bytes,我猜它只是在缓冲区的开头写回?

我尝试了一些事情: - 更改我读取套接字的缓冲区的大小。- 让我的 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

所以我猜分块编码需要额外的解析。

4

0 回答 0