2015-11-23 2 views
11

У меня есть приложение, которое принимает, обрабатывает и передает UDP-пакеты.Длинные задержки при отправке пакетов UDP

Все работает нормально, если номера портов для приема и передачи различны.

Если номера портов одинаковы и IP-адреса различны, он обычно работает нормально ИСКЛЮЧАЕТ, когда IP-адрес находится в той же подсети, что и компьютер, на котором запущено приложение. В этом последнем случае для функции send_to требуется несколько секунд, а не несколько миллисекунд, как обычно.

Rx Port Tx IP   Tx Port Result 

5001  Same   5002  OK Delay ~ 0.001 secs 
     subnet  

5001  Different  5001  OK Delay ~ 0.001 secs 
     subnet 

5001  Same   5001  Fails Delay > 2 secs 
     subnet 

Вот краткая программа, которая демонстрирует проблему.

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

using boost::asio::ip::udp; 
using std::cout; 
using std::endl; 

int test(const std::string& output_IP) 
{ 
    try 
    { 
     unsigned short prev_seq_no; 

     boost::asio::io_service io_service; 

     // build the input socket 

     /* This is connected to a UDP client that is running continuously 
     sending messages that include an incrementing sequence number 
     */ 

     const int input_port = 5001; 
     udp::socket input_socket(io_service, udp::endpoint(udp::v4(), input_port)); 

     // build the output socket 

     const std::string output_Port = "5001"; 
     udp::resolver resolver(io_service); 
     udp::resolver::query query(udp::v4(), output_IP, output_Port); 
     udp::endpoint output_endpoint = *resolver.resolve(query); 
     udp::socket output_socket(io_service); 
     output_socket.open(udp::v4()); 

     // double output buffer size 
     boost::asio::socket_base::send_buffer_size option(8192 * 2); 
     output_socket.set_option(option); 

     cout << "TX to " << output_endpoint.address() << ":" << output_endpoint.port() << endl; 



     int count = 0; 
     for (;;) 
     { 
      // receive packet 
      unsigned short recv_buf[ 20000 ]; 
      udp::endpoint remote_endpoint; 
      boost::system::error_code error; 
      int bytes_received = input_socket.receive_from(boost::asio::buffer(recv_buf,20000), 
           remote_endpoint, 0, error); 

      if (error && error != boost::asio::error::message_size) 
       throw boost::system::system_error(error); 

      // start timer 
      __int64 TimeStart; 
      QueryPerformanceCounter((LARGE_INTEGER *)&TimeStart); 

      // send onwards 
      boost::system::error_code ignored_error; 
      output_socket.send_to(
       boost::asio::buffer(recv_buf,bytes_received), 
       output_endpoint, 0, ignored_error); 

      // stop time and display tx time 
      __int64 TimeEnd; 
      QueryPerformanceCounter((LARGE_INTEGER *)&TimeEnd); 
      __int64 f; 
      QueryPerformanceFrequency((LARGE_INTEGER *)&f); 
      cout << "Send time secs " << (double) (TimeEnd - TimeStart)/(double) f << endl; 

      // stop after loops 
      if(count++ > 10) 
       break; 
     } 
    } 
    catch (std::exception& e) 
    { 
     std::cerr << e.what() << std::endl; 
    } 

} 
int main() 
{ 

    test("193.168.1.200"); 

    test("192.168.1.200"); 

    return 0; 
} 

Выход из этой программы, при работе на машине с адресом 192.168.1.101

TX to 193.168.1.200:5001 
Send time secs 0.0232749 
Send time secs 0.00541566 
Send time secs 0.00924535 
Send time secs 0.00449014 
Send time secs 0.00616714 
Send time secs 0.0199299 
Send time secs 0.00746081 
Send time secs 0.000157972 
Send time secs 0.000246775 
Send time secs 0.00775578 
Send time secs 0.00477618 
Send time secs 0.0187321 
TX to 192.168.1.200:5001 
Send time secs 1.39485 
Send time secs 3.00026 
Send time secs 3.00104 
Send time secs 0.00025927 
Send time secs 3.00163 
Send time secs 2.99895 
Send time secs 6.64908e-005 
Send time secs 2.99864 
Send time secs 2.98798 
Send time secs 3.00001 
Send time secs 3.00124 
Send time secs 9.86207e-005 

