2013-06-21 2 views
0

У меня есть код python, который передает сообщение и принимает широковещательное сообщение с использованием UDP (SOCK_DGRAM). Исходный код python находится в этом сообщении: https://stackoverflow.com/a/17055865/260127Реализация UDP-вещания в C++

Мне нужно перевести этот код на Python в C++/C. Я googled, чтобы вручную переводить функции один за другим, чтобы получить этот код.

#include <iostream> 
#include <memory> 
#include <sys/types.h> 

#include <string.h> 
#include <stdio.h> 
#include <unistd.h> 
#include <thread> 

#include <sys/socket.h> 
#include <netinet/in.h> 
#include <arpa/inet.h> 

using namespace std; 
// https://stackoverflow.com/questions/13898207/recvfrom-bad-address-sendto-address-family-not-supported-by-protocol 
// http://linux.die.net/man/3/setsockopt 

void pinger(string msg) 
{ 
    cout << "pinger spawned: " << msg; 

    int bytes_sent; 
    char data_sent[256] = "This is a test"; 
    struct sockaddr_in to; 
    int addrlen; 
    int s = socket(AF_INET, SOCK_DGRAM, 0); 

    memset(&to, 0, sizeof(to)); 
    to.sin_family = AF_INET; 
    to.sin_addr.s_addr = inet_addr("192.168.65.255"); 
    to.sin_port = htons(4499); 

    int optval = 1; 
    socklen_t optlen; 
    getsockopt(s, SOL_SOCKET, SO_BROADCAST, &optval, &optlen); 
    getsockopt(s, SOL_SOCKET, SO_REUSEADDR, &optval, &optlen); 
    if (optval != 0) { 
     cout << "SO_BROADCAST enabled on s!\n"; 
    } 

    sleep(0.1); 

    bytes_sent = sendto(s, data_sent, sizeof(data_sent), 0, 
      (struct sockaddr*)&to, sizeof(to)); 
} 

int main(int argc, char *argv[]) 
{ 

    thread pingerThread(pinger, "Message"); 
    pingerThread.join(); 

    // get the message 

    int bytes_received; 
    char data_received[256]; 

    struct sockaddr_in from; 

    memset(&from, 0, sizeof(from)); 
    from.sin_family = AF_INET; 
    from.sin_addr.s_addr = inet_addr("192.168.65.255"); 
    from.sin_port = htons(4499); 

    int s = socket(AF_INET, SOCK_DGRAM, 0); 

    if(s == -1) 
     perror("socket"); 

    if (bind(s, (struct sockaddr*)&from, sizeof(from)) == -1) 
    { 
     perror("Bind error"); 
    } 

    socklen_t len = sizeof from; 
    if(recvfrom(s, data_received, 256, 0, (struct sockaddr*)&from, &len)==-1) 
     perror("recvfrom"); 

    if(close(s) == -1) 
     perror("close"); 

} 

В компиляции ошибок нет, но когда я выполняю код, он, кажется, ждет навсегда. Я не могу получить сообщение cout pinger.

Что не так с этим кодом?

+0

"Ничего не происходит" является довольно универсальным. Почему бы вам не положить пару отпечатков, чтобы изолировать проблему? Возможно, один после join(), один до recvfrom и один после. Ваш код кажется правильным, поэтому, если он все время ждет, возможно, это recvfrom, который не получает никаких данных. – Rob013

ответ

2

Глядя на код Python, чтобы выяснить, что вы пытаетесь сделать, вот ваша ошибка:

thread pingerThread(pinger, "Message"); 
pingerThread.join(); 

Разница довольно очевидна: код Python не вызывает a.join() в любом месте (что означает нить неявно присоединяется в конце основного скрипта), но в C++-порту вы вставили сразу pingerThread.join() по какой-то причине.

Итак, почему это имеет значение? Потому что это гарантирует тупик. Нить пинга не может закончить, пока не получит сообщение. Он ожидает получить это сообщение из основного потока. Но основной поток застрял в том, что вызов join, ожидая завершения нити пингера.

