专注于互联网--专注于架构

最新标签
网站地图
文章索引
Rss订阅

首页 »C 教程 » vc编写windows服务:Windows服务编写原理及探讨(4) »正文

vc编写windows服务:Windows服务编写原理及探讨(4)

来源: 发布时间:星期四, 2009年2月12日 浏览:132次 评论:0


( 4)些问题讨论

  前面几章内容都是服务些通用编写原理但里面隐含着些问题编写简单服务时看不出来但遇到复杂应用就会出现些问题所以本章就是用来分析、解决这些问题适用于高级应用开发人员我这内容都是经过实验得到很有实际意义

  我在第章里面就说过是由个服务主线程执行CtrlHandler它将收到各种控制命令但是真正处理命令执行操作是ServiceMain线程现在个SERVICE_CONTROL_STOP到达的后你作为个开发者要怎样停止这个服务?在我看过些源代码里大部分只是简单TerminateThread去强行杀掉服务进程但应该稍稍有点线程编程常识就应该知道TerminateThread是可用中最为糟糕服务线程将得不到任何机会去做应该清理工作诸如清除内存、释放核心对象Dlls也得不到任何线程已经被毁通知

  所以停止服务适当思路方法是以某种方式激活服务线程让它停止继续提供服务功能然后执行完当前操作和清除工作后返回这就表示你必须在CtrlHandler线程和ServiceMain线程的间执行适当线程通信现在已知最好内部线程通信机制是I/O Completion Port(I/O 完成端口)假如你编写个大型服务需要同时处理为数众多请求并且运行在多处理器系统上面这个模型就可以提供最佳系统性能但也正复杂性较高在小规模应用上面不值得花费很多时间和精力这时作为开发者可以适当选取其它通信方式诸如异步过程队列、套接字和窗口消息以适应实际情况

  开发服务时另外个重要问题就是SetServiceStatus所有状态报告问题很多服务开发者为了在什么时候SetServiceStatus问题而常常产生争论般推荐思路方法就是:先SetServiceStatus报告SERVICE_STOP_PENDING状态然后将控制代码传给服务线程或者再建立个新线程让它去继续执行操作当该线程即将执行完操作的前再由它将服务状态设置成SERVICE_STOPPED然后服务正好停止

  上面主意从两个方面来讲还是很不错首先服务可以立即确认收到了控制代码并将在它认为适当时候进行处理;然后就是前面说过执行CtrlHandler是主线程如果按照这种工作思路方法CtrlHandler可以迅速返回不会影响到其它服务可能收到控制请求对含有多个服务来说响应各个服务控制代码速度会大大提高可是随的而来是问题—— race condition 即“竞争条件”产生

  摆在下面就是个竞争条件例子我花了点时间来修改我基本服务代码意图故意引发“竞争条件”发生我添加了个线程CtrlHandler线程在收到请求后立刻作出反应将当前服务状态设置成“请求正在被处理”即..._PENDING然后由我添加线程在睡眠了5秒的后再将服务状态设置成“请求已完成”状态——以模拟服务正在处理些不可中止事件只有处理完成后才会更改服务状态切就绪的后我尝试在短时间内连续发送两个“暂停”请求如果“竞争条件”不存在话应该只有先发送那个请求能够到达SCM而另个则应该返回请求发送失败信息天下太平

  事实上很不幸我成功了当我在两个区别“命令提示符”窗口分别同样输入下面命令:

net pause kservice

  的后在“事件查看器”里面我找到了我服务在“应用日志”里添加事件记录结果是我得到了这样事件列表:

