我的目标是从 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
所以我猜分块编码需要额外的解析。