Winsock bind()与WSAEADDRNOTAVAIL一起失败,用于定向广播地址

时间:2013-01-08 18:59:51

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

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

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

ipconfig indicates local IP is 192.168.202.213

可能是什么问题?

代码

我是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是控制台输出的包装器。由于我使用的是C ++ Builder及其VCL库,奇数String / c_str()强制转换为从C ++ wstrings转换为VCL Unicode字符串。 IP地址是一个窄(字符)字符串。

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
  • 该程序使用Embarcadero C++ Builder 2010编译为32位。
  • 该程序仅限控制台程序

2 个答案:

答案 0 :(得分:5)

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

答案 1 :(得分:4)

这里很困惑。我会逐一为您的启发解决这个问题,但如果您只想要工作代码,请跳到最后。

  

// Winsock 2.2 is supported in XP

实际上是Winsock 2.2 goes back to NT 4 SP4dates 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结构的内部结构。套接字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值)作为第一个参数传递,它将使用它代替。