簡體   English   中英

C ++中的UDP廣播實現

[英]UDP broadcasting implementation in C++

我有一個python代碼,用於廣播消息,並使用UDP(SOCK_DGRAM)接收廣播的消息。 python源代碼在這篇文章中: https : //stackoverflow.com/a/17055865/260127

我需要將此python代碼轉換為C ++ / C。 我用谷歌搜索手動翻譯功能,以獲取此代碼。

#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");

}   

編譯沒有錯誤,但是當我執行代碼時,它似乎會永遠等待。 我聽不到pinger的提示消息。

此代碼有什么問題?

查看Python代碼找出您要執行的操作,這是您的錯誤:

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

區別非常明顯:Python代碼不會在任何地方調用a.join() (這意味着線程在主腳本的末尾隱式連接),但是在C ++端口中,您插入了立即pingerThread.join()由於某種原因。

那么,為什么會有區別呢? 因為它保證了死鎖。 在收到消息之前,ping線程無法完成。 期望從主線程接收該消息。 但是主線程卡在該join調用中,等待pinger線程完成。

您不能僅通過刪除join來解決此問題,因為在C ++中,必須在每個std::thread超出范圍之前將其加入。 (在Python中使連接顯式是一個很好的主意,但是在C ++中,這不僅是個好主意,而且是法律,如果破壞了它,則程序將被終止。)因此,只需將其移至main功能。


您的代碼中還有其他一些嚴重的問題。


Python time.sleep需要一個帶有小數秒的浮點數; 您從C ++調用的POSIX sleep采用一個無符號的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並沒有什么神奇的。 線程總是被調度100ms,尤其是在繁忙的系統上。

唯一的解決方案是正確地排序事物。 這是否意味着更改操作順序,使用同步原語,使用套接字本身進行排序,更改邏輯(例如,對該問題的公認答案可以通過重復發送數據來解決該問題)。


Python代碼調用setsockopt ,以允許程序重用地址並打開廣播模式。 但是您的C ++端口調用getsockoptgetsockopt讀取兩個選項的值,而不更改任何內容。 因此,例如,如果您連續兩次運行同一程序,則第二次可能無法bind該地址。

另外,您不會將optlen的值初始化為任何值。 您必須將其設置為sizeof(optval)否則可能會遍歷整個堆棧-或僅讀取可選值的前0個字節,而不是全部4個字節,這意味着您根本不會檢查任何內容。

另外,您必須在使用返回的值之前檢查getsockopt的返回值。 並且沒有充分的理由連續兩次調用getsockopt並覆蓋第一個optval而無需檢查它。

同時,Python代碼已經在做錯了:您需要在調用bind那一側而不是發送給它的那一側設置SO_REUSEADDR


同樣,雖然Python的socket.sendto接收一個字符串並發送與該字符串中一樣多的字節,但是C的sendto接收一個字符串和一個長度,並發送length字節,即使該字符串在此之前終止。

因此,您發送的是256個字節而不是14個字節。


另外,您永遠不會關閉發送套接字,僅關閉偵聽套接字。

在您的Python代碼中,這已經是一個問題,但是在C ++代碼中,這是一個更糟糕的問題。 在Python中,如果您忘記close某些內容,則最終將收集垃圾,這有時就足夠了。 在C ++中,除了被設計為自我管理的類 (包括標准庫中的大多數C ++類,但不包括文件句柄之類的C級事物)之外,您還必須自己進行顯式清理。

對於將要立即退出的玩具程序,這可能並不重要。 但是在現實生活中,代碼確實可以。

根據答案,我修改了代碼以使其正常運行。

  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");

}    

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM