【问题标题】:How to terminate windows sockets when internet is down? (C++ WinAPI)互联网关闭时如何终止Windows套接字? (C++ WinAPI)
【发布时间】:2020-12-31 04:16:50
【问题描述】:

我已经建立了一个 Winsock2 连接,但我需要涵盖互联网中断的情况。这是我的代码;

#include <winsock2.h>
#include <windows.h>
#include <ctime>
int main()
{
    WSADATA w;
    if(WSAStartup(MAKEWORD(2,2),&w)) return 0;
    sockaddr_in sad;
    sad.sin_family=AF_INET;
    sad.sin_addr.s_addr=inet_addr("200.20.186.76");
    sad.sin_port=htons(123);
    sockaddr saddr;
    int saddr_l=sizeof(saddr);
    int s=socket(PF_INET,SOCK_DGRAM,IPPROTO_UDP);
    if(s==INVALID_SOCKET) return 0;
    char msg[48]={8};
    if(sendto(s,msg,sizeof(msg),0,(sockaddr*)&sad,sizeof(sad))==SOCKET_ERROR) return 0;
    if(recvfrom(s,msg,48,0,&saddr,&saddr_l)==SOCKET_ERROR) return 0;
    if(closesocket(s)==SOCKET_ERROR) return 0;
    if(WSACleanup()) return 0;
    return 0;
}

在这里它等待调用返回,因为它记录在案。我有两个问题。

  1. 我可以像使用select 时那样设置超时吗
  2. 我还能如何防止等待并使其立即返回?文档指出:

当发出诸如 sendto 之类的阻塞 Winsock 调用时,Winsock 可能需要等待网络事件才能完成调用。在这种情况下,Winsock 会执行警报等待,该等待可能会被安排在同一线程上的异步过程调用 (APC) 中断。

怎么做?

【问题讨论】:

  • AFAIK 无法检测到带有 UDP 的套接字的连接丢失,因为它是无连接的。您需要对此使用一些解决方法。
  • @UltimaWeapon 好的,谢谢。我会先尝试 ping 某处或考虑其他事情。
  • 使用读取超时,setsockopt()SO_RCVTIMEO
  • @user207421 我已经看过那些文档页面,但它们需要面向连接的协议,如 TCP。我使用的是 UDP,它是无连接的,所以这些方法可能不起作用。
  • "我可以像使用 select 时那样设置超时" - 您可以将 select() 与 UDP 套接字一起使用。仅供参考,SO_RCVTIMEO 也适用于 UDP。

标签: c++ winapi winsock winsock2


【解决方案1】:

如果你想发出一个 recvfrom() 并让它立即返回,然后你自己决定等待多长时间(我假设是 Windows,因为你包含了 winsock2.h),你可以发出一个异步 OVERLAPPED 请求,然后通过等待 OVERLAPPED 结构的 hEvent 成员发出信号,随时等待完成。

这是基于您的原始代码的更新示例。

  • 您可以使用 WaitForSingleObject 设置超时时间(下面我等待 10 秒 6 次)
  • 通过传递一个 OVERLAPPED 指针,您表示您将自己等待完成。请注意,在发出 hEvent 信号之前,OVERLAPPED 结构不能超出范围。 (或释放,如果 OVERLAPPED 是动态分配的)。
  • 在保证 IO 完成之前让 OVERLAPPED 超出范围是一个常见的 Winsock 错误(我已经在 Winsock 上工作了 10 多年 - 我已经看到了这个错误的许多变体)
  • 如下所述,如果您不知道 hEvent 已发出信号,那么在调用 closesocket 之后,您必须等待 hEvent 发出信号后再继续 - closesocket 不保证所有异步 IO 请求都已完成在返回之前。
