IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 系统运维 -> c++代码如何实现在win/linux下创建编译及部署后台服务,并管理其他服务 -> 正文阅读

[系统运维]c++代码如何实现在win/linux下创建编译及部署后台服务,并管理其他服务

一、实现基础

????????在win下,在#include <windows.h>提供了服务控制管理器相关操作函数,如OpenSCManager、CreateService、OpenService、DeleteService、CloseServiceHandle,可以实现服务的创建、打开、关闭、删除等操作。

??????? 在linux下,系统启用后台应用服务是按照先后次序去扫描特定文件目录去加载这些服务配置文件来启动服务,对于服务的管理本质上就是对于管道(文件句柄)的管理,因此借用popen、pclose等实现对进程的启停操作。

二、源码示例

文件目录结构:

svr_test
    bin
        install_linux.sh
        uinstall_linux.sh
    build_linux
    build_win
    include
        appexitio.h
        svc.h
    src
        main.cpp
    svc
        appexitio.cpp
        linuxSVC.cpp
        WinSVC.cpp
    CMakeLists.txt

文件源码:

appexitio.h

#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000

#ifndef _APP_EXIT_IO_H_
#define _APP_EXIT_IO_H_

/***********************************************************************
  *Copyright 2020-03-06, pyfree
  *
  *File Name       : appexitio.h
  *File Mark       : 
  *Summary         : 
  *程序退出信号处理接口
  *
  *Current Version : 1.00
  *Author          : pyfree
  *FinishDate      :
  *
  *Replace Version :
  *Author          :
  *FinishDate      :

 ************************************************************************/

#ifdef WIN32
#include <windows.h> 
#else
#include <signal.h>
#include <unistd.h>
#include <stdio.h>
#endif

#ifdef WIN32
bool ctrlhandler(unsigned long fdwctrltype);
#else
class SignalHandler {
public:
	SignalHandler();

  void printf_out();
public:
	// 程序退出时的函数操作
	static void handle_signal(int n, siginfo_t *siginfo, void *myact);
};

#endif // WIN32

#endif

svc.h

#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000
/***********************************************************************
  *Copyright 2020-04-21, pyfree
  *
  *File Name       : svc.h
  *File Mark       : 
  *Summary         : 服务管理函数集
  *
  *Current Version : 1.00
  *Author          : pyfree
  *FinishDate      :
  *
  *Replace Version :
  *Author          :
  *FinishDate      :

 ************************************************************************/
#ifndef _SVC_H_
#define _SVC_H_

/**
 * 启动服务
 * @param svr {char*} 服务名
 * @retuan {void} 
 */
void SvcStart(char *svr);

/**
 * 停止服务
 * @param svr {char*} 服务名
 * @retuan {void} 
 */
void SvcStop(char *svr);
/**
 * 查询服务
 * @param svr {char*} 服务名
 * @param svc_state {int&} 服务状态
 * @retuan {void} 
 */
void SvcQuery(char* svr, int &svc_state);

/**
 * 重启服务
 * @param svr {char*} 服务名
 * @retuan {void} 
 */
void SvcRestart(char *svr);

#endif //_SVC_H_

appexitio.cpp

#include "appexitio.h"

#ifdef WIN32
#include <windows.h>
#else
#include <stdlib.h>
#endif
#include <string>

namespace GlobalVar {
	extern bool exitFlag;
};

#ifdef WIN32
bool ctrlhandler(unsigned long fdwctrltype)
{
	bool exitEvent = false;
	switch (fdwctrltype)
	{
		// handle the ctrl-c signal.
	case CTRL_C_EVENT:
		printf("ctrl-c event\n\n");
		exitEvent = true;
		break;
		// ctrl-close: confirm that the user wants to exit.
	case CTRL_CLOSE_EVENT:
		printf("ctrl-close event\n\n");
		exitEvent = true;
		break;
		// pass other signals to the next handler.
	case CTRL_BREAK_EVENT:
		printf("ctrl-break event\n\n");
		break;
	case CTRL_LOGOFF_EVENT:
		printf("ctrl-logoff event\n\n");
		break;
	case CTRL_SHUTDOWN_EVENT:
		printf("ctrl-shutdown event\n\n");
		exitEvent = true;
		//清理
		break;
	default:
		break;
	}
	if (exitEvent)
	{
		Sleep(10);
	}
	GlobalVar::exitFlag = exitEvent;
	return exitEvent;
}
#else
SignalHandler::SignalHandler()
{
	/** install signal use sigaction **/
	struct sigaction act;
	sigemptyset(&act.sa_mask);   /** 清空阻塞信号 **/
	act.sa_flags = SA_SIGINFO;     /** 设置SA_SIGINFO 表示传递附加信息到触发函数 **/
	act.sa_sigaction = handle_signal;
	if (sigaction(SIGHUP, &act, NULL) < 0      // 1
		|| sigaction(SIGINT, &act, NULL) < 0  // 2
		|| sigaction(SIGQUIT, &act, NULL) < 0 // 3
											  //|| sigaction(SIGKILL,&act,NULL) < 0 // 9
		|| sigaction(SIGTERM, &act, NULL) < 0 // 15
		)
	{
		printf("install signal handler error");
	}
};

void SignalHandler::printf_out()
{

}

void SignalHandler::handle_signal(int n, siginfo_t *siginfo, void *myact)
{
	printf("SIGNAL received: signo=%d errno=%d code=%d "
		, siginfo->si_signo, siginfo->si_errno, siginfo->si_code);
	if (siginfo->si_signo == 1
		|| siginfo->si_signo == 2
		|| siginfo->si_signo == 3
		|| siginfo->si_signo == 9
		|| siginfo->si_signo == 15)
	{
		//程序退出,进行退出处理操作
		//G_VALUE::watchDogRunning = false;
		usleep(10000);
		exit(0);
	}
};
#endif // WIN32

linuxSVC.cpp
?

#ifdef __linux__

#include <string.h>
#include <string>
#include <stdlib.h>
#include <stdio.h>
#include <signal.h>

#include "svc.h"

