1 项目介绍
本次的项目是设计windows服务程序监听系统时间,对误差的时间进行修改,解决不连网下的本地时间的误差问题。
2 程序设计
当程序直接运行时为创建该程序为windows服务程序,创建的windows服务程序设置为开机自启且运行带参数"-k runservice"以进行区别为创建服务还是运行程序。
#define _WINSOCK_DEPRECATED_NO_WARNINGS
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <windows.h>
#include <winsvc.h>
#include <conio.h>
#include <winuser.h>
#include <tchar.h>
#pragma comment(lib,"Advapi32.lib")
#pragma comment(lib ,"User32.lib")
using namespace std;
TCHAR szServiceName[] = L"timeCompensation"; //服务名
SERVICE_STATUS serviceStatus;
SERVICE_STATUS_HANDLE serviceStatusHandle;
// 用于通知服务停止的事件句柄
HANDLE hStopEvent = NULL;
/*
@brief :递归创建文件夹
@param path : 创建文件夹的最后深路径
@return 成功返回true ,失败返回false
*/
bool CreateDirectoryRecursive(const std::wstring& path);
/*
@brief 服务控制主函数,这里实现对服务的控制,当在服务管理器上停止或其它操作时,将会运行此处代码
*/
void WINAPI ServiceCtrlHandler(DWORD controlCode);
/*
@brief 服务主函数,这在里进行控制对服务控制的注册
*/
void WINAPI ServiceMain(DWORD argc, LPTSTR* argv);
/*
@brief 程序日志,将内容写入系统日志
*/
void LogEvent(LPCTSTR pFormat, ...);
/*
@brief 停止windows service 程序并删除程序
@return 成功则返回true ,失败或者服务不存在则返回false
*/
BOOL Uninstall();
/*
@brief 判断windows服务程序是否已经安装
@return 如果已经安装,返回true ,否则返回false
*/
BOOL IsInstalled();
/*
@brief 安装windows service 程序
@return 如果成功安装返回true ,否则返回false
*/
BOOL Install();
/*
@brief 启动windows service服务程序
@return 成功启动返回true ,否则返回false
*/
BOOL StartScService();
/*
@brief 要实现的程序。更新时间,记录文件存在C:/ProgramData/timeUpdate/下的timeCmpensation.txt中,每24小时增加10.064S
*/
void updateTime();
bool CreateDirectoryRecursive(const std::wstring& path) {
if (path.empty()) {
return false;
}
// 检查路径是否已经存在
if (GetFileAttributesW(path.c_str()) != INVALID_FILE_ATTRIBUTES) {
// 如果路径存在且是目录,返回成功
if (FILE_ATTRIBUTE_DIRECTORY & GetFileAttributesW(path.c_str())) {
return true;
}
// 如果路径存在但不是目录,返回失败
return false;
}
// 拆分路径,找到最后一个\之前的部分(即父目录)
size_t found = path.find_last_of(L"\\/");
if (found == std::wstring::npos) {
// 如果没有找到\或/,则路径只包含一个目录名
return CreateDirectoryW(path.c_str(), NULL);
}
// 递归创建父目录
std::wstring parentPath = path.substr(0, found);
if (!CreateDirectoryRecursive(parentPath)) {
return false;
}
// 创建当前目录
return CreateDirectoryW(path.c_str(), NULL);
}
/*
void updateTime() {
CreateDirectoryRecursive(L"C:/ProgramData/timeUpdate"); //递归创建文件夹
//unsigned long size = 255;
//WCHAR UserName_b[255];
//char UserName_e[255];
//GetUserName(UserName_b, &size);//GetUserName()函数同上
//WideCharToMultiByte(CP_ACP, 0, UserName_b, -1, &UserName_e[0], 255, nullptr, nullptr); //将wchar_t * 转换成char *
char filePath[256];
snprintf(filePath, 255, "C:/ProgramData/timeUpdate/timeCompensation.txt");
time_t timep;
time(&timep); //获取从1970至今过了多少秒,存入time_t类型的timep
//获取精确到毫秒的时间
SYSTEMTIME timenow;
GetLocalTime(&timenow);
//printf("%I64d秒 , %I64d毫秒\n", timenow.wSecond, timenow.wMilliseconds);
//下一步llt,是转化成(毫秒级)的Unix时间戳,将tm的秒时间戳*1000加上timenow的毫秒时间戳得到毫秒时间戳
time_t llt = timep * 1000 + timenow.wMilliseconds;
FILE* file;
if (0 != fopen_s(&file, filePath, "r+")) {
//打不开说明服务刚创建,程序写下时间戳就退出
if (0 != fopen_s(&file, filePath, "w")) {
printf("打开文件失败\n");
return;
}
fprintf(file, "%I64d", llt);
fclose(file);
return;
}
//获取文件内的时间戳
char buf[64] = {};
fread_s(buf, sizeof(buf), 1, sizeof(buf), file);
fclose(file);
time_t file_times = atoll(buf);
if (llt - file_times >= 1000 * 60 * 60 * 24) {
//每超过24小时就执行时间补偿,增加时间补偿 ,24小时/补偿10.064S ,但是获取本机电脑到设置完时间,有1-2毫秒的程序运行时间差,所以多加1毫秒
time_t add_count = (llt - file_times) / (1000 * 60 * 60 * 24); //计算有多少个24小时
time_t kllt = (llt - file_times) % (1000 * 60 * 60 * 24); //保存没有被算进去的毫秒数
time_t llt_new = llt + add_count * (long long)(10064 + 1); //更新现在的毫秒数
time_t timestamp = llt_new / 1000; //更新当前秒数
time_t Milliseconds = llt_new % 1000; //计算当前剩余的毫秒数
tm t;
_localtime64_s(&t, ×tamp); //将更新后时间戳转化成标准时间,年份需要加1900,月份加1(0-11)
timenow.wYear = t.tm_year + 1900;
timenow.wMonth = t.tm_mon + 1;
timenow.wDay = t.tm_mday;
timenow.wHour = t.tm_hour;
timenow.wMinute = t.tm_min;
timenow.wSecond = t.tm_sec;
timenow.wDayOfWeek = t.tm_wday;
timenow.wMilliseconds = Milliseconds;
SetLocalTime(&timenow);
if (0 != fopen_s(&file, filePath, "w")) {
printf("打开文件失败\n");
return;
}
//更新保留的时间,在每24小时更新后,剩余的不足24小时加入下次计算
time_t wllt = llt_new - kllt;
fprintf(file, "%lld", wllt);
fclose(file);
}
}
*/
void updateTime() {
CreateDirectoryRecursive(L"C:/ProgramData/timeUpdate"); //递归创建文件夹
//unsigned long size = 255;
//WCHAR UserName_b[255];
//char UserName_e[255];
//GetUserName(UserName_b, &size);//GetUserName()函数同上
//WideCharToMultiByte(CP_ACP, 0, UserName_b, -1, &UserName_e[0], 255, nullptr, nullptr); //将wchar_t * 转换成char *
char filePath[256];
snprintf(filePath, 255, "C:/ProgramData/timeUpdate/timeCompensation.txt");
/*
1秒 = 1000毫秒
1毫秒 = 1000 微秒
1微秒 = 1000 纳秒
*/
// SYSTEMTIME结构表示的是本地时间,而FILETIME表示的是UTC时间(协调世界时)
FILETIME ft;
SYSTEMTIME st;
GetSystemTimeAsFileTime(&ft); // 获取当前时间,此值表示自 1601 年 1 月 1 日开始以来的 100 纳秒单位数
FileTimeToLocalFileTime(&ft, &ft); //GetSystemTimeAsFileTime获取的是UTC的时间 ,而我们这里的是UTC+8 所以要转为本地时间再使用
// 放入高32位和低32位得到64位,将FILETIME转换为64位整数
ULARGE_INTEGER ui;
ui.LowPart = ft.dwLowDateTime;
ui.HighPart = ft.dwHighDateTime;
time_t llt = ui.QuadPart;
FILE* file;
if (0 != fopen_s(&file, filePath, "r+")) {
//打不开说明服务刚创建,程序写下时间戳就退出
if (0 != fopen_s(&file, filePath, "w")) {
printf("打开文件失败\n");
return;
}
fprintf(file, "%lld", llt);
fclose(file);
return;
}
//获取文件内的时间戳
char buf[64] = {};
fread_s(buf, sizeof(buf), 1, sizeof(buf), file);
fclose(file);
time_t file_times = atoll(buf);
if (llt - file_times >= 864000000000) {
//每超过24小时就执行时间补偿,增加时间补偿 ,24小时/补偿10.064S ,但是获取本机电脑到设置完时间
time_t add_count = (llt - file_times) / 864000000000; //计算超出多少个24小时
time_t kllt = llt - file_times % 864000000000;
ui.QuadPart = ui.QuadPart + add_count * 10064 * 10000; //计算时间增加了多少
ft.dwLowDateTime = ui.LowPart ;
ft.dwHighDateTime = ui.HighPart ;
FileTimeToSystemTime(&ft, &st); //调整时间后将FILETIME转换为SYSTEMTIME
SetLocalTime(&st);
if (0 != fopen_s(&file, filePath, "w")) {
printf("打开文件失败\n");
return;
}
//更新保留的时间,在每24小时更新后,剩余的不足24小时加入下次计算
time_t wllt = ui.QuadPart - kllt;
fprintf(file, "%lld", wllt);
fclose(file);
}
}
BOOL IsInstalled()
{
BOOL bResult = FALSE;
//打开服务控制管理器
SC_HANDLE hSCM = ::OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
if (hSCM != NULL)
{
//打开服务
SC_HANDLE hService = ::OpenService(hSCM, szServiceName, SERVICE_QUERY_CONFIG);
if (hService != NULL)
{
bResult = TRUE;
::CloseServiceHandle(hService);
}
::CloseServiceHandle(hSCM);
}
return bResult;
}
BOOL Install()
{
if (IsInstalled())
return TRUE;
//打开服务控制管理器
SC_HANDLE hSCM = ::OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
if (hSCM == NULL)
{
MessageBox(NULL, L"Couldn't open service manager", szServiceName, MB_OK);
return FALSE;
}
// Get the executable file path
TCHAR szFilePath[MAX_PATH];
::GetModuleFileName(NULL, szFilePath, MAX_PATH); //
_tcscat(szFilePath , L" -k runservice"); //加参数,标明服务入口
//创建服务
SC_HANDLE hService = ::CreateService(
hSCM, szServiceName, szServiceName,
SERVICE_ALL_ACCESS, SERVICE_WIN32_OWN_PROCESS,
SERVICE_AUTO_START, SERVICE_ERROR_NORMAL,
szFilePath, NULL, NULL, L"", NULL, NULL);
if (hService == NULL)
{
::CloseServiceHandle(hSCM);
MessageBox(NULL, L"Couldn't create service", szServiceName, MB_OK);
return FALSE;
}
::CloseServiceHandle(hService);
::CloseServiceHandle(hSCM);
return TRUE;
}
BOOL StartScService()
{
if (!IsInstalled())
return FALSE;
//打开服务控制管理器
SC_HANDLE hSCM = ::OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
if (hSCM == NULL)
{
MessageBox(NULL, L"Couldn't open service manager", szServiceName, MB_OK);
return FALSE;
}
SC_HANDLE hService = ::OpenService(hSCM, szServiceName, SERVICE_START);
if (hService == NULL)
{
::CloseServiceHandle(hSCM);
MessageBox(NULL, L"Couldn't open service", szServiceName, MB_OK);
return FALSE;
}
BOOL bDelete = ::StartService(hService , NULL ,NULL);
if (!bDelete) {
//【1056】-服务的实例已在运行中。
char buf[255];
snprintf(buf , 255,"StartService serviceHandle=%p -> fail(%ld)", hService, GetLastError());
wchar_t wideStr[255] ;
// 使用MultiByteToWideChar进行转换
// 这里使用CP_ACP,它代表ANSI代码页。根据你的具体情况,你可能需要使用不同的代码页
MultiByteToWideChar(CP_ACP, 0, buf, strlen(buf), wideStr, 255);
MessageBox(NULL, wideStr, szServiceName, MB_OK);
}
else {
wchar_t wideStr[255];
char buf[255];
snprintf(buf, 255, "StartService serviceHandle=%p -> succ", hService);
// 使用MultiByteToWideChar进行转换
// 这里使用CP_ACP,它代表ANSI代码页。根据你的具体情况,你可能需要使用不同的代码页
MultiByteToWideChar(CP_ACP, 0, buf, strlen(buf), wideStr, 255);
MessageBox(NULL, wideStr, szServiceName, MB_OK);
}
::CloseServiceHandle(hService);
::CloseServiceHandle(hSCM);
return TRUE;
}
void LogEvent(LPCTSTR pFormat, ...)
{
TCHAR chMsg[256];
HANDLE hEventSource;
LPTSTR lpszStrings[1];
va_list pArg;
va_start(pArg, pFormat);
vswprintf_s(chMsg, pFormat, pArg);
va_end(pArg);
lpszStrings[0] = chMsg;
hEventSource = RegisterEventSource(NULL, szServiceName);
if (hEventSource != NULL)
{
ReportEvent(hEventSource, EVENTLOG_INFORMATION_TYPE, 0, 0, NULL, 1, 0, (LPCTSTR*)&lpszStrings[0], NULL);
DeregisterEventSource(hEventSource);
}
}
BOOL Uninstall()
{
if (!IsInstalled())
return TRUE;
SC_HANDLE hSCM = ::OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
if (hSCM == NULL)
{
MessageBox(NULL, L"Couldn't open service manager", szServiceName, MB_OK);
return FALSE;
}
SC_HANDLE hService = ::OpenService(hSCM, szServiceName, SERVICE_STOP | DELETE);
if (hService == NULL)
{
::CloseServiceHandle(hSCM);
MessageBox(NULL, L"Couldn't open service", szServiceName, MB_OK);
return FALSE;
}
SERVICE_STATUS status;
::ControlService(hService, SERVICE_CONTROL_STOP, &status);
//删除服务
BOOL bDelete = ::DeleteService(hService);
::CloseServiceHandle(hService);
::CloseServiceHandle(hSCM);
if (bDelete)
return TRUE;
LogEvent(szServiceName,L"Service could not be deleted");
return FALSE;
}
void WINAPI ServiceCtrlHandler(DWORD controlCode)
{
switch (controlCode)
{
case SERVICE_CONTROL_STOP:
// 设置服务状态为SERVICE_STOPPED_PENDING
serviceStatus.dwCurrentState = SERVICE_STOPPED;
serviceStatus.dwWin32ExitCode = 0;
SetServiceStatus(serviceStatusHandle, &serviceStatus);
// 通知服务主循环停止
SetEvent(hStopEvent);
return;
default:
break;
}
}
void WINAPI ServiceMain(DWORD argc, LPTSTR* argv)
{
// 注册服务控制处理函数
serviceStatusHandle = RegisterServiceCtrlHandler(szServiceName, ServiceCtrlHandler);
// 设置服务状态
serviceStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
serviceStatus.dwCurrentState = SERVICE_RUNNING;
serviceStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP;
serviceStatus.dwWin32ExitCode = 0;
serviceStatus.dwServiceSpecificExitCode = 0;
serviceStatus.dwCheckPoint = 0;
serviceStatus.dwWaitHint = 0;
SetServiceStatus(serviceStatusHandle, &serviceStatus);
// 创建一个自动重置的事件,用于通知停止
hStopEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
if (hStopEvent == NULL)
{
// 处理错误
return;
}
// 执行服务的主要逻辑
if (serviceStatus.dwCurrentState == SERVICE_RUNNING)
{
while (1) {
//以下用于循环函数
updateTime();
// 检查停止事件是否被设置
if (WaitForSingleObject(hStopEvent, 0) == WAIT_OBJECT_0)
{
// 停止事件被触发,退出循环
break;
}
//休眠一小时
Sleep(1000*60*60);
}
}
// 停止服务时的清理逻辑
// 设置服务状态为停止
serviceStatus.dwCurrentState = SERVICE_STOPPED;
SetServiceStatus(serviceStatusHandle, &serviceStatus);
}
int main(int argc, char* argv[]) {
/*
SYSTEMTIME timenow_s ,timenow_e;
GetLocalTime(&timenow_s);
updateTime();
GetLocalTime(&timenow_e);
printf(" %d年, %d月,%d日,%d时,%d分,%d秒 ,%d毫秒\n",
timenow_s.wYear, timenow_s.wMonth, timenow_s.wDay, timenow_s.wHour, timenow_s.wMinute, timenow_s.wSecond, timenow_s.wMilliseconds);
printf(" %d年, %d月,%d日,%d时,%d分,%d秒 ,%d毫秒\n",
timenow_e.wYear, timenow_e.wMonth, timenow_e.wDay, timenow_e.wHour, timenow_e.wMinute, timenow_e.wSecond, timenow_e.wMilliseconds);
_getch();
return 0;
*/
//GetModuleFileName(NULL, filePath_b, static_cast<DWORD>(256)); //获取当前程序的绝对路径
//WideCharToMultiByte(CP_ACP, 0, filePath_b, -1, &filePath_e[0], 256, nullptr, nullptr); //将wchar_t * 转换成char *
if (argc == 3) {
if ( 0 == strcmp(argv[1], "-k")) {
if (0 == strcmp(argv[2], "runservice")) {
SERVICE_TABLE_ENTRY serviceTable[] =
{
{ szServiceName, ServiceMain },
{ NULL, NULL }
};
// 启动服务控制分派器
StartServiceCtrlDispatcher(serviceTable);
}
}
}
else {
if (IsInstalled()) {
Uninstall();
}
if (!Install()) {
return 0;
}
StartScService();
}
return 0;
}
3 遇到的问题
3.1 存储路径错误导致操作文件异常
当初为了想其他程序一样规范,使保存时间戳的文件存储在 "C:\User\用户名" 下,使用 GetUserName 函数获取了用户名,但是它只能返回创建当前进程的用户名,并非真正的当前登录用户名。如果当前进程是服务进程,或者是由服务进程所创建,则GetUserName获得的用户名会是"SYSTEM"。当时找了半天资料都是权限和路径不对什么的,都解决不了,无法写入文件(获取到的不是用户名而是SYSTEM的话没有那个文件夹)无法自动创建。最终选择将保存的时间戳更改保存到 C:\ProgramData 目录下。
3.2 时间的换算
在修改时间时需要的是毫秒级别的,所以使用的是 SYSTEMTIME结构体 ,但是 GetLocalTime(Out LPSYSTEMTIME lpSystemTime);函数获取到的是日期,所以当时使用了time(time_t Time)来获取秒级的时间戳来进行转换,但是这样会显得麻烦,且两次获取的话还是可能会产生误差。所以后面使用了FILETIME结构体,使用GetSystemTimeAsFileTime(Out LPFILETIME lpSystemTimeAsFileTime)函数来获取100纳秒级的时间戳来进行计算。但是它获取到的为1601 年 1 月 1 日开始以来的 100 纳秒单位数,获取的是UTC的时间 。而我们这里的是需要的是本地时间,设置时间是使用的SetLocalTime(In CONST SYSTEMTIME lpSystemTime)函数也是使用本地时间,如果直接使用FileTimeToSystemTime(In CONST FILETIME* lpFileTime, Out LPSYSTEMTIME lpSystemTime)来将修改后的时间戳来转换的话会导致少8小时,所以获取时间戳后需要使用FileTimeToLocalFileTime(In CONST FILETIME* lpFileTime, Out LPFILETIME lpLocalFileTime)函数将时间转换为本地时间再使用。
4.参考文献
本文参考CSDN博主「Jackchenyj」的原创文章《用C/C++创建windows服务程序》 。原文链接:https://blog.csdn.net/chenyujing1234/article/details/8023816
标签:服务程序,return,SERVICE,windows,timenow,hSCM,c++,file,NULL From: https://www.cnblogs.com/mzcl/p/18244086