Несколько портов завершения сокетов IOCP в одном контейнере

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

У меня есть 2 сокета в одной структуре, которые имеют один и тот же порт завершения. Проблема в том, что они оба используют разные протоколы. Есть ли способ узнать, какой сокет сработал? Они называются game_socket и client_socket.

Пример кода будет примерно таким...

while (true) {
error = GetQueuedCompletionStatus(CompletionPort, &BytesTransfered, (PULONG_PTR)&Key, &lpOverlapped, 0);
srvc = CONTAINING_RECORD ( lpOverlapped, client , ol );
if ( error == TRUE ) {
cout << endl << "SOCKET: [" << srvc->client_socket << "] TRIGGERED - WORKER THREAD" << endl;
cout << endl << "BytesTransfered: [" << BytesTransfered << "]" << endl;

if ( srvc->game_client triggered ) {
// .. this code
} else {

// .. this code
}

Любые идеи или помощь будут оценены :)


person User    schedule 03.09.2012    source источник


Ответы (2)


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

В вашем случае вы НЕ должны использовать одну и ту же структуру OVERLAPPED для двух разных сокетов с двумя потенциально независимыми действиями. Если сокеты в каком-то смысле «связаны», то это следует отслеживать и поддерживать вне вашей структуры OVERLAPPED. Каждый должен иметь свою собственную структуру для своих отдельных завершений ввода-вывода. От всего остального откровенно голова болит.

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

Всего наилучшего.

person WhozCraig    schedule 03.09.2012
comment
Я боялся этого. Поскольку я пробовал множество вещей, таких как WSAEventSelect с WSAEnumNetworkEvents , но ничего не получалось. :( - person User; 03.09.2012
comment
Это может быть не так плохо, как вы думаете. Вы можете без проблем использовать один и тот же IOCP для нескольких сокетов. Просто убедитесь, что каждый запрос ввода-вывода связан с одной и ТОЛЬКО одной структурой OVERLAPPED. Если у вас есть два сокета, управляемых в каком-то экземпляре объекта, и они оба потенциально ожидают IOCP, то каждый из них, вероятно, должен иметь расширенную структуру OVERLAPPED, которая ссылается на объект, и идентифицирующий, какой сокет участвует в запросе ввода-вывода, представленном данными OVERLAPPED. Надеюсь, это имело смысл. Вы можете сделать это, просто подумайте об этом немного дольше. - person WhozCraig; 03.09.2012
comment
Я как бы подумал, что мне придется сделать что-то подобное! Спасибо за ответ, я обязательно попробую это на нескольких небольших тестовых серверах. :) - person User; 03.09.2012
comment
Обратите внимание, что идея использования одного OVERLAPPED с двумя сокетами концептуально неверна. На самом деле вы используете один OVERLAPPED для получения одного события, каким бы оно ни было. Это событие может исходить от одного из двух сокетов, но это не имеет значения. У него будет связанный CompletionKey, который позволит вам сказать, к чему он относится. - person Damon; 05.09.2012

При использовании IOCP у вас есть два элемента пользовательских данных, которые вы можете использовать для каждой асинхронной операции.

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

Вторая - это «расширенная перекрывающаяся» структура, которая представляет собой данные «за операцию». Для каждой параллельной операции ДОЛЖНА быть назначена уникальная перекрывающаяся структура.

В приведенном выше дизайне вы должны использовать данные «на соединение» для идентификации вашего соединения. У вас есть два связанных соединения (сокета), поэтому у меня было бы два связанных класса, по одному для каждой стороны соединения. Класс (или структура, если вы используете C, а не C++) будет содержать сокет, указатель на другую половину соединения и флаг, указывающий, какой это тип соединения. При использовании C++ эти два класса будут производными от общего базового класса, чтобы можно было вызвать функцию-член, чтобы определить, какой тип соединения представляет ключ завершения. Если вы используете C, используйте размеченный союз.

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

Как упоминает Криаг, каждая перекрывающаяся структура «уникальна для каждой операции», и в моем коде IOCP это, как правило, разрешает доступ к буферу данных для операции и флагу, который сообщает мне, что это была за операция (чтение/запись/принятие/подключение и т. д.). ).

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

У меня есть пример кода для IOCP, который использует эти концепции (хотя и не предоставляет пример «двух соединений»), вы можете скачать его здесь: http://www.serverframework.com/products---the-free-framework.html

person Len Holgate    schedule 05.09.2012
comment
На мой взгляд, это более точный ответ, чем принятый (хотя он не является неправильным). Значение CompletionKey — это именно то, что можно было бы использовать в этом случае. - person Damon; 05.09.2012
comment
Дэймон; вам нужен отдельный OVERLAPPED для каждой операции. В простой системе, где у вас никогда не бывает более одного чтения ИЛИ одной записи за раз, будет достаточно одного отдельного OVERLAPPED для каждого сокета; для чего-либо более сложного требуется уникальная параллельная операция OVERLAPPED PER. Я считаю, что даже простые транзакционные серверы будут работать лучше, если следующий recv будет выдан в конце обработки текущего завершения recv, а не после обработки соответствующего завершения записи из ответа, который вы написали... - person Len Holgate; 05.09.2012
comment
Извините, моя формулировка ввела в заблуждение (или действительно неверна). Да, вам, безусловно, нужно указать место в памяти для уникального OVERLAPPED для каждого отдельного запроса (скажем, sendto или readfile), который вы запускаете, но это просто непрозрачные блоки без большого количества полезной информации, которая требуется ОС во время перекрывающейся операции (я использовать их для добавления некоторых других данных). В моем выше для косметики не потому, что это необходимо, я подумал о какой-то ерунде ptr-to-OVERLAPPED, которая заполняется при получении статуса в очереди, но, если подумать, даже это не будет надежно работать с параллельными потоками . - person Damon; 05.09.2012
comment
(удалил эту строку выше, потому что она действительно вводит в заблуждение) - person Damon; 05.09.2012