/**
 * 启动服务
 * @param svr {char*} 服务名
 * @retuan {void} 
 */
void SvcStart(char *svr)
{
	FILE* fp = NULL;
	char command[128] = { 0 };
	sprintf(command, "systemctl start %s", svr);
	if ((fp = popen(command, "r")) == NULL)
	{
		return;
	}
	char buf[512] = { 0 };
	if ((fgets(buf, 512, fp)) == NULL)

	{
		pclose(fp);
		return;
	}
	printf("start ret:%s\n",buf);
	pclose(fp);
};

/**
 * 停止服务
 * @param svr {char*} 服务名
 * @retuan {void} 
 */
void SvcStop(char *svr)
{
	FILE* fp = NULL;
	char command[128] = { 0 };
	sprintf(command, "systemctl stop %s", svr);
	if ((fp = popen(command, "r")) == NULL)
	{
		return;
	}
	char buf[512] = { 0 };
	if ((fgets(buf, 512, fp)) == NULL)
	{
		pclose(fp);
		return;
	}
	printf("stop ret:%s\n",buf);
	pclose(fp);
};

/**
 * 查询服务
 * @param svr {char*} 服务名
 * @param svc_state {int&} 服务状态
 * @retuan {void} 
 */
void SvcQuery(char* svr, int &svc_state)
{
	svc_state = 0;
	FILE* fp = NULL;
	char command[128] = { 0 };
	sprintf(command, "systemctl status %s | grep Active", svr);
	if ((fp = popen(command, "r")) == NULL)
	{
		return;
	}
	char buf[512] = { 0 };
	if ((fgets(buf, 512, fp)) == NULL)
	{
		pclose(fp);
		return;
	}
	std::string comment = std::string(buf,strlen(buf));
	//std::string::size_type _pos = comment.find("running");
	//开机启动的状态,static不可被管理,disable未启动,enable启动
	//dead关闭,exited已读取完系统配置,闲置 waiting等待, running正在进行, mounted挂载, plugged加载插件, failed系统配置错误
	if(std::string::npos != comment.find("running"))
	{
		svc_state = 2;
	}else if(std::string::npos != comment.find("listening")){
		svc_state = 2;
	}else{
		svc_state = 1;
	}
	// printf("ret:%s,state:%d\n",buf,svc_state);
	pclose(fp);	
};

/**
 * 重启服务
 * @param svr {char*} 服务名
 * @retuan {void} 
 */
void SvcRestart(char *svr)
{
	SvcStop(svr);
	SvcStart(svr);
}

#endif

WinSVC.cpp

#ifdef WIN32
#pragma warning(disable: 4995)  //for swprintf

#include <windows.h>
#include <tchar.h>
#include <strsafe.h>

#include "atlcomtime.h"
#pragma comment(lib, "advapi32.lib")

#include "svc.h"

int MyMain(int argc, char * argv[]);		//Actual Main function, should be defined
extern char SVCNAME[128];					//SVC Name, should be defined
extern char SVCDESC[256];					//SVC Desc, should be defined


namespace WINSVC
{
	SERVICE_STATUS          gSvcStatus; 
	SERVICE_STATUS_HANDLE   gSvcStatusHandle; 
	HANDLE                  ghSvcStopEvent = NULL;

	/**
	* 本服务安装
	* @param {void}
	* @retuan {void} 
	*/
	VOID SvcInstall(void);
	/**
	* 本服务卸载
	* @param {void}
	* @retuan {void} 
	*/
	VOID DoDeleteSvc(void);
	/**
	* 查询服务
	* @param svr {char*} 服务名
	* @param svc_state {int&} 服务状态
	* @retuan {void} 
	*/
	VOID SvcQuery(char* svr, int &svc_state);
	/**
	* 启动服务
	* @param svr {char*} 服务名
	* @retuan {void} 
	*/
	VOID SvcStart(char *svr);
	/**
	* 停止服务
	* @param svr {char*} 服务名
	* @retuan {void} 
	*/
	VOID SvcStop(char *svr);
	/**
	* 重启服务
	* @param svr {char*} 服务名
	* @retuan {void} 
	*/
	VOID SvcRestart(char *svr);

	VOID WINAPI SvcCtrlHandler( DWORD ); 
	VOID WINAPI SvcMain( DWORD, LPTSTR * ); 

	VOID ReportSvcStatus( DWORD, DWORD, DWORD );
	/**
	* 本服务初始化
	* @param svr {char*} 服务名
	* @retuan {void} 
	*/
	VOID SvcInit( DWORD, LPTSTR * ); 
	VOID SvcReportEvent( LPTSTR );
	VOID CALLBACK MainThread(HWND hwnd,UINT uMsg,UINT_PTR idEvent,DWORD dwTime);

	HANDLE m_HThread;
	DWORD m_dwThreadID;
	BOOL g_bRun = FALSE;
}

void __cdecl _tmain(int argc, TCHAR *argv[]) 
{ 
	if( lstrcmpi( argv[1], TEXT("install")) == 0 )
	{
		WINSVC::SvcInstall();
		return;
	}
	else if( lstrcmpi( argv[1], TEXT("uninstall")) == 0 )
	{
		WINSVC::DoDeleteSvc();
		return;
	}
	SERVICE_TABLE_ENTRY DispatchTable[] = 
	{ 
		{ SVCNAME, (LPSERVICE_MAIN_FUNCTION) WINSVC::SvcMain }, 
		{ NULL, NULL } 
	}; 

	WINSVC::g_bRun = TRUE;
	if (!StartServiceCtrlDispatcher( DispatchTable )) 
	{ 
		WINSVC::SvcReportEvent(TEXT("StartServiceCtrlDispatcher"));
		MyMain(argc, argv);
		MSG msg;
		while (GetMessage(&msg, 0, 0, 0))
			DispatchMessage(&msg);
	} 
};

void SvcQuery(char* svr, int &svc_state)
{
	WINSVC::SvcQuery(svr, svc_state);
};

void SvcStart(char *svr)
{
	WINSVC::SvcStart(svr);
};

