【问题标题】:Winsock bind() failing with WSAEADDRNOTAVAIL for directed broadcast addressWinsock bind() 失败,WSAEADDRNOTAVAIL 用于定向广播地址
【发布时间】:2013-01-08 18:59:50
【问题描述】:

我正在设置一个 UDP 套接字并尝试将应该是有效的网络广播地址绑定到它 (192.168.202.255 : 23456),但 bind 失败并出现错误 10049, WSAEADDRNOTAVAIL。如果我使用 localhost 广播地址 127.0.0.255 就成功了。

WSAEADDRNOTAVAIL's documentation 表示“请求的地址在其上下文中无效。这通常是由于尝试绑定到对本地计算机无效的地址。这也可能是由于 connect、sendto、WSAConnect、 WSAJoinLeaf 或 WSASendTo,当远程地址或端口对远程计算机无效时(例如,地址或端口 0)。但我认为这个地址 192.168.202.255 应该是一个有效的广播地址,因为运行 ipconfig 时出现以下条目:

可能是什么问题?

代码

我是 Winsock 编程的新手,可能会犯一个基本错误,但我找不到。我到目前为止的代码是:

m_ulAddress = ParseIPAddress(strAddress);
// Winsock 2.2 is supported in XP
const WORD wVersionRequested = MAKEWORD(2, 2);
WSADATA oWSAData;
const int iError = WSAStartup(wVersionRequested, &oWSAData);
if (iError != 0) {
    PrintLine(L"Error starting the network connection: WSAStartup error " + IntToStr(iError));
} else if (LOBYTE(oWSAData.wVersion) != 2 || HIBYTE(oWSAData.wVersion) != 2) {
    PrintLine(L"Error finding version 2.2 of Winsock; got version " + IntToStr(LOBYTE(oWSAData.wVersion)) + L"." + IntToStr(HIBYTE(oWSAData.wVersion)));
} else {
    m_oSocket = socket(AF_INET, SOCK_DGRAM /*UDP*/, IPPROTO_UDP);
    if (m_oSocket == INVALID_SOCKET) {
        PrintLine(L"Error creating the network socket");
    } else {
        // Socket needs to be able to send broadcast messages
        int iBroadcast = true; // docs say int sized, but boolean values
        if (setsockopt(m_oSocket, SOL_SOCKET, SO_BROADCAST, (const char*)&iBroadcast, sizeof(iBroadcast)) != 0) {
            PrintLine(L"Error setting socket to allow broadcast addresses; error " + IntToStr(WSAGetLastError()));
        } else {
            m_oServer.sin_family = AF_INET;
            m_oServer.sin_port = m_iPort;
            m_oServer.sin_addr.S_un.S_addr = m_ulAddress;

            // !!! This is the failing call
            if (bind(m_oSocket, (sockaddr*)&m_oServer, sizeof(m_oServer)) == -1) {
                PrintLine(L"Error binding address " + String(strAddress.c_str()) + L":" + IntToStr(m_iPort) + L" to socket; error " + IntToStr(WSAGetLastError()));
            } else {
                m_bInitialisedOk = true;
            }
        }
    }
}

评论

