web-dev-qa-db-fra.com

Dois-je lier un socket UDP dans mon programme client pour recevoir des données? (Je reçois toujours WSAEINVAL)

Je crée un socket UDP (AF_INET, SOCK_DGRAM, IPPROTO_UDP) Via Winsock et j'essaie de recvfrom sur ce socket, mais il renvoie toujours -1 et Je reçois WSAEINVAL (10022). Pourquoi?

Lorsque je bind() le port, cela ne se produit pas, mais j'ai lu qu'il est très boiteux de lier le socket du client.

J'envoie des données à mon serveur, qui répond, ou du moins essaie.

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

Ceci est un exemple de travail que j'ai écrit il y a quelques instants, et cela fonctionne sans l'appel à bind() dans le client:

#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;
}
27
Incubbus

Avec UDP, vous devez bind() le socket dans le client car UDP est sans connexion, il n'y a donc pas d'autre moyen pour la pile de savoir à quel programme envoyer des datagrammes pour un port particulier.

Si vous pouviez recvfrom() sans bind(), vous demanderiez essentiellement à la pile de donner à votre programme tous les datagrammes UDP envoyés à cet ordinateur. Étant donné que la pile fournit des datagrammes à un seul programme, cela romprait le DNS, le voisinage réseau de Windows, la synchronisation de l'heure du réseau ...

Vous avez peut-être lu quelque part sur le net que la liaison dans un client est boiteuse, mais ce conseil ne s'applique qu'aux connexions TCP.

40
Warren Young

Votre autre exemple de code fonctionne car vous utilisez sendto avant recvfrom. Si un socket UDP n'est pas lié et que sendto ou connect sont appelés dessus, le système le liera automatiquement pour vous et donc l'appel recvfrom plus tard réussira. recvfrom ne liera pas de socket, car cet appel s'attend à ce que le socket soit déjà lié, ou une erreur sera levée.

29
Mecki

J'ai eu le même problème il y a quelques semaines, les remarques suivantes m'ont aidé à comprendre si un appel bind () explicite est nécessaire:

fonction recvfrom (MSDN)

La liaison explicite est déconseillée pour les applications clientes. Pour les applications clientes utilisant cette fonction, le socket peut être lié implicitement à une adresse locale via sendto , WSASendTo , ou WSAJoinLeaf .

fonction sendto (MSDN)

Remarque Si un socket est ouvert, un appel setsockopt est effectué, puis un sendto l'appel est effectué, Windows Sockets effectue un appel de fonction implicite bind . Si le socket n'est pas lié, des valeurs uniques sont attribuées à l'association locale par le système, puis le socket est marqué comme lié.

3
Stradivari

Ici il dit ce qui suit:

Paramètres

s [in]: descripteur identifiant une socket liée.

...

Valeur de retour

WSAEINVAL: le socket n'a pas été lié avec bind, ou un indicateur inconnu a été spécifié, ou MSG_OOB a été spécifié pour un socket avec SO_OOBINLINE activé, ou (pour les sockets de style flux d'octets uniquement) len était zéro ou négatif.

Pour autant que je me souvienne, la liaison n'est pas requise pour un socket UDP car un appel de liaison est effectué pour vous par la pile. Je suppose que c'est une chose Windows d'exiger une liaison sur un socket utilisé dans un appel recvfrom.

1