При реализации прикладного сервера и его клиентских библиотек на C++ у меня возникают проблемы с поиском чистого и надежного способа остановки клиентских процессов при завершении работы сервера в Windows.
Предполагая, что сервер и его клиенты работают под одним и тем же пользователем, требования следующие:
Что касается этого редактирования, большинство ответов ниже рекомендуют использование общей памяти (или другого механизма IPC) между сервером и его клиентами для передачи приказов на отключение и статуса клиента. Эти решения будут работать, но потребуют от клиентов успешной инициализации библиотеки.
Я не сказал, что сервер также используется для запуска клиентов и в некоторых случаях других программ / сценариев, которые вообще не используют клиентскую библиотеку. Решение, которое не полагалось бы на изящную связь между сервером и клиентами, было бы лучше (если возможно).
Некоторое время назад я наткнулся на фрагмент C (я полагаю, в MSDN), который делал следующее:
К сожалению, теперь, когда я ищу его, я не могу его найти, и результаты поиска, похоже, предполагают, что этот трюк больше не работает в Vista. Есть ли мнение экспертов по этому поводу?





Вам нужен какой-то IPC между клиентами и серверами. Если бы все клиенты были детьми, я думаю, что трубки были бы проще всего; поскольку это не так, я предполагаю, что управляемый сервером сегмент разделяемой памяти можно использовать для регистрации клиентов, выполнения команды выключения и сбора кодов возврата, отправленных туда клиентами, успешно завершившими работу.
В этой области разделяемой памяти клиенты помещают свои идентификаторы процессов, чтобы сервер мог принудительно убить любых не отвечающих клиентов (по модулю серверных привилегий), используя TerminateProcess ().
Это очень общий вопрос, и в нем есть некоторые несоответствия.
Хотя это не 100% правило, большинство консольных приложений работают до завершения, тогда как приложения с графическим интерфейсом работают до тех пор, пока пользователь не завершит их (а службы работают до остановки через SCM). Следовательно, проще запросить закрытие графического интерфейса. Вы отправляете им эквивалент Alt-F4. Но для консольной программы вы должны отправить им эквивалент Ctrl-C и надеяться, что они справятся с этим. В обоих случаях вы просто ждете. Если процесс задерживается, вы затем сбиваете его (TerminateProcess) и молитесь, чтобы урон был ограничен. Но ваш жесткий диск может заполниться временными файлами.
У приложения с графическим интерфейсом вообще нет кодов выхода - куда бы они пошли? А консольный процесс, принудительно завершенный по определению, не завершается, поэтому у него нет кода выхода. Итак, в сценарии выключения сервера не ждите кодов выхода.
Если к вам подключен отладчик, вы, как правило, не можете завершить процесс из другого приложения. Это сделало бы отладчиками невозможным отладку кода выхода!
И графические, и консольные приложения являются процессами и поэтому имеют коды выхода. Имеют ли они смысл или нет - совсем другой вопрос.
Если вы используете поток, простое решение - использовать именованное системное событие, поток «засыпает» событием, ожидая его сигнала, управляющее приложение может сигнализировать о событии, когда оно хочет, чтобы клиентские приложения завершились.
Для приложения пользовательского интерфейса он (поток) может публиковать сообщение в главном окне, WM_ CLOSE или QUIT, я забыл, что в консольном приложении он может выдавать CTRL-C или, если основной код консоли зацикливается, он может проверить какое-то условие выхода устанавливается по нитке.
В любом случае, вместо того, чтобы обнаруживать, что клиентские приложения говорят им о завершении, используйте ОС, чтобы подать сигнал, что они должны выйти. Спящий поток практически не потребляет ресурсов ЦП при условии, что он использует WaitForSingleObject для сна.
Если вы хотите использовать IPC-маршрут, сделайте нормальную связь между клиентом и сервером двунаправленной, чтобы сервер мог попросить клиентов завершить работу. Или, если это не удастся, попросите клиентов провести опрос. Или, в крайнем случае, клиенты должны быть проинструктированы о завершении работы при отправке запроса на сервер. Вы можете позволить пользователю библиотеки зарегистрировать обратный вызов выхода, но лучший способ, который я знаю, - это просто вызвать «exit» в клиентской библиотеке, когда клиенту предлагается завершить работу. Если клиент застревает в коде выключения, сервер должен иметь возможность обойти это, игнорируя структуры данных и соединение этого клиента.
Используйте PostMessage или именованное событие.
Re: PostMessage - приложения, отличные от GUI, а также потоки, отличные от потока GUI, могут иметь циклы сообщений, и это очень полезно для подобных вещей. (Фактически, COM использует циклы сообщений под капотом.) Я делал это раньше с помощью ATL, но немного устарел от этого.
Если вы хотите быть устойчивыми к злонамеренным атакам со стороны «плохих» процессов, включите закрытый ключ, совместно используемый клиентом / сервером, в качестве одного из параметров сообщения.
Подход с использованием именованных событий, вероятно, проще; используйте CreateEvent с именем, которое является секретом, совместно используемым клиентом / сервером, и попросите соответствующее приложение проверить статус события (например, WaitForSingleObject с таймаутом 0) в своем основном цикле, чтобы определить, следует ли завершить работу.
Вы совершенно правы насчет кодов выхода завершенных процессов. Поскольку мы устранили их, мы знали, что они все равно не вышли из игры. Однако оставшаяся часть вашего ответа меня удивляет. Я подробно расскажу ниже.