2011-12-31 32 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