void SvcStop(char *svr)
{
	WINSVC::SvcStop(svr);
};

void SvcRestart(char *svr)
{
	WINSVC::SvcRestart(svr);
};

VOID WINSVC::SvcInstall()
{
	SC_HANDLE schSCManager;
	SC_HANDLE schService;
	TCHAR szPath[MAX_PATH];

	if( !GetModuleFileName( NULL, szPath, MAX_PATH ) )
	{
		printf("Install service fail (%d), %s %s %d!"
			, GetLastError(), __FILE__, __FUNCTION__, __LINE__);
		return;
	}

	// Get a handle to the SCM database. 

	schSCManager = OpenSCManager( 
		NULL,                    // local computer
		NULL,                    // ServicesActive database 
		SC_MANAGER_ALL_ACCESS);  // full access rights 

	if (NULL == schSCManager) 
	{
		printf("Failed to open service manager (%d), %s %s %d!"
			, GetLastError(), __FILE__, __FUNCTION__, __LINE__);
		return;
	}

	// Create the service

	schService = CreateService( 
		schSCManager,              // SCM database 
		SVCNAME,                   // name of service 
		SVCNAME,                   // service name to display 
		SERVICE_ALL_ACCESS,        // desired access 
		SERVICE_WIN32_OWN_PROCESS, // service type 
		SERVICE_AUTO_START,      // start type 
		SERVICE_ERROR_NORMAL,      // error control type 
		szPath,                    // path to service's binary 
		NULL,                      // no load ordering group 
		NULL,                      // no tag identifier 
		NULL,                      // no dependencies 
		NULL,                      // LocalSystem account 
		NULL);                     // no password 

	if (schService == NULL) 
	{
		printf("Failed to create service (%d), %s %s %d!"
			, GetLastError(), __FILE__, __FUNCTION__, __LINE__);
		CloseServiceHandle(schSCManager);
		return;
	}
	else{
		printf("%s\n",SVCDESC);
	}
	CloseServiceHandle(schService); 
	CloseServiceHandle(schSCManager);
}

VOID WINSVC::DoDeleteSvc()
{
	SC_HANDLE schSCManager;
	SC_HANDLE schService;
	//SERVICE_STATUS ssStatus; 

	// Get a handle to the SCM database. 

	schSCManager = OpenSCManager( 
		NULL,                    // local computer
		NULL,                    // ServicesActive database 
		SC_MANAGER_ALL_ACCESS);  // full access rights 

	if (NULL == schSCManager) 
	{
		printf("Failed to open service manager (%d), %s %s %d!"
			, GetLastError(), __FILE__, __FUNCTION__, __LINE__);
		return;
	}

	// Get a handle to the service.

	schService = OpenService( 
		schSCManager,       // SCM database 
		SVCNAME,          // name of service 
		DELETE);            // need delete access 

	if (schService == NULL)
	{ 
		printf("Failed to get service (%d), %s %s %d!"
			, GetLastError(), __FILE__, __FUNCTION__, __LINE__);
		CloseServiceHandle(schSCManager);
		return;
	}

	// Delete the service.

	if (! DeleteService(schService) ) 
	{
		printf("Failed to delete service (%d), %s %s %d!"
			, GetLastError(), __FILE__, __FUNCTION__, __LINE__);
	}
	else{ 
		printf("Service successfully removed !\n");//log输出不及时造成无显示
		printf("Service successfully removed !"); 
	}
	CloseServiceHandle(schService); 
	CloseServiceHandle(schSCManager);
}

VOID WINSVC::SvcQuery(char *svr, int &svc_state)
{
	SC_HANDLE schSCManager;
	SC_HANDLE schService;
	// Get a handle to the SCM database. 
	schSCManager = OpenSCManager(
		NULL,                    // local computer
		NULL,                    // ServicesActive database 
		SC_MANAGER_ALL_ACCESS);  // full access rights 

	if (NULL == schSCManager)
	{
		printf("Failed to open service manager (%d), %s %s %d!"
			, GetLastError(), __FILE__, __FUNCTION__, __LINE__);
		return;
	}
	// Get a handle to the service.
	schService = OpenService(
		schSCManager,       // SCM database 
		svr,				// name of service 
		SERVICE_QUERY_STATUS);     // need query access 

	if (schService == NULL)
	{
		printf("Failed to get service(%s) and error(%d), %s %s %d!"
			, svr, GetLastError(), __FILE__, __FUNCTION__, __LINE__);
		CloseServiceHandle(schSCManager);
		return;
	}
	//调用QueryServiceStatus函数
	SERVICE_STATUS sStatus = { 0 };
	if (!QueryServiceStatus(schService, &sStatus))
	{
		CloseServiceHandle(schService);
		CloseServiceHandle(schSCManager);
		return;
	}
	switch (sStatus.dwCurrentState)
	{
	case SERVICE_STOP_PENDING: case SERVICE_STOPPED:
		svc_state = 1;
		break;
	case SERVICE_START_PENDING: case SERVICE_RUNNING: case SERVICE_CONTINUE_PENDING:
		svc_state = 2;
		break;
	case SERVICE_PAUSE_PENDING: case SERVICE_PAUSED:
		svc_state = 3;
		break;
	default:
		svc_state = 0;
		break;
	}
	CloseServiceHandle(schService);
	CloseServiceHandle(schSCManager);
};

