Полезная нагрузка разделяется на два TCP-пакета при использовании Boost ASIO, когда он вписывается в MTU - PullRequest
9 голосов
/ 27 июля 2011

У меня проблема с бустом :: asio :: ip :: tcp :: iostream. Я пытаюсь отправить около 20 сырых байтов. Проблема состоит в том, что эта полезная нагрузка в 20 байтов разделена на два пакета TCP по 1 байту, а затем 19 байтов. Простая проблема, почему это происходит, я понятия не имею. Я пишу это для устаревшего двоичного протокола, который очень требует, чтобы полезная нагрузка помещалась в один пакет TCP (стон).

Вставка всего исходного кода из моей программы будет долгой и чрезмерно сложной, я разместил здесь функциональную проблему только в двух функциях (проверено, она воспроизводит проблему);

#include <iostream>

// BEGIN cygwin nastyness
// The following macros and conditions are to address a Boost compile
// issue on cygwin. https://svn.boost.org/trac/boost/ticket/4816
//
/// 1st issue
#include <boost/asio/detail/pipe_select_interrupter.hpp>

/// 2nd issue
#ifdef __CYGWIN__
#include <termios.h>
#ifdef cfgetospeed
#define __cfgetospeed__impl(tp) cfgetospeed(tp)
#undef cfgetospeed
inline speed_t cfgetospeed(const struct termios *tp)
{
    return __cfgetospeed__impl(tp);
}
#undef __cfgetospeed__impl
#endif /// cfgetospeed is a macro

/// 3rd issue
#undef __CYGWIN__
#include <boost/asio/detail/buffer_sequence_adapter.hpp>
#define __CYGWIN__
#endif
// END cygwin nastyness.

#include <boost/array.hpp>
#include <boost/asio.hpp>
#include <iostream>

typedef boost::asio::ip::tcp::iostream networkStream;

void writeTestingData(networkStream* out) {
        *out << "Hello world." << std::flush;
//      *out << (char) 0x1 << (char) 0x2 << (char) 0x3 << std::flush;
}

int main() {
        networkStream out("192.168.1.1", "502");

        assert(out.good());

        writeTestingData(&out);
        out.close();
}

Чтобы добавить к странной проблеме, если я отправляю строку "Hello world.", Она идет в одном пакете. Если я отправлю 0x1, 0x2, 0x3 (необработанные значения байтов), я получу 0x1 в пакете 1, а затем остальные данные в следующем пакете TCP. Я использую wireshark для просмотра пакетов, между машиной dev и 192.168.1.1 есть только переключатель.

Ответы [ 5 ]

10 голосов
/ 09 августа 2011

Не волнуйтесь, вы из единственного, кто имеет эту проблему. Определенно есть решение. На самом деле, у вас TWO проблем с устаревшим протоколом, а не только с одним.

Ваш старый унаследованный протокол требует, чтобы одно «прикладное сообщение» вписывалось в «один и только один TCP-пакет» (поскольку он неправильно использует TCP-ориентированный протокол в качестве пакетно-ориентированного протокола). Поэтому мы должны убедиться, что:

  1. нет "сообщения приложения", разбитого на несколько пакетов TCP (проблема, которую вы видите)
  2. ни один TCP-пакет не содержит более одного "сообщения приложения" (вы этого не видите, но это может произойти)

Решение:

задача 1

Вы должны заполнить свой сокет всеми вашими данными "сообщения" сразу. В настоящее время этого не происходит, потому что, как об этом говорили другие люди, API-интерфейс потока потоков, который вы используете, помещает данные в сокет в виде отдельных вызовов, когда вы используете последовательное «<<», а базовый стек TCP / IP вашей ОС не буферизуется. этого достаточно (и с причинами для лучшей производительности) </p>

Несколько решений:

  • вы передаете буфер символов вместо отдельных символов, так что вы делаете только один вызов << </li>
  • вы забываете о boost, открываете сокет ОС и передаете его одним вызовом send () (в windows ищите API «winsock2» или «sys / socket.h» в unix / cygwin)

задача 2

Вы ДОЛЖНЫ активировать опцию TCP_NODELAY в своем сокете. Эта опция специально сделана для таких устаревших протоколов. Это гарантирует, что стек ОС TCP / IP отправляет ваши данные «без задержки» и не буферизует их вместе с другим приложением, которое вы можете отправить позже.

  • если вы используете Boost, ищите опцию TCP_NODELAY, она есть в документе
  • если вы используете сокеты ОС, вам придется использовать функцию setsockopt () в вашем сокете.

Заключение

Если вы решите эти две проблемы, у вас все будет хорошо!

API для сокетов ОС в Windows или Linux немного сложен в использовании, но вы получите полный контроль над его поведением. Пример Unix

9 голосов
/ 09 августа 2011

Ваш код:

out << (char) 0x1 << (char) 0x2 << (char) 0x3;

Выполнит 3 вызова функции operator<<.

Из-за алгоритма Nagle TCP, стек TCP будет отправлять доступные данные((char)0x1) для непосредственного вызова после / во время первого operator<< вызова.Таким образом, остальные данные (0x2 и 0x3) перейдут к следующему пакету.

Решение для избежания 1-байтовых сегментов TCP: вызов функций отправки с большим количеством данных.

1 голос
/ 09 августа 2011

Я согласен с ответом Пользователя1. Вы, вероятно, вызываете operator << несколько раз; при первом вызове он немедленно отправляет первый байт по сети, затем вступает в действие алгоритм Нейгла, поэтому оставшиеся данные отправляются в одном пакете.

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

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

1 голос
/ 27 июля 2011

Я не уверен, кто бы навязал такое требование, чтобы вся полезная нагрузка была в одном TCP-пакете. TCP по своей природе является потоковым протоколом, и большая часть информации о количестве отправленных пакетов, размере полезной нагрузки и т. Д. Оставлена ​​на усмотрение реализации операционной системы для стека TCP.

Я бы дважды проверил, действительно ли это ограничение вашего протокола.

0 голосов
/ 27 июля 2011

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

Есть предложения?

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

...