C++看门狗Watchdog

// WatchDog.h
#pragma once

#include <string>
#include <chrono>
#include <thread>
#include <functional>
#include <atomic>

class Watchdog
{
public:
	using WatchDogLogCallback = std::function<void(const std::string&)>;

	// 模式
	enum Mode
	{
		Master,
		Slave
	};

	// 配置结构体
	struct Config
	{
		std::chrono::milliseconds timeout;       // 超时时间
		std::chrono::milliseconds checkInterval; // 检查间隔
		std::string heartbeatFile;          // 心跳文件路径
		std::string mainProgram;            // 主程序路径
	};

	// 构造函数
	Watchdog(const Config& config);

	// 启动看门狗
	void Start(Mode mode = Watchdog::Master);

	// 停止看门狗
	void Stop();

	// 设置超时回调函数(可选)
	void SetTimeoutCallback(std::function<void()> callback);

	// 设置打印信息回调函数(可选)
	void SetLogCallback(WatchDogLogCallback callback);

	// 打印信息
	void log(const std::string& msg);

	// 获取当前毫秒级时间戳
	static long long CurrentMillisecondsEpoch();

private:
	Config m_config;                          // 看门狗配置
	std::thread m_watchdogThread;             // 看门狗线程
	std::atomic<bool> m_running;              // 看门狗运行状态
	std::function<void()> m_callback;         // 回调函数
	WatchDogLogCallback m_logCallback;        // 输出回调

	// 获取心跳文件的时间戳
	long long GetFileHeartbeatTimestamp();

	// 重启主程序
	void RestartMainProgram();

	// 看门狗任务
	void WatchdogMasterTask();
	void WatchdogSlaveTask();
};
// WatchDog.cpp
#include "WatchDog.h"

#include <filesystem>
#include <iostream>
#include <fstream>

Watchdog::Watchdog(const Config& config) : m_config(config), m_running(false)
{ }

void Watchdog::Start(Mode mode)
{
	if (m_running)
	{
		log("Watchdog is already running.");
		return;
	}
	else
	{
		log("Watchdog is running.");
	}
	m_running = true;
	if (mode == Watchdog::Master)
	{
		m_watchdogThread = std::thread(&Watchdog::WatchdogMasterTask, this);
	}
	else if (mode == Watchdog::Slave)
	{
		m_watchdogThread = std::thread(&Watchdog::WatchdogSlaveTask, this);
	}
	if (m_watchdogThread.joinable())
	{
		m_watchdogThread.join();
	}
}

void Watchdog::Stop()
{
	m_running = false;
}

void Watchdog::SetTimeoutCallback(std::function<void()> callback)
{
	m_callback = callback;
}

void Watchdog::SetLogCallback(WatchDogLogCallback callback)
{
	m_logCallback = callback;
}

void Watchdog::log(const std::string& msg)
{
	if (m_logCallback)
	{
		m_logCallback(msg);
	}
	else
	{
		std::cerr << msg;
	}
}

long long Watchdog::CurrentMillisecondsEpoch()
{
	return std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()).count();
}

long long Watchdog::GetFileHeartbeatTimestamp()
{
	long long timestamp = 0;
	if (!std::filesystem::exists(m_config.heartbeatFile))
	{
		log("Heartbeat file not found.");
		return timestamp;
	}

	std::ifstream heartbeat(m_config.heartbeatFile);
	if (!heartbeat.is_open())
	{
		log("Failed to open heartbeat file.");
		return timestamp;
	}

	heartbeat >> timestamp;
	return timestamp;
}

void Watchdog::RestartMainProgram()
{
	std::system(m_config.mainProgram.c_str());
#ifdef  _WIN32
	std::string stopCmd = "taskkill /f /im " + m_config.mainProgram;
	std::string startCmd = "start \"\" /d \"\" \"" + m_config.mainProgram + "\"";
#else
	std::string stopCmd = "pkill " + m_config.mainProgram;
	std::string startCmd = m_config.mainProgram + " &";
#endif
	log("stopping " + m_config.mainProgram + " program...");
	std::system(stopCmd.c_str());
	log("Restarting " + m_config.mainProgram + " program...");
	if (std::system(startCmd.c_str()))
	{
		log(m_config.mainProgram + " failed to run.");
	}
	else
	{
		log(m_config.mainProgram + " runs successfully!");
	}

}

void Watchdog::WatchdogSlaveTask()
{
	while (m_running)
	{
		std::this_thread::sleep_for(m_config.checkInterval);

		std::ofstream heartbeat(m_config.heartbeatFile);
		if (heartbeat.is_open())
		{
			heartbeat << CurrentMillisecondsEpoch();
			heartbeat.close();
		}
		else
		{
			log("Failed to update heartbeat file.");
		}
	}
}

void Watchdog::WatchdogMasterTask()
{
	while (m_running)
	{
		std::this_thread::sleep_for(m_config.checkInterval);
		try
		{
			auto lastHeartbeat = GetFileHeartbeatTimestamp();
			auto now = CurrentMillisecondsEpoch();

			log(std::to_string(now - lastHeartbeat) + " " + std::to_string(m_config.timeout.count()));

			if (now - lastHeartbeat > m_config.timeout.count())
			{
				log("Main program is not responding. Restarting...");

				if (m_callback)
				{
					m_callback();
				}
				else
				{
					RestartMainProgram();
				}
			}
		}
		catch (const std::exception& e)
		{
			log("Watchdog error: " + std::string(e.what()));
			if (m_callback)
			{
				m_callback();
			}
			else
			{
				RestartMainProgram();
			}
		}
	}
}

以下为Master模式的代码示例:

// example: Master
#include "WatchDog.h"
#include "Logger.hpp"

static std::string logFile = "watchdogmaster.log";

static void OnTimeout()
{
	// 引用Logger的日志输出
	LogToFile(LogLevel::Info, logFile, "Slave timeout.");
}

static void WatchDogLog(const std::string& msg)
{
	LogToFile(LogLevel::Info, logFile, msg.c_str());
}

int main()
{
	Watchdog::Config config = {
		std::chrono::milliseconds(10 * 1000), // 超时时间为 10 秒
		std::chrono::milliseconds(5 * 1000),  // 每 5 秒检查一次
		"heartbeat.txt",          // 心跳文件路径
		"WatchDogSlaveTest.exe"          // 主程序路径
	};
	Watchdog watchdog(config);
	watchdog.SetLogCallback(WatchDogLog);
	watchdog.SetTimeoutCallback(OnTimeout);
	watchdog.Start(Watchdog::Master);
	return 0;
}

以下为Slave模式的示例:

// example: Slave
#include "WatchDog.h"
#include "Logger.hpp"

static std::string logFile = "watchdogmaster.log";

static void WatchDogLog(const std::string& msg)
{
	LogToFile(LogLevel::Info, logFile, msg.c_str());
}

int main()
{
	Watchdog::Config config = {
		std::chrono::milliseconds(10 * 1000), // 无效
		std::chrono::milliseconds(2 * 1000),  // 每 2 秒写一次
		"heartbeat.txt",          // 心跳文件路径
		"WatchDogSlaveTest.exe"          // 无效
	};
	Watchdog watchdog(config);
	watchdog.SetLogCallback(WatchDogLog);
	watchdog.Start(Watchdog::Slave);
	return 0;
}

Logger引用的此前的日志记录 Logger

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

IT灰猫

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值