C++ / [Из песочницы] [asio::udp] Не кроссплатформенное поведение

в 13:06, , рубрики: boost, c plus plus, метки: ,

Итак, представьте ситуацию: у нас есть кроссплатформенный сервер который должен получать данные по UDP. Вооружившись Asio вы создаете сокет, создаете буфер для принимаемых данных и начинаете слушать.

udp::socket receiver(ios, udp::endpoint(udp::v4(), port)); char read_buf[buf_len]; udp::endpoint sender_point; receiver.receive_from(buffer(read_buf, sizeof(read_buf)), sender_point);

Что произойдет если в полученной дейтаграмме будет больше данных, чем вы выделили для буфера?

Будет несоответствие поведения на WIN/LINUX платформах. На Linux операция пройдет гладко, будет считано ровно столько, сколько было запрошено, остальное будет отброшено. На Win вы так же получите считанные данные, но вдогонку получите еще и исключение.

И вот, почему этому произойдет:

Работа функции receive_from сводиться к выполнению следующего кода

#if defined(BOOST_WINDOWS) || defined(__CYGWIN__)   int result = error_wrapper(::WSARecvFrom(s, bufs, recv_buf_count,         &bytes_transferred, &recv_flags, addr, &tmp_addrlen, 0, 0), ec);   *addrlen = (std::size_t)tmp_addrlen; ...   if (result != 0)     return socket_error_retval;   ec = boost::system::error_code();   return bytes_transferred; #else // defined(BOOST_WINDOWS) || defined(__CYGWIN__)   msghdr msg = msghdr();   init_msghdr_msg_name(msg.msg_name, addr);   msg.msg_namelen = *addrlen;   msg.msg_iov = bufs;   msg.msg_iovlen = count;   int result = error_wrapper(::recvmsg(s, &msg, flags), ec);   *addrlen = msg.msg_namelen;   if (result >= 0)     ec = boost::system::error_code();   return result;

Для получения данных из сокета используются функции WSARecvFrom для Win и recvmsg для Linux. Результат работы функции (успешно/не успешно) определяется следующей функцией.

template <typename ReturnType> inline ReturnType error_wrapper(ReturnType return_value,     boost::system::error_code& ec) { #if defined(BOOST_WINDOWS) || defined(__CYGWIN__)   ec = boost::system::error_code(WSAGetLastError(),       boost::asio::error::get_system_category()); #else   ec = boost::system::error_code(errno,       boost::asio::error::get_system_category()); #endif   return return_value; }

Из описания функции WSARecvFrom следует, что она возвращает статус ошибки:

If no error occurs and the receive operation has completed immediately, WSARecvFrom returns zero. Otherwise, a value of SOCKET_ERROR is returned, and a specific error code can be retrieved by calling WSAGetLastError.

Одной из которых как раз является:

WSAEMSGSIZE
The message was too large for the specified buffer and (for unreliable protocols only) any trailing portion of the message that did not fit into the buffer has been discarded.

Функции семейства recv возвращают число успешно считанных байт или -1 в случае ошибки и статус в errno. Но среди ее ошибок нет аналогичной вышеприведённой. Получается для Linux это не ошибка? Не совсем так. В передаваемую функции recvmsg (а именно она и используется в asio) структуру msghdr в поле msg_flags записывается код ошибки:

MSG_TRUNC
Возвращает реальную длину пакета, даже если она была больше, чем предоставленный буфер. Этот флаг можно использовать только с пакетными протоколами.

Таким образом, одна и та-же операция в Win расценивается как ошибочная, а на Linux как корректная. Что явно не учтено asio.

Одной из вытекающих трудностей является невозможность под Win определить то, что дейтаграмма была считана не вся.

Возможным решением это проблемы могло бы стать использовании функции available. Но и тут мы получаем еще более пугающее не соответствие. Данная функции на Win платформах возвращает общее число данных в UDP сокете (сумма данных всех дейтаграмм), а на Linux платформах, размер только первой (той, которая будет обработана при следующем чтении) дейтаграммы.

А вот вам полный пример всего вышеописанного:

source.

Output win:

Exception: receive_from: Сообщение, отправленное на сокет датаграмм, было больше, чем буфер внутренних сообщений или был превышен иной сетевой параметр. Также возможно, что буфер для принятия сообщения был меньше, чем размер сообщения
Some test
Available: 36
Available: 18
Available: 0

output linux:

Some test
Available: 18
Available: 18
Available: 0

Проверялось на версии boost 1.44 и 1.49.

Спасибо за внимание.

Автор: Cupper


* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js