Windows UDP套接字:recvfrom()错误10054。

12


大家好。
我正在尝试使用Windows sockets(C++)发送和接收UDP数据包。
一切都很正常,直到三天前,程序突然停止了正常运行。
总结如下:

  • 当调用WSAPoll()时,始终返回更新了每个revents的套接字(对应于我给出的每个事件),即使没有启动服务器。
  • 当调用recvfrom()且未启动服务器时,它将返回SOCKET_ERROR,错误代码为10054(*)。
  • 当调用recvfrom()且已启动服务器时,它可以正常工作-阻塞直到接收到数据。
  • 无论是尝试连接到本地主机还是远程主机,行为都是相同的。

(*) 我调查了这个错误。在UDP中,它意味着存在一个ICMP问题。(“在UDP数据报套接字上,此错误表示先前的发送操作导致了一个ICMP端口不可达消息。”)。
我确实在recvfrom()之前调用了sendto(),所以问题不在这里。
我试图关闭我的防火墙,看看是否有任何变化,但没有。我还试图关闭通过我的PC流动的所有网络。在这种状态下,我设法让程序运行了几分钟,但当我启用网络时,它又停止工作了。我试图重复这个过程,但它再也不能工作了。
我尝试使用Visual Studio(2015)和MinGW编译。
我也在另一台计算机上尝试过(在Windows 7下,我的是Windows 8.1),但没有成功。

这里是一个简单的测试文件,在我的电脑上无法运行。

#undef _WIN32_WINNT
#define _WIN32_WINNT 0x501
#include <winsock2.h>
#include <ws2tcpip.h>
#include <stdio.h>
#include <vector>
#include <iostream>

int main() {
  int clientSock;
  char buf[100];
  int serverPort;

  /* Initializing WSA */
  WSADATA wsaData;
  WSAStartup(MAKEWORD(2, 2), &wsaData);

  /* I create my socket */
  struct addrinfo specs;
  struct addrinfo *addr = new addrinfo;
  ZeroMemory(&specs, sizeof(specs));
  specs.ai_family = AF_INET;
  specs.ai_socktype = SOCK_DGRAM;
  specs.ai_flags = 0;
  getaddrinfo("127.0.0.1", "2324", &specs, &addr);

  clientSock = socket(addr->ai_family, addr->ai_socktype, addr->ai_protocol);

  /* I get the server's address */
  struct sockaddr_in serverAddr;
  serverAddr.sin_family = AF_INET;
  serverAddr.sin_addr.s_addr = inet_addr("127.0.0.1");
  serverAddr.sin_port = htons(2324);
  int len = sizeof(struct sockaddr);

  /* I'll poll & recvfrom on my socket */
  std::vector<pollfd> fds;
  pollfd fd;
  fd.fd = clientSock;
  fd.events = POLLRDNORM;
  fd.revents = -1;
  fds.push_back(fd);

  while(1) {
    memset(buf,0,sizeof(buf));
    printf("\nClient--->: ");
    gets(buf);
    /* It's UDP, so it doesn't matter if there is someone to receive the packet */
    sendto(clientSock, buf, strlen(buf), 0, (sockaddr*)&serverAddr ,len);

    memset(buf,0,sizeof(buf));
    int ret;
    /* Always returns "1" */
    if ((ret = WSAPoll(fds.data(), 1, 0)) > 0) {
      std::cout << ret;
      /* Always returns "-1" */
      std::cout << recvfrom(clientSock,buf,sizeof(buf),0, (sockaddr*)&serverAddr,&len) << std::endl;
      printf("\n--->From the server: ");
      printf("%s",buf);
    }
  }

  closesocket(clientSock);
  WSACleanup();

  return 0;
}

两个问题:

  1. 即使没有与其进行任何交互,为什么WSAPoll()总是返回更新的套接字?
  2. recvfrom()为什么会返回这个错误,我该如何修复它?我想这可能是来自我的计算机。我尝试允许ICMP通过我的防火墙,但没有改变任何东西,也许我做错了什么?

编辑:我通过忽略我收到的任何“错误10054”来修复了我的主要程序(此处未显示,因为它太大了)。现在它的工作方式与Unix相同。
不过,这并不是真正的解决方案(忽略错误代码...嗯),如果有人知道我在调用sendto()时为什么会收到“ICMP端口不可达”错误,我将很高兴听取建议。


“我确实在recvfrom()之前调用了sendto(),所以问题不在这里”是没有意义的。你调用了sendto(),返回了一个ICMP UNREACH,并在接下来的recvfrom()中检测到了它。 - user207421
是的,但这可能是一个绑定问题,如果我在调用sendto()之前没有任何隐式绑定(您在其他评论中谈到过它)。这就是我强调这一点的原因。 - Heowyn
但是你确实调用了 sendto()。你自己说的。即使你没有调用,调用 recvfrom() 也会隐式绑定。不清楚你在这里到底在说什么。 - user207421
当我在寻找解决问题的答案时,我找到了一个主题,在那里人们说我必须在调用 recvfrom() 之前调用 sendto(),否则它将无法工作。 我不知道 recvfrom() 也会隐式绑定。 是我的错。 - Heowyn
忽略任何UDP错误是完全合理的,因为它们本来就不可靠,而且通常很难弄清楚它们具体意味着什么。如果您没有得到应该得到的数据,则存在问题。否则,一切都很好。 - David Schwartz
2个回答

