2016-06-07 6 views
4

У меня есть следующий упрощенный IO Completion Port сервер C++ код:IO Completion Port Начальное чтение и Bi-Directional Data

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) Первый вопрос, который я имею с начальным чтения.

При подключении к клиенту (примите), я выдаю сообщение. Поскольку клиент еще не отправил никаких данных, WSAGetLastError() - WSA_IO_PENDING, и метод чтения возвращается.

Когда клиент отправляет данные, поток остается застрявшим в вызове GetQueuedCompletionStatus (как я предполагаю, мне нужен другой вызов WSARecv?).

Должен ли я продолжать цикл использования метода чтения до тех пор, пока данные не поступят? Это не кажется логичным, подумал я, выпустив начальное чтение GetQueuedCompletionStatus, которое завершится при поступлении данных.

2) Мне нужно читать и писать данные двунаправленно без подтверждения. Поэтому я также создал клиента с потоком IOCP. Возможно ли это сделать с помощью портов завершения или чтение должно сопровождаться записью?

Извините за то, что походит на основные вопросы, но после траления в Интернете и создания примеров IOCP я все еще не могу ответить на вопросы.

Большое спасибо заранее.

ответ

2

При подключении к клиенту (примите), я выдаю сообщение. Поскольку клиент еще не отправил никаких данных, WSAGetLastError() - WSA_IO_PENDING, и метод чтения возвращается.

Это нормальное поведение.

Когда клиент отправляет данные, поток остается застрявшим в вызове GetQueuedCompletionStatus (как я предполагаю, мне нужен другой вызов WSARecv?).

Нет, вам не нужен другой звонок. И если он застревает, то вы не связываете чтение с портом завершения ввода-вывода правильно.

Должен ли я продолжать цикл считывания до тех пор, пока данные не поступят?

№ Вам необходимо позвонить по номеру WSARecv() один раз для первоначального ознакомления. Ошибка WSA_IO_PENDING означает, что чтение ожидает данных и будет сигнализировать порт завершения ввода-вывода, когда данные действительно поступают. НЕ звоните WSARecv() (или любую другую функцию чтения), пока этот сигнал не поступит. Затем вы можете позвонить WSARecv() еще раз, чтобы дождаться большего количества данных. Повторяйте до разъединения разъема.

Я думал, выпустив начальное чтение GetQueuedCompletionStatus будет завершено при поступлении данных.

Именно это и должно произойти.

2) Мне нужно читать и писать данные двунаправленно без подтверждения. Поэтому я также создал клиента с потоком IOCP.Возможно ли это сделать с помощью портов завершения

Да. Чтение и запись - это отдельные операции, они не зависят друг от друга.

Чтение должно сопровождаться записью?

Нет, если ваш протокол не требует этого, нет.

Теперь, с учетом сказанного, есть некоторые проблемы с вашим кодом.

На второстепенной ноте WSAAccept() синхронно, вы должны использовать вместо этого AcceptEx(), чтобы он мог использовать один и тот же порт завершения ввода-вывода для сообщения новых подключений.

Но что еще более важно, когда операцияв ожидании ввода/вывода не удается, GetQueuedCompletionStatus() возвращает FALSE, возвращенное LPOVERLAPPED указатель будет не NULL, и GetLastError() сообщит, почему операция ввода/вывода не удалось. Однако, если GetQueuedCompletionStatus() сам не работает, возвращенный указатель LPOVERLAPPED будет NULL, а GetLastError() сообщит, почему GetQueuedCompletionStatus() не удалось. Эта разница четко объясняется в documentation, но ваш цикл 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; 
} 

На стороне записки, вместо того, чтобы использовать объект события, чтобы сигнализировать, когда completionPortThreadProc() должен выйти, рассмотреть возможность использования PostQueuedCompletionionStatus() вместо того, чтобы опубликовать терминации CompletionKey в I/O Completion Port, то ваш цикл может выглядеть для этого значения:

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

Привет Remy, Большое спасибо за долгое объяснение, это очень высоко, я схожу с ума здесь. Я буду работать с вашими комментариями и отчитываться! Я взял много кода из других примеров, которые, как я полагал, работал. – CAM79

+0

Hi Remy, ок, новый цикл был ключевым и специально проверял GetLastError в «if (perIoData)». Ошибка ввода-вывода с WSA_OPERATION_ABORTED, поэтому он так и не завершился. У меня был поток принятия, который выдал чтение, а затем закончился. Я бы подумал, что это все равно будет работать, но, очевидно, мой дизайн ошибочен. Большое спасибо за вашу помощь, я буду использовать ваши мысли в своем новом дизайне. Надеюсь, ваши комментарии также помогут другим, поскольку многие примеры, похоже, используют код, подобный моему. – CAM79

+0

Когда поток завершается, все запущенные операции ввода-вывода, которые все еще ожидаются, автоматически прерываются. Вы должны вызывать 'WSAAccept()' в цикле в потоке, который живет, по крайней мере, на протяжении жизни прослушивающего сокета (или переместить прием клиента в порт завершения ввода-вывода и выдавать начальный прием из такого потока), поэтому я/Os не прерывается .. –