2011-12-31 103 views
4

我建立使用BindIoCompletionCallback一個Visual C++的WinSock TCP服務器,它工作正常接收和發送數據,但我找不到檢測超時的好方法:setsockopt的/ SO_RCVTIMEO/SO_SNDTIMEO有對非阻塞套接字沒有影響,如果對等體沒有發送任何數據,則根本不調用CompletionRoutine。如何檢測的WinSock TCP超時與BindIoCompletionCallback

我在考慮使用帶有OVERLAPPED的hEvent字段的RegisterWaitForSingleObject,這可能會工作,但是完全不需要CompletionRoutine,我還在使用IOCP嗎?如果我僅使用RegisterWaitForSingleObject而不使用BindIoCompletionCallback,是否有性能問題?

更新:代碼示例:

我第一次嘗試:

bool CServer::Startup() { 
     SOCKET ServerSocket = WSASocket(AF_INET, SOCK_STREAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED); 
     WSAEVENT ServerEvent = WSACreateEvent(); 
     WSAEventSelect(ServerSocket, ServerEvent, FD_ACCEPT); 
     ...... 
     bind(ServerSocket......); 
     listen(ServerSocket......); 
     _beginthread(ListeningThread, 128 * 1024, (void*) this); 
     ...... 
     ...... 
    } 

    void __cdecl CServer::ListeningThread(void* param) // static 
    { 
     CServer* server = (CServer*) param; 
     while (true) { 
      if (WSAWaitForMultipleEvents(1, &server->ServerEvent, FALSE, 100, FALSE) == WSA_WAIT_EVENT_0) { 
       WSANETWORKEVENTS events = {}; 
       if (WSAEnumNetworkEvents(server->ServerSocket, server->ServerEvent, &events) != SOCKET_ERROR) { 
        if ((events.lNetworkEvents & FD_ACCEPT) && (events.iErrorCode[FD_ACCEPT_BIT] == 0)) { 
         SOCKET socket = accept(server->ServerSocket, NULL, NULL); 
         if (socket != SOCKET_ERROR) { 
          BindIoCompletionCallback((HANDLE) socket, CompletionRoutine, 0); 
          ...... 
         } 
        } 
       } 
      } 
     } 
    } 

    VOID CALLBACK CServer::CompletionRoutine(__in DWORD dwErrorCode, __in DWORD dwNumberOfBytesTransfered, __in LPOVERLAPPED lpOverlapped) // static 
    { 
     ...... 
     BOOL res = GetOverlappedResult(......, TRUE); 
     ...... 
    } 

    class CIoOperation { 
    public: 
     OVERLAPPED Overlapped; 
     ...... 
     ...... 
    }; 

    bool CServer::Receive(SOCKET socket, PBYTE buffer, DWORD length, void* context) 
    { 
     if (connection != NULL) { 
      CIoOperation* io = new CIoOperation(); 
      WSABUF buf = {length, (PCHAR) buffer}; 
      DWORD flags = 0; 
      if ((WSARecv(Socket, &buf, 1, NULL, &flags, &io->Overlapped, NULL) != 0) && (GetLastError() != WSA_IO_PENDING)) { 
       delete io; 
       return false; 
      } else return true; 
     } 
     return false; 
    } 

正如我所說的,它如果客戶端實際發送數據給我,「接收」工作正常,不堵,CompletionRoutine了所謂的數據接收,但這裏有一個問題,如果客戶端沒有向我發送任何數據,我怎麼能在超時後放棄?

由於setsockopt的/ SO_RCVTIMEO/SO_SNDTIMEO不會幫助在這裏,我想我應該在重疊stucture的IO完成時會發出信號使用hEvent領域,但對一個WaitForSingleObject的/ WSAWaitForMultipleEvents將阻止接收呼叫,我希望接收總是立即返回,所以我使用了RegisterWaitForSingleObject和WAITORTIMERCALLBACK。它工作,在超時後調用回調,或者IO完成,但是現在對任何單個IO操作,CompletionRoutine和WaitOrTimerCallback有兩個回調:如果IO完成,它們將被同時調用,如果IO沒有完成,WaitOrTimerCallback將被調用,然後我調用CancelIoEx,這會導致CompletionRoutine被調用,並帶有一些ABORTED錯誤,但這是一個競爭條件,也許IO將在我取消它之前完成,然後... blahblah,一切都很複雜。

然後我意識到我不實際需要BindIoCompletionCallback和CompletionRoutine可言,從WaitOrTimerCallback做的一切,它可以工作,但這裏是一個有趣的問題,我想首先建立一個基於IOCP-Winsock的服務器,並認爲BindIoCompletionCallback是最簡單的方法來做到這一點,使用Windows自身提供的線程池,現在我終於與沒有IOCP代碼的服務器?它仍然是IOCP?或者我應該忘記BindIoCompletionCallback並構建自己的IOCP線程池實現?爲什麼?

+0

你在用什麼語言工作?你能提供一個有限的代碼示例嗎? – 2011-12-31 06:57:08

