WSAsend для всех подключенных сокетов в многопоточном сервере iocp
Я работаю на сервере IOCP (перекрывающийся ввод-вывод, 4 потока, CreateIoCompletionPort
, GetQueuedCompletionStatus
, WSASend
так далее). Я также создал событие автоматического сброса и поместил дескриптор в структуру OVERLAPPED для каждой асинхронной операции ввода-вывода.
И вопрос такой: как правильно отправлять в буфер все подключенные сокеты? Каждый сокет хранится в связанном списке структур контекстной информации.
Я не уверен, что этот подход ниже в порядке?
...
DWORD WINAPI WorkerThread() { // 1 of 4 workthread
...
GetQueuedCompletionStatus(...);
...
PPER_SOCKET_CONTEXT pTmp1, pTmp2;
pTmp1 = g_pCtxtList; // start of linked list with sockets
EnterCriticalSection(&g_CriticalSection);
while( pTmp1 )
{
pTmp2 = pTmp1->pCtxtBack;
WaitForSingleObject(pTmp1->pIOContext->Overlapped.hEvent,infinite);
// if there is any pending wsasend on this socket,wait to completed, so
// we can post another wsasend using the same overlapped structure
// and buffer
WSASend(pTmp1->Socket,...,&(pTmp1->pIOContext->Overlapped), NULL);
pTmp1 = pTmp2;
}
LeaveCriticalSection(&g_CriticalSection);
...
}
И что произойдет, если другой поток также попытается выполнить ту же самую работу в то же время?
Это хорошая идея, чтобы использовать GQCS и ждет функции во всех потоках?
Любая подсказка о wsasends
всем клиентам в многопоточном сервере iocp будут оценены.
Спасибо
2 ответа
Как говорит Мартин, это будет работать ужасно и, вероятно, снизит производительность всего, что использует список сокетов, которые вы блокируете на протяжении всего времени отправки для всех соединений. Вы не говорите, является ли это UDP или TCP, но если это TCP, имейте в виду, что теперь вы передаете контроль над производительностью вашего сервера клиентам, поскольку управление потоком TCP при медленном клиентском соединении может вызвать задержку завершения записи (см. здесь)- и я предполагаю, что вы используете завершение записи, чтобы вызвать событие?
Я предполагаю, что ваше реальное требование заключается в том, что вы хотите избежать копирования данных на сервер и выделения нескольких буферов, по одному для каждого соединения, либо из-за ограничений памяти, либо потому, что вы профилировали копию памяти и обнаружили, что это дорого.
Способ, которым я справляюсь с этим, состоит в том, чтобы иметь один буфер с подсчетом ссылок и "дескриптор буфера", который представляет собой лишь слегка расширенную перекрывающуюся структуру, которая ссылается на ваш единственный буфер данных и обеспечивает необходимый вам WSABUF. Затем вы можете выполнить запись "запускай и забывай" для каждого соединения, используя уникальный "дескриптор буфера", каждый из которых относится к одному базовому буферу. Как только все записи завершены, счетчик ссылок в буфере уменьшается до нуля, и он очищается - и, как говорит Мартин, эта очистка лучше всего достигается путем помещения буфера в пул для последующего повторного использования.
Примечание: я не уверен, что я действительно понимаю, что вы пытаетесь сделать (поэтому я изначально удалил свой ответ), дайте мне знать, если я не подписан, и я настрою...
Не уверен, что понимаю кое-что из этого. IOCP обычно не использует поле hEvent в структуре OVL. Завершение ввода / вывода сигнализируется путем помещения сообщения о завершении в "порт завершения" (т. Е. Очередь). Похоже, вы используете поле hEvent для некоторых "необычных" дополнительных сигналов для управления одним буфером отправки данных и блоком OVL.
Очевидно, у меня нет всей истории из вашего поста, но мне кажется, что вы делаете тяжелую работу для себя на стороне передачи, и сериализация посылок задушит производительность:)
Нужно ли использовать один и тот же объект OVL/ буфер для последовательных отправок? Обычно я использую разные OVL/ буфер для каждой отправки и сразу же ставлю их в очередь. Ядро будет отправлять буферы последовательно и возвращать сообщение о завершении для каждого из них. Нет проблем с несколькими запросами IOCP tx на сокете - для этого предназначен блок OVL - чтобы связать их вместе в стеке ядра.
Существует проблема с наличием нескольких запросов приема IOCP для невыполненного сокета - может случиться так, что два потока пула получат пакеты завершения для одного и того же сокета одновременно, что может привести к неправильной обработке. Для исправления этой проблемы "должным образом" требуется что-то вроде увеличения порядкового номера в каждом выпущенном объекте rx buffer/OVL и критического раздела и списка буферов в каждом объекте сокета для "сохранения" буферов не по порядку, пока все более ранние те были обработаны. У меня есть подозрение, что многие IOCP-серверы просто уклоняются от этой проблемы, имея только один RX-запрос IOCP за раз (вероятно, за счет производительности).
Пройдя через многие буферы таким способом, можно несколько усложнить, если они постоянно создаются и уничтожаются, поэтому я обычно не беспокоюсь и просто создаю несколько тысяч из них при запуске и толкаю их (ОК, указатели на них) в очередь-пул производителя-потребителя, отбрасывая их, когда требуется tx или rx, и снова их подталкивая. В случае передачи это происходит, когда один из потоков пула IOCP получает сообщение о завершении отправки. В случае rx это может произойти, когда поток пула (или какой-либо другой поток, которому объект поставил в очередь поток пула) обработал его и больше не нуждается в нем.
Ааа.. вы хотите отправить точно такой же контент в список сокетов - как в чате типа сервера штуковин.
ХОРОШО. Так как насчет одного буфера и нескольких блоков OVL? Я не пробовал, но не понимаю, почему это не сработает. В объекте с одним буфером сохраняйте атомарный счетчик ссылок на количество перекрывающихся запросов на отправку, которые вы отправили в цикле "отправить всем клиентам". Когда вы вернете буферы обратно в пакеты завершения, уменьшите refCount до нуля и удалите / перезапустите буфер, когда опуститесь до 0.
Я думаю, что должно работать, (?).