Шаблон кодирования для зависимого перекрывающегося ввода-вывода в окнах

Я программист Linux и недавно участвовал в переносе клиента на основе epoll с двумя файловыми дескрипторами, написанными на c, в Windows.
Как вы знаете, в Linux используется epoll или select (я знаю, что Windows поддерживает select, но это неэффективно при все) вы можете блокировать файловые дескрипторы до тех пор, пока файловый дескриптор не будет готов, и вы сможете узнать, когда он будет готов для записи и когда он будет прочитан.

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

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

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

Существует два дескриптора, называемых fd1 и fd2, и завершениеKey1 содержит дескриптор и статус для f1 и завершениеKey2 для fd2, а переменная completeKey предназначена для получения завершения из GetQueuedCompletionStatus.

    GetQueuedCompletionStatus(port_handle, &completionKey.bufflen, (PULONG_PTR)&completionKey,(LPOVERLAPPED *)&ovl,INFINITE);

   switch (completionKey.status)
    {
        case READ:
            if(completionKey->handle == fd1)
            {
                fd1_read_is_done(completionKey.buffer,completionKey.bufflen);
                completionKey->status = WRITE;
                do_fd1_write(completionKey);
                completionKey2->status = WRITE;
                completionKey2->buffer = "somedata";
                do_fd2_write(completionKey2);
            }
            else if(completionKey->handle == fd2)
            {
                fd2_read_is_done(completionKey.buffer,completionKey.bufflen);
                completionKey->status = WRITE;
                do_fd2_write(completionKey);
                completionKey1->status = WRITE;
                completionKey1->buffer = "somedata";
                do_fd1_write(completionKey1);
            }
            break;
        case WRITE_EVENT:
            if(completionKey->handle == fd1)
            {
                fd1_write_is_done(completionKey.bufflen);
                completionKey->status = READ;
                do_fd1_read(completionKey);
                completionKey2->status = READ;
                do_fd2_read(completionKey2);
            }
            else if(completionKey->handle == fd2)
            {
                fd2_write_is_done(completionKey.bufflen);
                completionKey->status = READ;
                do_fd2_read(completionKey);
                completionKey1->status = READ;
                do_fd1_read(completionKey1);
            }
            break;
    }

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

Посмотрев на WSAsend или WSArecv, заметил, что для каждой отправки или получения можно установить параметр перекрытия. но это приводит к двум основным проблемам. согласно структуре WSAOVERLAPPED:

    typedef struct _WSAOVERLAPPED {
  ULONG_PTR Internal;
  ULONG_PTR InternalHigh;
  union {
    struct {
      DWORD Offset;
      DWORD OffsetHigh;
    };
    PVOID  Pointer;
  };
  HANDLE    hEvent;
} WSAOVERLAPPED, *LPWSAOVERLAPPED;

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

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

И поскольку мне не нужна многопоточность, есть ли другой способ решить мою проблему?
заранее спасибо
Изменить
Как я уже догадался, первая проблема, которую я упомянул в использовании перекрывающейся структуры, имеет решение, и мне нужно просто создать еще одну структуру со всеми буферами, статусом и т. д. и поместите OVERLAPPED в качестве первого файла. теперь ты решаешь мне другие ;)


person madz    schedule 20.01.2014    source источник
comment
Из любопытства, как вы решаете соответствующую проблему на стороне Linux? То есть, когда вы читаете запрос на сокете А и (в результате) хотите записать в сокет Б, но сокет Б еще не готов для записи?   -  person Harry Johnston    schedule 21.01.2014
comment
на самом деле у меня есть очередь для этого, и я просто пишу в очередь и просто пытаюсь убрать из очереди и записать ее в любое время.   -  person madz    schedule 21.01.2014
comment
Итак, вам нужны только две структуры OVERLAPPED на сокет? Один для одной ожидающей операции чтения и один для одной ожидающей операции записи?   -  person Harry Johnston    schedule 21.01.2014
comment
Я пытался объяснить по вашему ответу. Мне кажется, это невозможно сделать только с очередями. и очередь просто может случиться, чтобы написать. для чтения мне нужно просто слепо прочитать блок переключателей, и мне нужно, чтобы GetQueuedCompletionStatus не был БЕСКОНЕЧНЫМ. и кажется немного грязным   -  person madz    schedule 21.01.2014
comment
учитывая, что все две перекрывающиеся структуры могут находиться в ожидании на сокете   -  person madz    schedule 21.01.2014
comment
Вы были правы, Гарри, кажется логичным, что это можно сделать с помощью структуры OVERLAPPED, и я думал, имея в виду старый подход с ключом завершения. но все равно есть проблема в работе. пытаюсь сделать работу   -  person madz    schedule 21.01.2014


Ответы (1)


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

Однако легко использовать структуры OVERLAPPED (или WSAOVERLAPPED) для отслеживания состояния перекрывающихся запросов. Все, что вам нужно сделать, это встроить структуру OVERLAPPED в качестве первого элемента в более крупную структуру:

typedef struct _MyOverlapped
{
  WSAOVERLAPPED overlapped;
  ... your data goes here ...
} MyOverlapped, lpMyOverlapped;

