一、自动重启的原理
我不知道为什么很多程序员觉得自动重启很low,就像我始终不明白为什么有些人一听见我说“重新编译一下”就笑,难道不是重新编译一下大部分问题就解决了吗?
自动重启原理很简单,用一个进程监控另一个进程,挂了就再启动一个。细节也不算多,主要是正确判断进程状态和启动方式,其实最大的工作量是程序恢复时应该如何回到原来的状态,这意味着程序要随时保存状态。
只要你能做到用户无感,你在背后做了什么用户在意吗?
二、自动重启的实现
如果是UNIX,用fork然后监控子进程,挂了就再fork,一个循环就解决问题了。
windows上麻烦一些,监控进程,控制台程序:
#include "stdafx.h" #include "shellapi.h" #include <stdio.h> #include <string> using namespace std; bool bDebug = false; int APIENTRY wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPWSTR lpCmdLine, _In_ int nCmdShow) { UNREFERENCED_PARAMETER(hPrevInstance); // TODO: 在此处放置代码。 if (bDebug)MessageBox(NULL, lpCmdLine, TEXT("启动"), 0); wchar_t buf[256]; DWORD pid = 0; { LPWSTR* szArglist; int nArgs; int i; //从命令行获取要监控的进程的PID,参数-pid后的下一个参数 szArglist = CommandLineToArgvW(lpCmdLine, &nArgs);//注意,如果没有参数,会返回程序名,如果有参数,则不包括程序名(或许是个BUG) if (NULL == szArglist) { MessageBox(NULL, lpCmdLine, TEXT("CommandLineToArgvW失败"), 0); return 0; } else { wsprintf(buf, TEXT("参数个数%d"), nArgs); if (bDebug)MessageBox(NULL, buf, TEXT(""), 0); for (i = 0; i < nArgs; ++i) { if (bDebug)MessageBox(NULL, szArglist[i], TEXT("CommandLineToArgvW"), 0); if (0 == _tcsicmp(szArglist[i], TEXT("-pid")) && i + 1 < nArgs) { pid = _wtol(szArglist[i + 1]); wsprintf(buf, TEXT("%u"), pid); if (bDebug)MessageBox(NULL, buf, szArglist[i + 1], 0); } } } LocalFree(szArglist); } wsprintf(buf, TEXT("%u"), pid); if (bDebug)MessageBox(NULL, buf, TEXT("pid"), 0); //打开进程以供监控 HANDLE handle = OpenProcess(PROCESS_QUERY_INFORMATION | SYNCHRONIZE, FALSE, pid); //等待进程结束 DWORD state = WaitForSingleObject(handle, INFINITE); if (WAIT_OBJECT_0 == state) { DWORD exitCode; if (!GetExitCodeProcess(handle, &exitCode))//获得退出码 { MessageBox(NULL, TEXT("GetExitCodeProcess 出错"), TEXT("未能获取程序结束状态"), 0); } wsprintf(buf, TEXT("退出码 %u"), exitCode); //MessageBox(NULL, buf, TEXT("任务完成"), 0); if (0 != exitCode)//正常退出是返回码(return 返回码; 或者exit(返回码),一般约定正常返回0),异常结束肯定是非0 {//这一段就是以-r参数重启程序,两个程序必须在同一目录下 wchar_t _app_pathname[MAX_PATH]; GetModuleFileName(NULL, _app_pathname, MAX_PATH); wstring app_pathname = _app_pathname; size_t pos = app_pathname.find_last_of('\\'); if (pos != app_pathname.npos) { app_pathname.erase(pos + 1); app_pathname += TEXT("app.exe"); } else { app_pathname = TEXT("app.exe"); } wchar_t szCmdLine[256]; wsprintf(szCmdLine, TEXT(" -r")); PROCESS_INFORMATION info; STARTUPINFO startup; GetStartupInfo(&startup); BOOL bSucc = CreateProcess(app_pathname.c_str(), szCmdLine, NULL, NULL, FALSE, NORMAL_PRIORITY_CLASS, NULL, NULL, &startup, &info); if (!bSucc) { MessageBox(NULL, TEXT("CreateProcess 出错"), TEXT("恢复程序失败"), 0); } } } else if (WAIT_FAILED == state) { MessageBox(NULL, lpCmdLine, TEXT("WaitForSingleObject失败"), 0); } else { MessageBox(NULL, lpCmdLine, TEXT("WaitForSingleObject非预期的返回值"), 0); } return 0; }
这个程序是这样的,工作的主程序名叫“app.exe”,在适当的时候启动了监控进程(就是这个代码,名称任意,但是必须和app.exe放在一起),并把自己的pid传递给监控进程(命令行参数-pid 主进程),监控进程启动后从命令行获取到需要监控的pid,监视pid状态,如果是正常结束,就退出程序,如果是异常结束,以“-r”参数启动主进程。
主进程启动过程是这样的:启动到某个阶段,检查命令行,带有“-r”参数说明是自动恢复,走自动恢复流程,否则走正常流程,启动监控进程并把自己的pid传递过去。
为什么要做一个独立的监控程序,不用主进程自身呢?因为程序太大了,有很多静态初始化的话,不知道起两个会不会有什么问题。
为什么要通过命令行参数传递进程PID呢?主进程起子进程不是可以获得子进程的PID吗?因为好多程序喜欢套壳啊,返回的子进程又创建子进程干活,自己马上就退出了。
获取自身PID的方法:
DWORD pid = GetCurrentProcessId();
要在验证程序异常退出可以return一个非零值,或者调用abort()。如果不区分是否是重启则不用处理参数,启动监控进程的代码和监控进程启动主进程的相似。
三、相关知识点
3.1 CommandLineToArg
win32程序处理命令行真是费劲死了。
shellapi.h Shell32.dll/Shell32.lib LPWSTR * CommandLineToArgvW( [in] LPCWSTR lpCmdLine, [out] int *pNumArgs );
注意参数pNumArgs是在函数内部分配的,要在外部释放。这是C的习惯性做法。
3.2 LocalFree
释放本地内存对象。
HLOCAL LocalFree( [in] _Frees_ptr_opt_ HLOCAL hMem );
3.3 OpenProcess
打开进程对象,以便后续等待进程结束。
HANDLE OpenProcess( [in] DWORD dwDesiredAccess, [in] BOOL bInheritHandle, [in] DWORD dwProcessId );
3.4 WaitForSingleObject
等待对象,可以是进程、线程、控制台输入等类型的句柄,等待的事件包括信号、超时等,发生了某种事件函数就会返回。
DWORD WaitForSingleObject( [in] HANDLE hHandle, [in] DWORD dwMilliseconds );
3.5 GetExitCodeProcess
获得进程退出码。
BOOL GetExitCodeProcess( [in] HANDLE hProcess, [out] LPDWORD lpExitCode );
3.6 GetModuleFileName
一般用来获取程序的完整路径名。
DWORD GetModuleFileNameA( [in, optional] HMODULE hModule, [out] LPSTR lpFilename, [in] DWORD nSize );
3.7 GetStartupInfo
获得程序的启动信息,在这个代码里用来传递给新进程。
void GetStartupInfoW( [out] LPSTARTUPINFOW lpStartupInfo );
3.8 CreateProcess
创建进程。
BOOL CreateProcessA( [in, optional] LPCSTR lpApplicationName, [in, out, optional] LPSTR lpCommandLine, [in, optional] LPSECURITY_ATTRIBUTES lpProcessAttributes, [in, optional] LPSECURITY_ATTRIBUTES lpThreadAttributes, [in] BOOL bInheritHandles, [in] DWORD dwCreationFlags, [in, optional] LPVOID lpEnvironment, [in, optional] LPCSTR lpCurrentDirectory, [in] LPSTARTUPINFOA lpStartupInfo, [out] LPPROCESS_INFORMATION lpProcessInformation );
参数虽多,大部分都可以不用。
拓展
C++重启进程
步骤:
1、查找需要重启进程的进程id
2、启动需要重启的进程
3、杀死第一步进程id的进程
代码:
1、查找需要重启的进程的进程id
//通过进程名查找进程Id bool FindProcess(std::wstring strProcessName, DWORD& nPid) { TCHAR tszProcess[64] = { 0 }; lstrcpy(tszProcess, strProcessName.c_str()); //查找进程 STARTUPINFO st; PROCESS_INFORMATION pi; PROCESSENTRY32 ps; HANDLE hSnapshot; memset(&st, 0, sizeof(STARTUPINFO)); st.cb = sizeof(STARTUPINFO); memset(&ps, 0, sizeof(PROCESSENTRY32)); ps.dwSize = sizeof(PROCESSENTRY32); memset(&pi, 0, sizeof(PROCESS_INFORMATION)); // 遍历进程 hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); if (hSnapshot == INVALID_HANDLE_VALUE) return false; if (!Process32First(hSnapshot, &ps)) return false; do { if (lstrcmp(ps.szExeFile, tszProcess) == 0) { //找到制定的程序 nPid = ps.th32ProcessID; CloseHandle(hSnapshot); return true; } } while (Process32Next(hSnapshot, &ps)); CloseHandle(hSnapshot); return false; }
2、启动进程
//启动进程 bool StartPrcess(std::wstring strProcessName) { TCHAR tszProcess[64] = { 0 }; lstrcpy(tszProcess, strProcessName.c_str()); //启动程序 SHELLEXECUTEINFO shellInfo; memset(&shellInfo, 0, sizeof(SHELLEXECUTEINFO)); shellInfo.cbSize = sizeof(SHELLEXECUTEINFO); shellInfo.fMask = NULL; shellInfo.hwnd = NULL; shellInfo.lpVerb = NULL; shellInfo.lpFile = tszProcess; // 执行的程序名(绝对路径) shellInfo.lpParameters = NULL; shellInfo.lpDirectory = NULL; shellInfo.nShow = SW_MINIMIZE; //SW_SHOWNORMAL 全屏显示这个程序 shellInfo.hInstApp = NULL; ShellExecuteEx(&shellInfo); return true; }
3、杀死进程
//杀死进程 bool KillProcess(DWORD dwPid) { printf("Kill进程Pid = %d\n", dwPid); //关闭进程 HANDLE killHandle = OpenProcess(PROCESS_TERMINATE | PROCESS_QUERY_INFORMATION | // Required by Alpha PROCESS_CREATE_THREAD | // For CreateRemoteThread PROCESS_VM_OPERATION | // For VirtualAllocEx/VirtualFreeEx PROCESS_VM_WRITE, // For WriteProcessMemory); FALSE, dwPid); if (killHandle == NULL) return false; TerminateProcess(killHandle, 0); return true; }
以上就是C++程序自动重启的实现代码的详细内容,更多关于C++程序自动重启的资料请关注其它相关文章!