Моя цель — отправить из приложения 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
Так что я думаю, что кодирование по частям требует дополнительного разбора.
Задача ещё не решена.
Других решений пока нет …