Почему это происходит? Есть ли способ уменьшить задержку?

Примечания:

  • Построенная с использованием кода :: блоков, работающих под различными вкусами Windows,

  • пакетов являются 10000 байт длиной

  • Проблема уходит, если я подключить компьютер запуск приложения во вторую сеть. Например, WWLAN (сотовая сеть «ракета палочке»)

Насколько я могу судить, это ситуация, которую мы имеем:

Это работает (разные порты, так же LAN):

enter image description here

Это также работает (те же порты, различные LANS):

enter image description here

Это не работает (те же порты, так же LAN):

enter image description here

Это похоже на работу (одни и те же порты, так же LAN, двойной подключением Module2 хоста)

enter image description here

+2

Мне действительно трудно поверить. Я бы предложил удалить все исправления, чтобы повысить и создать ту же функциональность, используя необработанные сокеты BSD. – SergeyA

+0

В любом случае отправка может занять 3 секунды. И ни в коем случае не существует общей проблемы с отправкой пакетов UDP по локальной сети, где все слушатели привязаны к одному и тому же порту. – SergeyA

+0

@ the_non_believers Это происходит для трех разных людей в разных местах. – ravenspoint

ответ

2

Хорошо, составите код (см. Ниже). Понятно, что отправка занимает не более одной милисекунды большую часть времени. Это доказывает, что проблема связана с повышением.

#include <iostream> 
#include <string> 
#include <sys/types.h> 
#include <sys/socket.h> 
#include <netinet/in.h> 
#include <arpa/inet.h> 
#include <stdexcept> 
#include <poll.h> 
#include <string> 
#include <memory.h> 
#include <chrono> 
#include <stdio.h> 

void test(const std::string& remote, const std::string& hello_string, bool first) 
{ 
    try 
    { 
     const short unsigned input_port = htons(5001); 
     int sock = socket(AF_INET, SOCK_DGRAM, 0); 
     if (sock == -1) { 
      perror("Socket creation error: "); 
      throw std::runtime_error("Could not create socket!"); 
     } 

     sockaddr_in local_addr; 
     local_addr.sin_port = input_port; 
     local_addr.sin_addr.s_addr = INADDR_ANY; 
     if (bind(sock, (const sockaddr*)&local_addr, sizeof(local_addr))) { 
      perror("Error: "); 
      throw std::runtime_error("Can't bind to port!"); 
     } 

     sockaddr_in remote_addr; 
     remote_addr.sin_port = input_port; 
     if (!inet_aton(remote.c_str(), &remote_addr.sin_addr)) 
      throw std::runtime_error("Can't parse remote IP address!"); 

     std::cout << "TX to " << remote << "\n"; 

     unsigned char recv_buf[40000]; 

     if (first) { 
      std::cout << "First launched, waiting for hello.\n"; 
      int bytes = recv(sock, &recv_buf, sizeof(recv_buf), 0); 
      std::cout << "Seen hello from my friend here: " << recv_buf << ".\n"; 
     } 

     int count = 0; 
     for (;;) 
     { 

      std::chrono::high_resolution_clock::time_point start = std::chrono::high_resolution_clock::now(); 
      if (sendto(sock, hello_string.c_str(), hello_string.size() + 1, 0, (const sockaddr*)&remote_addr, sizeof(remote_addr)) != hello_string.size() + 1) { 
       perror("Sendto error: "); 
       throw std::runtime_error("Error sending data"); 
      } 
      std::chrono::high_resolution_clock::time_point end = std::chrono::high_resolution_clock::now(); 

      std::cout << "Send time nanosecs " << std::chrono::duration_cast<std::chrono::nanoseconds>(end - start).count() << "\n"; 

      int bytes = recv(sock, &recv_buf, sizeof(recv_buf), 0); 
      std::cout << "Seen hello from my friend here: " << recv_buf << ".\n"; 

      // stop after loops 
      if (count++ > 10) 
       break; 
     } 
    } 
    catch (std::exception& e) 
    { 
     std::cerr << e.what() << std::endl; 
    } 

} 
int main(int argc, char* argv[]) 
{ 
    test(argv[1], argv[2], *argv[3] == 'f'); 

    return 0; 
} 

