C ++中的UDP广播实现

时间:2013-06-21 00:05:35

标签: c++ python sockets networking udp

我有一个广播消息的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的cout消息。

此代码有什么问题?

2 个答案:

答案 0 :(得分:2)

查看Python代码以确定您要执行的操作,这是您的错误:

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

区别非常明显:Python代码不会在任何地方调用a.join()(这意味着线程在主脚本的末尾隐式连接),但在C ++端口中,您已经立即插入pingerThread.join()由于某种原因。

那么,为什么会有所作为呢?因为它保证了死锁。在收到消息之前,pinger线程无法完成。它期望从主线程接收该消息。但是主线程卡在join调用中,等待pinger线程完成。

您只能通过删除join来解决此问题,因为在C ++中,您需要在超出范围之前加入每个std::thread。 (在Python中使用连接显式是一个非常好的主意,但在C ++中,它不仅仅是一个好主意,它是法律,如果你打破它,你的程序将被终止。)所以,只需将它移到最后main函数。


您的代码中还有一些其他严重问题。


Python time.sleep采用带小数秒的浮点数;你从C ++调用的POSIX sleep采用unsigned 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调用来解决竞争条件。您所能做的就是将错误的可重现性调整到足以使您的程序在实践中无法使用的程度,但通常不足以调试它无法使用的原因。

等待100ms保证主线程已经到达recvfrom没有什么神奇之处。线程总是被计划100ms,特别是在繁忙的系统上。

唯一的解决方案是正确排序。这是否意味着更改操作顺序,使用同步原语,使用套接字本身进行排序,更改逻辑(例如,对该问题的接受答案通过重复发送数据来解决问题)。


Python代码调用setsockopt,允许程序重用该地址,并打开广播模式。但是你的C ++端口调用getsockopt,它只读取两个选项的值,而不是改变任何东西。因此,例如,如果您连续两次运行相同的程序,则第二次可能无法bind地址。

此外,您没有将optlen的值初始化为任何内容。你必须将它设置为sizeof(optval),否则你最终可能会在整个堆栈中踩踏 - 或者只读取可选值的前0个字节而不是全部4个字节,这意味着你根本不检查任何东西。 / p>

此外,您必须在使用返回值之前检查getsockopt的返回值。并且没有充分的理由连续两次调用getsockopt并覆盖第一个optval而不检查它。

与此同时,Python代码已经做错了:您需要在调用SO_REUSEADDR的一侧设置bind,而不是发送给它的那一方。


此外,虽然Python的socket.sendto接受字符串并发送字符串中的字节数,但C sendto接受字符串和长度,并发送length个字节,即使字符串在此之前终止。

所以,你发送256个字节而不是14个字节。


此外,您永远不会关闭发送套接字,只关闭侦听套接字。

这在您的Python代码中已经存在问题,但它在C ++代码中是一个更糟糕的问题。在Python中,如果你忘记close某些东西,它最终会被垃圾收集,有时候这已经足够了。在C ++中,除classes designed to be self-managing(其中包括标准库中的大多数C ++类,但不包括文件句柄等C级事物)外,您必须自己明确清理。

对于即将退出的玩具程序,它可能并不重要。但在实际代码中,确实如此。

答案 1 :(得分:1)

根据答案,我修改了代码以使其正常工作。

  1. 我不使用线程来广播消息,我只是调用了函数。
  2. 我使代码在绑定后发送消息。
  3. 这是修改后的代码:

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