web-dev-qa-db-ja.com

データを受信するには、クライアントプログラムでUDPソケットをバインドする必要がありますか? (私は常にWSAEINVALを取得します)

Winsock経由でUDPソケット(_AF_INET_、_SOCK_DGRAM_、_IPPROTO_UDP_)を作成し、このソケットでrecvfromを試行していますが、常に-1を返し、WSAEINVALを取得します(10022)。どうして?

ポートをbind()すると、それは起こりませんが、クライアントのソケットをバインドするのは非常に不自由だと読んでいます。

サーバーにデータを送信していますが、サーバーが応答するか、少なくとも試行します。

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

これは少し前に書いた実際の例であり、クライアントでbind()を呼び出さなくても機能します。

_#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

UDPでは、bind()クライアントのソケットを使用する必要があります。これは、UDPがコネクションレスであるため、特定のポートにデータグラムを配信するプログラムをスタックが知る他の方法がないためです。

recvfrom()なしでbind()できる場合、本質的には、スタックに、そのコンピューターに送信されたすべてのUDPデータグラムをプログラムに提供するように依頼することになります。スタックはデータグラムを1つのプログラムのみに配信するため、これによりDNS、Windowsのネットワークコンピュータ、ネットワーク時刻の同期が壊れます。

ネット上のどこかでクライアントのバインディングが不十分であると読んでいたかもしれませんが、そのアドバイスはTCP接続にのみ適用されます。

40
Warren Young

sendtoの前にrecvfromを使用しているため、他のコードサンプルが機能します。 UDPソケットがバインドされておらず、sendtoまたはconnectが呼び出された場合、システムは自動的にバインドします。したがって、後のrecvfrom呼び出しは成功します。 recvfromはソケットをバインドしません。これは、この呼び出しがソケットが既にバインドされていることを予期しているためです。そうでない場合、エラーがスローされます。

29
Mecki

数週間前に同じ問題が発生しましたが、明示的なbind()呼び出しが必要かどうかを理解するのに次のコメントが役立ちました。

recvfrom関数(MSDN)

クライアントアプリケーションでは、明示的なバインドはお勧めしません。この関数を使用するクライアントアプリケーションの場合、ソケットはsendtoWSASendTo、またはWSAJoinLeaf

送信機能(MSDN)

ソケットが開かれると、setsockopt呼び出しが行われ、次にsendto呼び出しが行われると、Windows Socketsは暗黙的なbind関数呼び出しを実行します。ソケットがバインドされていない場合、一意の値がシステムによってローカル関連付けに割り当てられ、ソケットはバインド済みとしてマークされます。

3
Stradivari

ここ それは次のことを言っています:

パラメーター

s [in]:バインドされたソケットを識別する記述子。

...

戻り値

WSAEINVAL:ソケットがバインドでバインドされていないか、不明フラグが指定されているか、SO_OOBINLINEが有効になっているソケットにMSG_OOBが指定されているか、(バイトストリームスタイルのソケットのみ)lenがゼロまたは負でした。

覚えている限り、バインド呼び出しはスタックによって行われるため、UDPソケットにはバインドは必要ありません。 recvfrom呼び出しで使用されるソケットにバインドを要求するのはWindowsのことだと思います。