1

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

这段代码有什么问题?

4

2 回答 2

2

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

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

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

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

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


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


Pythontime.sleep采用带小数秒的浮点数;您从 C++ 调用的 POSIXsleep采用无符号整数,并且不能用于休眠小数秒。这意味着您sleep(0.1)隐式地将0.1to转换为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 代码已经做错了:您需要设置SO_REUSEADDR在调用bind的一侧,而不是发送给它的一侧。


此外,虽然 Pythonsocket.sendto接受一个字符串并发送与字符串中一样多的字节,但 Csendto接受一个字符串和一个长度并发送length字节,即使字符串在此之前终止。

因此,您发送的是 256 个字节而不是 14 个字节。


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

这在您的 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 回答