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

莉仙滴甜

莉仙滴甜

2016-02-19 13:03

最近很多朋友喜欢上设计,但是大家却不知道如何去做,别担心有图老师给你解答,史上最全最棒的详细解说让你一看就懂。
(四)一些问题的讨论
  
    前面几章的内容都是服务的一些通用的编写原理,但里面隐含着一些问题,编写简单的服务时看不出来,但碰到复杂的应用就会出现一些问题,所以本章就是用来分析、解决这些问题的,适用于高级应用的开发人员。我这一章的内容都是经过实验得到的,很有实际意义。
  
    我在第一章里面就说过,是由一个服务的主线程执行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将他们引导到了错误的状态,他们只会责怪服务的作者——就是我或你...因此,在服务中怎么做才能防止这种问题发生呢?很简单,使服务快速有效的运行,并且总保持一个活动线程等待去处理控制代码。
  
    说起来似乎很轻易,但实际做起来就被那么简单了,这也不是我能够向各位解释的了,只有认真的调试自己的服务,才能找出最为适合处理方法。所以我的文章也真的到了该结束的时候了,感谢各位的浏览。假如我有什么地方说的不对,请不吝赐教,谢谢。
  
    下面是我写的一个服务的源代码,没什么功能,只能启动、停止和安装。
  
  #include windows.h
  #include stdio.h
  #include stdlib.h
  #include tchar.h
  
  
  #define SZAPPNAME "basicservice"
  #define SZSERVICENAME "KService"
  #define SZSERVICEDISPLAYNAME "KService"
  #define 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;
  
  
  int
展开更多 50%)
分享

猜你喜欢

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

编程语言 网络编程
Windows服务编写原理及探讨(4)

C编写Windows服务程序的五个步骤

编程语言 网络编程
C编写Windows服务程序的五个步骤

s8lol主宰符文怎么配

英雄联盟 网络游戏
s8lol主宰符文怎么配

VC编写有图形界面的Windows服务程序

编程语言 网络编程
VC编写有图形界面的Windows服务程序

深入详解C编写Windows服务程序的五个步骤

编程语言 网络编程
深入详解C编写Windows服务程序的五个步骤

lol偷钱流符文搭配推荐

英雄联盟 网络游戏
lol偷钱流符文搭配推荐

关于自定滤镜的原理和探讨

PS PS教程
关于自定滤镜的原理和探讨

拒绝服务攻击原理及解决方法(2)

电脑网络
拒绝服务攻击原理及解决方法(2)

lolAD刺客新符文搭配推荐

英雄联盟
lolAD刺客新符文搭配推荐

Java入门及faq__1(4)

Java入门及faq__1(4)

win7系统开始的搜索框消失了怎么办

win7系统开始的搜索框消失了怎么办
下拉加载更多内容 ↓