Winsock bind () a falhar com o WSAEADDRNOTAVAIL para o endereço de difusão directa

estou a configurar um socket UDP e tentar ligar o que deve ser um válido endereço de broadcast para ele (192.168.202.255 : 23456), mas bind falhar com o erro 10049, WSAEADDRNOTAVAIL. Se eu usar um endereço de transmissão local, 127.0.0.255, será bem sucedido.

WSAEADDRNOTAVAIL'a documentação do s diz que" o endereço solicitado não é válido no seu contexto. Isto normalmente resulta de uma tentativa de vincular a um endereço que não é válido para o computador local. Isto também pode resultar from connect, sendto, WSAConnect, WSAJoinLeaf, or WSASendTo when the remote address or port is not valid for a remote computer (for example, address or port 0)."Mas eu acho que este endereço, 192.168.202.255, deve ser um endereço de difusão válido por causa da seguinte entrada ao executar ipconfig:

ipconfig indicates local IP is 192.168.202.213

Qual é o problema?

Código

Sou novo na programação Winsock e estou provavelmente a cometer um erro elementar, mas não consigo encontrá-lo. O o código que tenho até agora é:

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;
            }
        }
    }
}

comentários

ParseIPAddress é um invólucro em torno de inet_addr; inspeccionando o valor de {[8] } parece estar correcto. m_oSocket é um SOCKET. Adicionei a chamada a setsockopt uma vez que não pode transmitir nada além do TCP por omissão (ver o segundo parágrafo nas observações sendto; esta chamada não faz qualquer diferença. PrintLine é uma embalagem para a saída da consola. Os moldes odd String / c_str() estão a converter de e para wstrings C++ para strings Unicode VCL, uma vez que I am usando C++ Builder e suas bibliotecas VCL. O endereço IP é uma cadeia estreita (char).

sendto documentation states that " If a socket is opened, a setsockopt call is made, and then a sendto call is made, Windows Sockets performs an implícito bind function call."Isto implica que bind não é necessário de todo. Se eu omitir a chamada, então ligarei assim:

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()));

falha com o erro 10047, WSAEAFNOSUPPORT, "família de endereços não suportada pelo protocolo familia."

a produção de netsh winsock show catalog (mencionado no final das observações de socket) é longo, mas inclui várias entradas que mencionam o UDP e o IPv4.

Uma possível complicação é que isto está a correr num servidor de fusão VMWare; O Fusion tem uma configuração estranha para redes. Também tenho um VPN da Cisco configurado a correr para o meu escritório. Ligar e desligar isto não faz diferença.

Uma coisa que me parece duvidosa é a dureza do ... m_oSocket para sockaddr, mas esta parece ser a prática normal para a programação Winsock quando eu tenho lido exemplos. A leitura pode ser necessária, uma vez que a interpretação subjacente depende da família do protocolo. Parece uma potencial fonte de erro, mas não sei como evitá-lo.

Alguma ideia? Estou perplexo.

configuração

  • O Windows 7 Pro está a funcionar na fusão VMware 4.1.3
  • o programa é compilado como 32 bits com Embarcadero C++ Builder 2010.
  • o programa é apenas um programa de consola
Author: Warren Young, 2013-01-08

2 answers

Muita confusão aqui. Vou tratar disso ponto por ponto para a tua edificação, mas se só queres código de trabalho, salta para o fim.

// Winsock 2.2 is supported in XP

Na verdade, Winsock 2.2 remonta a NT 4 SP4, que Data de 1998. Por causa disso, eu não me preocuparia em Verificar {[[2]} no caso de erro. Basicamente, não há hipótese de isto voltar a acontecer.

Se a portabilidade ampla for o teu objectivo, eu apontaria para Winsock 1.1, que é ... Tudo o que você precisa {[[31]} para o código que você mostra, e vai deixar o código construir e executar em qualquer coisa que suporte Winsock, mesmo de volta para o Windows 3.x.

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

Mau estilo. Deve utilizar PF_INET aqui em vez de AF_INET. Eles têm o mesmo valor, mas você não está especificando uma família de endereços (AF) aqui, você está especificando uma família de protocolos (PF). Além disso, o terceiro parâmetro pode ser zero com segurança, porque está implícito pelos dois primeiros parâmetros. Mais uma vez, é apenas um estilo conserta, não uma conserta funcional.

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

Sim. Não duvides dos médicos e usa o bool aqui. Lembre-se, Winsock é baseado em bases BSD, e isso remonta aos dias antes do C++ existir.

m_oServer.sin_addr.S_un.S_addr = m_ulAddress;

Não devias estar a investigar os internos da estrutura desta maneira. A API sockets tem um atalho para isso, que é mais curto e esconde alguns dos detalhes de implementação interna. É:

m_oServer.sin_addr.s_addr = m_ulAddress;

Continuando...

if (bind(m_oSocket, ...

Apesar do Remy ter razão em dizer que a chamada não está correcta, não precisas mesmo dela. Você pode depender da camada de roteamento do seu sistema para enviar o pacote para fora da interface correta. Não precisas de "ajudar"com uma chamada.

Só pode transmitir por defeito através do TCP (ver o segundo parágrafo nas observações do sendto);

Percebeste mal o que eu disse. A MSDN está a dizer-te. Quando você vê o termo "TCP / IP", muitas vezes (mas nem sempre!) inclui UDP. Estão a usá-lo nesse sentido genérico.

O bit MSDN que aponta para falar sobre TCP / IP porque o Winsock foi criado num mundo em que o TCP/IP ainda não tinha vencido as guerras do protocolo de rede. Eles estão tentando restringir a discussão para TCP / IP (UDP, na verdade) para que você não tenha a idéia de que o que eles estão dizendo se aplica a outros transportes de rede suportados por pilhas Winsock nos primeiros dias: NetBIOS, IPX, DECNet...

Na verdade, você pode apenas transmitir (ou multicast) usando 'sockets' UDP. TCP é ponto-a-ponto, apenas.

Uma coisa que me parece duvidosa é lançar o 'SOCKET m_oSocket' para sockaddr.
Isso também faz parte do suporte de transporte de várias redes em tomadas. Além de sockaddr_in, há sockaddr_ipx para IPX, sockaddr_dn para DECnet... Winsock é uma API C, Não uma API C++, por isso não podemos subclassar sockaddr e passar uma referência ao classe base, ou criar sobrecarga de função para cada uma das variações. Este truque de estruturas de fundição é uma maneira típica de obter um tipo de polimorfismo. Aqui está um exemplo de trabalho, que se constrói com o 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;
}

Ele envia para 255.255.255.255 (INADDR_BROADCAST) por padrão, mas se você passar um IP de transmissão dirigida (como o seu valor 192.168.202.255) como o primeiro parâmetro, ele vai usar isso em vez disso.

 4
Author: Warren Young, 2013-01-08 23:51:09

Você não deve {[[0]} para um endereço IP de transmissão. Você precisa de {[[0]} para um IP Adaptador de rede individual. Se você quiser enviar uma mensagem de Transmissão, você bind() para o adaptador que vai enviar a transmissão, e então sendto() o IP de transmissão. Se quiser receber uma mensagem de Transmissão, você bind() para o adaptador específico para o qual o IP corresponde ao IP de transmissão para o qual está a ser enviado.

 5
Author: Remy Lebeau, 2013-01-08 22:18:29