11
在Windows系统中,如果主机A使用UDP套接字并调用sendto()向主机B发送消息,但是B没有绑定任何端口,因此B无法接收消息,然后主机A调用recvfrom()接收一些消息,recvfrom()将会失败,并且WSAGetLastError()将返回10054
这是Windows的一个错误。如果UDP套接字在发送消息后接收到ICMP(端口不可达)消息,则会存储此错误,并且下一次调用recvfrom()将返回此错误。
有两种解决此问题的方法:
1.确保主机B已经绑定了您要发送到的端口。
2.通过使用以下代码禁用此错误:
#include <Winsock2.h>
#include <Mstcpip.h>
#include <stdio.h>

#pragma comment(lib, "ws2_32.lib")
#define SIO_UDP_CONNRESET _WSAIOW(IOC_VENDOR, 12)

BOOL bNewBehavior = FALSE;
DWORD dwBytesReturned = 0;
WSAIoctl(iSock, SIO_UDP_CONNRESET, &bNewBehavior, sizeof bNewBehavior, NULL, 0, &dwBytesReturned, NULL, NULL);

Reference: http://www.cnblogs.com/cnpirate/p/4059137.html


2
感谢提供答案。以下是使用boost套接字执行此操作的方法,以防其他人需要:WSAIoctl(boostSocket.get()->native(), SIO_UDP_CONNRESET, &bNewBehavior, sizeof bNewBehavior, NULL, 0, &dwBytesReturned, NULL, NULL); - Njål Arne Gjermundshaug

2
我已经简化了作者的代码,并包含了 simmerlee 的修复。这提供了一种更简单的方法来重现错误:
#include "stdafx.h"
#include <winsock2.h>
#include <stdio.h>
#pragma comment(lib, "ws2_32.lib")

#define SIO_UDP_CONNRESET _WSAIOW(IOC_VENDOR, 12)

void testCase(bool fixed)
{
    int clientSock;
    char rcvBuf[100];

    // create socket
    clientSock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);

    if(fixed)
    {
        BOOL bNewBehavior = FALSE;
        DWORD dwBytesReturned = 0;
        WSAIoctl(clientSock, SIO_UDP_CONNRESET, &bNewBehavior, sizeof bNewBehavior, NULL, 0, &dwBytesReturned, NULL, NULL);
    }

    // bind socket
    struct sockaddr_in clientAddr;
    clientAddr.sin_family = AF_INET;
    clientAddr.sin_addr.s_addr = inet_addr("127.0.0.1");
    clientAddr.sin_port = htons(61234);
    int sizeClientAddr = sizeof(clientAddr);
    bind(clientSock, (sockaddr*) &clientAddr, sizeClientAddr);

    struct sockaddr_in serverAddr = clientAddr;
    serverAddr.sin_port = htons(2324); // change port where nobody listens
    int sizeServerAddr = sizeof(struct sockaddr);

    int lasterror = 0;
    int status = 0;

    // send where nobody is listening
    printf("Send to nowhere--->:\n");
    /* It's UDP, so it doesn't matter if there is someone to receive the packet */
    status =sendto(clientSock, "Message", 7, 0, (sockaddr*)&serverAddr, sizeServerAddr);
    lasterror = WSAGetLastError(); 
    printf("sendto return %d (lasterror %d)\n", status, lasterror);

    // recvfrom with "failing" sendto before. 
    // fixed: This should block.
    // unfixed: WSAGetLastError is 10054
    memset(rcvBuf, 0, sizeof(rcvBuf));
    status = recvfrom(clientSock, rcvBuf, sizeof(rcvBuf), 0, (sockaddr*)&serverAddr, &sizeServerAddr);
    lasterror = WSAGetLastError(); 
    printf("recvfrom return %d (lasterror %d)\n", status, lasterror);
    printf("--->From the server: -%s-\n", rcvBuf);

    closesocket(clientSock);
}

int _tmain(int argc, _TCHAR* argv[])
{
    /* Initializing WSA */
    WSADATA wsaData;
    WSAStartup(MAKEWORD(2, 2), &wsaData);

    printf("##### UNFIXED\n");
    testCase(false);
    printf("##### FIXED\n");
    testCase(true);

    WSACleanup();

    // pause
    char buf[100];
    gets(buf);
    return 0;
}

这应该返回:
##### UNFIXED
Send to nowhere--->:
sendto return 7 (lasterror 0)
recvfrom return -1 (lasterror 10054)
--->From the server: --
##### FIXED
Send to nowhere--->:
sendto return 7 (lasterror 0)

然后阻塞。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接