#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 этот комментарий должен быть ответом
Ответ №1:
Мне кажется, что в вашем протоколе может быть проблема: откуда ваш клиент знает длину lengthString ? Вы преобразуете произвольное число в строку, а затем записываете его в буфер, без заполнения или чего-либо еще? Не видя больше кода, его трудно увидеть, но это определенно выглядит подозрительно.
Обычно при наличии сообщений переменной длины вы определяете какой-то формат пакета, например, поле длины равно 4 байтам (выберите правильное количество байтов для ваших сообщений), за которым следует сообщение, а затем вы можете отправить (в данном случае) int клиенту, который сначала прочитает 4 байта, а затемзнать, сколько еще байтов нужно прочитать из сокета. Возможно, вам захочется взглянуть на такие функции, как htons и т. Д.
Если у вас заголовок переменной длины, у вас должен быть какой-то разделитель для чтения. Этот тип заголовка проиллюстрирован в примерах HTTP asio, где » r n r n» является используемым разделителем. Этот пример также может быть полезен для вас, поскольку, как заявил рейма, вам нужно будет изменить свой клиент, чтобы сначала прочитать заголовок, а затем тело сообщения.
Комментарии:
1. Спасибо за ваше предложение! Я проверил пример HTTP и согласен, это было бы самым элегантным решением. К сожалению, структура строки для меня является своего рода черным ящиком, поскольку я использую буферы протокола для сериализации, поэтому я не могу прочитать только фрагмент сообщения и полагаться на разделители.
2. В этом случае я бы использовал первый подход, т.Е. отправил 4 байта с указанием длины строки в порядке сетевого хоста, а затем отправил строки переменной длины. В вашем примере кода вы отправляете длину в виде строки, в чем может быть проблема.
Ответ №2:
Кажется, что вы отправляете запрос на сервер, чтобы запросить размер предстоящего ответа, но поскольку потоки TCP сами по себе не имеют разделителя, вы можете получать размер вместе с частью или всем ответом, ожидая только размер! Вы должны принять во внимание эту часть данных, прежде чем пытаться прочитать оставшуюся часть ответа (и, очевидно, дождаться меньшего количества байтов).