NoviceCoder871987 Ответов: 1

Нужно ли wsarecv wsabuf выравнивать по страницам?


Вызов WSARecv с адресом WSABUF. buf+TotalReceivedBytes будет иметь очень странные результаты. Я использую этот метод для преодоления буферного копирования.

TotalReceivedBytes просто в переменную ввода-вывода для каждого пользователя totalbytes

полученный через GetQueuedCompletionStatus затем каждый раз я добавляю lpNumberOfBytes

к моей определенной переменной. Но когда это происходит и TotalReceivedBytes не выровнен по DWORD например 0x1015 возвращается из GetQueuedCompletionStatus

функция это приводит к тому, что GetQueuedCompletionStatus перестает возвращаться и спит вечно, и даже события закрытия сокета не разбудят его.

Это привело меня к утечке памяти, так как буферы и ресурсы ввода-вывода никогда не освобождались. Но это хорошо работает, если TotalReceivedBytes были 0x2000, но я не могу контролировать, сколько
receive я использовал WSARecv с размером буфера 0x2000, но он просто возвращает данные, полученные 0x1015 при первом вызове.

Так как же мне преодолеть эту проблему? должен ли я всегда перераспределять буфер с помощью функций кучи? но это делает приложение медленным. Каково решение этой проблемы?

Что я уже пробовал:

Пытался использовать значение таймаута на
GetQueuedCompletionStatus 
но он возвращается с 0 GetLastError reports WAIT_TIMEOUT. И
lpNumberOfBytes
возвращает также 0, который пропускает мой вызов WSARecv, и поэтому данные, застрявшие там, никогда не опрашивались.

использование динамического перераспределения буфера каждый раз через HeapRealloc работает нормально, но это имеет слишком много накладных расходов, когда вызывается слишком много WSARecv.

1 Ответов

Рейтинг:
12

Jochen Arndt

Пожалуйста, проверьте код ошибки (еще раз). 0x1015 не является определенной ошибкой.

Обратите внимание, что вы также должны изменить WSABUF.len соответственно при изменении WSABUF.buf.

Но я бы не стал этого делать. Намерение таких буферов-это то, что подразумевает название: буферизация. Если вы ожидаете больших объемов блоков данных, используйте небольшие буферы с фиксированным размером и копируйте (добавляйте) данные в конечный пункт назначения после каждого завершения. Копирование таких небольших блоков памяти происходит быстро и не добавляет больших накладных расходов.


NoviceCoder871987

Спасибо. 0x1015-это не код ошибки, это lpNumberOfBytes, возвращаемый GetQueuedCompletionStatus. Так что на самом деле он сначала не прочитал 0x2000 байт. Данные были разбиты на куски. И я также обновлял len, но это не стабильный метод.

Теперь я заставил его выделять каждый раз, когда прием вызывается через HEAPREALLOCATE API, но после прочтения вашего ответа я сделаю его фиксированным буфером, а затем добавлю к окончательному полученному буферу. Можете ли вы предложить, сколько байтов я должен получить для быстрой передачи? или количество запрашиваемых байтов, которые должны быть получены, не влияет на скорость передачи?

Теперь у меня есть еще один вопрос, с которым я действительно не знаю, как справиться.

Я использую код для идентификации каждого завершения ввода-вывода, устанавливая этот код в пример OP_WRITE для отправки. OP_READ для чтения. Все это задано в структуре данных Per IO.


В первом WSARecv инициировать чтение ввода-вывода. Я установил этот код в OP_READ
и по возвращении из GetQueuedCompletionStatus я извлекаю этот ключ, чтобы узнать, какова текущая завершенная операция с помощью switch case. Но мой вопрос в том, что делать в случае отправки и получения одновременно? является ли возвращение из GetQueuedCompletionStatus секвенированным? Я имею в виду, что если я вызову WSASend и будет получено больше данных, должно ли быть отправлено следующее завершение ввода-вывода из GetQueuedCompletionStatus? или это просто происходит случайно?

Как я могу отправлять и получать одновременно? это сбивает меня с толку. Как справиться с этой ситуацией после возвращения GetQueuedCompletionStatus?. Должен ли я создать больше сокетов от другого клиента для использования или 1 сокета достаточно как для отправки, так и для получения одновременно?

Jochen Arndt

На этот вопрос нельзя ответить полностью в разделе комментариев и требуется больше знаний о требованиях (количество пакетов / клиентов, частота пакетов, средний размер пакетов).

