IO完成端口初始读取和双向数据

Bre*_*ell 5 c++ iocp winsock2

我有以下简化的 IO 完成端口服务器 C++ 代码:

int main(..)
{
    startCompletionPortThreadProc();

    // Await client connection

    sockaddr_in clientAddress;
    int clientAddressSize = sizeof( clientAddress );
    SOCKET acceptSocket = WSAAccept( serverSocket, (SOCKADDR*)&clientAddress, &clientAddressSize, NULL, NULL);  

    // Connected

    CreateIoCompletionPort( (HANDLE)acceptSocket, completionPort, 0, 0 );

    // Issue initial read
    read( acceptSocket );
}


DWORD WINAPI completionPortThreadProc( LPVOID param )
{
    DWORD bytesTransferred = 0;
    ULONG_PTR completionKey = NULL;
    LPPER_IO_DATA perIoData = NULL;

    while( GetQueuedCompletionStatus( completionPort, &bytesTransferred, &completionKey, (LPOVERLAPPED*)&perIoData, INFINITE ) )
    {
        if( WaitForSingleObject( exitEvent, 0 ) == WAIT_OBJECT_0 )
        {
            break;
        }

        if( !perIoData )
            continue;

        if( bytesTransferred == 0 )
        {
            //TODO
        }

        switch( perIoData->operation )
        {
            case OPERATION_READ:
            {
                // Bytes have been received

                if( bytesTransferred < perIoData->WSABuf.len )
                {
                    // Terminate string
                    perIoData->WSABuf.buf[bytesTransferred]   = '\0';
                    perIoData->WSABuf.buf[bytesTransferred+1] = '\0';
                }

                // Add data to message build
                message += std::tstring( (TCHAR*)perIoData->WSABuf.buf );

                // Perform next read
                    perIoData->WSABuf.len = sizeof( perIoData->inOutBuffer );
                    perIoData->flags = 0; 

                    if( WSARecv( perIoData->socket, &( perIoData->WSABuf ), 1, &bytesTransferred, &( perIoData->flags ), &( perIoData->overlapped ), NULL ) == 0 )
                    {
                        // Part message
                        continue;
                    }

                    if( WSAGetLastError() == WSA_IO_PENDING )
                    {
                        // End of message
//TODO: Process message here
                        continue;
                    }
                }
            }
            break;

            case OPERATION_WRITE:
            {
                perIoData->bytesSent += bytesTransferred;

                if( perIoData->bytesSent < perIoData->bytesToSend )
                {
                    perIoData->WSABuf.buf = (char*)&( perIoData->inOutBuffer[perIoData->bytesSent] );
                    perIoData->WSABuf.len = ( perIoData->bytesToSend - perIoData->bytesSent);
                }
                else
                {
                    perIoData->WSABuf.buf  = (char*)perIoData->inOutBuffer;
                    perIoData->WSABuf.len  = _tcslen( perIoData->inOutBuffer ) * sizeof( TCHAR );
                    perIoData->bytesSent   = 0;
                    perIoData->bytesToSend = perIoData->WSABuf.len;
                }

                if( perIoData->bytesToSend )
                {
                    if( WSASend( perIoData->socket, &( perIoData->WSABuf ), 1, &bytesTransferred, 0, &( perIoData->overlapped ), NULL ) == 0 )
                        continue;

                    if( WSAGetLastError() == WSA_IO_PENDING )
                        continue;
                }
            }
            break;
        }
    }

    return 0;
}

bool SocketServer::read( SOCKET socket, HANDLE completionPort )
{
    PER_IO_DATA* perIoData = new PER_IO_DATA;
    ZeroMemory( perIoData, sizeof( PER_IO_DATA ) );

    perIoData->socket            = socket;
    perIoData->operation         = OPERATION_READ;
    perIoData->WSABuf.buf        = (char*)perIoData->inOutBuffer; 
    perIoData->WSABuf.len        = sizeof( perIoData->inOutBuffer );
    perIoData->overlapped.hEvent = WSACreateEvent();

    DWORD bytesReceived = 0;
    if( WSARecv( perIoData->socket, &( perIoData->WSABuf ), 1, &bytesReceived, &( perIoData->flags ), &( perIoData->overlapped ), NULL ) == SOCKET_ERROR )
    {
        int gle = WSAGetLastError();
        if( WSAGetLastError() != WSA_IO_PENDING )
        {
            delete perIoData;
            return false;
        }
    }

    return true;
}

bool SocketServer::write( SOCKET socket, std::tstring& data )
{
    PER_IO_DATA* perIoData = new PER_IO_DATA;
    ZeroMemory( perIoData, sizeof( PER_IO_DATA ) );

    perIoData->socket            = socket; 
    perIoData->operation         = OPERATION_WRITE;
    perIoData->WSABuf.buf        = (char*)data.c_str();
    perIoData->WSABuf.len        = _tcslen( data.c_str() ) * sizeof( TCHAR );
    perIoData->bytesToSend       = perIoData->WSABuf.len;  
    perIoData->overlapped.hEvent = WSACreateEvent();

    DWORD bytesSent = 0;
    if( WSASend( perIoData->socket, &( perIoData->WSABuf ), 1, &bytesSent, 0, &( perIoData->overlapped ), NULL ) == SOCKET_ERROR )
    {
        if( WSAGetLastError() != WSA_IO_PENDING )
        {
            delete perIoData;
            return false;
        }
    }

    return true;
}
Run Code Online (Sandbox Code Playgroud)

