2015-07-15 3 views
0

Я пытаюсь создать класс клиента/сервера UDP, который полагается на порты завершения ввода-вывода с использованием Winsock, но мне не удалось вернуть функцию GetQueuedCompletionStatus(), когда будут возвращены новые данные доступный. Вероятно, это связано с некоторыми недоразумениями с моей стороны, но примеры/документация по IOCP с UDP вместо TCP немного и далеко друг от друга.GetQueuedCompletionStatus блокирует неограниченное время на UDP-сокете

Вот соответствующие биты моего класса сервера с ошибкой проверки удалено для краткости:

Server::Server() 
{ 
    m_iocp = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0); 
} 

void Server::StartReceiving() 
{ 
    StopReceiving(); 

    m_iocp = CreateIoCompletionPort((HANDLE)m_receiveSocket, m_iocp, (DWORD)this, 0); 

    //WSAEVENT event = WSACreateEvent(); 
    //WSAEventSelect(m_receiveSocket, event, FD_ACCEPT | FD_CLOSE); 

    // Start up worker thread for receiving data 
    m_receiveThread.reset(new std::thread(&Server::ReceiveWorkerThread, this)); 
} 

void Server::Host(const std::string& port) 
{ 
    if (!m_initialized) 
     Initialize(); 

    addrinfo hints = {}; 
    hints.ai_family = AF_INET; 
    hints.ai_socktype = SOCK_DGRAM; 
    hints.ai_protocol = IPPROTO_UDP; 
    hints.ai_flags = AI_PASSIVE; 

    // Resolve the server address and port 
    const char* portStr = port.empty() ? kDefaultPort.c_str() : port.c_str(); 
    int result; 
    AddressInfo addressInfo = AddressInfo::Create(nullptr, portStr, &hints, result); // Calls getaddrinfo() 

    m_receiveSocket = WSASocket(addressInfo.GetFamily(), addressInfo.GetSocketType(), addressInfo.GetProtocol(), nullptr, 0, WSA_FLAG_OVERLAPPED); 

    // Bind receiving socket 
    result = bind(m_receiveSocket, addressInfo.GetSocketAddress(), addressInfo.GetAddressLength()); 

    StartReceiving(); 
} 

void Server::ReceiveWorkerThread() 
{ 
    SOCKADDR_IN senderAddr; 
    int senderAddrSize = sizeof(senderAddr); 
    DWORD bytesTransferred = 0; 
    OVERLAPPED* pOverlapped = nullptr; 
    WSABUF wsaBuf = { (ULONG)m_buffer.GetWriteBufferSize(), m_buffer.GetWriteBufferPointer() }; 

    DWORD flags = 0; 
    DWORD bytesReceived; 
    int result = WSARecvFrom(m_receiveSocket, &wsaBuf, 1, &bytesReceived, &flags, (sockaddr*)&senderAddr, &senderAddrSize, pOverlapped, nullptr); 

    // Process packets until signaled to exit 
    while (true) 
    { 
     DWORD context = 0; 
     BOOL success = GetQueuedCompletionStatus(
      m_iocp, 
      &bytesTransferred, 
      &context, 
      &pOverlapped, 
      INFINITE); 

     wsaBuf.len = (ULONG)m_buffer.GetWriteBufferSize(); 
     wsaBuf.buf = m_buffer.GetWriteBufferPointer(); 
     flags = 0; 

     result = WSARecvFrom(m_receiveSocket, &wsaBuf, 1, &bytesReceived, &flags, (sockaddr*)&senderAddr, &senderAddrSize, pOverlapped, nullptr); 

     // Code to process packet would go here 

     if (m_exiting.load() == true) 
      break; // Kill worker thread 
    } 
} 

Когда мой клиент отправляет данные на сервер, первый WSARecvFrom подбирает данные правильно, но блоки сервера на компьютере вызовите GetQueuedCompletionStatus и никогда не вернетесь, даже если отправлено больше датаграмм. Я также попытался поместить сокет в неблокирующий режим с помощью WSAEventSelect (код для этого комментируется выше), но это не имело никакого значения.

От чтения this похожее сообщение похоже, что должно быть хотя бы одно чтение на сокете, чтобы вызвать IOCP, поэтому я добавил первый вызов WSARecvFrom из основного цикла. Надеюсь, я прав, если предположить, что код клиента не имеет значения, если сервер получает данные без IOCP, поэтому я его не разместил.

Я уверен, что я делаю что-то неправильно, но я просто не вижу его.

ответ

1

Вам нужно проверить код результата от WSARecvFrom и вызвать GetQueuedCompletionStatus только если код возврата ERROR_IO_PENDING - если это не либо операция завершена без блокировки и у вас есть данные, или произошла ошибка, но ни в одном из в этих случаях он не был отправлен на порт завершения ввода-вывода, и поэтому он никогда не будет поднят на GetQueuedCompletionStatus, и вызов будет заблокирован.

И вы не должны делать это в одном потоке. Общий подход состоит в том, чтобы иметь поток, который только опросает порт завершения ввода-вывода и вызывает некоторые обратные вызовы в объектах контекста, чтобы уведомлять о входящих/исходящих данных, а отправляющие принимающие вызовы вызывают везде, где это необходимо.

+0

Спасибо. Если я правильно понимаю это, я должен иметь несколько потоков (вероятно, исходя из количества процессоров), вызывающих GetQueuedCompletionStatus в цикле? Будут ли эти потоки такжезывать WSARecvFrom, или это будет обрабатываться другой рабочей процедурой? –

+0

Да, используйте пул потоков, которые петлят на GetQueuedCompletionStatus(). Когда вы запустите свой сервер, вызовите WSARecvFrom(), чтобы получить возможность прокатиться. Когда любой из рабочих потоков получает завершенный пакет, обработайте данные по мере необходимости, а затем вызовите WSARecvFrom() для очереди следующего пакета. –

+0

@NickBlakely хорошо, если ваше дело тривиально, вы также можете быть хорошим с примером, который у вас есть. Я говорю, что общий подход заключается в том, чтобы иметь какой-то класс/структуру, представляющий одно соединение, и использовать это либо в перекрывающейся структуре, либо в качестве ключа завершения (зависит от потока ввода-вывода), поэтому вы можете вызвать let say функцию 'OnBytesWritten' /' OnBytesRead' для этого объекта, когда возвращается GetQueuedCompletionStatus'. Затем эти методы будут либо продолжать читать/писать, либо делать что-то еще. Таким образом, у вас есть логика отдельно от механизма, который выполняет ввод-вывод. –