Как и ожидалось, никакой задержки нет.Вот выход из одной из пар (я запускаю код в парах на двух машинах в одной и той же сети):

./socktest x.x.x.x 'ThingTwo' f 
TX to x.x.x.x 
First launched, waiting for hello. 
Seen hello from my friend here: ThingOne. 
Send time nanosecs 17726 
Seen hello from my friend here: ThingOne. 
Send time nanosecs 6479 
Seen hello from my friend here: ThingOne. 
Send time nanosecs 6362 
Seen hello from my friend here: ThingOne. 
Send time nanosecs 6048 
Seen hello from my friend here: ThingOne. 
Send time nanosecs 6246 
Seen hello from my friend here: ThingOne. 
Send time nanosecs 5691 
Seen hello from my friend here: ThingOne. 
Send time nanosecs 5665 
Seen hello from my friend here: ThingOne. 
Send time nanosecs 5930 
Seen hello from my friend here: ThingOne. 
Send time nanosecs 6082 
Seen hello from my friend here: ThingOne. 
Send time nanosecs 5493 
Seen hello from my friend here: ThingOne. 
Send time nanosecs 5893 
Seen hello from my friend here: ThingOne. 
Send time nanosecs 5597 
+0

К сожалению, это не компилируется под кодом :: blocks на окнах. Я попытался заменить sockets.h на winsock.h, но теперь все связано с проблемами POSIX – ravenspoint

+0

@ravenspoint, это должно работать на Windows при условии, что вы инициализируете winsock. AFAIR, все эти функции также определены для Windows? Если нет, просто замените их на экземпляры WSA. – SergeyA

+0

Вам не нужен какой-либо системный заголовок, чем просто удалить все из них. – SergeyA

1