VOID WINSVC::SvcStart(char *svr)
{
	SC_HANDLE schSCManager;
	SC_HANDLE schService;
	// Get a handle to the SCM database. 
	schSCManager = OpenSCManager(
		NULL,                    // local computer
		NULL,                    // ServicesActive database 
		SC_MANAGER_ALL_ACCESS);  // full access rights 

	if (NULL == schSCManager)
	{
		printf("Failed to open service manager (%d), %s %s %d!"
			, GetLastError(), __FILE__, __FUNCTION__, __LINE__);
		return;
	}
	// Get a handle to the service.
	schService = OpenService(
		schSCManager,       // SCM database 
		svr,				// name of service 
		SERVICE_START | SERVICE_QUERY_STATUS);     // need start and query access 

	if (schService == NULL)
	{
		printf("Failed to get service(%s) and error(%d), %s %s %d!"
			, svr,GetLastError(), __FILE__, __FUNCTION__, __LINE__);
		CloseServiceHandle(schSCManager);
		return;
	}
	StartService(schService, 0, NULL);//开始Service
	//调用QueryServiceStatus函数
	SERVICE_STATUS sStatus = { 0 };
	if (!QueryServiceStatus(schService, &sStatus))
	{
		CloseServiceHandle(schService);
		CloseServiceHandle(schSCManager);
		return;
	}
	if (SERVICE_RUNNING == sStatus.dwCurrentState || SERVICE_START_PENDING == sStatus.dwCurrentState)
	{
		printf("start service(%s) success, %s %s %d!"
			, svr, __FILE__, __FUNCTION__, __LINE__);
	}
	CloseServiceHandle(schService);
	CloseServiceHandle(schSCManager);
};

VOID WINSVC::SvcStop(char *svr)
{
	SC_HANDLE schSCManager;
	SC_HANDLE schService;
	// Get a handle to the SCM database. 
	schSCManager = OpenSCManager(
		NULL,                    // local computer
		NULL,                    // ServicesActive database 
		SC_MANAGER_ALL_ACCESS);  // full access rights 

	if (NULL == schSCManager)
	{
		printf("Failed to open service manager(%d), %s %s %d!"
			, GetLastError(), __FILE__, __FUNCTION__, __LINE__);
		return;
	}
	// Get a handle to the service.
	schService = OpenService(
		schSCManager,       // SCM database 
		svr,				// name of service 
		SERVICE_STOP | SERVICE_QUERY_STATUS);            // need stop or query access 

	if (schService == NULL)
	{
		printf("Failed to get service(%s) and error(%d), %s %s %d!"
			, svr,GetLastError(), __FILE__, __FUNCTION__, __LINE__);
		CloseServiceHandle(schSCManager);
		return;
	}
	//调用QueryServiceStatus函数
	SERVICE_STATUS sStatus = { 0 };
	if (!QueryServiceStatus(schService, &sStatus))
	{
		CloseServiceHandle(schService);
		CloseServiceHandle(schSCManager);
		return;
	}
	if (SERVICE_RUNNING == sStatus.dwCurrentState || SERVICE_PAUSED == sStatus.dwCurrentState)
	{
		ControlService(schService, SERVICE_CONTROL_STOP, &sStatus);
	}
	if (!QueryServiceStatus(schService, &sStatus))
	{
		CloseServiceHandle(schService);
		CloseServiceHandle(schSCManager);
		return;
	}
	if (SERVICE_STOPPED == sStatus.dwCurrentState || SERVICE_STOP_PENDING == sStatus.dwCurrentState)
	{
		printf("stop service(%s) success, %s %s %d!"
			, svr, __FILE__, __FUNCTION__, __LINE__);
	}
	CloseServiceHandle(schService);
	CloseServiceHandle(schSCManager);
};

VOID WINSVC::SvcRestart(char *svr)
{
	SC_HANDLE schSCManager;
	SC_HANDLE schService;
	// Get a handle to the SCM database. 
	schSCManager = OpenSCManager(
		NULL,                    // local computer
		NULL,                    // ServicesActive database 
		SC_MANAGER_ALL_ACCESS);  // full access rights 

	if (NULL == schSCManager)
	{
		printf("Failed to open service manager and error(%d), %s %s %d!"
			, GetLastError(), __FILE__, __FUNCTION__, __LINE__);
		return;
	}
	// Get a handle to the service.
	schService = OpenService(
		schSCManager,       // SCM database 
		svr,				// name of service 
		SERVICE_START | SERVICE_STOP | SERVICE_QUERY_STATUS);     // need pause and query access 

	if (schService == NULL)
	{
		printf("Failed to get service(%s) and error(%d), %s %s %d!"
			, svr,GetLastError(), __FILE__, __FUNCTION__, __LINE__);
		CloseServiceHandle(schSCManager);
		return;
	}
	//调用QueryServiceStatus函数
	SERVICE_STATUS sStatus = { 0 };
	if (!QueryServiceStatus(schService, &sStatus))
	{
		CloseServiceHandle(schService);
		CloseServiceHandle(schSCManager);
		return;
	}
	if (SERVICE_RUNNING == sStatus.dwCurrentState)
	{
		ControlService(schService, SERVICE_CONTROL_STOP, &sStatus);
	}
	if (!QueryServiceStatus(schService, &sStatus))
	{
		CloseServiceHandle(schService);
		CloseServiceHandle(schSCManager);
		return;
	}
	if (SERVICE_STOPPED == sStatus.dwCurrentState || SERVICE_STOP_PENDING == sStatus.dwCurrentState)
	{
		printf("stop service (%s) success, %s %s %d!"
			, svr, __FILE__, __FUNCTION__, __LINE__);
		StartService(schService, 0, NULL);//开始Service
	}
	if (!QueryServiceStatus(schService, &sStatus))
	{
		CloseServiceHandle(schService);
		CloseServiceHandle(schSCManager);
		return;
	}
	if (SERVICE_RUNNING == sStatus.dwCurrentState || SERVICE_START_PENDING == sStatus.dwCurrentState)
	{
		printf("restart service (%s) success, %s %s %d!"
			, svr, __FILE__, __FUNCTION__, __LINE__);
	}
	CloseServiceHandle(schService);
	CloseServiceHandle(schSCManager);
};
//

VOID WINAPI WINSVC::SvcMain( DWORD dwArgc, LPTSTR *lpszArgv )
{
	// Register the handler function for the service

	gSvcStatusHandle = RegisterServiceCtrlHandler( 
		SVCNAME, 
		SvcCtrlHandler);

	if( !gSvcStatusHandle )
	{ 
		SvcReportEvent(TEXT("RegisterServiceCtrlHandler")); 
		return; 
	} 

	// These SERVICE_STATUS members remain as set here

	gSvcStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS; 
	gSvcStatus.dwServiceSpecificExitCode = 0;    

	// Report initial status to the SCM

	ReportSvcStatus( SERVICE_START_PENDING, NO_ERROR, 3000 );

	// Perform service-specific initialization and work.

	SvcInit( dwArgc, lpszArgv );
}