+0

你可能想看看這段代碼:http://www.codeproject.com/KB/IP/iocp_server_client.aspx?msg = 1133926 Jeffrey Richter的「微軟Windows 2000編程服務器端應用程序」的舊副本目前在其他地方,所以我不能給你任何幫助:( – paulsm4 2011-12-31 06:58:56

+0

好吧,它的Visual C++,我會更新問題 – WalkingCat 2011-12-31 08:04:37

回答

0

我所做的是強制超時/完成通知進入套接字對象的關鍵部分。一旦進入,獲勝者可以設置套接字狀態變量並執行其操作,無論可能如何。如果I/O完成最先進入,則I/O緩衝區陣列以正常方式處理,並且任何超時都指向由狀態機重新啓動。同樣,如果超時先進入,I/O獲取CancelIOExd,任何後來的排隊完成通知都會被狀態引擎丟棄。由於這些可能的「遲到」通知,我將釋放的套接字放到超時隊列中,並且僅在五分鐘後將它們循環到套接字對象池中,這與TCP堆棧本身將套接字放入「TIME_WAIT」中的方式類似。

要做超時,我有一個線程在超時對象的FIFO增量隊列上運行,每個超時限制有一個隊列。線程在輸入隊列上等待新對象,並根據隊列頭部對象的最小超時 - 到期時間計算超時值。

在服務器中只有少數超時使用,所以我使用在編譯時固定的隊列。通過向線程輸入隊列發送適當的'命令'消息來添加新的隊列或修改超時是非常容易的,與新的套接字混合,但是我沒有那麼深入。

超時後,線程在對象中調用一個事件,在套接字的情況下,它將進入套接字對象CS保護的狀態機(這是一個TimeoutObject類,它是套接字的後繼類, )。

更多:

我伺候控制超時線程輸入隊列的信號。如果有信號,我會從輸入隊列中獲取新的TimeoutObject,並將其添加到請求的任何超時隊列的末尾。如果信號量等待超時,我檢查超時FIFO隊列頭部的項目,並通過將當前時間與超時時間分離來重新計算其剩餘時間間隔。如果間隔爲0或負數,則會調用超時事件。在迭代隊列和頭部時,我在下一次超時之前保留最小的剩餘間隔。 Hwn所有隊列中的所有頭項都有非零的剩餘時間間隔,我用積累的最小剩餘時間間隔返回等待隊列信號量。

事件調用返回一個枚舉。此枚舉指示超時線程如何處理其剛剛觸發事件的對象。一種選擇是通過重新計算超時時間並在最後將對象推回超時隊列來重新啓動超時。

我沒有使用RegisterWaitForSingleObject(),因爲它需要.NET並且我的Delphi服務器全部都是非託管的(我很久以前寫過我的服務器!)。

那,並且因爲IIRC它有64個句柄的限制,比如WaitForMultipleObjects()。我的服務器超過23000個客戶端超時。我發現單個超時線程和多個FIFO隊列變得更加靈活 - 只要它是從TimeoutObject傳下來的,任何舊對象都可以超時 - 不需要額外的OS調用/句柄。

+0

嗯...所以你在某些事件上依次使用專用線程和WaitForSingleObject?isn是什麼RegisterWaitForSingleObject是專爲什麼設計的? – WalkingCat 2012-01-05 03:13:36

+0

我只等待過一個同步對象 - 爲超時線程輸入隊列保存計數的信號量。編輯我的答案添加一些更多的細節 – 2012-01-05 17:59:38

+0

嗯...我會稍後仔細閱讀你的更新,但RegisterWaitForSingleObject是Win32 API,不依賴於.NET :) – WalkingCat 2012-01-09 09:25:26

0

其基本思想是,由於您在系統線程池中使用了異步I/O,因此您不需要通過事件檢查超時,因爲您沒有阻塞任何線程。

檢查過期連接的recommended way是撥getsockoptSO_CONNECT_TIME選項。這將返回套接字已連接的秒數。我知道這是一個輪詢操作,但如果你對查詢這個值的方式和時間很敏感,它實際上是一個很好的管理連接的機制。我在下面解釋這是如何完成的。

通常我會在兩個地方呼叫getsockopt:一個是在我的完成回調期間(以便我有一個時間戳記,上次在該套接字上發生I/O完成時),一個位於我的接受線程中。

accept線程通過WSAEventSelectFD_ACCEPT參數監視我的套接字積壓。這意味着只有Windows確定存在需要接受的傳入連接時,纔會執行接受線程。在這個時候,我列舉了我接受的套接字,並且爲每個套接字再次查詢SO_CONNECT_TIME。我從該值中減去連接上一次I/O完成的時間戳,如果差值超過指定的閾值,我的代碼認爲連接超時。

+0

是的,你沒有阻塞任何線程,但套接字仍然會使用系統資源,直到你注意到時間滯後爲止,對吧?這真的足夠好,可以防止連接失效嗎? – 2014-01-04 07:41:44