Это хорошая практика, чтобы отделить Tx и Rx портов. Я получаю свой собственный класс сокетов из CAsynchSocket, поскольку у него есть насос сообщений, который отправляет системное сообщение, когда данные получены в вашем сокете, и yanks функция OnReceive (либо ваша, если u переопределяет базовую виртуальную функцию или значение по умолчанию, если вы не используете

+0

Благодарим за отзыв. CAsynchSocket, я считаю, является частью MFC. Этот вопрос касается boost :: asio. Не забудьте проверить теги вопросов. – ravenspoint

5

Учитывая, что это наблюдается в Windows для больших дейтаграмм с адресом назначения несуществующего партнера в той же подсети, что и отправитель, проблема, скорее всего, является результатом блокировки send(), ожидающей ответа Address Resolution Protocol (ARP), так что кадр ethernet ethernet может

  • При отправке данных рамка ethernet layer2 будет заполнена медиа-доступом (MAC) Адрес следующего прыжка на маршруте. Если отправитель не знает MAC-адрес для следующего перехода, он будет транслировать запрос ARP и ответы в кеше. Используя маску подсети отправителя и адрес назначения, отправитель может определить, находится ли следующий скачок в той же подсети, что и отправитель, или данные должны проходить через шлюз по умолчанию. На основании результатов в вопросе, при отправке больших дейтаграмм:

    • датаграммы, предназначенные для другой подсети не имеет задержек, поскольку шлюз по умолчанию MAC-адрес находится в ARP кэша отправителя
    • дейтаграммы, предназначенной для несуществующего всмотреться в подсети отправителя влечет за собой задержку в ожидании разрешения ARP
  • сокета send buffer size (SO_SNDBUF) создается для 16384 байтов, но размер датаграммы отправляются в 10000. Неизвестно поведение поведения send(), когда буфер насыщен, но в некоторых системах будет наблюдаться блокировка send(). В этом случае насыщение будет происходить довольно быстро, если какие-либо датаграммы несут задержку, например, ожидая ответа ARP.

    // Datagrams being sent are 10000 bytes, but the socket buffer is 16384. 
    boost::asio::socket_base::send_buffer_size option(8192 * 2); 
    output_socket.set_option(option); 
    

    Рассмотрите возможность управления ядром размером буфера сокета или увеличением его на основе ожидаемой пропускной способности.

  • При отправке дейтаграммы с размером, превышающим параметр реестра FastSendDatagramThreshold‌, вызов send() может блокироваться до тех пор, пока датаграмма не будет отправлена. Для получения более подробной информации, см Microsoft TCP/IP Implementation Details:

    датаграмм меньше, чем значение этого параметра пройти быстрый путь ввода/вывода или в буфере отправки. Большие сохраняются до тех пор, пока фактическая датаграмма не будет отправлена. Значение по умолчанию было обнаружено при тестировании как лучшее общее значение для производительности. Быстрый ввод-вывод означает копирование данных и обход подсистемы ввода-вывода вместо сопоставления памяти и прохождения через подсистему ввода-вывода. Это выгодно для небольших объемов данных. Изменение этого значения обычно не рекомендуется.

Если один наблюдает задержки для каждого send() к существующей пэра на подсети отправителя, а затем профиль и анализ сети:

  • Использования iperf для измерения сети потенциальных пропускной способности
  • Используйте wireshark, чтобы получить более глубокое представление о том, что происходит на данном узле. Ищите запрос и ответы ARP.
  • От машины отправителя, ping peer, а затем проверьте APR-кеш. Убедитесь, что есть запись кэша для сверстника и что она правильная.
  • Попробуйте использовать другой порт и/или TCP. Это может помочь определить, регулируют ли политики сетей или формируют трафик для определенного порта или протокола.

отметить также, что передача дейтаграмм ниже значения FastSendDatagramThreshold в быстрой последовательности во время ожидания ARP для разрешения может привести к дейтаграмм быть отвергнуто:

ARP очереди только один исходящий IP датаграммы на определенный адрес назначения, пока этот IP-адрес решается на адрес управления доступом к среде передачи. Если приложение с протоколом User Datagram Protocol (UDP) отправляет несколько дейтаграмм IP на один адрес назначения без каких-либо пауз между ними, некоторые из датаграмм могут быть удалены, если нет записи ARP-кэша, уже присутствующей. Приложение может компенсировать это, вызывая процедуру iphlpapi.dllSendArp(), чтобы установить запись кэша ARP, прежде чем отправлять поток пакетов.

+0

Я добавил HKLM \ System \ CurrentControlSet \ Services \ Afd \ Parameters \ FastSendDatagramThreshold в мой реестр со значением 2048 (десятичный), перезагрузил и попытался настроить. Без изменений. – ravenspoint

+0

@ravenspoint Как отмечено выше, изменение «FastSendDatagramThreshold» обычно не рекомендуется.Я бы настоятельно рекомендовал решить проблему, а не симптом. Если вы готовы принять весь риск, связанный с изменением «FastSendDatagramThreshold», обратитесь к сведениям о реализации TCP/IP для версии Windows, которую вы используете для определения местоположения параметров реестра AFD. –

+0

«Я бы настоятельно рекомендовал решить проблему» Да, это то, что я пытался сделать в течение последних пяти дней. Я понял, что ваша рекомендация заключалась в том, чтобы изменить эту ценность. Что вы рекомендуете? – ravenspoint

Смежные вопросы