#define _WINSOCK_DEPRECATED_NO_WARNINGS
#include <winsock2.h>
#include <windows.h>
#include <ctime>
int main()
{
    WSADATA w;
    if (WSAStartup(MAKEWORD(2, 2), &w)) return 0;
    sockaddr_in sad;
    sad.sin_family = AF_INET;
    sad.sin_addr.s_addr = inet_addr("200.20.186.76");
    sad.sin_port = htons(123);
    sockaddr saddr;
    int saddr_l = sizeof(saddr);
    int s = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);
    if (s == INVALID_SOCKET) return 0;
    char msg[48] = { 8 };
    if (sendto(s, msg, sizeof(msg), 0, (sockaddr*)&sad, sizeof(sad)) == SOCKET_ERROR) return 0;
    OVERLAPPED ov{};
    ov.hEvent = CreateEvent(nullptr, TRUE, FALSE, nullptr);
    if (ov.hEvent == nullptr) return 0;
    WSABUF wsabuffer{};
    wsabuffer.buf = msg;
    wsabuffer.len = 48;
    DWORD flags = 0;
    if (WSARecvFrom(s, &wsabuffer, 1, nullptr, &flags, &saddr, &saddr_l, &ov, nullptr) == SOCKET_ERROR)
    {
        DWORD gle = WSAGetLastError();
        if (gle != WSA_IO_PENDING) return 0;
    }
    for (DWORD recv_count = 0; recv_count < 6; ++recv_count)
    {
        DWORD wait = WaitForSingleObject(ov.hEvent, 10000);
        if (wait == WAIT_FAILED) return 0;
        if (wait == WAIT_OBJECT_0) break; // WSARecvFrom completed
        if (wait == WAIT_TIMEOUT) continue; // WSARecvFrom is still pended waiting for data
    }
    // assuming WSARecvFrom completed - i.e. ov.hEvent was signaled
    DWORD transferred;
    if (WSAGetOverlappedResult(s, &ov, &transferred, FALSE, &flags))
    {
        // WSARecvFrom completed successfully - 'transferred' shows the # of bytes that were received
    }
    else
    {
        DWORD gle = WSAGetLastError();
        gle;
        // WSARecvFrom failed with the error code in 'gle'
    }
    if (closesocket(s) == SOCKET_ERROR) return 0;
    // with real code, we must guarantee that hEvent is set after calling closesocket
    // e.g. if we get here in an error path
    // closesocket() won't guarantee all async IO has completed before returning
    WaitForSingleObject(ov.hEvent, INFINITE);
    if (WSACleanup()) return 0;
    return 0;
}

【讨论】:

  • 感谢您的回答,我现在正在尝试。顺便说一句,我有一个问题,如果我们不等待程序结束时的 hEvent 会发生什么?它会导致未定义的行为、内存泄漏或崩溃吗?如果它不会引起这些问题,那又有什么关系呢?
  • 在这个例子中不需要第二次等待事件,因为第一次等待已经保证事件被设置。并且以这种方式在循环中调用WaitForSingleObject() 是完全没有必要的,因为带有总超时的单个调用将完成完全相同的事情。然而,当select()SO_RCVTIMEO 可以用更少的代码完成完全相同的事情时,这段代码有点过头了。这不是重叠 I/O 的最佳用途。
  • 这是拿锤子敲坚果。 SO_RCVTIMEO 螺母。
  • 如前所述,如果您已经保证 IO 已经完成(hEvent 已经发出信号),那么您不需要再次等待。如果代码超时,那么您必须在调用 closesocket() 后等待。如果您不等待,那么在将来某个随机/未定义的时间,Winsock(来自内核)将更新该 OVERLAPPED 结构 - 可能会破坏堆栈。您还可以使用 select 来了解何时发布 recvfrom。但我个人更喜欢 OVERLAPPED IO - 我认为代码不会复杂得多,如果您需要等待其他信号退出,它会为您提供更多选择。
  • (注意:我在循环中调用它只是为了举例说明如何在必要时重复调用它 - 这只是示例代码。如果只有一次等待,那么当然不会循环调用)。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-04-12
  • 1970-01-01
  • 2015-11-23
相关资源
最近更新 更多