2013-07-23 2 views
9

Проблема:Названных трубы эффективного асинхронного дизайн

Для разработки эффективного и очень быстрая структуры клиента-сервера с именем-труба.

Текущее состояние:

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

Я уже использую OVERLAPED режим для труб, но потом блок с WaitForSingleObject или WaitForMultipleObjects так, почему я нужен один поток для каждого соединения на стороне сервера

Желаемый раствор:

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

Я видел пример MSDN, который использует массив структур OVERLAPED, а затем использует WaitForMultipleObjects, чтобы ждать их всех. Я нахожу этот плохой дизайн. Две проблемы, которые я вижу здесь. Сначала вам нужно поддерживать массив, который может расти довольно большой, и удаление будет дорогостоящим. Во-вторых, у вас много событий, по одному для каждого элемента массива.

Я также видел порты завершения, такие как CreateIoCompletionPort и GetQueuedCompletionStatus, но я не вижу, как они лучше.

Что мне нужно, это ReadFileEx и WriteFileEx do, они называют обратный вызов , когда операция завершена. Это истинный асинхронный стиль программирования. Но проблема в том, что ConnectNamedPipe не поддерживает это, и, кроме того, я видел, что поток должен быть в аварийном состоянии, и вам нужно вызвать некоторые из функций Ex для этого.

Итак, как решить эту проблему лучше всего?

Вот как MSDN делает это: http://msdn.microsoft.com/en-us/library/windows/desktop/aa365603(v=vs.85).aspx

Проблема, которую я вижу с этим подходом является то, что я не могу видеть, как вы могли бы иметь 100 клиентов, подключенных на один раз, если предел WaitForMultipleObjects 64 ручки. Конечно, я могу отключить канал после каждого запроса, но идея состоит в том, чтобы иметь постоянное клиентское соединение, как и на сервере TCP, и отслеживать клиента на протяжении всего жизненного цикла, при этом каждый клиент имеет уникальные идентификационные данные и данные для конкретного клиента.

Идеальный псевдо-код должен быть таким:

repeat 
    // wait for the connection or for one client to send data 
    Result = ConnectNamedPipe or ReadFile or Disconnect; 

    case Result of 
    CONNECTED: CreateNewClient; // we create a new client 
    DATA: AssignWorkerThread; // here we process client request in a thread 
    DISCONNECT: CleanupAndDeleteClient // release the client object and data 
    end; 
until Aborted; 

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

P.S. Мой текущий код не должен быть важен. Я кодирую это в Delphi, но его чистый WinAPI, поэтому язык не имеет значения.

РЕДАКТИРОВАТЬ:

На данный момент IOCP выглядеть решение:

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

+4

Также имейте в виду, что ['WaitForMultipleObject'] (http://msdn.microsoft.com/en-us/library/windows/desktop/ms687025) имеет ограничение в 64 дескриптора (MAXIMUM_WAIT_OBJECTS)! –

+0

Исключения не являются дорогостоящими. Что это вообще значит? Я голосую за WFMO за перекрывающиеся структуры. Я не вижу ничего плохого в этом. Просто нужно еще одно событие в ожидающем массиве, которое остановило бы ожидание, если массив нуждается в настройке, или завершение прерывания. – Dialecticus

+0

@Dealecticus: Деление на динамический массив является дорогостоящим. Если вы удаляете элемент посередине, вам нужно переместить все предметы, следующие за ним. Это структуры программирования 101 :) Хорошо, если у вас есть до 100 предметов, они не будут отображаться. Но я могу подключить более 100 клиентов. – Runner

ответ

3

Если сервер должен обрабатывать более 64 событий (чтение/запись), любое решение с использованием WaitForMultipleObjects становится неосуществимым. Именно по этой причине Microsoft представила порты ввода IO для Windows. Он может обрабатывать очень большое количество операций ввода-вывода, используя наиболее подходящее количество потоков (обычно это число процессоров/ядер).

Проблема с IOCP заключается в том, что реализовать ее очень сложно. Скрытые проблемы распространяются как мин в поле: [1], [2] (раздел 3.6). Я бы рекомендовал использовать некоторые рамки. Маленький googling предлагает что-то названное Indy для разработчиков Delphi. Возможно, другие.

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

+0

Спасибо за ответ. Я думаю, что это правильно. Я знаю Indy, и у меня такой же тип фреймворков, что и у меня для моего IPC. У меня есть две реализации моей структуры: одна называется named-pipe based и поэтому IPC, а другая IMC (межмашинная связь) и основана на Indy. Я прислушаюсь к вашему предупреждению, но все-таки попробую реализовать. Если это не сработает, то, по крайней мере, я узнаю что-то новое. – Runner

1

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

