Windows 服务被设计用于需要在后台运行的应用程序以及实现没有用户交互的任务。本文将建立并实现一个简单的服务程序,其功能是建立一个UDP服务器,接收客户端发送的信息,写入一个文本文件。
第一步:主函数和全局定义
函数声明:
现在,准备工作已经就绪,你可以开始编码了。服务程序控制台程序的一个子集。因此,开始你可以定义一个 main 函数,它是程序的入口点。对于服务程序来说,main 的代码令人惊讶地简短,因为它只创建分派表并启动控制分派机。
一个程序可能包含若干个服务。每一个服务都必须列于专门的分派表中(为此该程序定义了一个 ServiceTable 结构数组)。这个表中的每一项都要在 SERVICE_TABLE_ENTRY 结构之中。它有两个域:
lpServiceName: 指向表示服务名称字符串的指针;当定义了多个服务时,那么这个域必须指定;
lpServiceProc: 指向服务主函数的指针(服务入口点);
分派表的最后一项必须是服务名和服务主函数域的 NULL 指针,文本例子程序中只宿主一个服务,所以服务名的定义是可选的。
服务控制管理器(SCM:Services Control Manager)是一个管理系统所有服务的进程。当 SCM 启动某个服务时,它等待某个进程的主线程来调用 StartServiceCtrlDispatcher 函数。将分派表传递给 StartServiceCtrlDispatcher。这将把调用进程的主线程转换为控制分派器。该分派器启动一个新线程,该线程运行分派表中每个服务的 ServiceMain 函数(本文例子中只有一个服务)分派器还监视程序中所有服务的执行情况。然后分派器将控制请求从 SCM 传给服务。
注意:如果 StartServiceCtrlDispatcher 函数30秒没有被调用,便会报错,为了避免这种情况,他们必须在 ServiceMain 函数中(参见本文例子)或在非主函数的单独线程中初始化服务分派表。本文所描述的服务不需要防范这样的情况。
分派表中所有的服务执行完之后(例如,用户通过“服务”控制面板程序停止它们),或者发生错误时。StartServiceCtrlDispatcher 调用返回。然后主进程终止。
第二步:ServiceMain 函数
第五步:安装和配置服务
程序编好了,将之编译成 exe 文件。为了在机器上安装这个服务,需要用 SC.EXE 可执行文件,它是系统自带一个工具,可以直接在命令行中使用。使用这个实用工具可以安装和移除服务,启动、停止、设置等。以下是用命令行安装服务的方法:
指定 “delete” 选项和服务名。此服务将被标记为删除,下次系统重启后,该服务将被完全移除
第一步:主函数和全局定义
首先,包含所需的头文件。
#include<winsock2.h>
#include <windows.h>
#include <stdio.h>
//Socket编程
#pragma comment(lib,"WS2_32.lib")
接着,定义一个常量:
#define LOGFILE "C:\\UDP.txt"
//UDP.text 定义日志文件的路径,你将会用 WriteToLog 函数将内存查询的结果输出到该文件,WriteToLog 函数定义如下:
int WriteToLog(char* str)
{
FILE* log;
log = fopen(LOGFILE, "a+");
if (log == NULL)
return -1;
fprintf(log, "%s\n", str);
fclose(log);
return 0;
}
声明几个全局变量,以便在程序的多个函数之间共享它们值。
SERVICE_STATUS ServiceStatus;
SERVICE_STATUS_HANDLE hStatus;
//Socket变量
SOCKET s;
sockaddr_in addr,addr2;
函数声明:
int WriteToLog(char* );
int UDPServer();
void ServiceMain(int argc, char** argv);
void ControlHandler(DWORD request);
现在,准备工作已经就绪,你可以开始编码了。服务程序控制台程序的一个子集。因此,开始你可以定义一个 main 函数,它是程序的入口点。对于服务程序来说,main 的代码令人惊讶地简短,因为它只创建分派表并启动控制分派机。
int main()
{
SERVICE_TABLE_ENTRY ServiceTable[2];
ServiceTable[0].lpServiceName = (LPWSTR)"MemoryStatus";
ServiceTable[0].lpServiceProc = (LPSERVICE_MAIN_FUNCTION)ServiceMain;
ServiceTable[1].lpServiceName = NULL;
ServiceTable[1].lpServiceProc = NULL;
WSADATA data;
WORD w = MAKEWORD(2,0);
WSAStartup(w,&data);
s = socket(AF_INET,SOCK_DGRAM,0);
addr.sin_family = AF_INET;
addr.sin_port = htons(1002);
addr.sin_addr.S_un.S_addr = INADDR_ANY;
bind(s,(sockaddr *)&addr,sizeof(addr));
//启动服务的控制分派机线程
StartServiceCtrlDispatcher(ServiceTable);
return 0;
}
一个程序可能包含若干个服务。每一个服务都必须列于专门的分派表中(为此该程序定义了一个 ServiceTable 结构数组)。这个表中的每一项都要在 SERVICE_TABLE_ENTRY 结构之中。它有两个域:
lpServiceName: 指向表示服务名称字符串的指针;当定义了多个服务时,那么这个域必须指定;
lpServiceProc: 指向服务主函数的指针(服务入口点);
分派表的最后一项必须是服务名和服务主函数域的 NULL 指针,文本例子程序中只宿主一个服务,所以服务名的定义是可选的。
服务控制管理器(SCM:Services Control Manager)是一个管理系统所有服务的进程。当 SCM 启动某个服务时,它等待某个进程的主线程来调用 StartServiceCtrlDispatcher 函数。将分派表传递给 StartServiceCtrlDispatcher。这将把调用进程的主线程转换为控制分派器。该分派器启动一个新线程,该线程运行分派表中每个服务的 ServiceMain 函数(本文例子中只有一个服务)分派器还监视程序中所有服务的执行情况。然后分派器将控制请求从 SCM 传给服务。
注意:如果 StartServiceCtrlDispatcher 函数30秒没有被调用,便会报错,为了避免这种情况,他们必须在 ServiceMain 函数中(参见本文例子)或在非主函数的单独线程中初始化服务分派表。本文所描述的服务不需要防范这样的情况。
分派表中所有的服务执行完之后(例如,用户通过“服务”控制面板程序停止它们),或者发生错误时。StartServiceCtrlDispatcher 调用返回。然后主进程终止。
第二步:ServiceMain 函数
void ServiceMain(int argc, char** argv)
{
ServiceStatus.dwServiceType = SERVICE_WIN32;
ServiceStatus.dwCurrentState = SERVICE_START_PENDING;
ServiceStatus.dwControlsAccepted = SERVICE_ACCEPT_PAUSE_CONTINUE | SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN;
ServiceStatus.dwWin32ExitCode = 0;
ServiceStatus.dwServiceSpecificExitCode = 0;
ServiceStatus.dwCheckPoint = 0;
ServiceStatus.dwWaitHint = 0;
hStatus = RegisterServiceCtrlHandler(
(LPWSTR)"MemoryStatus",
(LPHANDLER_FUNCTION)ControlHandler);
if (hStatus == (SERVICE_STATUS_HANDLE)0)
{
return;
}
ServiceStatus.dwCurrentState = SERVICE_RUNNING;
SetServiceStatus (hStatus, &ServiceStatus);
while (ServiceStatus.dwCurrentState == SERVICE_RUNNING)
{
int result = UDPServer();
if (result)
{
ServiceStatus.dwCurrentState = SERVICE_STOPPED;
ServiceStatus.dwWin32ExitCode = -1;
SetServiceStatus(hStatus, &ServiceStatus);
return;
}
Sleep(SLEEP_TIME);
}
return;
}
第三步:ControlHandler函数
void ControlHandler(DWORD request)
{
switch(request)
{
case SERVICE_CONTROL_STOP:
WriteToLog("Monitoring stopped.");
ServiceStatus.dwWin32ExitCode = 0;
ServiceStatus.dwCurrentState = SERVICE_STOPPED;
SetServiceStatus (hStatus, &ServiceStatus);
return;
case SERVICE_CONTROL_SHUTDOWN:
WriteToLog("Monitoring stopped.");
ServiceStatus.dwWin32ExitCode = 0;
ServiceStatus.dwCurrentState = SERVICE_STOPPED;
SetServiceStatus (hStatus, &ServiceStatus);
return;
default:
break;
}
SetServiceStatus (hStatus, &ServiceStatus);
return;
}
第四步:建立自己的startFunc()函数----愿意写点什么就写点什么吧。:)
我们在这里实现UDP的服务器端的编写,与客户端通讯。
int UDPServer()
{
int n = sizeof(addr2);
char buff[100];
//memset(buff,0,100);
char sztext[] = "已收到数据一切正常!\n";
int len = recvfrom(s,buff,strlen(buff),0,(sockaddr *)&addr2,&n);
if( len > 0)
{
buff[len] = '\0';
WriteToLog(buff);
//返回客户端
sendto(s,sztext,strlen(sztext),0,(sockaddr *)&addr2,n);
}
return 0;
}
第五步:安装和配置服务
程序编好了,将之编译成 exe 文件。为了在机器上安装这个服务,需要用 SC.EXE 可执行文件,它是系统自带一个工具,可以直接在命令行中使用。使用这个实用工具可以安装和移除服务,启动、停止、设置等。以下是用命令行安装服务的方法:
sc create [service name] [binPath= ] <option1> <option2>...
CREATE OPTIONS:
NOTE: The option name includes the equal sign.
type= <own|share|interact|kernel|filesys|rec>
(default = own)
start= <boot|system|auto|demand|disabled>
(default = demand)
error= <normal|severe|critical|ignore>
(default = normal)
binPath= <BinaryPathName>
group= <LoadOrderGroup>
tag= <yes|no>
depend= <Dependencies(separated by / (forward slash))>
obj= <AccountName|ObjectName>
(default = LocalSystem)
DisplayName= <display name>
password= <password>
发出此创建命令。指定服务名和二进制文件的路径(注意 binpath= 和路径之间的那个空格)。安装成功后,便可以用服务控制面板来控制这个服务(参见图一)。用控制面板的工具栏启动和终止这个服务。
指定 “delete” 选项和服务名。此服务将被标记为删除,下次系统重启后,该服务将被完全移除
sc <server> delete [service name]
完整代码如下:
#include<winsock2.h>
#include <windows.h>
#include <stdio.h>
#pragma comment(lib,"WS2_32.lib")
#define LOGFILE "C:\\UDP.txt"
int WriteToLog(char* );
int UDPServer();
SERVICE_STATUS ServiceStatus;
SERVICE_STATUS_HANDLE hStatus;
SOCKET s;
sockaddr_in addr,addr2;
void ServiceMain(int argc, char** argv);
void ControlHandler(DWORD request);
int main()
{
SERVICE_TABLE_ENTRY ServiceTable[2];
ServiceTable[0].lpServiceName = (LPWSTR)"MemoryStatus";
ServiceTable[0].lpServiceProc = (LPSERVICE_MAIN_FUNCTION)ServiceMain;
ServiceTable[1].lpServiceName = NULL;
ServiceTable[1].lpServiceProc = NULL;
WSADATA data;
WORD w = MAKEWORD(2,0);
WSAStartup(w,&data);
s = socket(AF_INET,SOCK_DGRAM,0);
addr.sin_family = AF_INET;
addr.sin_port = htons(1002);
addr.sin_addr.S_un.S_addr = INADDR_ANY;
bind(s,(sockaddr *)&addr,sizeof(addr));
StartServiceCtrlDispatcher(ServiceTable);
return 0;
}
void ServiceMain(int argc, char** argv)
{
ServiceStatus.dwServiceType = SERVICE_WIN32;
ServiceStatus.dwCurrentState = SERVICE_START_PENDING;
ServiceStatus.dwControlsAccepted = SERVICE_ACCEPT_PAUSE_CONTINUE | SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN;
ServiceStatus.dwWin32ExitCode = 0;
ServiceStatus.dwServiceSpecificExitCode = 0;
ServiceStatus.dwCheckPoint = 0;
ServiceStatus.dwWaitHint = 0;
hStatus = RegisterServiceCtrlHandler(
(LPWSTR)"MemoryStatus",
(LPHANDLER_FUNCTION)ControlHandler);
if (hStatus == (SERVICE_STATUS_HANDLE)0)
{
return;
}
ServiceStatus.dwCurrentState = SERVICE_RUNNING;
SetServiceStatus (hStatus, &ServiceStatus);
while (ServiceStatus.dwCurrentState == SERVICE_RUNNING)
{
int result = UDPServer();
if (result)
{
ServiceStatus.dwCurrentState = SERVICE_STOPPED;
ServiceStatus.dwWin32ExitCode = -1;
SetServiceStatus(hStatus, &ServiceStatus);
return;
}
}
return;
}
void ControlHandler(DWORD request)
{
switch(request)
{
case SERVICE_CONTROL_STOP:
WriteToLog("Monitoring stopped.");
ServiceStatus.dwWin32ExitCode = 0;
ServiceStatus.dwCurrentState = SERVICE_STOPPED;
SetServiceStatus (hStatus, &ServiceStatus);
return;
case SERVICE_CONTROL_SHUTDOWN:
WriteToLog("Monitoring stopped.");
ServiceStatus.dwWin32ExitCode = 0;
ServiceStatus.dwCurrentState = SERVICE_STOPPED;
SetServiceStatus (hStatus, &ServiceStatus);
return;
default:
break;
}
SetServiceStatus (hStatus, &ServiceStatus);
return;
}
int UDPServer()
{
int n = sizeof(addr2);
char buff[100];
char sztext[] = "已收到数据一切正常!\n";
int len = recvfrom(s,buff,strlen(buff),0,(sockaddr *)&addr2,&n);
if( len > 0)
{
buff[len] = '\0';
WriteToLog(buff);
sendto(s,sztext,strlen(sztext),0,(sockaddr *)&addr2,n);
}
return 0;
}
int WriteToLog(char* str)
{
FILE* log;
log = fopen(LOGFILE, "a+");
if (log == NULL)
return -1;
fprintf(log, "%s\n", str);
fclose(log);
return 0;
}