**
VC实现内网时间同步+进程守护
**
问题的由来
单位上现有服务器实体机+虚拟机共200+,一直存在时间准确性和一致性方面的问题。期间用过不少时间同步工具,例如atomic time synchronizer-原子时钟同步器、XiaoMoTimeSync等,但都不太理想。加上服务器需要保证业务进程和服务连续不断地工作,也找不到合适的监控管理软件,无奈之下,最后自己亲手操刀写了一个。
服务器的时间问题
服务器(以Window服务器为例)的时间来源大致有以下几种:1、主板晶振(时间不准);2、Window自带的对时功能(通常连不上时间服务器);3、应用程序校时(例如以数据库服务器的时间为准);4、虚拟化平台提供时间源(例如vCenter、ESXI主机);5、使用第三方对时软件。
上述前两种方法明显不靠谱,第三、第四种方法的时间源仍然是个问题,最终还是需要我们自己搭建一个内网的NTP服务器,这个服务器通过访问外网的NTP服务器(例如cn.pool.ntp.org、ntp1.aliyun.com)校准自己的时间,同时对内网提供NTP授时服务。
网络对时的实现方法
编程环境:Win7旗舰版(x64)+VC2005
不用很新的版本,用VC2005编写+静态编译,完成后程序只有400K,操作系统只需要安装有VC2005的运行库就能正常运行,不需要额外安装DotNet。
对时方法1:Socket 编程
具体代码,参考了网上的资料:
vc++6.0 同步本机时间到Internet NTP服务器 编译通过
https://blog.csdn.net/jiftlixu/article/details/6013837?locationNum=3
对时方法2:注册表编程
网上这方面的资料比较多,需要修改的注册表键基本上都在HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\W32Time\下(服务端和客户端不一样),还有一个在HKEY_LOCAL_MACHINE\SOFTWARE\MICROSOFT\WINDOWS\CURRENTVERSION\DATETIME\SERVERS\。
重启w32time服务和开启防火墙UDP123端口,可以用批处理实现。例如:
CString strCmd = "/c sc start w32time";
ShellExecute(NULL, "open", "cmd.exe", strCmd, NULL, SW_HIDE);
strCmd = "/c netsh firewall set portopening udp 123 NTPSERVER enable";
ShellExecute(NULL, "open", "cmd.exe", strCmd, NULL, SW_HIDE);
程序本身做NTP服务器也是通过修改注册表来实现。
关于服务器的进程和服务
服务器需要保证业务连续,不能随意中断。当服务器开机时,业务进程和服务需要自动运行。开机后,当业务进程和服务出现异常时(进程异常退出、服务异常停止等),需要及时发现并重新启动进程或者拉起服务。
要实现上述目的,只要将需要守护的进程和监控的服务写到配置文件(ini文件、xml文件等),然后定时轮询即可。
//监控配置文件,包含两个Section:[Service]和[Process]
CString monFile = "C:\\Daemon.cfg";
rowNum GetRows() //获得需要监控的服务和进程的数量
{ //rowNum是自己定义的结构体
trowNum retNum;
CStdioFile file;
CString str;
int i=0,j=0,k=0;
if(file.Open(monFile,CFile::modeRead))
{
while(file.ReadString(str))
{
i++;
if(str == "[Service]") j = i; //前面有注释语句
if(str == "[Process]") k = i;
}
file.Close();
int row1 = k - j -1; //监控服务的数量
int row2 = i - k; //监控进程的数量
retNum.num1 = row1;
retNum.num2 = row2;
}
else
{
int row1 = 0; //监控服务的数量
int row2 = 0; //监控进程的数量
retNum.num1 = row1;
retNum.num2 = row2;
}
return retNum;
}
进程守护的实现方法
1、判断指定进程是否存在
BOOL IsOpen(CString strFind) //判断进程是否存在
{
PROCESSENTRY32 pe32;
pe32.dwSize = sizeof(pe32);
HANDLE hProcessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS,0);
if( hProcessSnap == INVALID_HANDLE_VALUE ) return FALSE;
BOOL bMore = Process32First( hProcessSnap,&pe32 );
while(bMore)
{
bMore = Process32Next( hProcessSnap,&pe32 );
CString strExeFile;
strExeFile.Format("%s",pe32.szExeFile);
if( strExeFile.CompareNoCase(strFind) == 0 )
{
CloseHandle( hProcessSnap );
return TRUE;
}
}
CloseHandle( hProcessSnap );
return FALSE;
}
2、启动进程:
nRet = WinExec(strPath,SW_SHOWNORMAL);
服务监控的实现方法
1、获取指定服务的状态
CString GetStatus(CString pServiceName)
{
SC_HANDLE hSC = ::OpenSCManager( NULL,NULL, GENERIC_EXECUTE);
if( hSC == NULL)
{
//return SEV_ERROR; //无法连接服务管理器
return "无法连接服务管理器";
}
// 打开服务。
SC_HANDLE hSvc = ::OpenService( hSC, pServiceName, SERVICE_START | SERVICE_QUERY_STATUS | SERVICE_STOP);
if( hSvc == NULL)
{
::CloseServiceHandle( hSC);
//return SEV_NO; //无法打开服务
return "无法打开服务";
}
// 获得服务的状态
SERVICE_STATUS status;
CString strRet;
if( ::QueryServiceStatus( hSvc, &status) == FALSE)
{
// TRACE( "Get Service state error。");
::CloseServiceHandle( hSvc);
::CloseServiceHandle( hSC);
return "获取服务状态失败";
}
else
{
::CloseServiceHandle( hSvc);
::CloseServiceHandle( hSC);
DWORD dwValue = status.dwCurrentState;
switch(dwValue)
{
case SERVICE_CONTINUE_PENDING:
strRet="恢复挂起";
break;
case SERVICE_PAUSE_PENDING:
strRet="暂停挂起";
break;
case SERVICE_PAUSED:
strRet="服务暂停";
break;
case SERVICE_RUNNING:
strRet="正在运行";
break;
case SERVICE_START_PENDING:
strRet="正在启动";
break;
case SERVICE_STOP_PENDING:
strRet="正在停止";
break;
case SERVICE_STOPPED:
strRet="服务停止";
break;
default:
strRet="未知状态";
break;
}
return strRet;
}
}
2、启动服务
方法同上,用ShellExecute函数。
程序开机自启的实现
同样是通过写注册表来实现,这里是先把自身复制到Windows目录后,再写注册表。注册表路径:“Software\Microsoft\Windows\CurrentVersion\Run”。
另外,我在程序里加入了注册激活的功能,没注册的使用功能会受限制。
程序界面如下: