Приветствуем вас и заблаговременно за ваш интерес.
В течение последних двух недель я боролся с чем-то, что сводило меня с ума. У меня есть APACHE (2.2.22) и PHP (5.4.3), установленные на моем ящике Windows, и я пытаюсь вызвать программу из скрипта PHP, который вызывает другую программу одновременно. Обе программы написаны на C / C ++ и скомпилированы с MINGW32. Что касается версии Windows, я протестировал Windows 2003 Server и Windows 7 Professional, и оба дают мне те же проблемы.
Позвольте мне представить эти две программы:
1) mytask.exe: это программа, которая должна исполняться в фоновом режиме и периодически заполняет ее статус файлу.
2) job.exe: это программа, которую я хочу вызвать из скрипта PHP. Его цель – запустить mytask.exe как самостоятельный процесс (не как поток).
Если я запустил из окна консоли команду ниже, то job.exe немедленно вернется и оставит файл mytask.exe на фоновом режиме до тех пор, пока он не завершится.
> job.exe spawn mytask.exe jobid=18874111458879FED
Обратите внимание, что job.exe сбрасывает идентификатор, который используется для управления mytask.exe. Например:
> job.exe status 18874111458879FED RUNNING
Я проверил, что если я запустил первую команду из скрипта PHP, скрипт PHP случайным образом блокируется навсегда. Если я посмотрю на диспетчер задач Windows, я вижу, что job.exe находится в состоянии, подобном зомби. Я могу утверждать, что job.exe эффективно достигает обычного return 0;
в своей main()
рутине, так что это похоже на что-то под деревом, во время выполнения C. Кроме того, если я пишу простой файл mytask.exe, который просто спит в течение 10 секунд, тогда скрипт PHP блокируется и в течение 10 секунд (или блокирует навсегда последующее случайное поведение, о котором я только что упомянул). Другими словами, у меня нет способа сделать job.exe порождением процесса, не дожидаясь его окончания, когда я вызываю job.exe из PHP-скрипта.
Итак: есть что-то, что я делаю неправильно, когда порождает mytask.exe, и теперь вот вторая часть этого отступления.
Я использую функцию CreateProcess () WINAPI для запуска задач из job.exe. Что касается документации MSDN, я вызываю CreateProcess с помощью bInheritHandles = FALSE
, чтобы избежать того, чтобы дочерний процесс bInheritHandles = FALSE
блокировки ввода-вывода с помощью PHP-скрипта. Я также закрываю обработчики процессов, возвращаемые CreateProcess () в структуре PROCESS_INFORMATION. Единственное, чего я не делаю, это ждать завершения процесса. С другой стороны, что касается стороны PHP, я пробовал функции exec()
и proc_open()
PHP для вызова job.exe без успеха.
Мои последние наблюдения, однако, кажутся правильными, но они меня не убеждают, потому что я не понимаю, почему они работают. Дело в том, что если mytask.exe делает fclose(stdout)
перед сном, тогда PHP-скрипт немедленно возвращается. НО КАК??? Я сказал CreateProcess (), чтобы не наследовать дескрипторы, так почему я получаю эти результаты? Во всяком случае, я не могу придерживаться этого патча, потому что программы, запущенные job.exe, могут не знать о том, кто их вызывает, поэтому закрытие stdout из этих программ не является хорошим решением. В UNIX все так просто … Один просто вызывает fork()
, закрывает стандартные потоки, а затем вызывает execve
для вызова программы. В Windows я также попытался создать поток оболочки с помощью CreateThread () (для эмуляции fork ()), а затем вызвать CreateProcess () из этого потока после закрытия стандартных потоков … но это также закрыло потоки job.exe !
Весь этот вопрос может быть синтезирован в одном: как я могу выполнить из PHP программу, которая создает другие процессы?
Надеюсь, кто-то может пролить свет на этот вопрос … Большое вам спасибо!
Я думаю, что я прибил решение, которое разделено на две части:
1) Что касается того факта, что основной процесс останавливается до окончания детского процесса.
Что касается документации MSDN, это определение CreateProcess()
:
BOOL WINAPI CreateProcess( _In_opt_ LPCTSTR lpApplicationName, _Inout_opt_ LPTSTR lpCommandLine, _In_opt_ LPSECURITY_ATTRIBUTES lpProcessAttributes, _In_opt_ LPSECURITY_ATTRIBUTES lpThreadAttributes, _In_ BOOL bInheritHandles, _In_ DWORD dwCreationFlags, _In_opt_ LPVOID lpEnvironment, _In_opt_ LPCTSTR lpCurrentDirectory, _In_ LPSTARTUPINFO lpStartupInfo, _Out_ LPPROCESS_INFORMATION lpProcessInformation );
Как я уже сказал в своем вопросе, я пропускаю FALSE
для bInheritHandles
, но я также передавал 0
в dwCreationFlags
. После небольшого количества исследований я обнаружил, что есть флаг DETACHED_PROCESS
, для которого MSDN говорит:
Для консольных процессов новый процесс не наследует консоль своего родителя (по умолчанию). Новый процесс может вызвать функцию AllocConsole позднее, чтобы создать консоль. Дополнительные сведения см. В разделе Создание консоли.
Теперь job.exe возвращается немедленно, несмотря на то, что дочерний процесс продолжает выполнение.
2) Что касается того факта, что скрипт PHP случайно зависает при вызове exec ()
Кажется, это ошибка PHP. Выполнение функций семейства exec()
в контексте сеанса PHP может привести к случайному зависанию APACHE, что необходимо для перезапуска сервера. Я нашел поток в Интернете, в котором пользователь заметил, что закрытие сеанса (через session_write_close()
) перед вызовом exec()
предотвратит зависание скрипта. То же самое относится к proc_open/proc_close
. Итак, мой скрипт теперь выглядит так:
session_write_close(); //Close the session before proc_open() $proc = proc_open($cmd,$pipedesc,$pipes); //do stuff with pipes... //... and close pipes $retval = proc_close($proc); session_start(); //restore session
Надеюсь это поможет.