SERVICE_PAUSE_PENDING
SERVICE_PAUSE_PENDING
SERVICE_PAUSED
SERVICE_PAUSED

  看上去很奇怪是不是?服务处于正在暂停状态时候它不应该被再次暂停但事实摆在眼前很多服务都曾明确报告过上面顺序状态我曾经认为这时SCM应该说些什么或做些什么以阻止“竞争状态”出现但实验结果告诉我SCM似乎对此无能为力它不能控制状态代码在什么时候被发送当用户使用“管理工具”里面“服务”工具来管理服务状态时候个“暂停”请求已经发出的后不能再次用这个工具向它发出“暂停”请求如果正在暂停服务会有个对话框出现阻止你按下它后面“服务”工具工具栏上任何按钮如果已经暂停“暂停“按钮将变成灰色但是这时用命令行工具 net.exe 就可以很顺利地将暂停请求再次送到服务证据就是我添加其他事件记录里面记下了SetServiceStatus全都成功了这更进介绍说明了我提交两个暂停请求都经过SCM然后到达了我服务

  接下来我又进行了其它测试例如先发送“暂停”请求后发送“停止”请求和先发送“停止”请求再发送“暂停”或“停止”请求种情况更加糟糕先发送“暂停”请求和后发送“停止”请求都没有得到什么好下场虽然SCM老老实实先暂停了服务后停止了服务但 net.exe 两个例子均告失败不过在测试先发送停止“请求”时候所有现象都表示这两个请求只有先发送“停止”到达了SCM这还算是个好消息...

  为了解决这个问题当服务得到个“停止”“暂停”或“继续”请求时候应该首先检查服务是否已经在处理另外个请求如果是就依情况而定:是不SetServiceStatus直接返回还是暂时忍耐直到前个请求动作完成再SetServiceStatus这是你作为个开发者要自己决定

  如果说前面问题已经足够麻烦了下面问题会令你觉得更加怪异它其实是种可以解决上面问题思路方法:当CtrlHandler线程收到SERVICE_PAUSE_PENDING请求的后SetServiceStatus报告服务正在暂停然后由它自己SuspendThread来暂停服务线程然后再由它自己SetServiceStatus报告服务已经被暂停这样做确避免了“竞争条件”出现所有工作都是由来做现在需要注意不是“竞争条件”而是服务本身挂起服务线程会不会暂停服务呢?答案是会但是暂停服务意味着什么呢?

  假如我服务是用来处理网络客户请求那么暂停对于我服务来说应该是停止接受新请求如果我现在正处在处理请求过程中那么我应该如何办?也许我应该结束它使客户不至于无限期悬挂但如果我只是简单SuspendThread那么不排除服务线程正处于孤立中间状态可能或者正在malloc去尝试分配内存如果运行在同个进程中个服务也调内存分配那么它也会被挂起这肯定不是我期望结果



  还有个问题:用户认为自己可以被允许去停止个已经被暂停了服务吗?我认为是这样而且很明显微软也这么认为当我们在“服务”管理工具里面选中个已暂停服务的后“停止”按钮是可以被按下但我要怎样停止个由于线程被挂起才处于暂停状态服务呢?不不要TerminateThread请别跟我提起它

  解决这所有混乱最好思路方法就是有个能够把所有事做好线程而且它应该是服务线程而不是CtrlHandler线程当CtrlHandler得到控制代码的后它要迅速将控制代码通过线程内部通讯手段送到服务线程中排队然后CtrlHandler就应该返回它决不应该调SetServiceStatus这样服务可以随心所欲控制每件事情没有什么比它更有发言权没有“竞争条件”服务决定暂停意味着什么服务能够允许自己在已经暂停情况下停止服务决定什么内部通讯机制是最好——并且CtrlHandler必须简单和这种机制相

  事情没有完美上面思路方法也不例外它仅有个小缺陷:就是假定当服务收到控制代码后在较短时间内就能做出应有响应如果服务线程正在忙于处理个客户请求控制代码可能进入等待队列而且SetServiceStatus可能也无法迅速如果真是这样负责发送通知SCP可能会认为你服务已经失败并向用户报告个消息框事实上服务并没有失败而且也不会被终止

  这种情况够糟糕了没有用户会去责怪SCP——虽然SCP将他们引导到了状态他们只会责怪服务作者——就是我或你...因此在服务中如何做才能防止这种问题发生呢?很简单使服务快速有效运行并且总保持个活动线程等待去处理控制代码

  说起来好像很容易但实际做起来就被那么简单了这也不是我能够向各位解释只有认真调试自己服务才能找出最为适合处理思路方法所以我文章也真到了该结束时候了感谢各位浏览如果我有什么地方说不对请不吝赐教谢谢

  下面是我写个服务源代码没什么功能只能启动、停止和安装

# <windows.h>
# <stdio.h>
# <stdlib.h>
# <tchar.h>


# SZAPPNAME \"basicservice\"
# SZSERVICENAME \"KService\"
# SZSERVICEDISPLAYNAME \"KService\"
# SZDEPENDENCIES \"\"

void WINAPI KServiceMain(DWORD argc, LPTSTR * argv);
void InstallService(const char * szServiceName);
void LogEvent(LPCTSTR pFormat, ...);
void Start;
void Stop;


SERVICE_STATUS ssStatus;
SERVICE_STATUS_HANDLE sshStatusHandle;


