2010-06-16 16 views
19

Estoy creando un socket UDP (AF_INET, SOCK_DGRAM, IPPROTO_UDP) a través de Winsock y tratando de recvfrom en este socket, pero siempre devuelve -1 y obtengo WSAEINVAL (10022). ¿Por qué?¿Debo vincular un socket UDP en mi programa cliente para recibir datos? (Siempre obtengo WSAEINVAL)

Cuando bind() el puerto, eso no sucede, pero he leído que es muy difícil vincular el socket del cliente.

Estoy enviando datos a mi servidor, que responde, o al menos lo intenta.

Inc::STATS CConnection::_RecvData(sockaddr* addr, std::string &strData) 
{ 
    int ret;   // return code 
    int len;   // length of the data 
    int fromlen;  // sizeof(sockaddr) 
    char *buffer;  // will hold the data 
    char c; 

    //recv length of the message 
    fromlen = sizeof(sockaddr); 
    ret = recvfrom(m_InSock, &c, 1, 0, addr, &fromlen); 
    if(ret != 1) 
    { 
#ifdef __MYDEBUG__ 
     std::stringstream ss; 
     ss << WSAGetLastError(); 
     MessageBox(NULL, ss.str().c_str(), "", MB_ICONERROR | MB_OK); 
#endif 
     return Inc::ERECV; 
    } 
    ... 

Este es un ejemplo de trabajo que escribí hace unos momentos, y funciona sin la llamada a bind() en el cliente:

#pragma comment(lib, "Ws2_32.lib") 

#define WIN32_LEAN_AND_MEAN 

#include <WS2tcpip.h> 
#include <Windows.h> 
#include <iostream> 

using namespace std; 

int main() 
{ 
    SOCKET sock; 
    addrinfo* pAddr; 
    addrinfo hints; 
    sockaddr sAddr; 
    int fromlen; 
    const char czPort[] = "12345"; 
    const char czAddy[] = "some ip"; 

    WSADATA wsa; 
    unsigned short usWSAVersion = MAKEWORD(2,2); 

    char Buffer[22] = "TESTTESTTESTTESTTEST5"; 
    int ret; 

    //Start WSA 
    WSAStartup(usWSAVersion, &wsa); 

    //Create Socket 
    sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); 

    //Resolve host address 
    memset(&hints, 0, sizeof(hints)); 
    hints.ai_family = AF_INET; 
    hints.ai_protocol = IPPROTO_UDP; 
    hints.ai_socktype = SOCK_DGRAM; 

    if(getaddrinfo(czAddy, czPort, &hints, &pAddr)) 
    { 
     std::cerr << "Could not resolve address...\n"; 
     std::cin.get(); 
     return 1; 
    } 

    //Start Transmission 
    while(1) 
    { 
     ret = sendto(sock, Buffer, sizeof(Buffer), 0, pAddr->ai_addr, 
        pAddr->ai_addrlen); 
     if(ret != sizeof(Buffer)) 
     { 
      std::cerr << "Could not send data\n"; 
      std::cin.get(); 
      return 1; 
     } 

     fromlen = sizeof(SOCKADDR); 
     ret = recvfrom(sock, Buffer, sizeof(Buffer), 0, &sAddr, &fromlen); 
     if(ret != sizeof(Buffer)) 
     { 
      std::cout << "Could not receive data - error: " << 
          WSAGetLastError() << std::endl; 
      std::cin.get(); 
      return 1; 
     } 

     Buffer[ret-1] = '\0'; 
     std::cout << "Received: " << Buffer << std::endl; 
    } 
    return 0; 
} 

Respuesta

31

Con UDP, tiene que bind() el socket en el cliente porque UDP no tiene conexión, por lo que no hay otra forma para que la pila sepa a qué programa entregar los datagramas para un puerto en particular.

Si pudiera recvfrom() sin bind(), le pediría a la pila que le proporcione a su programa todos los datagramas UDP enviados a esa computadora. Como la pila entrega datagramas a un solo programa, esto rompería el DNS, el Entorno de red de Windows, la sincronización de tiempo de la red ...

Puede haber leído en algún lugar de la red que el enlace en un cliente es cojo, pero ese consejo solo se aplica a las conexiones TCP.

+0

Bueno ... Pero estoy creando un puerto, que estoy recibiendo de. No le estoy diciendo al SO que me envíe un mensaje, cuando en cualquier puerto hay datos ... Estoy haciendo una llamada para solicitarlo ... Como puedo obtener todos los datos en el puerto , esto debería ser suficiente, en mi opinión – Incubbus

+1

