Warning: file_get_contents(/data/phpspider/zhask/data//catemap/3/sockets/2.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C++ 无法使用IOCP正确接受新连接-套接字句柄无效_C++_Sockets_Iocp - Fatal编程技术网

C++ 无法使用IOCP正确接受新连接-套接字句柄无效

C++ 无法使用IOCP正确接受新连接-套接字句柄无效,c++,sockets,iocp,C++,Sockets,Iocp,我正在学习IOCP,并决定根据以下文章编写自己的包装器类: 我的项目是一个使用IOCP的C++ TCP服务器。客户机使用send()和recv()发送和接收我无法更改的数据(我被告知这不会导致任何问题,但我只是以防万一)。它还使用socket()而不是WSASocket()创建套接字。 一切似乎都很正常(CreateIoCompletionPort没有错误,我可以向现有的完成端口添加套接字描述符,没有任何错误。我通过在每个函数之后添加对WSAGetLastError()的调用检查了一切) (在

我正在学习IOCP,并决定根据以下文章编写自己的包装器类:

我的项目是一个使用IOCP的C++ TCP服务器。客户机使用send()和recv()发送和接收我无法更改的数据(我被告知这不会导致任何问题,但我只是以防万一)。它还使用socket()而不是WSASocket()创建套接字。 一切似乎都很正常(CreateIoCompletionPort没有错误,我可以向现有的完成端口添加套接字描述符,没有任何错误。我通过在每个函数之后添加对WSAGetLastError()的调用检查了一切)

(在任何事情之前,请不要介意不一致的编码风格。我喜欢先让东西工作,然后再把它清理干净。)

检测到传入连接后(我使用WSAwaitForMultipleEvents和WSAEnumNetworkEvents,它们工作时不会触发任何错误),我使用以下代码接受客户端(这就是问题的开始):

由于这不起作用,我的工作线程中的GetQueuedCompletionStatus()例程一直挂起(或者至少,我认为这就是原因)

我做的有什么不对吗?从昨天晚上开始,我一直在试着四处搜索并修复它,我知道时间不多,但我真的看不出我做得不对

更新: 我已更改了为AcceptEx()初始化套接字的方式


AcceptEx()仍然返回false,但是WSAGetLastError()返回的错误现在是997(WSA_IO_挂起)。我不确定什么I/O操作是挂起的,以及我将如何解决它。

我在学习I/O完成端口(IOCP)时遇到了类似的障碍

我认为问题在于,在IOCP套接字模型的方案中,最复杂的部分是“套接字接受”的开始阶段。这就是为什么大多数教程跳过它,而是开始讨论如何处理发送/接收

如果您想对IOCP有足够的了解,以便能够实现一个生产软件,那么我给您的建议是研究它,直到您完全掌握它(下面的答案是不够的)。我推荐的一个文档是“Microsoft Windows网络编程-第二版”的第5章。这本书可能很旧,但对IOCP有效。此外,文章“”还涉及IOCP的某些方面,尽管没有足够的信息用于制作软件

我会尽力解释,但是,我必须警告你,这可能还不够。就这样

因此,您缺少的部分是“如何在IOCP套接字模型中进行“套接字接受”

首先,让我们检查服务器上典型的Winsock(非IOCP)调用序列

// (1) Create listen socket on server.
WSASocket()            

// (2) Bind an address to your listen socket.
bind()                 

// (3) Associate the listen socket with an event object on FD_ACCEPT event.
WSAEventSelect(,, FD_ACCEPT )       

// (4) Put socket in listen state - now, Windows listening for new 
//     connection requests. If new requests comes, the associated 
//     event object will be set.
listen()               

// (5) Wait on the event object associated on listen socket. This 
//     will get signaled when a new connection request comes.
WaitForSingleObject() {

     // (6) A network activity has occurred. Verify that FD_ACCEPT has 
     //     raised the event object. This also resets the event object
     //     so WaitForSingleObject() does not loop non-stop.
     WSAEnumNetworkEvents()  


     // (7) Understanding this part is important. The WSAAccept() doesn't
     //     just accept connection, it first creates a new socket and
     //     then associates it with the newly accepted connection.
     WSAAccept()
}
步骤(7)适用于非基于IOCP的模型。然而,从性能的角度来看,套接字创建是昂贵的。这会减慢连接接受过程

在IOCP模型中,为新的传入连接请求预先创建套接字。不仅套接字是预先创建的,它们甚至在连接请求到来之前就与侦听套接字相关联。为了实现这一点,微软提供了扩展功能。IOCP模型需要两个这样的函数:AcceptEx()&GetAcceptExSockaddrs()

注意:使用这些扩展函数时,需要在运行时加载它们,以避免性能损失。这可以通过使用WSAIoctl()实现。有关更多信息,请参阅AcceptEx()上的MSDN文档

注意事项:AcceptEx()可用于设置新套接字以接收一些数据,作为连接接受过程的一部分。需要禁用此功能,因为它使应用程序容易受到DoS攻击,即发出连接请求但不发送数据。接收应用程序将无限期地等待该套接字。要避免这种情况,只需为其“dwReceiveDataLength”参数传递0值

如何为IOCP模型设置连接验收代码? 一种方法是

// (1) Create IO completion port
CreateIoCompletionPort()

// (2) Have a method that creates worker threads say 'CreateWorkerThreads()'. 
//     This assign same method (say WorkerThread_Func()) to all worker threads. 
//     In the WorkerThread_Func() threads are blocked on call to 
//     GetQueuedCompletionStatus().
CreateWorkerThreads()

// (3) Create listen socket.
WSASocket()            

// (4) Associate listen socket to IO Completion Port created earlier.
CreateIoCompletionPort()

// (5) Bind an address to your listen socket.
bind()                 

// (6) Put socket in listen state - now, Windows listening for new 
//     connection requests. If a new request comes, GetQueuedCompletionStatus() 
//     will release a thread.
listen() 

// (7) Create sockets in advance and call AcceptEx on each of 
//     these sockets. If a new connection requests comes 
//     Windows will pick one of these sockets and associate the 
//     connection with it.
//
//     As an example, below loop will create 1000 sockets.


GUID GuidAcceptEx = WSAID_ACCEPTEX;
DWORD dwBytes;
LPFN_ACCEPTEX lpfnAcceptEx;

// First, load extension method.
int retCode = WSAIoctl(listenSocket,
                        SIO_GET_EXTENSION_FUNCTION_POINTER,
                        &GuidAcceptEx,
                        sizeof(GuidAcceptEx),
                        &lpfnAcceptEx,
                        sizeof(lpfnAcceptEx),
                        &dwBytes,
                        NULL,
                        NULL
                        );

for( /* loop for 1000 times */ ) {
    SOCKET preemptiveSocket = WSASocket(, , , , , WSA_FLAG_OVERLAPPED);
    lpfnAcceptEx(listenSocket, preemptiveSocket,,,,,,);
}
这实质上是让您的应用程序以IOCP的方式接受套接字。当新的连接请求到来时,等待GetQueuedCompletionStatus()的工作线程之一将被释放,并将指针交给数据结构。这将具有由lpfnAcceptEx()创建的套接字。 这个过程完成了吗?还没有。通过AcceptEx()调用接受的套接字不继承listenSocket的属性。要做到这一点,你需要打电话

  setsockopt( acceptSocket, SOL_SOCKET, SO_UPDATE_ACCEPT_CONTEXT, 
                          (char*)&listenSocket, sizeof(listenSocket) );
现在,acceptSocket很适合用于WSASend/WSARecv

少了一些东西!我跳过了关于工作线程如何从GetQueuedCompletionStatus()获取acceptedSocket的部分? 答案是,通过将精心编制的结构传递给lpfnAcceptEx()。当GetQueuedCompletionStatus()返回时,它将具有包含您将要传递的套接字的数据结构。 如何制作这样的结构?通过创建一个将“WSAOVERLAPPED”作为其第一个成员的结构。在第一个成员之后,您可以拥有自己的任何成员。例如,我的结构看起来像

typedef struct _WSAOVERLAPPEDPLUS
{
    WSAOVERLAPPED ProviderOverlapped; // 'WSAOVERLAPPED' has to be the first member.
    SOCKET client;           // Use this to pass preemptive socket.
    SOCKET listenSocket;     // Use this to pass the listenSocket.
    DWORD dwBytes;
    SOCKET_OPERATION operation;       // Enum to assist in knowing what socket operation ...
} WSAOVERLAPPEDPLUS, *LPWSAOVERLAPPEDPLUS;

...
typedef enum SOCKET_OPERATION { 
                            UNINITIALIZED_ENUM, // To protect against memory leaks and uninitialized buffers.
                            OP_ACCEPTEX, 
                            OP_RECEIVE, 
                            OP_SEND 
                          };
...

//
// So the previously mentioned for() loop will become;
//

for( /* loop for 1000 times */ ) {
    SOCKET preemptiveSocket = WSASocket(, , , , , WSA_FLAG_OVERLAPPED);
    LPWSAOVERLAPPEDPLUS pOl = new WSAOVERLAPPEDPLUS();

    // Initialize our "extended" overlapped structure
    memset(pOl, 0, sizeof(WSAOVERLAPPEDPLUS));
    pOl->operation = OP_ACCEPTEX;
    pOl->client = preemptiveSocket;
    pOl->listenSocket = listenSocket;

    int buflen = (sizeof(SOCKADDR_IN) + 16) * 2;
    char* pBuf = new char[buflen];
    memset(pBuf, 0, buflen);

    m_lpfnAcceptEx(listenSocket,
                    preemptiveSocket,
                    pBuf,
                    0,  // Passed 0 to avoid reading data on accept which in turn
                        // avoids DDoS attack i.e., connection attempt without data will 
                        // cause AcceptEx to wait indefinitely.
                    sizeof(SOCKADDR_IN) + 16,
                    sizeof(SOCKADDR_IN) + 16,
                    &pOl->dwBytes,
                    &pOl->ProviderOverlapped
                    );
}
。。。当GetQueuedCompletionStatus()返回时,在工作线程中


我希望这能让您了解如何在IOCP中使用AcceptEx()。

AcceptEx
需要一个有效(但未连接或绑定)的套接字作为第二个参数,但您给它一个
无效的\u socket
值。另外,在将其传递给AcceptEx之前,您没有初始化WSAOoverlapped,因此它可能很容易包含随机数据。谢谢,我编辑了我的问题,不幸的是,它似乎对我不起作用。
ERROR\u IO\u PENDING
实际上是可以的,它只是意味着accept已成功启动并且仍在进行中。它将触发一个事件
 WSAOVERLAPPED wsaovl = {};
// (1) Create listen socket on server.
WSASocket()            

// (2) Bind an address to your listen socket.
bind()                 

// (3) Associate the listen socket with an event object on FD_ACCEPT event.
WSAEventSelect(,, FD_ACCEPT )       

// (4) Put socket in listen state - now, Windows listening for new 
//     connection requests. If new requests comes, the associated 
//     event object will be set.
listen()               

// (5) Wait on the event object associated on listen socket. This 
//     will get signaled when a new connection request comes.
WaitForSingleObject() {

     // (6) A network activity has occurred. Verify that FD_ACCEPT has 
     //     raised the event object. This also resets the event object
     //     so WaitForSingleObject() does not loop non-stop.
     WSAEnumNetworkEvents()  


     // (7) Understanding this part is important. The WSAAccept() doesn't
     //     just accept connection, it first creates a new socket and
     //     then associates it with the newly accepted connection.
     WSAAccept()
}
// (1) Create IO completion port
CreateIoCompletionPort()

// (2) Have a method that creates worker threads say 'CreateWorkerThreads()'. 
//     This assign same method (say WorkerThread_Func()) to all worker threads. 
//     In the WorkerThread_Func() threads are blocked on call to 
//     GetQueuedCompletionStatus().
CreateWorkerThreads()

// (3) Create listen socket.
WSASocket()            

// (4) Associate listen socket to IO Completion Port created earlier.
CreateIoCompletionPort()

// (5) Bind an address to your listen socket.
bind()                 

// (6) Put socket in listen state - now, Windows listening for new 
//     connection requests. If a new request comes, GetQueuedCompletionStatus() 
//     will release a thread.
listen() 

// (7) Create sockets in advance and call AcceptEx on each of 
//     these sockets. If a new connection requests comes 
//     Windows will pick one of these sockets and associate the 
//     connection with it.
//
//     As an example, below loop will create 1000 sockets.


GUID GuidAcceptEx = WSAID_ACCEPTEX;
DWORD dwBytes;
LPFN_ACCEPTEX lpfnAcceptEx;

// First, load extension method.
int retCode = WSAIoctl(listenSocket,
                        SIO_GET_EXTENSION_FUNCTION_POINTER,
                        &GuidAcceptEx,
                        sizeof(GuidAcceptEx),
                        &lpfnAcceptEx,
                        sizeof(lpfnAcceptEx),
                        &dwBytes,
                        NULL,
                        NULL
                        );

for( /* loop for 1000 times */ ) {
    SOCKET preemptiveSocket = WSASocket(, , , , , WSA_FLAG_OVERLAPPED);
    lpfnAcceptEx(listenSocket, preemptiveSocket,,,,,,);
}
  setsockopt( acceptSocket, SOL_SOCKET, SO_UPDATE_ACCEPT_CONTEXT, 
                          (char*)&listenSocket, sizeof(listenSocket) );
typedef struct _WSAOVERLAPPEDPLUS
{
    WSAOVERLAPPED ProviderOverlapped; // 'WSAOVERLAPPED' has to be the first member.
    SOCKET client;           // Use this to pass preemptive socket.
    SOCKET listenSocket;     // Use this to pass the listenSocket.
    DWORD dwBytes;
    SOCKET_OPERATION operation;       // Enum to assist in knowing what socket operation ...
} WSAOVERLAPPEDPLUS, *LPWSAOVERLAPPEDPLUS;

...
typedef enum SOCKET_OPERATION { 
                            UNINITIALIZED_ENUM, // To protect against memory leaks and uninitialized buffers.
                            OP_ACCEPTEX, 
                            OP_RECEIVE, 
                            OP_SEND 
                          };
...

//
// So the previously mentioned for() loop will become;
//

for( /* loop for 1000 times */ ) {
    SOCKET preemptiveSocket = WSASocket(, , , , , WSA_FLAG_OVERLAPPED);
    LPWSAOVERLAPPEDPLUS pOl = new WSAOVERLAPPEDPLUS();

    // Initialize our "extended" overlapped structure
    memset(pOl, 0, sizeof(WSAOVERLAPPEDPLUS));
    pOl->operation = OP_ACCEPTEX;
    pOl->client = preemptiveSocket;
    pOl->listenSocket = listenSocket;

    int buflen = (sizeof(SOCKADDR_IN) + 16) * 2;
    char* pBuf = new char[buflen];
    memset(pBuf, 0, buflen);

    m_lpfnAcceptEx(listenSocket,
                    preemptiveSocket,
                    pBuf,
                    0,  // Passed 0 to avoid reading data on accept which in turn
                        // avoids DDoS attack i.e., connection attempt without data will 
                        // cause AcceptEx to wait indefinitely.
                    sizeof(SOCKADDR_IN) + 16,
                    sizeof(SOCKADDR_IN) + 16,
                    &pOl->dwBytes,
                    &pOl->ProviderOverlapped
                    );
}
while (TRUE)
{
    bOk = ::GetQueuedCompletionStatus(hCompPort, &bytes_transferred, &completion_key, &pOverlapped, INFINITE);

    if (bOk) {
        // Process a successfully completed I/O request

        if (completion_key == 0) {
            // Safe way to extract the customized structure from pointer 
            // is to use 'CONTAINING_RECORD'. Read more on 'CONTAINING_RECORD'.
            WSAOVERLAPPEDPLUS *pOl = CONTAINING_RECORD(pOverlapped, WSAOVERLAPPEDPLUS, ProviderOverlapped);

            if (pOl->operation == OP_ACCEPTEX) {
                // Before doing any WSASend/WSARecv, inherit the 
                // listen socket properties by calling 'setsockopt()'
                // as explained earlier.
                // The listenSocket and the preemptive socket are available 
                // in the 'pOl->listenSocket' & 'pOl->client', respectively.

            }
            delete pOl;
        }
    }
    else {
           // Handle error ...
    }