C++程序自動重啟的實現(xiàn)代碼
一、自動重啟的原理
我不知道為什么很多程序員覺得自動重啟很low,就像我始終不明白為什么有些人一聽見我說“重新編譯一下”就笑,難道不是重新編譯一下大部分問題就解決了嗎?
自動重啟原理很簡單,用一個進(jìn)程監(jiān)控另一個進(jìn)程,掛了就再啟動一個。細(xì)節(jié)也不算多,主要是正確判斷進(jìn)程狀態(tài)和啟動方式,其實最大的工作量是程序恢復(fù)時應(yīng)該如何回到原來的狀態(tài),這意味著程序要隨時保存狀態(tài)。
只要你能做到用戶無感,你在背后做了什么用戶在意嗎?
二、自動重啟的實現(xiàn)
如果是UNIX,用fork然后監(jiān)控子進(jìn)程,掛了就再fork,一個循環(huán)就解決問題了。
windows上麻煩一些,監(jiān)控進(jìn)程,控制臺程序:
#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; //從命令行獲取要監(jiān)控的進(jìn)程的PID,參數(shù)-pid后的下一個參數(shù) szArglist = CommandLineToArgvW(lpCmdLine, &nArgs);//注意,如果沒有參數(shù),會返回程序名,如果有參數(shù),則不包括程序名(或許是個BUG) if (NULL == szArglist) { MessageBox(NULL, lpCmdLine, TEXT("CommandLineToArgvW失敗"), 0); return 0; } else { wsprintf(buf, TEXT("參數(shù)個數(shù)%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); //打開進(jìn)程以供監(jiān)控 HANDLE handle = OpenProcess(PROCESS_QUERY_INFORMATION | SYNCHRONIZE, FALSE, pid); //等待進(jìn)程結(jié)束 DWORD state = WaitForSingleObject(handle, INFINITE); if (WAIT_OBJECT_0 == state) { DWORD exitCode; if (!GetExitCodeProcess(handle, &exitCode))//獲得退出碼 { MessageBox(NULL, TEXT("GetExitCodeProcess 出錯"), TEXT("未能獲取程序結(jié)束狀態(tài)"), 0); } wsprintf(buf, TEXT("退出碼 %u"), exitCode); //MessageBox(NULL, buf, TEXT("任務(wù)完成"), 0); if (0 != exitCode)//正常退出是返回碼(return 返回碼; 或者exit(返回碼),一般約定正常返回0),異常結(jié)束肯定是非0 {//這一段就是以-r參數(shù)重啟程序,兩個程序必須在同一目錄下 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("恢復(fù)程序失敗"), 0); } } } else if (WAIT_FAILED == state) { MessageBox(NULL, lpCmdLine, TEXT("WaitForSingleObject失敗"), 0); } else { MessageBox(NULL, lpCmdLine, TEXT("WaitForSingleObject非預(yù)期的返回值"), 0); } return 0; }
這個程序是這樣的,工作的主程序名叫“app.exe”,在適當(dāng)?shù)臅r候啟動了監(jiān)控進(jìn)程(就是這個代碼,名稱任意,但是必須和app.exe放在一起),并把自己的pid傳遞給監(jiān)控進(jìn)程(命令行參數(shù)-pid 主進(jìn)程),監(jiān)控進(jìn)程啟動后從命令行獲取到需要監(jiān)控的pid,監(jiān)視pid狀態(tài),如果是正常結(jié)束,就退出程序,如果是異常結(jié)束,以“-r”參數(shù)啟動主進(jìn)程。
主進(jìn)程啟動過程是這樣的:啟動到某個階段,檢查命令行,帶有“-r”參數(shù)說明是自動恢復(fù),走自動恢復(fù)流程,否則走正常流程,啟動監(jiān)控進(jìn)程并把自己的pid傳遞過去。
為什么要做一個獨(dú)立的監(jiān)控程序,不用主進(jìn)程自身呢?因為程序太大了,有很多靜態(tài)初始化的話,不知道起兩個會不會有什么問題。
為什么要通過命令行參數(shù)傳遞進(jìn)程PID呢?主進(jìn)程起子進(jìn)程不是可以獲得子進(jìn)程的PID嗎?因為好多程序喜歡套殼啊,返回的子進(jìn)程又創(chuàng)建子進(jìn)程干活,自己馬上就退出了。
獲取自身PID的方法:
DWORD pid = GetCurrentProcessId();
要在驗證程序異常退出可以return一個非零值,或者調(diào)用abort()。如果不區(qū)分是否是重啟則不用處理參數(shù),啟動監(jiān)控進(jìn)程的代碼和監(jiān)控進(jìn)程啟動主進(jìn)程的相似。
三、相關(guān)知識點(diǎn)
3.1 CommandLineToArg
win32程序處理命令行真是費(fèi)勁死了。
shellapi.h Shell32.dll/Shell32.lib LPWSTR * CommandLineToArgvW( [in] LPCWSTR lpCmdLine, [out] int *pNumArgs );
注意參數(shù)pNumArgs是在函數(shù)內(nèi)部分配的,要在外部釋放。這是C的習(xí)慣性做法。
3.2 LocalFree
釋放本地內(nèi)存對象。
HLOCAL LocalFree( [in] _Frees_ptr_opt_ HLOCAL hMem );
3.3 OpenProcess
打開進(jìn)程對象,以便后續(xù)等待進(jìn)程結(jié)束。
HANDLE OpenProcess( [in] DWORD dwDesiredAccess, [in] BOOL bInheritHandle, [in] DWORD dwProcessId );
3.4 WaitForSingleObject
等待對象,可以是進(jìn)程、線程、控制臺輸入等類型的句柄,等待的事件包括信號、超時等,發(fā)生了某種事件函數(shù)就會返回。
DWORD WaitForSingleObject( [in] HANDLE hHandle, [in] DWORD dwMilliseconds );
3.5 GetExitCodeProcess
獲得進(jìn)程退出碼。
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
獲得程序的啟動信息,在這個代碼里用來傳遞給新進(jìn)程。
void GetStartupInfoW( [out] LPSTARTUPINFOW lpStartupInfo );
3.8 CreateProcess
創(chuàng)建進(jìn)程。
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 );
參數(shù)雖多,大部分都可以不用。
拓展
C++重啟進(jìn)程
步驟:
1、查找需要重啟進(jìn)程的進(jìn)程id
2、啟動需要重啟的進(jìn)程
3、殺死第一步進(jìn)程id的進(jìn)程
代碼:
1、查找需要重啟的進(jìn)程的進(jìn)程id
//通過進(jìn)程名查找進(jìn)程Id bool FindProcess(std::wstring strProcessName, DWORD& nPid) { TCHAR tszProcess[64] = { 0 }; lstrcpy(tszProcess, strProcessName.c_str()); //查找進(jìn)程 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)); // 遍歷進(jìn)程 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、啟動進(jìn)程
//啟動進(jìn)程 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; // 執(zhí)行的程序名(絕對路徑) shellInfo.lpParameters = NULL; shellInfo.lpDirectory = NULL; shellInfo.nShow = SW_MINIMIZE; //SW_SHOWNORMAL 全屏顯示這個程序 shellInfo.hInstApp = NULL; ShellExecuteEx(&shellInfo); return true; }
3、殺死進(jìn)程
//殺死進(jìn)程 bool KillProcess(DWORD dwPid) { printf("Kill進(jìn)程Pid = %d\n", dwPid); //關(guān)閉進(jìn)程 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++程序自動重啟的實現(xiàn)代碼的詳細(xì)內(nèi)容,更多關(guān)于C++程序自動重啟的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
C++基于socket UDP網(wǎng)絡(luò)編程實現(xiàn)簡單聊天室功能
這篇文章主要為大家詳細(xì)介紹了C++基于socket UDP網(wǎng)絡(luò)編程實現(xiàn)簡單聊天室功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2021-07-07Qt數(shù)據(jù)庫應(yīng)用之實現(xiàn)通用數(shù)據(jù)庫分頁
數(shù)據(jù)庫分頁展示,在所有的涉及到數(shù)據(jù)庫記錄的項目中都是需要的。本文將利用Qt實現(xiàn)通用數(shù)據(jù)庫的分頁展示,感興趣的小伙伴可以跟隨小編學(xué)習(xí)一下2022-02-02一篇文章帶你了解C++ static的作用,全局變量和局部變量的區(qū)別
這篇文章介紹了C++ static的作用,全局變量和局部變量的區(qū)別,需要的朋友可以過來參考下,希望能夠給你帶來幫助2021-09-09