【问题标题】:UDP broadcasting implementation in C++C++中的UDP广播实现
【发布时间】:2013-06-21 00:05:35
【问题描述】:

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

这段代码有什么问题?

【问题讨论】:

  • “什么都没有发生”是一个非常通用的词。您为什么不打印几张照片来隔离问题?也许一个在 join() 之后,一个在 recvfrom 之前,一个在之后。您的代码似乎是正确的,所以如果它永远等待,可能是 recvfrom 没有收到任何数据。

标签: c++ python sockets networking udp


【解决方案1】:

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

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

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

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

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


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


Python time.sleep 采用带小数秒的浮点数;您从 C++ 调用的 POSIX sleep 采用无符号整数,并且不能用于休眠小数秒。这意味着您的 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 毫秒,尤其是在繁忙的系统上。

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


Python 代码调用setsockopt,以允许程序重用地址,并打开广播模式。但是您的 C++ 端口调用getsockopt,它只是读取两个选项的值,不更改任何内容。因此,例如,如果您连续两次运行同一个程序,第二次它可能会失败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++ 中,除了 classes designed to be self-managing(包括标准库中的大多数 C++ 类,但不包括文件句柄等 C 级内容),您必须自己显式清理。

对于一个将立即退出的玩具程序,这可能无关紧要。但在现实生活中的代码中,确实如此。

【讨论】:

  • 谢谢!这是一个很大的帮助。
  • 另请注意,睡眠与“yield”相同,许多旧的和一些现存的睡眠实现实际上会导致内核让出整个进程。
  • @kfsone:好点。 POSIX specifies 它“将导致调用线程暂停执行”,但较旧的 *nix 平台(如 Solaris,如果您使用 2 级线程),以及提供一些 POSIX-的非 *nix 平台他们的 libc(尤其是 Windows)中的类似功能可能不受此约束。另外,在 Python 中,在某些平台上,持有 GIL 的线程在不释放 GIL 的情况下进入睡眠状态是很常见的,这具有相同的基本效果。
【解决方案2】:

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

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

}    

【讨论】:

  • 这绝对简单多了。当然,它并不是那么有用,但是对于一个用于自学的玩具示例来说,这并不重要。但它并不等同于您尝试移植的 Python 代码,因为它是完全同步的,所以它不是大多数实际有趣部分的示例。
  • @abarnert:感谢您的评论,我确实想用线程来实现它,但我无法让它工作。
  • 好吧,我想值得先做这件事,然后再做线程,这样你就可以确保你理解 C++ 中的套接字并把它排除在外,然后再确保你理解 C++ 中的线程C++。无论如何,我的回答应该表明用线程做这件事的困难部分;如果您基本上可以正常工作,但排序有问题(例如,有时您在发送任何内容之前就收到了),您可以随时发布新问题。
猜你喜欢
  • 2016-07-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-07-12
  • 2010-12-16
  • 2017-04-19
  • 1970-01-01
  • 2012-09-17
相关资源
最近更新 更多