Обычно у вас есть заголовок, который определяет размер следующих данных, чтобы вы могли их выделить. Чтобы избежать многократного распределения, вы можете повторно использовать существующие буферы (не выделяйте (повторно), если существующий буфер достаточно велик). Это может быть, наконец, какой-то пул памяти.

Обработки асинхронных событий. Таким образом, вы не получаете секвенированных результатов. Какие именно данные будут возвращены вызовом, указывается в выходных параметрах. Из них вы должны быть в состоянии определить, какая операция завершена и что делать дальше.

С TCP вы обычно используете один сокет для отправки и получения. Отправка и получение одновременно (даже инициированные из разных потоков) обрабатываются ОС, когда не используется блокирующий ввод-вывод.

У вас будут перекрывающиеся структуры для отправки и получения. При использовании функции WSAWaitForMultipleEvents () возвращаемое значение указывает, какое событие произошло. С GetQueuedCompletionStatus () вы можете использовать дескриптор события перекрывающейся структуры.

NoviceCoder871987

Спасибо за помощь. Под несколькими сокетами я имел в виду, что если я хочу отправить несколько файлов с клиента на сервер, тогда сервер должен создать выделенный сокет для каждого файла? или просто использовать 1 сокет для всех операций отправки? и когда я добавляю случай переключения после того, как вызов GetQueuedCompletionStatus возвращает, я использую OP_WRITE, OP_READ для идентификации события сокета. Теперь я не понимаю, как я собираюсь отправлять и получать одновременно, потому что, если я получаю, я устанавливаю этот код OP на OP_READ, тогда скажем, я хочу отправить что-то, пока чтение данных все еще выполняется, поэтому я установил этот код OP для OP_WRITE теперь происходит: следующая структура OVERLAPPED, возвращенная из вызова GetQueuedCompletionStatus, будет иметь установленный код OP_WRITE, так что же произойдет с ожидающими данными, которые я хочу получить? switch case выполняет только 1 блок инструкции, поэтому он либо читает, либо пишет, как сделать оба в switch? или мне нужно добавить это значение константы OP_XXXX, например OP_WRITE + OP_READ? Я не освобождаю структуру OVERLAPPED для всех операций ввода-вывода, если клиент не закрыл соединение.

Jochen Arndt

Сокет связан с подключением. Если у вас есть существующее соединение, вы обычно не открываете другое, а используете его для передачи, например, нескольких файлов.

Вы должны создать две перекрывающиеся структуры: одну для отправки и одну для получения.

NoviceCoder871987

Спасибо за совет. Теперь я создал 2 перекрывающиеся структуры. Нужно ли использовать критические разделы? я имею в виду, что в буферном копировании и totalbytesreceived изменяются значения памяти, должен ли я поместить их внутрь EnterCriticalSection LeaveCriticalSection? или она не нужна? где я должен использовать критический раздел в IOCP?

Jochen Arndt

Нет необходимости блокировать буфер recv, потому что вы копируете данные из буфера перед повторным вызовом recv.

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

NoviceCoder871987

Йохен, спасибо за помощь. Не могли бы вы проверить мой другой вопрос и предоставить некоторую информацию, если у вас есть какие-то идеи, которые могут помочь? я боролся в течение нескольких дней, но безуспешно.
https://www.codeproject.com/Questions/1214871/IOCP-sharing-completion-key-instances-between-each

Jochen Arndt

Я ничем не могу помочь. Поэтому я буду Ансер здесь.

Похоже, что вам нужно создать и сохранить какой-то уникальный идентификатор для каждого пользователя. Это может быть частью используемого протокола (например, при первом подключении / входе в систему вы отправляете этот идентификатор обратно клиенту, и он должен предоставить ему все дальнейшие пакеты).

Никогда не используйте SendMessage() для передачи данных из одного потока в основной (GUI) поток. Это происходит не потому, что он блокирует, а потому, что он может привести к сбою вашего приложения (параллельный доступ к данным окна). Вместо SendNotifyMessage () вы также можете использовать PostMessage ().

NoviceCoder871987

Да, это то, что я назвал уникальным идентификатором соединения или ключом, который будет использоваться во всех соединениях для дифференциации пользователя. Я думаю, что это не очень хорошо использовать несколько сокетов для одного и того же пользователя, как вы можете видеть, это дает больше сложности. Поэтому я думаю, что вместо этого я сделаю только 1 сокет и буду использовать несколько экземпляров данных per io вместо нескольких экземпляров контекстного ключа. Таким образом, у меня будет 1 сокет для всех операций, но несколько буферов данных per io и de выделять их при каждом завершении и выделять новые при каждом запуске операции opf.
Спасибо за вашу помощь. Что очень информативно.