ParseIPAddressinet_addr 的包装;检查m_oServer.sin_addr.S_un.S_addr 的值似乎是正确的。 m_oSocketSOCKET。我添加了对setsockopt 的调用,因为默认情况下除了 TCP 之外您不能通过其他任何方式进行广播(请参阅second paragraph in sendto's Remarks);这个电话没有任何区别。 PrintLine 是控制台输出的包装器。奇怪的 String / c_str() 强制转换正在与 C++ wstrings 转换为 VCL Unicode 字符串,因为我正在使用 C++ Builder 及其 VCL 库。 IP 地址是一个窄 (char) 字符串。

sendto documentation 声明“如果打开套接字,进行 setsockopt 调用,然后进行 sendto 调用,Windows 套接字将执行隐式绑定函数调用。”这意味着根本不需要bind。如果我省略了通话,则像这样调用sendto

const int iLengthBytes = strMessage.length() * sizeof(char); // Narrow string
    const int iSentBytes = sendto(m_oSocket, strMessage.c_str(), iLengthBytes, 0, (sockaddr*)&m_oServer, sizeof(m_oServer));
    if (iSentBytes != iLengthBytes) {
        PrintLine(L"Error sending network message; error: " + IntToStr(WSAGetLastError()));

失败,错误 10047,WSAEAFNOSUPPORT, "Address family not supported by protocol family."

netsh winsock show catalog(mentioned at the bottom of socket's Remarks) 的输出很长,但确实包含几个提到 UDP 和 IPv4 的条目。

一个可能的复杂情况是它在 VMWare Fusion 主机上运行; Fusion 确实有一个奇怪的网络设置。我还配置了一个 Cisco VPN 并运行回我的办公室。连接和断开连接没有区别。

对我来说似乎很狡猾的一件事是将SOCKET m_oSocket 硬转换为sockaddr,但是当我阅读示例时,这似乎是 Winsock 编程的正常做法。可能需要阅读它,因为底层解释取决于协议族。这似乎是一个潜在的错误来源,但我不知道如何避免它。

有什么想法吗?我被难住了:)

设置

  • 在 VMWare Fusion 4.1.3 上运行的 Windows 7 Pro
  • 程序编译为 32 位,Embarcadero C++ Builder 2010
  • 该程序只是一个控制台程序

【问题讨论】:

    标签: networking network-programming udp winsock c++builder


    【解决方案1】:

    这里有很多混乱。我将逐点解决它以供您启发,但如果您只想要工作代码,请跳到最后。

    // Winsock 2.2 is supported in XP

    其实Winsock 2.2 goes back to NT 4 SP4,也就是dates it to 1998。因此,我不会在错误情况下检查oWSAData.wVersion。基本上不会再发生这种情况了。

    如果广泛的可移植性是您的目标,我会以 Winsock 1.1 为目标,即all you need 用于您展示的代码,并将让代码在任何支持 Winsock 的设备上构建和运行,甚至回到 Windows 3.x。

    m_oSocket = socket(AF_INET, SOCK_DGRAM /*UDP*/, IPPROTO_UDP);

    风格不好。您应该在这里使用PF_INET 而不是AF_INET。它们具有相同的值,但您没有在此处指定地址族 (AF),而是指定了协议族 (PF)。此外,第三个参数可以安全地为零,因为前两个参数隐含了它。同样,这只是样式修复,而不是功能修复。

    int iBroadcast = true; // docs say int sized, but boolean values

    是的。不要猜测文档并在此处使用bool。请记住,Winsock 是基于 BSD 套接字的,这可以追溯到 C++ 存在之前的日子。

    m_oServer.sin_addr.S_un.S_addr = m_ulAddress;

    您真的不应该以这种方式深入了解sockaddr_in 结构的内部。 sockets API 有一个快捷方式,它更短并且隐藏了一些内部实现细节。它是:

    m_oServer.sin_addr.s_addr = m_ulAddress;

    继续……

    if (bind(m_oSocket, ...

    虽然 Remy 认为 bind() 调用不正确是正确的,但您实际上根本不需要它。您可以依靠系统的路由层将数据包从正确的接口发送出去。您无需通过bind() 呼叫“帮助”它。

    默认情况下,您不能通过 TCP 以外的任何方式进行广播(请参阅 sendto 备注中的第二段);

    您误解了 MSDN 告诉您的内容。当您看到术语“TCP/IP”时,它通常(但不总是!)包括 UDP。他们在这里使用它的一般意义。

    您指向的 MSDN 位谈论 TCP/IP,因为 Winsock 是在 TCP/IP 尚未赢得网络协议战争的世界中创建的。他们试图将讨论限制在 TCP/IP(实际上是 UDP)上,所以你不明白他们所说的适用于早期 Winsock 堆栈支持的其他网络传输:NetBIOS、IPX、DECNet ...

    事实上,您可以使用 UDP 套接字进行广播(或多播)。 TCP 只是点对点的。

    对我来说似乎很狡猾的一件事是将 SOCKET m_oSocket 强制转换为 sockaddr,

    这也是套接字中多网络传输支持的一部分。除了sockaddr_in,还有sockaddr_ipx代表IPX,sockaddr_dn代表DECnet... Winsock是C API,不是C++ API,所以我们不能继承sockaddr并传递对基类的引用,或为每个变体创建函数重载。这种铸造结构的技巧是一种典型的 C 方法来获得一种多态性。

    这是一个工作示例,它使用 MinGWg++ foo.cpp -o foo.exe -lwsock32 构建:

    #include <winsock.h>
    #include <iostream>
    #include <string.h>
    
    using namespace std;
    
    
    int main(int argc, char* argv[])
    {
        WSADATA wsa;
        if (WSAStartup(MAKEWORD(1, 1), &wsa)) {
            cerr << "Failed to init Winsock!" << endl;
            return 1;
        }
    
        // Get datagram socket to send message on
        SOCKET sd = socket(PF_INET, SOCK_DGRAM, 0);
        if (sd < 0) {
            cerr << "socket() failed: " << WSAGetLastError() << endl;
            return 1;
        }
    
        // Enable broadcasts on the socket
        int bAllow = 1;
        if (setsockopt(sd, SOL_SOCKET, SO_BROADCAST, (char*)&bAllow,
                sizeof(bAllow)) < 0) {
            cerr << "setsockopt() failed: " << WSAGetLastError() << endl;
            closesocket(sd);
            return 1;
        }
    
        // Broadcast the request
        string msg = "Hello, world!";
        const int kMsgLen = msg.length();
        struct sockaddr_in sin;
        memset(&sin, 0, sizeof(sin));
        const uint16_t kPort = 54321;
        sin.sin_port = htons(kPort);
        sin.sin_family = AF_INET;
        if (argc == 1) {
            sin.sin_addr.s_addr = INADDR_BROADCAST;
        }
        else if ((sin.sin_addr.s_addr = inet_addr(argv[1])) == INADDR_NONE) {
            cerr << "Couldn't parse IP '" << argv[1] << "'!" << endl;
        }
        int nBytes = sendto(sd, msg.c_str(), kMsgLen, 0,
                 (sockaddr*)&sin, sizeof(struct sockaddr_in));
        closesocket(sd);
    
        // How well did that work out, then?
        if (nBytes < 0) {
            cerr << "sendto() IP " << inet_ntoa(sin.sin_addr) <<
                    " failed" << WSAGetLastError() << endl;
            return 1;
        }
        else if (nBytes < kMsgLen) {
            cerr << "WARNING: Short send, " << nBytes << " bytes!  "
                    "(Expected " << kMsgLen << ')' << endl;
            return 1;
        }
        else {
            cerr << "Sent " << kMsgLen << "-byte msg to " <<
                    inet_ntoa(sin.sin_addr) << ':' << kPort << '.' << endl;
        }
    
        return 0;
    }
    

    默认情况下它发送到 255.255.255.255 (INADDR_BROADCAST),但如果您将定向广播 IP(例如您的 192.168.202.255 值)作为第一个参数传递,它将使用它。

    【讨论】:

    • 谢谢沃伦 - 这是一个非常有用的答案!我对混淆并不感到惊讶 - 这是我第一次使用 Winsock - 虽然我认为我在阅读 MSDN 时做得很好:/ 我根据你的建议进行了更改,删除了 bind,并添加了 htons(port)(我忘记了网络字节之前订购端口号!)现在一切正常。感谢您的帮助!
    • 关于WS2.2的评论只是这个程序至少需要在XP上运行;我不想硬编码一个没有注释的版本号。我认为它甚至可以用于带有一些附加组件的 Win95!
    【解决方案2】:

    您不应将bind() 发送到广播 IP 地址。您需要 bind() 改为单独的网络适配器 IP。如果你想发送一个广播消息,你 bind() 到要发送广播的适配器,然后 sendto() 广播 IP。如果您想接收广播消息,请bind() 到其 IP 与要发送到的广播 IP 匹配的特定适配器。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2017-04-21
      • 1970-01-01
      • 2011-02-28
      • 2019-11-25
      • 2011-08-27
      • 1970-01-01
      • 2011-01-06
      • 2011-02-15
      相关资源
      最近更新 更多