Вы не можете решить эту проблему, просто удалив join, потому что в C++ вы должны присоединиться к каждому std::thread, прежде чем он выйдет из сферы действия. (Это очень хорошая идея, чтобы ваши соединения были явными в Python, но на C++ это не просто хорошая идея, это закон, и ваша программа будет прекращена, если вы ее сломаете.) Итак, просто переместите ее до конца функция main.


В вашем коде есть и другие серьезные проблемы.


Python time.sleep принимает число с плавающей точкой с долей секунд; POSIX sleep, который вы вызываете из C++, принимает неподписанный int и не может использоваться для сна за дробные секунды. Это означает, что ваш sleep(0.1) неявно отличает 0.1 до 0.

Ваш компилятор должен предупредить вас об этом, что-то вроде этого:

pinger.cpp:40:11: warning: implicit conversion from 'double' to 'unsigned int' 
     changes value from 0.1 to 0 [-Wliteral-conversion] 
    sleep(0.1); 
    ~~~~~ ^~~ 

Если ваша платформа имеет POSIX sleep, это, вероятно, также имеет POSIX nanosleep (если это не очень старый, и в этом случае он, вероятно, по крайней мере, имеет BSD usleep), поэтому используйте это вместо этого.

Однако, несмотря на то, что автор этого кода на Python сказал, sleep(0.1) действительно не решает проблему № 3 («Иногда поток возникает так быстро, что слушатель просто пропускает данные широковещания») в первую очередь.

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

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

Единственным решением является правильное упорядочение. Независимо от того, означает ли это изменение порядка операций, использование примитивов синхронизации, использование самих сокетов для последовательности, изменение вашей логики (например, принятый ответ на этот вопрос решает проблему, посылая данные повторно).


код Питон называет setsockopt, чтобы позволить программе повторно использовать адрес, а также включить режим вещания. Но ваш порт C++ вызывает getsockopt, который просто считывает значения двух параметров, ничего не меняя. Так, если, например, вы запускаете одну и ту же программу дважды подряд, во второй раз она, скорее всего, не удастся выполнить адрес bind.

Кроме того, вы не инициализируете значение optlen на все. Вы должны установить его на sizeof(optval), или вы можете закончить топать по всему стеку или просто прочитать только первые 0 байтов необязательного значения, а не все 4 байта, что означает, что вы вообще ничего не проверяете.

Кроме того, вы должны проверить возвращаемое значение от getsockopt перед использованием возвращаемого значения. И нет веских причин дважды звонить в getsockopt и перезаписывать первый optval, не проверяя его.

Между тем, код Python уже делал это неправильно: вам нужно установить SO_REUSEADDR со стороны, которая вызывает bind, а не ту сторону, которая отправляет на нее.


Кроме того, в то время как в Python socket.sendto принимает строку и посылает столько байт, сколько в строке, Кассиопеяне sendto принимает строку и длину, и посылает length байт, даже если строка заканчивается раньше.

Итак, вы отправляете 256 байт вместо 14


Кроме того, вы никогда не закрывая отправки сокета, только сокет.

Это уже проблема в вашем коде на Python, но это намного хуже в коде на C++. В Python, если вы забудете close что-то, он в конечном итоге получит сбор мусора, и иногда это достаточно хорошо. В C++, за исключением classes designed to be self-managing (который включает в себя большинство классов C++ в стандартной библиотеке, но не C-уровневые вещи, такие как дескрипторы файлов), вы должны явно очистить после себя.

Для игрушечной программы, которая только что выйдет немедленно, это, вероятно, не имеет значения. Но в реальном коде это так.

+0

Спасибо! Это была большая помощь. – prosseek

+1

также отмечают, что сон * не * такой же, как «выход», многие старые и некоторые существующие реализации сна на самом деле заставляют ядро ​​выполнить весь процесс. – kfsone

+0

@kfsone: Хорошая точка. POSIX [указывает] (http://pubs.opengroup.org/onlinepubs/009695399/functions/sleep.html), что он «приведет к приостановке вызывающего потока из исполнения», но более старые * nix-платформы (например, Solaris, если вы используете двухуровневую потоковую обработку), и не-nix-платформы, которые предоставляют некоторые функции, подобные POSIX, в их libc (особенно Windows), возможно, не связаны этим. Кроме того, на Python, на некоторых платформах, общий поток, содержащий GIL, должен лечь спать, не выпуская GIL, который имеет тот же базовый эффект. – abarnert

1

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

  1. Я не использую поток для трансляции сообщения, я просто вызвал функцию.
  2. Я сделал код для отправки сообщения после привязки.

Это модифицированный код:

#include <iostream> 
#include <memory> 
#include <sys/types.h> 
#include <string.h> 
#include <stdio.h> 
#include <unistd.h> 
#include <thread> 

#include <sys/socket.h> 
#include <netinet/in.h> 
#include <arpa/inet.h> 

#include <cassert> 

using namespace std; 
// http://stackoverflow.com/questions/13898207/recvfrom-bad-address-sendto-address-family-not-supported-by-protocol 
// http://linux.die.net/man/3/setsockopt 

void pinger(string msg) 
{ 
    sockaddr_in si_me, si_other; 
    int s; 

    assert((s=socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP))!=-1); 

    int port=4499; 

    int broadcast=1; 
    setsockopt(s, SOL_SOCKET, SO_BROADCAST, 
       &broadcast, sizeof broadcast); 

    memset(&si_me, 0, sizeof(si_me)); 
    si_me.sin_family = AF_INET; 
    si_me.sin_port = htons(port); 
    si_me.sin_addr.s_addr = inet_addr("192.168.65.255"); 

    unsigned char buffer[10] = "hello"; 
    int bytes_sent = sendto(s, buffer, sizeof(buffer), 0, 
       (struct sockaddr*)&si_me, sizeof(si_me)); 
    cout << bytes_sent; 
} 

int main(int argc, char *argv[]) 
{ 

     sockaddr_in si_me; 
     unsigned char buffer[20]; 
     int s; 

     assert((s=socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP))!=-1); 

     int port=4499; 
     memset(&si_me, 0, sizeof(si_me)); 
     si_me.sin_family = AF_INET; 
     si_me.sin_port = htons(port); 
     si_me.sin_addr.s_addr = inet_addr("192.168.65.255"); 


     if (bind(s, (struct sockaddr*)&si_me, sizeof(si_me)) == -1) 
     { 
      perror("Bind error"); 
     } 

     // Send the message after the bind  
     pinger("hello"); 

     socklen_t len = sizeof si_me; 
     if(recvfrom(s, buffer, 20, 0, (struct sockaddr*)&si_me, &len)==-1) 
      perror("recvfrom"); 

     cout << "\nRECEIVE" << buffer; 

     if(close(s) == -1) 
      perror("close"); 

}  
+0

Это определенно намного проще. Конечно, это не все так полезно, но для примера с игрушкой, используемого для обучения, это не имеет значения. Но это не эквивалентно коду Python, который вы пытались выполнить, поскольку он полностью синхронный, поэтому он не является примером большинства реальных интересных частей. – abarnert

+0

@abarnert: Спасибо за комментарий, я действительно хотел реализовать его с помощью потока, но я не мог заставить его работать. – prosseek

+0

Ну, я полагаю, что сначала стоит сделать это, а затем сделать поток, чтобы вы могли убедиться, что вы понимаете сокеты на C++ и избавляетесь от него, прежде чем убедиться, что вы понимаете потоки на C++. Во всяком случае, мой ответ должен показать жесткую часть этого с потоками; если вы в основном работаете, но имеете проблемы с секвенированием (например, иногда вы получаете, прежде чем что-то отправлено), вы всегда можете задать новый вопрос. – abarnert