EOF с повышением :: asio :: чтение

#c #networking #boost #client-server #boost-asio

#c #сеть #повышение #клиент-сервер #повышение-asio

Вопрос:

У меня небольшая проблема с моим клиент-серверным приложением на C . Он использует boost::asio для удаленной связи и буферы протокола для сериализации. Здесь клиент:

 // Time to write
char sizeMessage[20];
string content = request.SerializeAsString();
size_t request_length = content.size();            

boost::asio::write(s, boost::asio::buffer(content.c_str(), request_length));
boost::system::error_code error;

boost::asio::read(s, boost::asio::buffer(sizeMessage, 20));

int bufferSize = atoi(sizeMessage);
char* responseString = new char[bufferSize];        
size_t reply_length = boost::asio::read(s, boost::asio::buffer(responseString, bufferSize), boost::asio::transfer_all(), error);
if(!error){
    response.ParseFromArray((const void*)responseString, reply_length);
    success = true;                
}else{
    response.set_status(ExecResponse::ERROR);                
}


delete[] responseString;
  

В этом конкретном сценарии моего приложения размер ответа сервера может варьироваться от нескольких байт до нескольких мегабайт. По этой причине сервер сначала записывает размер выходных данных, которые я затем использую для выделения нужного объема памяти для моего буфера. По какой-то причине я получаю сообщение об ошибке при чтении данных (размер всегда считывается правильно). Небольшая отладка выявила asio.misc:2, который, как я понял, является EOF (фактически количество прочитанных байтов меньше размера буфера). Здесь серверная часть:

 ExecResponse response;
ExecutionServerHandler execHandler(this->sharedData);
execHandler.process(rd, request, response);   
string output = response.SerializeAsString();
//First write the length
ostringstream stringLength;
stringLength << output.size();
response.PrintDebugString();
string lengthString = stringLength.str();    
size_t bw = ba::write(bsocket_, ba::buffer(lengthString));
bw = ba::write(bsocket_, ba::buffer(output));
  

На самом деле я впервые работаю с boost, поэтому я не опытный разработчик. Более того, важно упомянуть, что в других частях моего приложения этот механизм работает, но другие мои сообщения имеют некоторые отличия: хотя они по-прежнему имеют разный размер, они всегда небольшие (несколько сотен байт). Серверная часть идентична, но на стороне клиента я использую boost ::asio::transfer_at_least(1), и я не пишу размер заранее. Если я применю эти изменения к этому случаю, я заметил, что вызов read способен извлекать до 65536 байт и не более (преждевременный возврат EOF). Моими машинами разработки являются Debian 5 и Ubuntu 10.04 для 64 бит, и я получаю такое же поведение для Boost 1.40 и 1.47. Если кто-нибудь поймет, что я делаю что-то не так, я был бы признателен, если бы указал на это. Я в отчаянии, пытаясь заставить это работать.

Комментарии:

1. Я тоже не эксперт по boost :: asio, но написание строки переменной длины ( lengthString ) на стороне сервера и всегда чтение 20 байт на стороне клиента выглядит подозрительно для меня. Всякий lengthString раз, когда он короче 20 байт, вы уже считываете часть полезной нагрузки sizeMessage , таким образом, не оставляя достаточно байтов для заполнения responseString .

2. Привет, спасибо за ваш комментарий. Это дало мне подсказку. Я изменил размер строки с длиной на стороне сервера, чтобы она всегда содержала 20 символов (заполните оставшееся пространство символом ) при принудительном использовании клиента all . Это решило проблему. Моя гипотеза заключается в том, что второй вызов read считывал оставшиеся символы строки, содержащей длину, поэтому я получал преждевременный EOF. Большое спасибо!

3. @reima этот комментарий должен быть ответом

4. boost.org/doc/libs/1_43_0/boost/asio/error.hpp

Ответ №1:

Мне кажется, что в вашем протоколе может быть проблема: откуда ваш клиент знает длину lengthString ? Вы преобразуете произвольное число в строку, а затем записываете его в буфер, без заполнения или чего-либо еще? Не видя больше кода, его трудно увидеть, но это определенно выглядит подозрительно.

Обычно при наличии сообщений переменной длины вы определяете какой-то формат пакета, например, поле длины равно 4 байтам (выберите правильное количество байтов для ваших сообщений), за которым следует сообщение, а затем вы можете отправить (в данном случае) int клиенту, который сначала прочитает 4 байта, а затемзнать, сколько еще байтов нужно прочитать из сокета. Возможно, вам захочется взглянуть на такие функции, как htons и т. Д.

Если у вас заголовок переменной длины, у вас должен быть какой-то разделитель для чтения. Этот тип заголовка проиллюстрирован в примерах HTTP asio, где » r n r n» является используемым разделителем. Этот пример также может быть полезен для вас, поскольку, как заявил рейма, вам нужно будет изменить свой клиент, чтобы сначала прочитать заголовок, а затем тело сообщения.

Комментарии:

1. Спасибо за ваше предложение! Я проверил пример HTTP и согласен, это было бы самым элегантным решением. К сожалению, структура строки для меня является своего рода черным ящиком, поскольку я использую буферы протокола для сериализации, поэтому я не могу прочитать только фрагмент сообщения и полагаться на разделители.

2. В этом случае я бы использовал первый подход, т.Е. отправил 4 байта с указанием длины строки в порядке сетевого хоста, а затем отправил строки переменной длины. В вашем примере кода вы отправляете длину в виде строки, в чем может быть проблема.

Ответ №2:

Кажется, что вы отправляете запрос на сервер, чтобы запросить размер предстоящего ответа, но поскольку потоки TCP сами по себе не имеют разделителя, вы можете получать размер вместе с частью или всем ответом, ожидая только размер! Вы должны принять во внимание эту часть данных, прежде чем пытаться прочитать оставшуюся часть ответа (и, очевидно, дождаться меньшего количества байтов).