затем приведите LPWSAOVERLAPPED, отправленный процедуре завершения, к lpMyOverlapped, чтобы получить доступ к вашим данным контекста.

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

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

person Harry Johnston    schedule 21.01.2014
comment
Дополнительное, более субъективное замечание: вы можете рассмотреть возможность использования очереди в любом случае. Я не уверен, что есть какие-либо гарантии, что поставщик сокетов сможет справиться с неопределенным количеством ожидающих обработки асинхронных запросов в одном и том же сокете. Кроме того, если соединение разорвано или возникла какая-либо другая ошибка, вероятно, было бы проще, если бы вы возвращали только одну ошибку, а не отдельную ошибку для каждой ожидающей записи. Очередь должна быть проста в реализации; когда данный запрос на запись завершается, обработать следующий элемент в очереди. - person Harry Johnston; 21.01.2014
comment
спасибо Гарри. вы правы в том, как создать правильную перекрывающуюся структуру. в приведенном выше псевдокоде, когда чтение завершено, я записываю буфер в очередь. дело в том, что в iocp вы просто читаете или пишете, и для каждого чтения или записи вам нужно перекрытие, которое не используется в других ожидающих io. считайте, что запись в fd1 только что завершена. тогда мне нужно прочитать из fd1 и fd2. нет необходимости в новом оврлапе для fd1, я могу использовать тот, который только что получил бесплатно. но для fd2 мне нужно создать новый. Если я начну каждый раз выделять новый оврлап, а освобождать его по завершению, это будет затратно. - person madz; 21.01.2014
comment
если я создам массив ovrlap и посмотрю, есть ли один свободный, в случае, если его нет, чтение не может быть выполнено, и это может закончиться ситуацией, когда GetQueuedCompletionStatus блокируется навсегда, и очередь не поможет. (поскольку действие дескрипторов зависит друг от друга. Если от одного не поступают данные, другой ничего не сделает), на ум приходит некоторая работа. сначала установите тайм-аут для GetQueuedCompletionStatus и попробуйте снова выполнить чтение и запись за пределами этого блока переключателя, когда такая ситуация произойдет. но звучит сумбурно. Мне было интересно, есть ли чистый способ для этого - person madz; 21.01.2014
comment
Подождите - ведь вы не можете выполнять более одного чтения за раз на данном сокете? Это не имеет смысла, что бы это вообще значило? - person Harry Johnston; 21.01.2014
comment
В версии для Linux, что вы делаете, когда хотите выполнить чтение, но сокет не готов для чтения? - person Harry Johnston; 21.01.2014
comment
нет проблем с чтением более одного. проблема заключалась в выделении правильного перекрытия и изменении переключателя таким образом, чтобы завершение чтения в сокете выполняло другое чтение в том же сокете и то же самое для записи. но звучит не так, как я ожидаю. Я работаю над этим. - person madz; 21.01.2014
comment
в linux я все жду, пока linux не скажет, что в сокете есть данные, и я просто читаю их - person madz; 21.01.2014
comment
@pendrive посмотрите на эти сообщения перекрывающаяся структура в многопоточном сервере iocp"> stackoverflow.com/questions/20843270/ и stackoverflow.com/questions/19760522/ - person maciekm; 21.01.2014
comment
спасибо @maciekm Использование связанного списка для перекрывающихся структур - хорошая идея в c, но в моем случае вектор звучит проще. но, похоже, у меня есть прорыв в использовании 4 перекрытий и в том, чтобы сделать этот блок переключателей более умным, чтобы не вызывать голодания. но после некоторой отправки и получения данных двумя дескрипторами программа застревает в блокировке GetQueuedCompletionStatus навсегда. Я не уверен, почему это происходит, сейчас пытаюсь отладить - person madz; 21.01.2014
comment
Если у вас есть более одной операции чтения, ожидающей выполнения на одном сокете, невозможно сказать, какая из них получит какие-либо новые данные, так что вряд ли это имеет смысл. (Аналогичное утверждение есть и на стороне Linux; если я вас правильно понял, у вас есть состояние гонки.) Если у вас есть только одна операция чтения, ожидающая выполнения на данном сокете, то вам нужны только две перекрывающиеся структуры на сокет ( один для чтения и один для записи), поэтому нет необходимости в отдельном пуле для перекрывающихся структур, просто поместите их с остальными данными контекста этого сокета. - person Harry Johnston; 21.01.2014
comment
Можно эмулировать семантику select, используя асинхронный ввод-вывод и подпрограммы завершения. По сути, для каждого сокета вам нужна пара флагов (а также две перекрывающиеся структуры и два буфера), и все, что делает процедура завершения, это устанавливает соответствующий флаг. Таким образом, вы можете дождаться завершения данной операции, продолжая отслеживать состояние других операций. - person Harry Johnston; 21.01.2014
comment
Что касается проблемы, над которой вы сейчас работаете: убедились ли вы, что для каждой ожидающей операции записи выделен отдельный буфер, и что буфер остается действительным до завершения операции? - person Harry Johnston; 21.01.2014