С помощью MAXIMUM_WAIT_OBJECTS (или меньше) прослушивающих экземпляров именованных каналов вы можете использовать один поток, предназначенный для прослушивания, используя WaitForMultipleObjectsEx. Эта же резьба может также обрабатывать остальную часть ввода-вывода с использованием ReadFileEx и WriteFileEx и APC. Рабочие потоки будут помещать APC в поток ввода-вывода, чтобы инициировать ввод-вывод, а поток ввода-вывода может использовать пул задач для возврата результатов (а также информировать рабочие потоки о новых соединениях).

Нити/O основная функция я бы выглядеть примерно так:

create_events(); 
for (index = 0; index < MAXIMUM_WAIT_OBJECTS; index++) new_pipe_instance(i); 

for (;;) 
{ 
    if (service_stopping && active_instances == 0) break; 

    result = WaitForMultipleObjectsEx(MAXIMUM_WAIT_OBJECTS, connect_events, 
        FALSE, INFINITE, TRUE); 

    if (result == WAIT_IO_COMPLETION) 
    { 
     continue; 
    } 
    else if (result >= WAIT_OBJECT_0 && 
        result < WAIT_OBJECT_0 + MAXIMUM_WAIT_OBJECTS) 
    { 
     index = result - WAIT_OBJECT_0; 
     ResetEvent(connect_events[index]); 

     if (GetOverlappedResult(
       connect_handles[index], &connect_overlapped[index], 
       &byte_count, FALSE)) 
      { 
       err = ERROR_SUCCESS; 
      } 
      else 
      { 
       err = GetLastError(); 
      } 

     connect_pipe_completion(index, err); 
     continue; 
    } 
    else 
    { 
     fail(); 
    } 
} 

Единственная сложность заключается в том, что при вызове ConnectNamedPipe может вернуться ERROR_PIPE_CONNECTED, чтобы указать, что вызов удалось сразу или ошибка, кроме ERROR_IO_PENDING, если вызов завершился неудачно. В этом случае вам необходимо сбросить событие, а затем обработать соединение:

void new_pipe(ULONG_PTR dwParam) 
{ 
    DWORD index = dwParam; 

    connect_handles[index] = CreateNamedPipe(
     pipe_name, 
     PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED, 
     PIPE_TYPE_MESSAGE | PIPE_WAIT | PIPE_ACCEPT_REMOTE_CLIENTS, 
     MAX_INSTANCES, 
     512, 
     512, 
     0, 
     NULL); 

    if (connect_handles[index] == INVALID_HANDLE_VALUE) fail(); 

    ZeroMemory(&connect_overlapped[index], sizeof(OVERLAPPED)); 
    connect_overlapped[index].hEvent = connect_events[index]; 

    if (ConnectNamedPipe(connect_handles[index], &connect_overlapped[index])) 
    { 
     err = ERROR_SUCCESS; 
    } 
    else 
    { 
     err = GetLastError(); 

     if (err == ERROR_SUCCESS) err = ERROR_INVALID_FUNCTION; 

     if (err == ERROR_PIPE_CONNECTED) err = ERROR_SUCCESS; 
    } 

    if (err != ERROR_IO_PENDING) 
    { 
     ResetEvent(connect_events[index]); 
     connect_pipe_completion(index, err); 
    } 
} 

connect_pipe_completion функция создаст новую задачу в пул задач для обработки экземпляра вновь подключенного трубопровода, а затем очереди БТР позвонить new_pipe для создания новой прослушивающей трубы с одним и тем же индексом.

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

+0

Да, это возможно. На самом деле у меня сейчас что-то похожее. Но то, что мне не нравится в этом, заключается в том, что каждое новое соединение удерживает поток из пула в течение всего времени соединения. Если у вас 1000 подключений, он будет использовать 1000 потоков, и это очень плохой дизайн. Я мог подключиться и отключиться для каждого запроса, но тогда у меня не может быть списка активного соединения и клиентов на стороне сервера. Я теряю следы клиентов, и у меня нет дуплексных каналов связи. – Runner

+0

Один запрос всегда быстрый. Запросы представляют собой пакеты двоичных данных. Моя структура очень ориентирована на высоком уровне и абстрагирует трубы от пользователя. Посмотрите на код здесь: http://www.cromis.net/blog/downloads/cromis-ipc/ – Runner

+0

Нет, в моем дизайне вы не используете нить для каждого подключения.Когда происходит соединение, задача создается в пуле задач; эта задача выполняет любую необходимую предварительную обработку (например, проверку правил доступа), а затем инициирует «ReadFileEx» или «WriteFileEx», если это необходимо (путем очередности APC для потока ввода-вывода), а затем завершается. Когда этот ввод-вывод завершается, для его обработки создается другая задача и т. Д. –

Смежные вопросы