//

VOID WINSVC::SvcInit( DWORD dwArgc, LPTSTR *lpszArgv)
{
	// TO_DO: Declare and set any required variables.
	//   Be sure to periodically call ReportSvcStatus() with 
	//   SERVICE_START_PENDING. If initialization fails, call
	//   ReportSvcStatus with SERVICE_STOPPED.

	// Create an event. The control handler function, SvcCtrlHandler,
	// signals this event when it receives the stop control code.

	TCHAR szPath[MAX_PATH];
	GetModuleFileName( NULL, szPath, MAX_PATH );
	TCHAR drive[MAX_PATH],dir[MAX_PATH],fname[MAX_PATH],ext[MAX_PATH];
	_tsplitpath_s( szPath,drive,dir,fname,ext );
	strcpy_s( szPath, drive );
	strcat_s( szPath, dir );
	SetCurrentDirectory( szPath );

	ghSvcStopEvent = CreateEvent(
		NULL,    // default security attributes
		TRUE,    // manual reset event
		FALSE,   // not signaled
		NULL);   // no name

	if ( ghSvcStopEvent == NULL)
	{
		ReportSvcStatus( SERVICE_STOPPED, NO_ERROR, 0 );
		return;
	}

	m_HThread = CreateThread( (LPSECURITY_ATTRIBUTES)NULL, 0, (LPTHREAD_START_ROUTINE)MainThread,	0, 0,   &m_dwThreadID);
	if( m_HThread != NULL )
	{
		g_bRun = TRUE;
	}
	// Report running status when initialization is complete.
	ReportSvcStatus( SERVICE_RUNNING, NO_ERROR, 0 );
	bool bWriteFlag = false;
	while(1)
	{
		// Check whether to stop the service.

		WaitForSingleObject(ghSvcStopEvent, INFINITE);
		if(!bWriteFlag)
		{
			printf("Service normal stop: %s %s %d"
			, __FILE__, __FUNCTION__, __LINE__);
			bWriteFlag = true;
		}
		g_bRun = FALSE;
		ReportSvcStatus( SERVICE_STOPPED, NO_ERROR, 0 );
	}
}

//
VOID WINSVC::ReportSvcStatus( DWORD dwCurrentState,
	DWORD dwWin32ExitCode,
	DWORD dwWaitHint)
{
	static DWORD dwCheckPoint = 1;

	// Fill in the SERVICE_STATUS structure.

	gSvcStatus.dwCurrentState = dwCurrentState;
	gSvcStatus.dwWin32ExitCode = dwWin32ExitCode;
	gSvcStatus.dwWaitHint = dwWaitHint;

	if (dwCurrentState == SERVICE_START_PENDING)
		gSvcStatus.dwControlsAccepted = 0;
	else gSvcStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP;

	if ( (dwCurrentState == SERVICE_RUNNING) ||
		(dwCurrentState == SERVICE_STOPPED) )
		gSvcStatus.dwCheckPoint = 0;
	else gSvcStatus.dwCheckPoint = dwCheckPoint++;

	// Report the status of the service to the SCM.
	SetServiceStatus( gSvcStatusHandle, &gSvcStatus );
}

//
VOID WINAPI WINSVC::SvcCtrlHandler( DWORD dwCtrl )
{
	// Handle the requested control code. 

	switch(dwCtrl) 
	{  
	case SERVICE_CONTROL_STOP: 
		ReportSvcStatus(SERVICE_STOP_PENDING, NO_ERROR, 0);

		// Signal the service to stop.

		SetEvent(ghSvcStopEvent);
		ReportSvcStatus(gSvcStatus.dwCurrentState, NO_ERROR, 0);

		return;

	case SERVICE_CONTROL_INTERROGATE: 
		break; 

	default: 
		break;
	} 

}

//
VOID WINSVC::SvcReportEvent(LPTSTR szFunction) 
{ 
	HANDLE hEventSource;
	LPCTSTR lpszStrings[2];
	TCHAR Buffer[80];

	hEventSource = RegisterEventSource(NULL, SVCNAME);

	if( NULL != hEventSource )
	{
		StringCchPrintf(Buffer, 80, TEXT("%s failed with %d, %s %s %d"), szFunction
			, GetLastError(), __FILE__, __FUNCTION__, __LINE__);

		lpszStrings[0] = SVCNAME;
		lpszStrings[1] = Buffer;

		ReportEvent(hEventSource,        // event log handle
			EVENTLOG_ERROR_TYPE, // event type
			0,                   // event category
			0,           		 // event identifier
			NULL,                // no security identifier
			2,                   // size of lpszStrings array
			0,                   // no binary data
			lpszStrings,         // array of strings
			NULL);               // no binary data

		DeregisterEventSource(hEventSource);
	}
}

VOID CALLBACK WINSVC::MainThread(HWND hwnd,UINT uMsg,UINT_PTR idEvent,DWORD dwTime)
{	
	int argc = 0;
	char * pArgv = NULL;
	MyMain(argc, &pArgv);
	printf("Main thread quit successfully");
}
#endif

?main.cpp

//
#include <stdio.h>
#include <stdlib.h>
#include <string>

#ifdef __linux__
#include <memory>
#endif // __linux__

#include "appexitio.h"

namespace GlobalVar {
	bool exitFlag = false;
};