( argc, char * argv)
{
((argc2) && (::strcmp(argv[1]+1, \"\")0))
{
InstallService(\"KService\");
0;
}

SERVICE_TABLE_ENTRYservice_table_entry =
{
{ \"KService\", KServiceMain },
{ NULL, NULL }
};
::StartServiceCtrlDispatcher(service_table_entry);
0;
}

void InstallService(const char * szServiceName)
{
SC_HANDLE handle = ::OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
char szFilename[256];
::GetModuleFileName(NULL, szFilename, 255);
SC_HANDLE hService = ::CreateService(handle, szServiceName,
szServiceName, SERVICE_ALL_ACCESS, SERVICE_WIN32_OWN_PROCESS,
SERVICE_DEMAND_START, SERVICE_ERROR_IGNORE, szFilename, NULL,
NULL, NULL, NULL, NULL);
::CloseServiceHandle(hService);
::CloseServiceHandle(handle);
}

SERVICE_STATUS servicestatus;
SERVICE_STATUS_HANDLE servicestatushandle;

void WINAPI ServiceCtrlHandler(DWORD dwControl)
{
switch (dwControl)
{

//下面虽然添加了暂停、继续等请求处理代码但没有实际作用
//这是为什么呢?到了下面KServiceMain里面就明白了...

SERVICE_CONTROL_PAUSE:
servicestatus.dwCurrentState = SERVICE_PAUSE_PENDING;
// TODO: add code to dwCheckPo & dwWaitH
// This value need to try a lot to confirm
// ...
::SetServiceStatus(servicestatushandle, &servicestatus);
// TODO: add code to pause the service
// not called in this service
// ...
servicestatus.dwCurrentState = SERVICE_PAUSED;
// TODO: add code to dwCheckPo & dwWaitH to 0
;

SERVICE_CONTROL_CONTINUE:
servicestatus.dwCurrentState = SERVICE_CONTINUE_PENDING;
// TODO: add code to dwCheckPo & dwWaitH
::SetServiceStatus(servicestatushandle, &servicestatus);
// TODO: add code to unpause the service
// not called in this service
// ...
servicestatus.dwCurrentState = SERVICE_RUNNING;
// TODO: add code to dwCheckPo & dwWaitH to 0


;

SERVICE_CONTROL_STOP:
servicestatus.dwCurrentState = SERVICE_STOP_PENDING;
// TODO: add code to dwCheckPo & dwWaitH
::SetServiceStatus(servicestatushandle, &servicestatus);
// TODO: add code to stop the service
Stop;
servicestatus.dwCurrentState = SERVICE_STOPPED;
// TODO: add code to dwCheckPo & dwWaitH to 0
;

SERVICE_CONTROL_SHUTDOWN:
// TODO: add code for system shutdown
// as quick as possible
;

SERVICE_CONTROL_INTERROGATE:
// TODO: add code to the service status
// ...
servicestatus.dwCurrentState = SERVICE_RUNNING;
;
}
::SetServiceStatus(servicestatushandle, &servicestatus);
}

void WINAPI KServiceMain(DWORD argc, LPTSTR * argv)
{
servicestatus.dwServiceType = SERVICE_WIN32;
servicestatus.dwCurrentState = SERVICE_START_PENDING;
servicestatus.dwControlsAccepted = SERVICE_ACCEPT_STOP;//上面问题答案就在这里
servicestatus.dwWin32ExitCode = 0;
servicestatus.dwServiceSpecicExitCode = 0;
servicestatus.dwCheckPo = 0;
servicestatus.dwWaitH = 0;

servicestatushandle =
::RegisterServiceCtrlHandler(\"KService\", ServiceCtrlHandler);
(servicestatushandle (SERVICE_STATUS_HANDLE)0)
{
;
}

bool bInitialized = false;
// Initialize the service
// ...
Start;

bInitialized = true;

servicestatus.dwCheckPo = 0;
servicestatus.dwWaitH = 0;
(!bInitialized)
{
servicestatus.dwCurrentState = SERVICE_STOPPED;
servicestatus.dwWin32ExitCode = ERROR_SERVICE_SPECIFIC_ERROR;
servicestatus.dwServiceSpecicExitCode = 1;
}

{
servicestatus.dwCurrentState = SERVICE_RUNNING;
}
::SetServiceStatus(servicestatushandle, &servicestatus);
;
}


void Start
{
LogEvent(\"Service Starting...\");
}

void LogEvent(LPCTSTR pFormat, ...)
{
TCHAR chMsg[256];
HANDLE hEventSource;
LPTSTR lpszStrings[1];
va_list pArg;

va_start(pArg, pFormat);
_vstprf(chMsg, pFormat, pArg);
va_end(pArg);

lpszStrings[0] = chMsg;

(1)
{
// Get a handle to use with ReportEvent.
hEventSource = RegisterEventSource(NULL, \"KService\");
(hEventSource != NULL)
{
// Write to event log.
ReportEvent(hEventSource, EVENTLOG_INFORMATION_TYPE, 0, 0, NULL, 1, 0, (LPCTSTR*) &lpszStrings[0], NULL);
DeregisterEventSource(hEventSource);
}
}

{
// As we are not running as a service, just write the error to the console.
_putts(chMsg);
}
}

void Stop
{
LogEvent(\"Service Stoped.\");
}


0

相关文章

读者评论

发表评论

  • 昵称:
  • 内容: