2011-02-07 165 views
2

我遇到了TCP套接字的一个奇怪的错误。看来默认情况下,所有套接字上都启用了SO_KEEPALIVEWindows TCP套接字默认启用SO_KEEPALIVE?

我写了一个简短的测试用例来创建套接字并连接到服务器。连接后立即检查SO_KEEPALIVEgetsockopt。该值非零,根据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 
+0

您是否在WSAIoctl()/ SIO_KEEPALIVE_VALS下获得了相同的结果?我也会输出“test”的值,在Unix中,存入optval的值往往是0和1,而不是0,并且是一个“非常随机的结果”。 – CoreyStup 2011-02-07 16:29:46

+0

我删除了测试的输出以保持代码片段简洁,值始终为0.快速搜索,我没有看到如何使用WSAIoctl检索设置,只知道如何设置它们。这是一种垃圾值,因为它每隔一段时间都会更改一次,但是MSDN文档会声明启用了任何非零均值。 – 2011-02-07 16:35:10

回答

4

首先在VM上全新安装操作系统时运行测试。或许,我怀疑你已经安装的其他东西已经摆脱了保持活跃的状态。

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

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

我用这个很好,my server framework;事实上,我有一个标准的'async read timeout' connection filter和一个'connection re-establishment' filter,这使得确保连接始终处于活动状态是微不足道的。所有读取超时都会中止现有连接,并且连接重新建立代码开始重新创建连接,就像连接因任何其他原因而关闭时一样。