#ifdef WIN32
#include <windows.h>
//server conf
char SVCNAME[128] = "pyfreeMgr";
char SVCDESC[256] =
"\r\n pyfree Technology Ltd \r\n "
"https://www.pyfree.com \r\n "
"email:pyxxx@163.com \r\n "
"pyfree-xxx system service \r\n "
"Service installation success";
#endif // WIN32
//linux服务名依赖于安装脚本配置,详情参考demo下的脚本
#ifdef WIN32
int MyMain(int argc, char* argv[])
#else
int main(int argc, char *argv[])
#endif
{
#ifdef WIN32
	if (!SetConsoleCtrlHandler((PHANDLER_ROUTINE)ctrlhandler, true))
	{
		printf("install signal handler error!");
	}
#else
	SignalHandler * g_exit_handler = NULL;
	g_exit_handler = new SignalHandler();
	if(!g_exit_handler){
		printf("install signal handler error!");
	}
#endif // WIN32
	//这里做真正业务处理
	//...
	
	//
	while (!GlobalVar::exitFlag)
	{
		sleep(10);
	}
	//
	return 0;
}

三、编译及测试

本文编译采用win:cmake+Visual Studio,Linux:cmake+gcc

CMakeLists.txt

# CMake 最低版本号要求
cmake_minimum_required (VERSION 2.8)
# 项目信息
project (server_test)
#
if(WIN32)
    message(STATUS "windows compiling...")
    add_definitions(-D_PLATFORM_IS_WINDOWS_)
	set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /MT")
  set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /MTd")
  set(WIN_OS true)
else(WIN32)
    message(STATUS "linux compiling...")
    add_definitions( -D_PLATFORM_IS_LINUX_)
    add_definitions("-Wno-invalid-source-encoding")
	  # add_definitions("-O2")
    set(UNIX_OS true)
    set(_DEBUG true)
    
endif(WIN32)

#
set(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin)

# 指定源文件的目录,并将名称保存到变量
SET(source_h
    #
	${PROJECT_SOURCE_DIR}/include/appexitio.h
	${PROJECT_SOURCE_DIR}/include/svc.h
  )
  
SET(source_cpp
    #
	${PROJECT_SOURCE_DIR}/svc/appexitio.cpp
	${PROJECT_SOURCE_DIR}/src/main.cpp
  )
  
#头文件目录
include_directories(${PROJECT_SOURCE_DIR}/include)

if (${UNIX_OS})

#execute_process(COMMAND ${PROJECT_SOURCE_DIR}/build.sh)
SET(source_cpp_linux
  ${PROJECT_SOURCE_DIR}/svc/linuxSVC.cpp
)

add_definitions(
  "-W"
  "-fPIC"
  "-Wall"
  # "-Wall -g"
  "-Werror"
  "-Wshadow"
  "-Wformat"
  "-Wpointer-arith"
  "-D_REENTRANT"
  "-D_USE_FAST_MACRO"
  "-Wno-long-long"
  "-Wuninitialized"
  "-D_POSIX_PTHREAD_SEMANTICS"
  "-DACL_PREPARE_COMPILE"
  "-Wno-unused-parameter"
  "-fexceptions"
  )
  set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -O0")

link_directories()
# 指定生成目标
add_executable(server_test ${source_h} ${source_cpp} ${source_cpp_linux})
#link
target_link_libraries(server_test 
  -lpthread -pthread -lz -lrt -ldl
)

endif(${UNIX_OS})

if (${WIN_OS})

SET(source_cpp_win
  ${PROJECT_SOURCE_DIR}/svc/WinSVC.cpp
)

set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /wd4819")

add_definitions(
  "-D_CRT_SECURE_NO_WARNINGS"
  "-D_WINSOCK_DEPRECATED_NO_WARNINGS"
  "-DNO_WARN_MBCS_MFC_DEPRECATION"
  "-DWIN32_LEAN_AND_MEAN"
)

link_directories()

if (CMAKE_BUILD_TYPE STREQUAL "Debug")

set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_DEBUG ${PROJECT_SOURCE_DIR}/bin)
# 指定生成目标
add_executable(server_testd ${source_h} ${source_cpp} ${source_cpp_win})

#target_link_libraries(server_testd *.lib)

else(CMAKE_BUILD_TYPE)

set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE ${PROJECT_SOURCE_DIR}/bin)
# 指定生成目标
add_executable(server_test ${source_h} ${source_cpp} ${source_cpp_win})

#target_link_libraries(server_test *.lib)

endif (CMAKE_BUILD_TYPE)

endif(${WIN_OS})

3.1 win下编译及实现:

【1】cmake编译,进入svr_test\build_win编译:

cmake -G "Visual Studio 10 2010 Win64" -DCMAKE_BUILD_TYPE=Release ..

msbuild server_test.sln /p:Configuration="Release" /p:Platform="x64"

Visual Studio不同版本写法如下:

  Visual Studio 15 2017 [arch] = Generates Visual Studio 2017 project files.
                                 Optional [arch] can be "Win64" or "ARM".
  Visual Studio 14 2015 [arch] = Generates Visual Studio 2015 project files.
                                 Optional [arch] can be "Win64" or "ARM".
  Visual Studio 12 2013 [arch] = Generates Visual Studio 2013 project files.
                                 Optional [arch] can be "Win64" or "ARM".
  Visual Studio 11 2012 [arch] = Generates Visual Studio 2012 project files.
                                 Optional [arch] can be "Win64" or "ARM".
  Visual Studio 10 2010 [arch] = Generates Visual Studio 2010 project files.
                                 Optional [arch] can be "Win64" or "IA64".
  Visual Studio 9 2008 [arch]  = Generates Visual Studio 2008 project files.
                                 Optional [arch] can be "Win64" or "IA64".
  Visual Studio 8 2005 [arch]  = Generates Visual Studio 2005 project files.
注:超出列表版本请自行查阅资料

本文编译release版本详细过程如下:

d:\workForMy\workspace\svr_test\build_win>cmake -G "Visual Studio 10 2010 Win64" -DCMAKE_BUILD_TYPE=Release ..
-- windows compiling...
CMake Warning (dev) in CMakeLists.txt:
  A logical block opening on the line

    D:/workForMy/workspace/svr_test/CMakeLists.txt:95 (if)

  closes on the line

    D:/workForMy/workspace/svr_test/CMakeLists.txt:111 (endif)

  with mis-matching arguments.
This warning is for project developers.  Use -Wno-dev to suppress it.

