【问题标题】:Do I have to bind a UDP socket in my client program to receive data? (I always get WSAEINVAL)我是否必须在客户端程序中绑定 UDP 套接字才能接收数据? (我总是得到 WSAEINVAL)
【发布时间】:2010-06-16 20:44:28
【问题描述】:

我正在通过 Winsock 创建一个 UDP 套接字(AF_INETSOCK_DGRAMIPPROTO_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;
}

【问题讨论】:

    标签: c++ winsock


    【解决方案1】:

    使用 UDP,您必须 bind() 客户端中的套接字,因为 UDP 是无连接的,因此堆栈没有其他方法可以知道将数据报传递给特定端口的哪个程序。

    如果您可以在没有bind() 的情况下使用recvfrom(),那么您实际上是在要求堆栈为您的程序提供所有发送到该计算机的UDP 数据报。由于堆栈只将数据报传递给一个程序,这会破坏 DNS、Windows 的网络邻居、网络时间同步......

    您可能在网上某处读到客户端中的绑定很糟糕,但该建议仅适用于 TCP 连接。

    【讨论】:

    • 嗯...但是我正在创建一个端口,我从中接收。我不是告诉操作系统向我发送消息,当任何端口上的任何地方都有任何数据时......我正在打电话询问......因为我能够获取端口上的所有数据,这应该够了,在我眼里
    • 没有看到您的代码,我只能猜测,但我认为您将端口号放入您为“from”参数传递的结构中。那没有帮助。该参数不被堆栈读取,仅被写入,因此您的程序可以知道谁发送了数据报。注意它是可选的,所以你可以在这里传递 0,所以你又回到了和以前一样的情况,堆栈必须有某种方法来知道将数据报传递给哪个程序,仅基于它来的端口号在上。这就是 bind() 的作用:它将程序与端口相关联。
    • 该代码中没有任何地方 sn-p 是您希望堆栈为其提供数据报的 UDP 端口号。您需要调用 bind() 来建立 UDP 端口号和您的程序之间的关联。
    • 我不会说 sendto() 会绑定套接字,但确实不需要 bind() 也可以使用 sendto()。 recvfrom() 是不同的,因为 UDP 的无连接性质。本质上,使用 recvfrom() 的程序是一个“服务器”,即使您将其视为客户端,因为与 TCP 服务器一样,调用 recvfrom() 的程序必须等待来自任何可能的发送者的数据。跨度>
    【解决方案2】:

    您的其他代码示例有效,因为您在recvfrom 之前使用了sendto。如果 UDP 套接字未绑定,并且在其上调用了 sendtoconnect,系统将自动为您绑定它,因此稍后的 recvfrom 调用将成功。 recvfrom 不会绑定套接字,因为这个调用期望套接字已经被绑定,否则会抛出错误。

    【讨论】:

    • 您确定 connect 也会尝试绑定未绑定的 UDP 套接字吗?另一个SO线程stackoverflow.com/questions/4487002/…表示UDP socket接收数据需要显式绑定。
    • @FaceBro 是的,我敢肯定,找出系统在向特定目标 IP 地址发送数据时将使用哪个本地 IP 地址的典型方法如下:创建一个新的 UDP 套接字,将其连接到dest IP,查询其本地IP。并且仅当绑定套接字时查询本地 IP 才有效,因为绑定是为套接字提供本地 IP 的操作。
    【解决方案3】:

    几周前我遇到了同样的问题,以下评论帮助我了解是否需要显式调用 bind():

    recvfrom function (MSDN)

    不鼓励客户端应用程序使用显式绑定。对于使用此函数的客户端应用程序,套接字可以通过 sendtoWSASendToWSAJoinLeaf 隐式绑定到本地地址。

    sendto function (MSDN)

    注意如果打开套接字,调用 setsockopt,然后调用 sendto,Windows 套接字执行隐式 bind 函数调用。 如果套接字未绑定,系统会为本地关联分配唯一值,然后将套接字标记为已绑定。

    【讨论】:

    • 错误第一句话说“客户端应用程序不鼓励显式绑定”。为什么这个?在这种情况下,正确的解决方案是什么?如果我有一个客户端需要连接并开始接收数据而不发送 anityhing,我该怎么办?
    • 已经有一段时间了,但这里有一个answer 来回答我当时发现的类似问题。
    【解决方案4】:

    Here 上面写着:

    参数

    s [in]:标识绑定套接字的描述符。

    ...

    返回值

    WSAEINVAL: 套接字尚未与 bind 绑定,或者指定了未知标志,或者为启用了 SO_OOBINLINE 的套接字指定了 MSG_OOB,或者(仅对于字节流样式的套接字)len 为零或负数。

    据我所知,UDP 套接字不需要绑定,因为堆栈会为您进行绑定调用。我想这是 Windows 的事情,需要在 recvfrom 调用中使用的套接字上进行绑定。

    【讨论】:

    • 是的,我同意你的看法...我想知道,为什么会这样...在我制作的最后一个 Windows 程序中,我从来没有这样做过...
    • 你能发布一些代码吗?也许是 recvfrom 的另一个参数导致了这种情况。 Ans 还有您使用的是什么版本的 Windows?
    • Inc::STATS CConnection::_RecvData(sockaddr* addr, std::string strData) { int ret, len, fromlen; //返回码/数据长度/sizeof(sockaddr) char *buffer; //将保存数据 char c; //接收消息的长度 fromlen = sizeof(sockaddr); ret = recvfrom(m_InSock, &c, 1, 0, addr, &fromlen); if(ret != 1) {
    • 将代码添加到您的问题中,以便能够正确格式化。
    • 添加了上面的代码并添加了另一个我很快写的工作示例......
    猜你喜欢
    • 2011-03-16
    • 2018-08-05
    • 2017-09-08
    • 2016-11-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-07-15
    • 2021-05-05
    相关资源
    最近更新 更多