2

我遇到了一个奇怪的 TCP 套接字错误。似乎SO_KEEPALIVE默认情况下在所有套接字上启用。

我写了一个简短的测试用例来创建一个套接字并连接到服务器。连接后,我立即检查SO_KEEPALIVE. getsockopt该值非零,根据 MSDN,这意味着启用了保持活动。也许我误解了这一点。

我最近遇到了一个奇怪的错误,即服务器连续两次断开连接。一些客户端处于已发送登录信息并等待响应的状态。即使有一个重叠WSARecv发布到连接到服务器的套接字,也没有发布完成以通知客户端服务器崩溃,所以我假设套接字没有完全关闭。

大约 2 小时后(实际上是大约 1 小时 59 分 19 秒),一个完成数据包被发布用于读取,通知客户端连接不再打开。这是我开始怀疑的地方SO_KEEPALIVE

我试图理解为什么会这样。这引起了一些问题,因为出于任何原因失去连接的客户端都应该自动重新连接到服务器;在这种情况下,由于没有通知断开连接,客户端直到 2 小时后才重新连接。

一个明显的解决方法是设置超时,但我想知道这种情况是如何发生的。

SO_KEEPALIVE我的应用程序服务器或客户端未在套接字上设置。

// Error checking is removed for this snippet, but all winsock calls succeed.
int main() {
    WORD wVersionRequested;
    WSADATA wsaData;
    int err;

    wVersionRequested = MAKEWORD(2, 2);
    err = WSAStartup(wVersionRequested, &wsaData);

    SOCKET foo = WSASocket(AF_INET, SOCK_STREAM, IPPROTO_TCP, 0, 0, 0);

    DWORD optval;
    int optlen = sizeof(optval);
    int test = 0;
    test = getsockopt(foo, SOL_SOCKET, SO_KEEPALIVE, (char*)&optval, &optlen);
    std::cout << "Returned " << optval << std::endl;

    sockaddr_in clientService; 
    clientService.sin_family = AF_INET;
    clientService.sin_addr.s_addr = inet_addr("127.0.0.1");
    clientService.sin_port = htons(446);

    connect(foo, (SOCKADDR*) &clientService, sizeof(clientService));

    test = getsockopt(foo, SOL_SOCKET, SO_KEEPALIVE, (char*)&optval, &optlen);
    std::cout << "Returned " << optval << std::endl;

    std::cin.get();
    return 0;
}

// Example output:
// Returned 2883584
// Returned 2883584
4

1 回答 1

5

首先在虚拟机上全新安装操作系统上运行测试。我怀疑您安装的其他东西可能会影响保持活动设置。

其次,我怀疑启用保持活动是您问题的原因。如果未启用保持活动状态,那么您将永远不会从该挂起的读取中获得连接关闭通知。TCP 应该是这样工作的,它允许中间路由器离开然后回来,而你既不知道也不关心。唯一一次您会被告知失败是如果您尝试发送并且连接断开(或者,在这种情况下,如果您尝试发送并且服务器已经反弹)。启用保持活动的事实意味着在 1 小时 59 分钟标记 TCP 堆栈传输保持活动并注意到连接已关闭。如果没有启用保持活动,那么您将不得不等到您传输某些内容。

如果您的客户需要知道连接是否断开,那么最好完全忽略保持活动(如您所见,即使您不是启用它的人,它也会影响整个机器,而对我来说,这使它成为一个糟糕的解决方案)。如果可以,请在您的协议中添加应用程序级别的 ping 和/或超时。因此,也许,每个命令都希望在 30 秒内得到响应,并且您每分钟从服务器发送一个响应……然后,您将尽快发现死连接,您可以在那时断开连接并重新连接。

I've used this pretty well with my server framework; in fact I have a standard 'async read timeout' connection filter and a 'connection re-establishment' filter which make it trivial to ensure that the connections are always live. All the read timeout does is abort the existing connection and the connection re-establishment code kicks in to recreate the connection just as it would if the connection had been closed for any other reason.

于 2011-02-08T07:29:26.643 回答