Sin ver tu código, solo puedo adivinar, pero creo que estás poniendo el número de puerto en la estructura que pasas para el parámetro "de". Eso no ayuda. Ese parámetro no es leído por la pila, solo se escribe para que su programa pueda saber quién envió el datagrama. Observe que es opcional, por lo que puede pasar 0 aquí, así que de nuevo está en la misma situación que antes, donde la pila tiene que tener alguna forma de saber a qué programa entregar un datagrama basándose únicamente en el número de puerto del que vino en adelante Eso es lo que bind() hace: asocia un programa con un puerto. –

+0

http://codepad.org/4ZmoP4K5 <- esta es la pieza del código responsable (todavía no sé cómo mostrar el código aquí, así que lo pegaré en el teclado) ... el puntero addr apunta a una valid sockaddr *, y m_InSock se inicializó con éxito ... (Como mencioné anteriormente, ya envié datos que se han recibido ...) Pero estoy seguro de que debería funcionar para el cliente sin el bind() , porque probé y ejemplo de un tutorial, que funcionó ... – Incubbus

1

Here que dice lo siguiente:

Parámetros

s [in]: Un descriptor que identifica un socket encuadernado.

...

Valor de retorno

WSAEINVAL: El conector no ha sido atado con bind, o se especifica una bandera desconocida, o MSG_OOB fue especificada para una toma de corriente con SO_OOBINLINE activado, o (a byte sockets de estilo de secuencia solamente) len era cero o negativo.

Por lo que recuerdo, el enlace no es necesario para un socket UDP porque la pila realiza una llamada de enlace. Supongo que es algo de Windows requerir un enlace en un socket utilizado en una llamada de recvfrom.

+0

Sí, estoy de acuerdo contigo ... Me pregunto, por qué eso es, también ... Nunca tuve que hacerlo en los últimos programas de Windows que hice ... – Incubbus

+0

¿Se puede publicar algún código? Quizás hay otro parámetro de recvfrom que está causando esto. ¿Respónde también qué versión de Windows estás utilizando? –

+0

Inc :: STATS CConnection :: _ RecvData (sockaddr * addr, std :: string strData) { \t int ret, len, fromlen; \t \t // código de retorno/longitud de los datos/sizeof (sockaddr) \t char * buffer; \t \t // contendrá los datos \t char c; \t // longitud de recv del mensaje \t fromlen = sizeof (sockaddr); \t ret = recvfrom (m_InSock, & c, 1, 0, addr, y fromlen); \t if (ret! = 1) \t { – Incubbus

25

Su otro ejemplo de código funciona porque está usando sendto antes de recvfrom. Si un socket UDP está libre y se le llama a sendto o connect, el sistema lo vinculará automáticamente y, por lo tanto, la llamada recvfrom más adelante tendrá éxito. recvfrom no enlazará un socket, ya que esta llamada espera que el socket ya se haya enlazado, o se producirá un error.

+0

¿Estás seguro de que connect también intentará enlazar el socket UDP no vinculado? Otro hilo SO https://stackoverflow.com/questions/4487002/problem-using-connect-send-recv-with-udp-sockets/19368670#19368670 indica que se requiere un enlace explícito para que el socket UDP reciba datos. – FaceBro

+0

@FaceBro Sí, estoy seguro, ya que la forma típica de averiguar qué dirección IP local utilizará un sistema al enviar datos a una dirección IP de destino específica es la siguiente: crear un nuevo socket UDP, conectarlo a la IP de destino, consultar su IP local. Y consultar el IP local solo funciona cuando un socket está vinculado ya que el enlace es la operación que le da a un socket una IP local. – Mecki

2

que tenían el mismo problema hace un par de semanas, las siguientes observaciones me ayudaron a entender si una explícita de vinculación() es necesario:

recvfrom function (MSDN)

explícita de unión se desanima para las aplicaciones cliente.Para las aplicaciones cliente que usan esta función, el socket puede vincularse implícitamente a una dirección local a través de sendto, WSASendTo, o WSAJoinLeaf.

sendto function (MSDN)

Nota Si se abre un socket, se realiza una llamada setsockopt, y luego se hace una llamada sendto , Windows Sockets realiza una implícita unen llamada a la función. Si el socket está libre, el sistema asigna valores únicos a la asociación local y el socket se marca como vinculado.

+0

Error: la primera observación dice "Se desaconseja la vinculación explícita para las aplicaciones cliente". ¿Por qué esto? ¿Cuál es la solución correcta en este caso? Si tengo un cliente que necesita conectarse y comenzar a recibir datos sin enviar un mensaje, ¿qué debo hacer? – Magallo

+0

Ha pasado un tiempo, pero he aquí una [respuesta] (https://www.gamedev.net/topic/252865-recvfrom-and-bind-questions/) a una pregunta similar que encontré en ese momento. – Stradivari

Cuestiones relacionadas