-- Configuring done
-- Generating done
-- Build files have been written to: D:/workForMy/workspace/svr_test/build_win

d:\workForMy\workspace\svr_test\build_win>msbuild server_test.sln /p:Configuration="Release" /p:Platform="x64"
Microsoft(R) 生成引擎版本 4.8.4084.0
[Microsoft .NET Framework 版本 4.0.30319.42000]
版权所有 (C) Microsoft Corporation。保留所有权利。

在此解决方案中一次生成一个项目。若要启用并行生成,请添加“/m”开关。
生成启动时间为 2022/9/1 13:47:15。
节点 1 上的项目“d:\workForMy\workspace\svr_test\build_win\server_test.sln”(默认目标)。
ValidateSolutionConfiguration:
  正在生成解决方案配置“Release|x64”。
ValidateProjects:
  在解决方案配置“Release|x64”中未选定生成项目“ALL_BUILD”。
项目“d:\workForMy\workspace\svr_test\build_win\server_test.sln”(1)正在节点 1 上生成“d:\workForMy\workspace\svr_test\build_win\ZERO_CHECK.vcxproj”(2) (默认目标)。
InitializeBuildStatus:
  正在创建“x64\Release\ZERO_CHECK\ZERO_CHECK.unsuccessfulbuild”,因为已指定“AlwaysCreate”。
CustomBuild:
  所有输出均为最新。
FinalizeBuildStatus:
  正在删除文件“x64\Release\ZERO_CHECK\ZERO_CHECK.unsuccessfulbuild”。
  正在对“x64\Release\ZERO_CHECK\ZERO_CHECK.lastbuildstate”执行 Touch 任务。
已完成生成项目“d:\workForMy\workspace\svr_test\build_win\ZERO_CHECK.vcxproj”(默认目标)的操作。

项目“d:\workForMy\workspace\svr_test\build_win\server_test.sln”(1)正在节点 1 上生成“d:\workForMy\workspace\svr_test\build_win\server_test.vcxproj.metaproj”(3) (默认目标)。
项目“d:\workForMy\workspace\svr_test\build_win\server_test.vcxproj.metaproj”(3)正在节点 1 上生成“d:\workForMy\workspace\svr_test\build_win\server_test.vcxproj”(4) (默认目标)。
InitializeBuildStatus:
  正在创建“server_test.dir\Release\server_test.unsuccessfulbuild”,因为已指定“AlwaysCreate”。
CustomBuild:
  所有输出均为最新。
ClCompile:
  所有输出均为最新。
Link:
  所有输出均为最新。
  d:\Program Files (x86)\Microsoft Visual Studio 10.0\VC\bin\x86_amd64\link.exe /ERRORREPORT:QUEUE /OUT:"D:\workForMy\workspace\svr_test\bin\server_test.exe" /INCREMENTAL:NO /NOLOGO /LIBPA
  TH:"D:\workForSoftware\Visual Leak Detector\lib\Win64" kernel32.lib user32.lib gdi32.lib winspool.lib shell32.lib ole32.lib oleaut32.lib uuid.lib comdlg32.lib advapi32.lib /MANIFEST /Man
  ifestFile:"server_test.dir\Release\server_test.exe.intermediate.manifest" /MANIFESTUAC:"level='asInvoker' uiAccess='false'" /PDB:"D:/workForMy/workspace/svr_test/bin/server_test.pdb" /SU
  BSYSTEM:CONSOLE /TLBID:1 /DYNAMICBASE /NXCOMPAT /IMPLIB:"D:/workForMy/workspace/svr_test/bin/Release/server_test.lib" /MACHINE:X64 server_test.dir\Release\appexitio.obj
  server_test.dir\Release\main.obj
  server_test.dir\Release\WinSVC.obj   /machine:x64
  server_test.vcxproj -> D:\workForMy\workspace\svr_test\bin\server_test.exe
Manifest:
  C:\Program Files (x86)\Microsoft SDKs\Windows\v7.0A\bin\mt.exe /nologo /verbose /outputresource:"D:\workForMy\workspace\svr_test\bin\server_test.exe;#1" /manifest server_test.dir\Release
  \server_test.exe.intermediate.manifest
FinalizeBuildStatus:
  正在删除文件“server_test.dir\Release\server_test.unsuccessfulbuild”。
  正在对“server_test.dir\Release\server_test.lastbuildstate”执行 Touch 任务。
已完成生成项目“d:\workForMy\workspace\svr_test\build_win\server_test.vcxproj”(默认目标)的操作。

已完成生成项目“d:\workForMy\workspace\svr_test\build_win\server_test.vcxproj.metaproj”(默认目标)的操作。

已完成生成项目“d:\workForMy\workspace\svr_test\build_win\server_test.sln”(默认目标)的操作。


已成功生成。
    0 个警告
    0 个错误

已用时间 00:00:01.07

d:\workForMy\workspace\svr_test\build_win>

win安装编译服务及测试:

进入D:\workForMy\workspace\svr_test\bin

本文安装及卸载服务示例:
D:\workForMy\workspace\svr_test\bin>server_test.exe install

 pyfree Technology Ltd
 https://www.pyfree.com
 email:pyxxx@163.com
 pyfree-xxx system service
 Service installation success

D:\workForMy\workspace\svr_test\bin>server_test.exe uninstall
Service successfully removed !
Service successfully removed !
D:\workForMy\workspace\svr_test\bin>

?安装成功示例:

?3.2 Linux下编译及实现

【1】本文是在centos7编译的,进入svr_test/build_linux,具体编译过程如下:

