国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 學(xué)院 > 開發(fā)設(shè)計 > 正文

Windows服務(wù)編寫原理及探討(4)

2019-11-17 05:37:04
字體:
供稿:網(wǎng)友

  (四)一些問題的討論

  前面幾章的內(nèi)容都是服務(wù)的一些通用的編寫原理,但里面隱含著一些問題,編寫簡單的服務(wù)時看不出來,但碰到復(fù)雜的應(yīng)用就會出現(xiàn)一些問題,所以本章就是用來分析、解決這些問題的,適用于高級應(yīng)用的開發(fā)人員。我這一章的內(nèi)容都是經(jīng)過實驗得到的,很有實際意義。

  我在第一章里面就說過,是由一個服務(wù)的主線程執(zhí)行CtrlHandler函數(shù),它將收到各種控制命令,但是真正處理命令,執(zhí)行操作的是ServiceMain的線程。現(xiàn)在,當(dāng)一個SERVICE_CONTROL_STOP到達(dá)之后,你作為一個開發(fā)者,要怎樣停止這個服務(wù)?在我看過的一些源代碼里,大部分只是簡單的調(diào)用TerminateThread函數(shù)去強(qiáng)行殺掉服務(wù)進(jìn)程。但應(yīng)該稍稍有點線程編程的常識就應(yīng)該知道TerminateThread函數(shù)是可用的調(diào)用中最為糟糕的一個,服務(wù)線程將得不到任何機(jī)會去做應(yīng)該的清理工作,諸如清除內(nèi)存、釋放核心對象,Dlls也得不到任何線程已經(jīng)被毀的通知。

  所以停止服務(wù)的適當(dāng)方法是以某種方式激活服務(wù)線程,讓它停止繼續(xù)提供服務(wù)功能,然后執(zhí)行完當(dāng)前操作和清除工作后返回。這就表示你必須在CtrlHandler線程和ServiceMain線程之間執(zhí)行適當(dāng)?shù)木€程通信。現(xiàn)在已知的最好的內(nèi)部線程通信機(jī)制是I/O Completion Port(I/O 完成端口),假如你編寫的是一個大型的服務(wù),需要同時處理為數(shù)眾多的請求,并且運行在多處理器系統(tǒng)上面,這個模型就可以提供最佳的系統(tǒng)性能。但也正因為它的復(fù)雜性較高,在小規(guī)模的應(yīng)用上面不值得花費很多的時間和精力,這時作為開發(fā)者可以適當(dāng)?shù)倪x取其它的通信方式,諸如異步過程調(diào)用隊列、套接字和窗口消息,以適應(yīng)實際情況。

  開發(fā)服務(wù)時的另外一個重要問題就是調(diào)用SetServiceStatus函數(shù)時的所有狀態(tài)報告問題。很多的服務(wù)開發(fā)者為了在什么時候調(diào)用SetServiceStatus的問題而經(jīng)常產(chǎn)生爭論,一般推薦的方法就是:先調(diào)用SetServiceStatus函數(shù),報告SERVICE_STOP_PENDING狀態(tài),然后將控制代碼傳給服務(wù)線程或者再建立一個新的線程,讓它去繼續(xù)執(zhí)行操作,當(dāng)該線程即將執(zhí)行完操作之前,再由它將服務(wù)的狀態(tài)設(shè)置成SERVICE_STOPPED,然后服務(wù)正好停止。

  上面的主意從兩個方面來講還是很不錯的。首先服務(wù)可以立即確認(rèn)收到了控制代碼,并將在它認(rèn)為適當(dāng)?shù)臅r候進(jìn)行處理;然后就是因為前面說過的,執(zhí)行CtrlHandler函數(shù)的是主線程,假如按照這種工作方法,CtrlHandler函數(shù)可以迅速的返回,不會影響到其它服務(wù)可能收到的控制請求,對含有多個服務(wù)的程序來說,響應(yīng)各個服務(wù)的控制代碼的速度會大大的提高。可是,隨之而來的是問題—— race condition 即“競爭條件”的產(chǎn)生。

  擺在下面的就是一個競爭條件的例子,我花了一點時間來修改我的基本服務(wù)的代碼,意圖故意引發(fā)“競爭條件”的發(fā)生。我添加了一個線程,CtrlHandler函數(shù)的線程在收到請求后馬上作出反應(yīng),將當(dāng)前的服務(wù)狀態(tài)設(shè)置成“請求正在被處理”即..._PENDING,然后由我添加的線程在睡眠了5秒之后再將服務(wù)狀態(tài)設(shè)置成“請求已完成”狀態(tài)——以模擬服務(wù)正在處理一些不可中止的事件,只有處理完成后才會更改服務(wù)的狀態(tài)。一切就緒之后,我嘗試在短時間內(nèi)連續(xù)發(fā)送兩個“暫停”請求,假如“競爭條件”不存在的話應(yīng)該只有先發(fā)送的那個請求能夠到達(dá)SCM,而另一個則應(yīng)該返回請求發(fā)送失敗的信息,天下太平。

  事實上很不幸的,我成功了。當(dāng)我在兩個不同的“命令提示符”窗口分別同樣的輸入下面的命令:

