【问题标题】:Same BSD socket API program works in Python but not in C++ or Rust相同的 BSD 套接字 API 程序适用于 Python,但不适用于 C++ 或 Rust
【发布时间】:2020-11-03 06:48:39
【问题描述】:

我一直在尝试收听来自 Yeelight 智能灯泡的多播 UDP 消息。每隔一段时间,灯泡就会在 IP 239.255.255.250 端口 1982 上广播它的存在。此外,当在同一 IP 和端口上发送特定请求 (found on page 5 - section 3.1) 时,灯泡会响应。

使用套接字,我正在尝试建立通信。在两台机器(macOS 和 Linux)上,下面显示的 Python 程序在 REPL 上运行,但下面显示的 Rust 代码和 C++ 代码(基本上做同样的事情)不能。 Rust 程序似乎两次成功接收到 0 字节,然后挂起等待。在 C++ 程序中,recv 函数只是立即挂起,永远不会返回。

可能是什么问题?为什么只有 Python 才能成功通信?

import socket as s
import struct

sock = s.socket(s.AF_INET, s.SOCK_DGRAM, s.IPPROTO_UDP)

sock.setsockopt(s.IPPROTO_IP, s.IP_ADD_MEMBERSHIP, struct.pack("4sL", s.inet_aton("239.255.255.250"), s.INADDR_ANY))

sock.sendto("M-SEARCH * HTTP/1.1\r\nHOST: 239.255.255.250:1982\r\nMAN: \"ssdp:discover\"\r\nST: wifi_bulb\r\n".encode('UTF-8'), ("239.255.255.250", 1982))

sock.recv(4096) # Message received successfully right away from first call
#[macro_use] extern crate log;
use socket2::{Socket, Domain, Type, Protocol, SockAddr};
use std::net::{SocketAddrV4, Ipv4Addr};

fn main() {
    env_logger::init();

    let yeelight_ip = Ipv4Addr::new(239, 255,255, 250);
    let yeelight_port: u16 = 1982;

    let socket = Socket::new(
        Domain::ipv4(), 
        Type::dgram(), 
        Some(Protocol::udp())
    ).expect("Failed to create socket!");

    socket.join_multicast_v4(
        &yeelight_ip, 
        &Ipv4Addr::UNSPECIFIED
    ).expect("Unable to join multicast broadcast!");

    let msg = "M-SEARCH * HTTP/1.1\r\nHOST: 239.255.255.250:1982\r\nMAN: \"ssdp:discover\"\r\nST: wifi_bulb\r\n";
    match socket.send_to(
        msg.as_bytes(), 
        &SockAddr::from(
            SocketAddrV4::new(
                yeelight_ip, 
                yeelight_port
            )
        )
    ) {
        Ok(bytes_sent) => {
            // Some lines of debug printing
        },
        Err(_) => eprintln!("Error broadcasting request for identification!")

    loop {
        let mut buffer = Vec::with_capacity(1024 * 1024);
        let received_bytes = socket
            .recv(&mut buffer)
            .expect("Unable to receive message!");
        debug!("Received {} bytes", received_bytes);
    }
}
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#include <iostream>
#include <string>
#include <array>

const std::string msg = "M-SEARCH * HTTP/1.1\r\nHOST: 239.255.255.250:1982\r\nMAN: \"ssdp:discover\"\r\nST: wifi_bulb\r\n";

int main() {
    auto yeelight_ip = in_addr();
    inet_aton("239.255.255.250", &yeelight_ip);

    unsigned int yeelight_port = 1982;

    auto sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
    std::cerr << "Created socket!" << std::endl;

    auto mreq = ip_mreq();
    mreq.imr_interface.s_addr = INADDR_ANY;
    mreq.imr_multiaddr.s_addr = yeelight_ip.s_addr;

    if (setsockopt(sock, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq)) != 0) {
        std::cerr << "Error joining multicast!" << std::endl;
        exit(errno);
    }
    else {
        std::cerr << "Joined multicast!" << std::endl;
    }

    auto addr = sockaddr_in();
    addr.sin_port = yeelight_port;
    for (int i = 0; i < 8; i++) addr.sin_zero[i] = 0;
    addr.sin_family = AF_INET;
    addr.sin_addr = yeelight_ip;

    if (sendto(sock, msg.c_str(), msg.length(), 0, (sockaddr*)(&addr), sizeof(addr)) == -1) {
        std::cerr << "Error broadcasting request for identification!" << std::endl;
        std::cerr << errno << std::endl;
    }
    else {
        std::cerr << "Sent broadcast request for idetification!" << std::endl;
    }

    while (true) {
        std::array<char, 1024 * 1024> buffer;
        auto bytes_read = recv(sock, buffer.data(), buffer.size(), 0);
        if (bytes_read == -1) {
            std::cerr << "Unable to receive message!" << std::endl;
            std::cerr << errno << std::endl;
        }
        else {
            std::cerr << "Read " << bytes_read << " bytes..." << std::endl;
            std::cout << std::string(buffer.begin(), buffer.begin() + bytes_read) << std::endl;
        }
    }

    return 0;
}

【问题讨论】:

  • 你的 Rust 程序循环接收:你的 Swift 程序接收一次。它们不是等价的。当您在 Rust 代码中打开接收到的缓冲区时,您忽略了消息长度。
  • 我看不出这些如何被视为问题。 Python 立即接收而 Rust 没有在循环中接收的事实应该更加令人担忧。另外,我忽略了多少消息长度?
  • String::from_utf8(buffer) 忽略 received_bytes 的值。我看不到循环中的读取如何fail 成为问题循环只能在您did 接收到某些内容时进行迭代,并且未能正确使用长度就足够了说明你认为你收到了 0 个字节。
  • 如果您查看最后一个 debug!() 调用,我正在打印 received_bytes,即 0。
  • 但是您没有正确制作message

标签: python c++ c sockets rust


【解决方案1】:

由于 Python 在后台很好地实现了转换,因此 Python 程序按预期工作。

C(++) 程序无法运行,因为端口的字节序错误。我忘了我必须使用addr.sin_port = htons(yeelight_port);

关于 Rust 程序,我忘记了 with_capacity 只分配了内部缓冲区,但 Vec 仍然是空的。解决方案,描述here,是调用buffer.resize(buffer.capacity(), 0);

【讨论】:

    猜你喜欢
    • 2021-11-30
    • 2020-08-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-11-09
    相关资源
    最近更新 更多