1)我遇到的第一个问题是初步阅读。

在客户端连接(接受)上,我发出读取。由于客户端尚未发送任何数据,WSAGetLastError() 为 WSA_IO_PENDING,并且 read 方法返回。

当客户端发送数据时,线程仍然停留在 GetQueuedCompletionStatus 调用中(因为我假设我需要另一个 WSARecv 调用?)。

我应该继续循环读取方法直到数据到达吗?这似乎不合逻辑,我认为通过发出初始读取 GetQueuedCompletionStatus 将在数据到达时完成。

2)我需要在没有确认的情况下双向读写数据。因此我还创建了一个带有 IOCP 线程的客户端。实际上是否可以使用完成端口来执行此操作,或者读取后必须先写入?

很抱歉感觉像是基本问题,但在搜索互联网并构建 IOCP 示例后,我仍然无法回答这些问题。

提前谢谢了。

Rem*_*eau 3

在客户端连接(接受)上,我发出读取。由于客户端尚未发送任何数据,WSAGetLastError() 为 WSA_IO_PENDING,并且 read 方法返回。

这是正常行为。

当客户端发送数据时,线程仍然停留在 GetQueuedCompletionStatus 调用中(因为我假设我需要另一个 WSARecv 调用?)。

不,您不需要再次致电。如果它被卡住,那么您就没有正确地将读取与 I/O 完成端口关联起来。

我应该继续循环读取方法直到数据到达吗?

不可以。您需要致电WSARecv()一次进行初次阅读。该WSA_IO_PENDING错误意味着读取正在等待数据,并且当数据实际到达时将向 I/O 完成端口发出信号。在该信号实际到达之前,请勿调用WSARecv()(或任何其他读取函数)。然后您可以WSARecv()再次调用以等待更多数据。重复此操作,直到套接字断开连接。

我认为通过发出初始读取 GetQueuedCompletionStatus 将在数据到达时完成。

这正是应该发生的事情。

2)我需要在没有确认的情况下双向读写数据。因此我还创建了一个带有 IOCP 线程的客户端。是否真的可以使用完成端口来做到这一点

是的。读和写是独立的操作,它们不相互依赖。

读操作后必须先写操作吗?

如果您的协议不需要,则不会。

现在,话虽如此,您的代码存在一些问题。

需要注意的是,WSAAccept()它是同步的,您应该考虑使用它,AcceptEx()以便它可以使用相同的 I/O 完成端口来报告新连接。

但更重要的是,当挂起的 I/O 操作失败时,GetQueuedCompletionStatus()返回 FALSE,返回的LPOVERLAPPED指针将是非 NULL,并且GetLastError()会报告 I/O 操作失败的原因。然而,如果GetQueuedCompletionStatus()本身失败,返回的LPOVERLAPPED指针将为NULL,并GetLastError()报告GetQueuedCompletionStatus()失败原因。文档中清楚地解释了这种差异,但您的while循环没有考虑到它。使用do..while循环并根据LPOVERLAPPED指针进行操作:

DWORD WINAPI completionPortThreadProc( LPVOID param )
{
    DWORD bytesTransferred = 0;
    ULONG_PTR completionKey = NULL;
    LPPER_IO_DATA perIoData = NULL;

    do
    {
        if( GetQueuedCompletionStatus( completionPort, &bytesTransferred, &completionKey, (LPOVERLAPPED*)&perIoData, INFINITE ) )
        {
            // I/O success, handle perIoData based on completionKey as needed...
        }
        else if( perIoData )
        {
            // I/O failed, handle perIoData based on completionKey as needed...
        }
        else
        {
            // GetQueuedCompletionStatus() failure...
            break;
        }    
    }
    while( WaitForSingleObject( exitEvent, 0 ) == WAIT_TIMEOUT );

    return 0;
}
Run Code Online (Sandbox Code Playgroud)

顺便说一句,不要使用事件对象来发出何时completionPortThreadProc()应该退出的信号,而是考虑使用PostQueuedCompletionionStatus()将终止completionKey发布到I/O完成端口,然后你的循环可以查找该值:

DWORD WINAPI completionPortThreadProc( LPVOID param )
{
    DWORD bytesTransferred = 0;
    ULONG_PTR completionKey = NULL;
    LPPER_IO_DATA perIoData = NULL;

    do
    {
        if( GetQueuedCompletionStatus( completionPort, &bytesTransferred, &completionKey, (LPOVERLAPPED*)&perIoData, INFINITE ) )
        {
            if( completionKey == MyTerminateKey )
                break;

            if( completionKey == MySocketIOKey )
            {
                // I/O success, handle perIoData as needed...
            }
        }
        else if( perIoData )
        {
            // I/O failed, handle perIoData based on completionKey as needed...
        }
        else
        {
            // GetQueuedCompletionStatus() failure...
            break;
        }    
    }
    while( true );

    return 0;
}
Run Code Online (Sandbox Code Playgroud)

CreateIoCompletionPort( (HANDLE)acceptSocket, completionPort, MySocketIOKey, 0 );
Run Code Online (Sandbox Code Playgroud)

PostQueuedCompletionStatus( completionPort, 0, MyTerminateKey, NULL );
Run Code Online (Sandbox Code Playgroud)