2011-02-07 78 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,這使得確保連接始終處於活動狀態是微不足道的。所有讀取超時都會中止現有連接,並且連接重新建立代碼開始重新創建連接,就像連接因任何其他原因而關閉時一樣。