Winsock bind() failing with WSAEADDRNOTAVAIL for directed broadcast address

一个人想着一个人 提交于 2019-12-06 11:18:40

Much confusion here. I'll address it point by point for your edification, but if you just want working code, skip to the end.

// Winsock 2.2 is supported in XP

Actually, Winsock 2.2 goes back to NT 4 SP4, which dates it to 1998. Because of that, I wouldn't bother checking oWSAData.wVersion in the error case. There's basically no chance this is going to happen any more.

If broad portability is your goal, I'd target Winsock 1.1, which is all you need for the code you show, and will let the code build and run on anything that supports Winsock, even back to Windows 3.x.

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

Bad style. You should use PF_INET here instead of AF_INET. They have the same value, but you're not specifying an address family (AF) here, you're specifying a protocol family (PF). Also, the third parameter can safely be zero, because it's implied by the first two parameters. Again, it's just a style fix, not a functional fix.

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

Yup. Don't second-guess the docs and use bool here. Remember, Winsock is based on BSD sockets, and that goes back to the days before C++ existed.

m_oServer.sin_addr.S_un.S_addr = m_ulAddress;

You really shouldn't be digging into the internals of the sockaddr_in structure this way. The sockets API has a shortcut for that, which is shorter and hides some of the internal implementation details. It is:

m_oServer.sin_addr.s_addr = m_ulAddress;

Moving on...

if (bind(m_oSocket, ...

Although Remy is right that the bind() call isn't correct, you actually don't need it at all. You can depend on your system's routing layer to send the packet out the right interface. You don't need to "help" it with a bind() call.

you can't broadcast via anything but TCP by default (see the second paragraph in sendto's Remarks);

You've misunderstood what MSDN is telling you. When you see the term "TCP/IP", it often (but not always!) includes UDP. They're using it in that generic sense here.

The MSDN bit you point to talks about TCP/IP because Winsock was created in a world when TCP/IP had not yet won the network protocol wars. They're trying to restrict the discussion to TCP/IP (UDP, really) so you don't get the idea that what they're saying applies to other network transports supported by Winsock stacks in the early days: NetBIOS, IPX, DECNet...

In fact, you can only broadcast (or multicast) using UDP sockets. TCP is point-to-point, only.

One thing that seems dodgy to me is hard-casting the SOCKET m_oSocket to sockaddr,

That's also part of the multiple network transport support in sockets. In addition to sockaddr_in, there's sockaddr_ipx for IPX, sockaddr_dn for DECnet... Winsock is a C API, not a C++ API, so we can't subclass sockaddr and pass a reference to the base class, or create function overloads for each of the variations. This trick of casting structures is a typical C way to get a kind of polymorphism.

Here's a working example, which builds with MinGW, g++ 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;
}

It sends to 255.255.255.255 (INADDR_BROADCAST) by default, but if you pass a directed broadcast IP (such as your 192.168.202.255 value) as the first parameter, it will use that instead.

You should not bind() to a broadcast IP address. You need to bind() to an individual network adapter IP instead. If you want to send out a broadcast message, you bind() to the adapter that is going to send the broadcast, and then sendto() the broadcast IP. If you want to receive a broadcast message, you bind() to the specific adapter whose IP matches the broadcast IP being sent to.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!