cmake ..  
make  
[***@pyfree build_linux]$ make
Scanning dependencies of target server_test
[ 25%] Building CXX object CMakeFiles/server_test.dir/svc/linuxSVC.cpp.o
[ 50%] Linking CXX executable ../bin/server_test
[100%] Built target server_test
[***@pyfree build_linux]$ cmake ..
-- The C compiler identification is GNU 4.8.5
-- The CXX compiler identification is GNU 4.8.5
-- Check for working C compiler: /usr/bin/cc
-- Check for working C compiler: /usr/bin/cc -- works
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Detecting C compile features
-- Detecting C compile features - done
-- Check for working CXX compiler: /usr/bin/c++
-- Check for working CXX compiler: /usr/bin/c++ -- works
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- linux compiling...
-- Configuring done
-- Generating done
-- Build files have been written to: /mnt/hgfs/workForMy/workspace/svr_test/build_linux
[***@pyfree build_linux]$ make
Scanning dependencies of target server_test
[ 25%] Building CXX object CMakeFiles/server_test.dir/svc/appexitio.cpp.o
[ 50%] Building CXX object CMakeFiles/server_test.dir/src/main.cpp.o
[ 75%] Building CXX object CMakeFiles/server_test.dir/svc/linuxSVC.cpp.o
[100%] Linking CXX executable ../bin/server_test
[100%] Built target server_test
[***@pyfree build_linux]$ 

【2】服务安全及部署:

前面说过,在Linux下,所谓的内部服务启动就是将进程文件加载到特性文件目录并配置好这些文件,系统在启动过程中会自动加载启动,并linux有自己的服务管理器,就像win下的任务管理器一样,差别在于win提供界面工具,linux采用命令。

我们提供两个脚本实现本进程的服务安装及卸载:

install_linux.sh

#!/bin/bash
#@pyfree 2022-08-26,pyXXX@163.com
#创建程序目录
#run for root
#定义程序全局路径变量
#程序根目录
dir='/usr/local/sysmgr'
curdir=$(pwd)

if [ ! -d $dir ]; then
    mkdir -p $dir
fi
#
echo "appdir="$dir
echo "curdir="$curdir
#复制程序文件到程序目录
/bin/cp -f server_test $dir
#
ls $dir
#
if [ ! -f "$dir/sysmgrStart.sh" ];then
#创建程序启动脚本sysmgr
cat > $dir/sysmgrStart.sh << eof
#!/bin/bash
pid=\`ps -ef | grep "server_test*" | grep -v "grep" | wc -l\`
if [ \$pid -eq 0 ];then
    cd $dir
	echo "The sysmgr server will be start"
    nohup ./server_test & > /dev/null
else
    echo "The sysmgr server is alreadly running"
fi 
eof
fi

if [ ! -f "$dir/sysmgrStop.sh" ];then
#创建程序关闭脚本sysmgr
cat > $dir/sysmgrStop.sh << eof
#!/bin/bash
pid=\`ps -ef | grep "server_test*" | grep -v "grep" | wc -l\`
if [ \$pid -ne 0 ];then
	echo "The sysmgr server will be stop"
    killall -9 server_test
else
	echo "The sysmgr server is stoping, don't stop it"
fi 
eof
fi

#给服务启动脚本添加执行权限
chmod o+x $dir/{sysmgrStart.sh,sysmgrStop.sh}

if [ ! -f "/usr/lib/systemd/system/pyfreeMgr.service" ];then
#创建程序启动文件到centos7服务启动路径/usr/lib/systemd/system
cat > /usr/lib/systemd/system/pyfreeMgr.service <<eof
[Unit]
Description=server_test
After=syslog.target network.target remote-fs.target nss-lookup.target
[Service]
Type=forking
ExecStart=$dir/sysmgrStart.sh
ExecReload=
ExecStop=$dir/sysmgrStop.sh
PrivateTmp=true
[Install]
WantedBy=multi-user.target
eof
fi

cd $curdir

#设置开机启动gather服务
systemctl daemon-reload
chmod o-x /usr/lib/systemd/system/pyfreeMgr.service
systemctl enable pyfreeMgr.service
#查看服务是否开机启动:
#systemctl is-enabled pyfreeMgr.service
#设置开机时禁用服务
#systemctl disable pyfreeMgr.service
#启动服务
#systemctl start pyfreeMgr.service

#停止服务
#systemctl stop pyfreeMgr.service

uninstall_linux.sh

#!/bin/bash
#@pyfree 2022-08-26,pyXXX@163.com
#创建程序目录
#run for root
#定义程序全局路径变量
#程序根目录
dir='/usr/local/sysmgr'

systemctl stop pyfreeMgr.service
systemctl disable pyfreeMgr.service
rm -f /usr/lib/systemd/system/pyfreeMgr.service

rm -rf $dir

脚本运行过程如下:

[root@pyfree bin]# ./install_linux.sh 
appdir=/usr/local/sysmgr
curdir=/mnt/hgfs/workForMy/workspace/svr_test/bin
server_test  sysmgrStart.sh  sysmgrStop.sh
[root@pyfree bin]# systemctl is-enabled pyfreeMgr.service
enabled
[root@pyfree bin]# systemctl status pyfreeMgr.service
● pyfreeMgr.service - server_test
   Loaded: loaded (/usr/lib/systemd/system/pyfreeMgr.service; enabled; vendor preset: disabled)
   Active: inactive (dead)
[root@pyfree bin]# ./uinstall_linux.sh 
Removed symlink /etc/systemd/system/multi-user.target.wants/pyfreeMgr.service.
[root@pyfree bin]# 

四、服务管理函数使用说明

在svc.h中提供里四个服务管理函数,本服务程序可以通过这些函式实现对其他应有函数的启停、查询状态等管理,前提是自身服务具有相关权限。大家可以在main.cpp的业务应用代码段测试实现对系统内其他程序进行管理。

  系统运维 最新文章
配置小型公司网络WLAN基本业务(AC通过三层
如何在交付运维过程中建立风险底线意识,提
快速传输大文件,怎么通过网络传大文件给对
从游戏服务端角度分析移动同步(状态同步)
MySQL使用MyCat实现分库分表
如何用DWDM射频光纤技术实现200公里外的站点
国内顺畅下载k8s.gcr.io的镜像
自动化测试appium
ctfshow ssrf
Linux操作系统学习之实用指令(Centos7/8均
上一篇文章      下一篇文章      查看所有文章
加:2022-09-04 01:48:04  更:2022-09-04 01:52:05 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年12日历 -2024/12/28 19:12:51-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码
数据统计