1

メッセージをブロードキャストし、UDP (SOCK_DGRAM) を使用してブロードキャストされたメッセージを受信する Python コードがあります。Python のソース コードは次の投稿にあります: https://stackoverflow.com/a/17055865/260127

この Python コードを C++/C に変換する必要があります。このコードを取得するために、Google で関数を 1 つずつ手動で翻訳しました。

#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 メッセージを取得できません。

このコードの何が問題になっていますか?

4

2 に答える 2

2

Pythonコードを見て、何をしようとしているのかを理解すると、ここにあなたの間違いがあります:

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

違いは明らかです。Python コードはa.join()どこにも呼び出されません (つまり、スレッドはメイン スクリプトの最後で暗黙的に結合されます)。ただし、C++ ポートでは、pingerThread.join()何らかの理由で即時を挿入しています。

では、なぜそれが違いを生むのでしょうか?デッドロックを保証するからです。ピンガー スレッドは、メッセージを受信するまで終了できません。メインスレッドからそのメッセージを受信することを期待しています。しかし、メイン スレッドはそのjoin呼び出しでスタックし、pinger スレッドが終了するのを待っています。

を削除するだけではこれを解決できません。C++ では、範囲外になる前にjoinすべてに参加する必要があるためです。std::thread(Python で結合を明示的にすることは非常に良い考えですが、C++ では、単に良い考えというだけでなく、それが法律であり、プログラムを壊すとプログラムが終了します。)main関数。


コードには他にも深刻な問題があります。


Pythontime.sleepは小数秒の浮動小数点数を取ります。C++ から呼び出している POSIXsleepは unsigned int を取り、小数秒のスリープには使用できません。これは、 to を暗黙的にキャストすることを意味しsleep(0.1)ます。0.10

コンパイラは、これについて次のように警告する必要があります。

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スレッド化の最初のルールは、呼び出しで競合状態を解決できないことです。あなたにできることは、バグの再現性を調整して、プログラムが実際に使用できないほど頻繁に発生するが、使用できない理由をデバッグするのに十分な頻度で発生しないようにすることです。

メインスレッドがrecvfrom. 特にビジー状態のシステムでは、スレッドは常に 100 ミリ秒にわたってスケジュール解除されます。

唯一の解決策は、物事を適切に順序付けることです。これが操作の順序を変更すること、同期プリミティブを使用すること、ソケット自体をシーケンスに使用すること、ロジックを変更することを意味するかどうか (たとえば、その質問に対する受け入れられた回答は、データを繰り返し送信することによって問題を解決します)。


Python コードは を呼び出しsetsockoptて、プログラムがアドレスを再利用できるようにし、ブロードキャスト モードをオンにします。しかし、C++ ポートは を呼び出しますgetsockopt。これは、2 つのオプションの値を読み取るだけで、何も変更しません。したがって、たとえば、同じプログラムを 2 回続けて実行すると、2 回目bindはアドレスに失敗する可能性があります。

また、値optlenを何にも初期化していません。に設定する必要があります。そうしないsizeof(optval)と、スタック全体を踏みにじることになります。または、4 バイトすべてではなく、オプションの値の最初の 0 バイトだけを読み取るだけです。つまり、何もチェックしていません。

また、戻り値をgetsockopt使用する前に、戻り値を確認する必要があります。getsockoptまた、2 回続けて呼び出し、最初の呼び出しoptvalをチェックせずに上書きする正当な理由はありません。

一方、Python コードはすでに間違ったやり方をしていました。送信側ではなく、SO_REUSEADDR呼び出し側で設定する必要があります。bind


また、Pythonsocket.sendtoは文字列を受け取り、文字列内のバイト数だけ送信しますが、Csendtoは文字列と長さを受け取り、文字列がlengthそれより前に終了していても、バイトを送信します。

したがって、14 ではなく 256 バイトを送信しています。


また、送信ソケットを閉じることはなく、リッスン ソケットのみを閉じます。

これは Python コードではすでに問題でしたが、C++ コードではさらに深刻な問題です。Python では、何かを忘れるとclose、最終的にはガベージ コレクションが行われますが、それで十分な場合もあります。C++ では、自己管理用に設計されたクラス(標準ライブラリにはほとんどの C++ クラスが含まれますが、ファイル ハンドルなどの C レベルのものは含まれません) を除いて、自分で明示的にクリーンアップする必要があります。

すぐに終了するおもちゃのプログラムの場合、おそらく問題にはなりません。しかし、実際のコードではそうです。

于 2013-06-21T00:35:44.657 に答える
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");

}    
于 2013-06-21T02:07:06.230 に答える