5

我的软件的一些用户最近来找我,告诉我它在 Windows 8 上不起作用。经过调查发现由于某种奇怪的原因,我的服务器套接字并不总是接受连接,而是让它们超时.

更奇怪的是:连接到本地主机时也会发生这种情况,而不仅仅是远程访问它时。

“你试过什么?”

  • 显而易见的东西:关闭防火墙(无效),查看其他软件是否有效(有效),尝试修改随机代码行(无效)。
  • 不太明显的东西:使用全局 WSAStartup 而不是每个客户端或服务器实例一个(无效)。

提醒:完全相同的代码在 Windows XP 和 Windows 7 上运行良好,它也会影响 localhost 连接(不是硬件问题)。此外,只有三分之一的连接失败,其余连接正常。

好的,现在是一些真正的代码,因为它比所有这些词更有用。

套接字设置:

int iResult;

struct addrinfo *result = NULL;
struct addrinfo hints;

ZeroMemory(&hints, sizeof(hints));
hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = IPPROTO_TCP;
hints.ai_flags = AI_PASSIVE;

// "Resolve" our localhost
iResult = getaddrinfo(NULL, port, &hints, &result);
if (iResult != 0) {
    printf("error (2) : %d\n", iResult);
    return false;
}

// Create the socket
listenSocket = socket(result->ai_family, result->ai_socktype, result->ai_protocol);
if (listenSocket == INVALID_SOCKET) {
    freeaddrinfo(result);
    printf("error (3) : %d\n", WSAGetLastError());
    return false;
}

// Bind it
iResult = bind(listenSocket, result->ai_addr, result->ai_addrlen);
if (iResult == SOCKET_ERROR) {
    freeaddrinfo(result);
    closesocket(listenSocket);
    printf("error (4) : %d\n", WSAGetLastError());
    return false;
}

freeaddrinfo(result);

// Listen
iResult = listen(listenSocket, SOMAXCONN);
if (iResult == SOCKET_ERROR) {
    closesocket(listenSocket);
    printf("%d\n", WSAGetLastError());
    return false;
}

正如您可能看到的,它几乎直接取自 MSDN,应该没问题。此外,它适用于 2/3 的连接,所以我真的怀疑是设置代码有问题。

接收方代码:

    if (listenSocket == INVALID_SOCKET) return false;
#pragma warning(disable:4127)
    fd_set fds;

    SOCKET client;
    do {
        FD_ZERO(&fds);
        FD_SET(listenSocket, &fds);

        struct timeval timeout;
        timeout.tv_sec = 5;
        timeout.tv_usec = 0;
        if (!select(1, &fds, NULL, NULL, &timeout)) continue; // See you next loop!

        struct sockaddr_in addr;
        socklen_t addrlen = sizeof(addr);

        // Accept the socket
        client = accept(listenSocket, (struct sockaddr *)&addr, &addrlen);

        if (client == INVALID_SOCKET) {
            printf("[HTTP] Invalid socket\n");
            closesocket(listenSocket);
            return false;
        }

        // Set a 1s timeout on recv()
        struct timeval tv;
        tv.tv_sec = 1;
        tv.tv_usec = 0;
        setsockopt(client, SOL_SOCKET, SO_RCVTIMEO, (char*)&tv, sizeof(tv));

        // Receive the request
        char recvbuf[513];
        int iResult;
        std::stringbuf buf;

        clock_t end = clock() + CLOCKS_PER_SEC; // 1s from now

        do {
            iResult = recv(client, recvbuf, 512, 0);
            if (iResult > 0) {
                buf.sputn(recvbuf, iResult);
            } else if (iResult == 0) {
                // Hmm...
            } else {
                printf("[HTTP] Socket error: %d\n", WSAGetLastError());
                break;
            }
        } while (!requestComplete(&buf) && clock() < end);

这段代码吐出一个“[HTTP] Socket error: 10060”错误,所以它之后的任何代码都是相当不相关的。

调用在select那里是因为实际的循环也做了一些其他的事情,但我把它省略了,因为它与套接字无关。

更奇怪的是:根据 Wireshark 的说法,Windows 似乎正在犯实际的网络错误:http: //i.imgur.com/BIrbD.png

我一直在努力解决这个问题,我可能只是在做一些愚蠢的事情,所以我非常感谢你的所有回答。

4

2 回答 2

4

我已经为这个烦人的问题工作了一整天,并最终通过从头开始重写整个服务器并以不同的方式实现它来最终解决它。我确实将问题追溯到setsockopt似乎SO_RCVTIMEO不再需要很好的问题,导致超时变为零秒,这使得随机连接超时。

我的新实现不再使用超时,现在只是非阻塞和异步的。工作得很好,但它需要更多的代码。

我假设这只是 Windows 8 中的一个错误,将在发布之前通过更新修复。我怀疑微软是否想像这样更改 Berkeley Sockets API。

于 2012-10-05T15:46:14.123 回答
4

在 Windows 中,SO_RCVTIMEO 选项需要 MILLISECONDS 中的 DWORD 参数,但不需要 timeval 结构。请参阅http://msdn.microsoft.com/en-us/library/windows/desktop/ms740476(v=vs.85).aspx

传递 timeval 会导致 windows 将其解释为 DWORD,并且 seconds 成员被读取为毫秒。我不知道为什么 timeval 参数在 Win 8 之前的版本中有效,可能是未记录的功能,在 win 8 中已删除。

于 2013-03-28T07:22:03.197 回答