net pause kservice

  之后在“事件查看器”里面,我找到了我的服務(wù)在“應(yīng)用程序日志”里添加的事件記錄,結(jié)果是我得到了這樣的事件列表:

SERVICE_PAUSE_PENDING
SERVICE_PAUSE_PENDING
SERVICE_PAUSED
SERVICE_PAUSED

  看上去很希奇是不是?因為服務(wù)處于正在暫停狀態(tài)的時候,它不應(yīng)該被再次暫停的。但事實擺在眼前,很多服務(wù)都曾明確的報告過上面的順序狀態(tài)。我曾經(jīng)認(rèn)為這時SCM應(yīng)該說些什么或做些什么,以阻止“競爭狀態(tài)”的出現(xiàn),但實驗結(jié)果告訴我SCM似乎對此無能為力,因為它不能控制狀態(tài)代碼在什么時候被發(fā)送。當(dāng)用戶使用“治理工具”里面的“服務(wù)”工具來治理服務(wù)的狀態(tài)的時候,在一個“暫停”請求已經(jīng)發(fā)出之后不能再次用這個工具向它發(fā)出“暫停”請求,假如正在暫停服務(wù),會有一個對話框出現(xiàn),阻止你按下它后面的“服務(wù)”工具的工具欄上的任何按鈕,假如已經(jīng)暫停,“暫停“按鈕將變成灰色。但是這時用命令行工具 net.exe 就可以很順利地將暫停請求再次送到服務(wù)。證據(jù)就是我添加的其他事件記錄里面記下了SetServiceStatus的調(diào)用全都成功了,這更進(jìn)一步的說明了我提交的兩個暫停請求都經(jīng)過SCM,然后到達(dá)了我的服務(wù)。

  接下來我又進(jìn)行了其它的測試,例如先發(fā)送“暫停”請求,后發(fā)送“停止”請求,和先發(fā)送“停止”請求,再發(fā)送“暫停”或“停止”請求。前一種情況更加糟糕,先發(fā)送的“暫停”請求和后發(fā)送的“停止”請求都沒有得到什么好下場,雖然SCM老老實實的先暫停了服務(wù),后停止了服務(wù),但 net.exe 的兩個實例的調(diào)用均告失敗。不過在測試先發(fā)送停止“請求”的時候,所有的現(xiàn)象都表示這兩個請求只有先發(fā)送的“停止”到達(dá)了SCM,這還算是個好消息...

  為了解決這個問題,當(dāng)服務(wù)得到一個“停止”“暫停”或“繼續(xù)”請求的時候,應(yīng)該首先檢查服務(wù)是否已經(jīng)在處理另外的一個請求,假如是,就依情況而定:是不調(diào)用SetServiceStatus直接返回還是暫時忍耐直到前一個請求動作完成再調(diào)用SetServiceStatus,這是你作為一個開發(fā)者要自己決定的。


  假如說前面的問題已經(jīng)足夠麻煩了,下面的問題會令你覺得更加怪異。它其實是一種可以解決上面的問題的方法:當(dāng)CtrlHandler函數(shù)的線程收到SERVICE_PAUSE_PENDING請求之后,它調(diào)用SetServiceStatus報告服務(wù)正在暫停,然后由它自己調(diào)用SuspendThread來暫停服務(wù)的線程,然后再由它自己調(diào)用SetServiceStatus報告服務(wù)已經(jīng)被暫停。這樣做的確避免了“競爭條件”的出現(xiàn),因為所有的工作都是由一個函數(shù)來做的。現(xiàn)在需要注重的不是“競爭條件”而是服務(wù)本身,掛起服務(wù)的線程會不會暫停服務(wù)呢?答案是會的。但是暫停服務(wù)意味著什么呢?

  假如我的服務(wù)是用來處理網(wǎng)絡(luò)客戶的請求,那么暫停對于我的服務(wù)來說應(yīng)該是停止接受新的請求。假如我現(xiàn)在正處在處理請求的過程中,那么我應(yīng)該怎么辦?也許我應(yīng)該結(jié)束它,使客戶不至于無限期懸掛。但假如我只是簡單的調(diào)用SuspendThread,那么不排除服務(wù)線程正處于孤立的中間狀態(tài)的可能,或者正在調(diào)用malloc函數(shù)去嘗試分配內(nèi)存,假如運行在同一個進(jìn)程中的另一個服務(wù)也調(diào)內(nèi)存分配函數(shù),那么它也會被掛起,這肯定不是我期望的結(jié)果。

  還有一個問題:用戶認(rèn)為自己可以被答應(yīng)去停止一個已經(jīng)被暫停了的服務(wù)嗎?我認(rèn)為是這樣的,而且很明顯的,微軟也這么認(rèn)為。因為當(dāng)我們在“服務(wù)”治理工具里面選中一個已暫停的服務(wù)之后,“停止”按鈕是可以被按下的。但我要怎樣停止一個由于線程被掛起才處于暫停狀態(tài)的服務(wù)呢?不,不要TerminateThread,請別跟我提起它。

  解決這所有的混亂的最好方法,就是有一個能夠把所有事做好的線程,而且它應(yīng)該是服務(wù)線程,而不是CtrlHandler線程。當(dāng)CtrlHandler函數(shù)得到控制代碼之后,它要迅速的將控制代碼通過線程內(nèi)部通訊手段送到服務(wù)線程中排隊,然后CtrlHandler函數(shù)就應(yīng)該返回,它決不應(yīng)該調(diào)SetServiceStatus。這樣,服務(wù)可以隨心所欲的控制每件事情,因為沒有什么比它更有發(fā)言權(quán)的了,沒有“競爭條件”。服務(wù)決定暫停意味著什么,服務(wù)能夠答應(yīng)自己在已經(jīng)暫停的情況下停止,服務(wù)決定什么內(nèi)部通訊機(jī)制是最好的——并且CtrlHandler函數(shù)必須簡單的與這種機(jī)制相一致。

  事情沒有完美的,上面的方法也不例外,它僅有一個小缺陷:就是假定當(dāng)服務(wù)收到控制代碼后,在較短的時間內(nèi)就能做出應(yīng)有的響應(yīng)。假如服務(wù)線程正在忙于處理一個客戶的請求,控制代碼可能進(jìn)入等待隊列,而且SetServiceStatus可能也無法迅速的被調(diào)用。假如真是這樣的話,負(fù)責(zé)發(fā)送通知的SCP可能會認(rèn)為你的服務(wù)已經(jīng)失敗,并向用戶報告一個消息框。事實上服務(wù)并沒有失敗,而且也不會被終止。

  這種情況夠糟糕了,沒有用戶會去責(zé)怪SCP——雖然SCP將他們引導(dǎo)到了錯誤的狀態(tài),他們只會責(zé)怪服務(wù)的作者——就是我或你...因此,在服務(wù)中怎么做才能防止這種問題發(fā)生呢?很簡單,使服務(wù)快速有效的運行,并且總保持一個活動線程等待去處理控制代碼。

  說起來似乎很輕易,但實際做起來就被那么簡單了,這也不是我能夠向各位解釋的了,只有認(rèn)真的調(diào)試自己的服務(wù),才能找出最為適合處理方法。所以我的文章也真的到了該結(jié)束的時候了,感謝各位的瀏覽。假如我有什么地方說的不對,請不吝賜教,謝謝。

  下面是我寫的一個服務(wù)的源代碼,沒什么功能,只能啟動、停止和安裝。

#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

發(fā)表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發(fā)表
主站蜘蛛池模板: 枝江市| 丹寨县| 万全县| 黎川县| 奉新县| 乌拉特后旗| 龙口市| 昌宁县| 宕昌县| 突泉县| 迁西县| 射洪县| 克山县| 竹山县| 天气| 张北县| 安国市| 中江县| 安乡县| 武城县| 丹东市| 柞水县| 汉寿县| 北辰区| 博爱县| 白银市| 安化县| 县级市| 巨鹿县| 林西县| 大丰市| 马山县| 玉环县| 平遥县| 怀来县| 临湘市| 孟村| 贺州市| 普安县| 奉贤区| 独山县|