2016-06-07 7 views
4

Ich habe folgenden vereinfachen IO Completion-Port-Server C++ Code:IO Completion-Port Initial lesen und bidirektionaler Daten

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; 
} 

1) Die erste Frage, die ich habe mit der anfänglichen Lese ist.

Auf Client-Verbindung (accept), gebe ich einen Lesevorgang aus. Da der Client noch keine Daten gesendet hat, ist WSAGetLastError() WSA_IO_PENDING und die Lesemethode gibt zurück.

Wenn der Client dann Daten sendet, bleibt der Thread im GetQueuedCompletionStatus-Aufruf stecken (wie ich annehme, brauche ich einen anderen WSARecv-Aufruf?).

Soll ich die Lesemethode weiterschleifen, bis Daten eintreffen? Das scheint nicht logisch zu sein, dachte ich, indem ich den ersten Read-Befehl GetQueuedCompletionStatus abgab, wenn Daten eintrafen.

2) Ich muss bidirektionale Daten ohne Bestätigungen lesen und schreiben. Deshalb habe ich auch einen Client mit dem IOCP-Thread erstellt. Ist es tatsächlich möglich, dies mit Completion-Ports zu tun, oder muss ein Read gefolgt von einem Write?

Entschuldigung für grundlegende Fragen, aber nachdem ich im Internet geforscht und IOCP-Beispiele erstellt habe, kann ich die Fragen immer noch nicht beantworten.

Vielen Dank im Voraus.

Antwort

2

Auf Client-Verbindung (akzeptieren), gebe ich einen Lesevorgang aus. Da der Client noch keine Daten gesendet hat, ist WSAGetLastError() WSA_IO_PENDING und die Lesemethode gibt zurück.

Das ist normales Verhalten.

Wenn der Client dann Daten sendet, bleibt der Thread im GetQueuedCompletionStatus-Aufruf stecken (wie ich annehme, brauche ich einen anderen WSARecv-Aufruf?).

Nein, Sie brauchen keinen weiteren Anruf. Und wenn es hängen bleibt, verknüpfen Sie den Lesevorgang nicht mit dem E/A-Fertigstellungs-Port.

Soll ich die Lesemethode weiterschleifen, bis Daten eintreffen?

Nein. Zum erstmaligen Lesen müssen Sie WSARecv() einmal anrufen. Der Fehler WSA_IO_PENDING bedeutet, dass der Lesevorgang auf Daten wartet und den E/A-Abschluss-Port meldet, wenn Daten tatsächlich ankommen. Rufen Sie WSARecv() (oder eine andere Lesefunktion) NICHT an, bis dieses Signal tatsächlich ankommt. Dann können Sie erneut WSARecv() aufrufen, um auf weitere Daten zu warten. Wiederholen Sie dies, bis der Socket getrennt ist.

Ich dachte durch die Ausgabe der ersten Lesung GetQueuedCompletionStatus würde abgeschlossen, wenn Daten angekommen sind.

Das ist genau das, was passieren soll.

2) Ich muss Daten bidirektional ohne Bestätigungen lesen und schreiben. Deshalb habe ich auch einen Client mit dem IOCP-Thread erstellt.Ist es tatsächlich möglich, dies mit Abschluss-Ports zu tun

Ja. Lesen und Schreiben sind getrennte Operationen, sie sind nicht voneinander abhängig.

Muss ein Lesevorgang von einem Schreibvorgang gefolgt werden?

Nicht, wenn Ihr Protokoll es nicht erfordert, nein.

Jetzt, mit dem gesagt, gibt es einige Probleme mit Ihrem Code.

Eine kleine Anmerkung, WSAAccept() ist synchron, sollten Sie stattdessen AcceptEx() verwenden, damit es den gleichen E/A-Abschluss-Port für die Meldung neuer Verbindungen verwenden kann.

Aber noch wichtiger ist, wenn eine anstehende E/A-Operation ausfällt, GetQueuedCompletionStatus() kehrt FALSCH, der zurück LPOVERLAPPED Zeiger werden nicht NULL und GetLastError() berichten, warum der E/A-Vorgang ist fehlgeschlagen. Wenn jedoch GetQueuedCompletionStatus() selbst fehlschlägt, wird der zurückgegebene LPOVERLAPPED Zeiger NULL sein, und GetLastError() wird melden, warum GetQueuedCompletionStatus() fehlgeschlagen ist. Dieser Unterschied ist in der documentation klar erklärt, aber Ihre while Schleife berücksichtigt dies nicht. Verwenden eine do..while Schleife statt und wirkt entsprechend den LPOVERLAPPED pointer:

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; 
} 

Auf einer Seite beachten, anstatt ein Ereignisobjekt zu verwenden, um zu signalisieren, wenn completionPortThreadProc() beenden sollte, sollten unter Verwendung PostQueuedCompletionionStatus() stattdessen eine Beendigung completionKey an die I posten/O-Completion-Port, dann können Sie Ihre Schleife für diesen Wert aussehen:

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; 
} 

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

PostQueuedCompletionStatus(completionPort, 0, MyTerminateKey, NULL); 
+0

Hallo Remy, Vielen Dank für Ihre lange Erklärung, ist es sehr zu schätzen, ich werde hier verrückt. Ich werde Ihre Kommentare bearbeiten und Ihnen Bericht erstatten! Ich habe einen guten Teil des Codes von anderen Beispielen genommen, von denen ich annahm, dass sie funktionierten. – CAM79

+0

Hallo Remy, ok, die neue Schleife war der Schlüssel und überprüft GetLastError in 'if (perIoData)'. I/O war mit WSA_OPERATION_ABORTED fehlgeschlagen, weshalb es nie abgeschlossen wurde. Ich hatte einen Annahmethread, der das Lesen ausgab und dann endete. Ich hätte gedacht, es hätte noch funktioniert, aber offensichtlich ist mein Design falsch. Vielen Dank für Ihre Hilfe, ich werde Ihre Gedanken in meinem neuen Design verwenden. Hoffentlich werden Ihre Kommentare auch anderen helfen, da viele Beispiele Code wie meinen zu verwenden scheinen. – CAM79

+0

Wenn ein Thread beendet wird, werden alle begonnenen E/A-Vorgänge, die noch ausstehen, automatisch abgebrochen. Sie sollten 'WSAAccept()' in einer Schleife innerhalb eines Threads aufrufen, der mindestens die Lebensdauer des Listening-Sockets aushält (oder die Client-Akzeptanz zum IO-Fertigstellungs-Port verschieben und das ursprüngliche Akzeptieren von einem solchen Thread ausgeben), damit ich/Os wird nicht abgebrochen. –

Verwandte Themen