DCOM: как закрыть соединение в server при сбое клиента?

У меня довольно старый проект: клиент и сервер DCOM, оба на C++ATL, только платформа Windows. Все работает нормально: локальные и удаленные клиенты подключаются к серверу и работают одновременно без каких либо проблем.

но когда удаленный клиент падает или убивается Диспетчером задач или командой "taskkill" или выключением питания - у меня проблема. Мой сервер ничего не знает о сбое клиента и пытается отправить новые события всем клиентам (также уже разбился). В результате я имейте паузу (сервер не может отправлять данные уже разбитому клиенту), и ее продолжительность пропорциональна количеству разбитых удаленных клиентов. После 5 разбитых клиентов паузы настолько длинные, что это равно полностью остановке сервера.

Я знаю о механизме "ping" DCOM (DCOM должен отключать клиентов, которые не отвечают на "каждые 2 минуты ping" после 6 минут тишины). И действительно, после 6 минут зависания у меня есть небольшой период нормальной работы, но затем сервер возвращается к "пауза" государства.

Что я могу сделать со всем этим? Как сделать DCOM "ping" работает нормально? Если я буду реализовывать свой собственный код "ping", можно ли отключить старое соединение клиентов DCOM вручную? Как это сделать?

4 ответов


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

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

static InfoStruct {
    IRemoteHost *pRemote;
    BSTR someData;
};

static DWORD WINAPI InvokeClientAsync(LPVOID lpInfo) {
  CoInitializeEx(COINIT_MULTITHREADED);

  InfoStruct *is = (InfoStruct *)lpInfo;
  is->pRemote->notify(someData);
  is->pRemote->Release();
  SysFreeString(is->someData);
  delete is;

  CoUninitialize();
  return 0;
}

void InvokeClient(IRemoteHost *pRemote, BSTR someData) {

  InfoStruct *is = new InfoStruct;
  is->pRemote = pRemote;
  pRemote->AddRef();

  is->someData = SysAllocString(someData);
  QueueUserWorkItem(InvokeClientAsync, (LPVOID)is, WT_EXECUTELONGFUNCTION);
}

если ваш основной поток находится в STA, это только немного сложнее; вам просто нужно использовать CoMarshalInterThreadInterfaceInStream и CoGetInterfaceAndReleaseStream для передачи указателя интерфейса между квартирами:

static InfoStruct {
    IStream *pMarshalledRemote;
    BSTR someData;
};

static DWORD WINAPI InvokeClientAsync(LPVOID lpInfo) {
  CoInitializeEx(COINIT_MULTITHREADED); // can be STA as well

  InfoStruct *is = (InfoStruct *)lpInfo;
  IRemoteHost *pRemote;
  CoGetInterfaceAndReleaseStream(is->pMarshalledRemote, __uuidof(IRemoteHost), (LPVOID *)&pRemote);

  pRemote->notify(someData);
  pRemote->Release();
  SysFreeString(is->someData);
  delete is;

  CoUninitialize();

  return 0;
}

void InvokeClient(IRemoteHost *pRemote, BSTR someData) {
  InfoStruct *is = new InfoStruct;
  CoMarshalInterThreadInterfaceInStream(__uuidof(IRemoteHost), pRemote, &is->pMarshalledRemote);

  is->someData = SysAllocString(someData);
  QueueUserWorkItem(InvokeClientAsync, (LPVOID)is, WT_EXECUTELONGFUNCTION);
}

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

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


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


используйте DCOM для создания именованного канала уведомлений. Разъединение отрегулировано более лучше с трубами. Слушатель реагирует (почти) мгновенно на сообщения. например, Server - >Client (как называется ваша труба?). Клиент - >сервер отвечает именем, которое включает машину. Клиент создает именованный канал и слушает. Сервер открывает канал немедленно или при необходимости.


вы можете реализовать свой собственный механизм ping, чтобы ваши клиенты время от времени вызывали метод ping сервера. Вы уже поддерживаете какой-то контейнер для своих клиентов на стороне сервера. На этой карте отметьте каждого клиента меткой времени последнего ping. Затем проверьте, жив ли клиент, прежде чем отправлять события этому клиенту. Вы можете настроить стратегию остановки отправки событий, возможно, на основе времени или количества пропущенных пингов, типа события или некоторых других факторов. Вы, наверное, не нужно беспокоиться об удалении клиентов - это может подождать, пока DCOM не поймет, что конкретный клиент мертв. Эта схема может не устранить проблему полностью, так как клиент может умереть непосредственно перед отправкой события, но вы будете иметь полный контроль над тем, сколько таких клиентов может существовать путем настройки периода ping. Чем меньше этот период, тем меньше мертвых клиентов, хотя вы платите трафиком.