迷途書童

          敏感、勤學、多思
          隨筆 - 77, 文章 - 4, 評論 - 86, 引用 - 0
          數據加載中……

          如何使用Java編寫NT服務

          內容簡介:本文通過例子講解了如何利用Java的特性快速編寫安全可靠的NT服務,并展示了Java的多線程如何實施,以及如何應用套接字實現網絡服務。

          關鍵詞:Java??JntSvc.exe?NT服務?多線程?套接字編程

          ?

          一、NT服務介紹

          所謂NT服務,實際上是一類特殊的應用程序所謂NT服務,實際上就是一個可以在系統啟動時自動在一定身份下啟動的伴隨系統長時間存在的進程。象FTP?server、HTTP?server、脫機打印等都是采用NT服務的形式提供的。這實際上類似Unix的root?daemon進程。NT服務歸納起來,NT服務又以下幾個特征:

          1、可以自啟動,不需要交互啟動。這對于服務器來說是一個重要的特征。當然,你可以決定服務是否自啟動,甚至可以屏蔽某個服務。
          ????2、NT服務沒有用戶界面,基本上類似一個DOS?程序,因為NT服務必須長時間運行,所以不想普通win32進程一樣有自己的界面。但是NT服務可以同用戶有界面交互,這是一類特殊的服務進程。可以通過NT的任務管理器來看到服務進程。
          ????3、NT服務通過SCM(Services?Control?Manager)接口來管理,安裝、啟動、停止、撤除等都需要SCM的接口功能來進行。控制面板的服務控制器就是利用SCM接口來管理系統中的所有服務的。實際上,還有一些可以控制服務的程序或者命令,有net.exe?、服務器管理器等?、SCM.exe等。
          ????4、這些進程都以一定的身份運行,以方便進行服務器資源的存取。一般情況下使用域中的LocalSystem賬號運行,此賬號對本機上的大多數資源(除非特別禁止)有完全的存取權限,這樣可以保證服務程序的“強大”。但是,也有些服務采用特別的賬號運行,你也可以特別設定一個服務的帳號。
          ????5、由系統自動以線程方式運行,一般情況下不過多占用系統資源,這同普通的進程有所區別,如果不采用線程方式,一般進程往往消耗整個CPU資源。一般需要時時存在,又不能過多消耗資源的任務以服務來實現最合適。

          ?

          二、Java編寫服務的準備

          1、作為本地化的實現,實現NT服務的Java程序當然不是100%純Java,單靠標準類庫是無法實現我們的編寫NT服務的目的,所以MS提供了一套SDK?for?Java(本文采用的是Microsoft?SDK?for?Java?4.0),提到了如何利用MS提供的擴展類庫和相應的工具,實現符合Windows平臺需要的程序。其中包括了實現NT服務的所需要的類庫API框架以及將Java編譯的class文件組裝成標準的NT服務程序的工具。SDK的下載路徑可以從www.microsoft.com/java/查找到。

          2、安裝完SDK后可以看到在安裝目錄下有jntsvc目錄,此目錄就包含了service.zip文件,它實際上是一個NT?services的類庫框架,封裝了一些NT服務實現細節,使得我們可以按照框架舒服實現我們關心的細節。將service.zip展開至開發機器的系統安裝Service庫到Java擴展庫\Winnt\java\TrustLib下,如果在其他操作系統下進行開發,參照此系統目錄進行安裝文件。

          3、在該目錄下還有一個jntsvc.exe文件,也就是Java?NT?Service的意思啦。她可以幫助您實現將按照SDK提供的框架實現的編譯后的class文件組裝成一個標準的NT服務可執行文件。JntSvc幫助我們在已經編譯好的.class文件基礎上設置了所有NT服務程序必須的特征,是很重要的工具,得到NT服務取決于如何有效利用她。為了我們能夠方便從任何其他目錄的控制臺窗口調用她,我們將JntSvc.exe所在的目錄全路徑加入path環境變量。這可以通過設置系統屬性的高級屬性頁當中進行環境變量的設定。

          4、按照要求,我們寫好各項代碼,然后編譯編寫Java程序,得到class文件。我們當然不會在Vj?Studio中啟動她,因為它目前還沒有可執行文件的入口,系統無法啟動她。為了得到NT服務程序,我們需要在class文件所在目錄的控制臺窗口執行一個命令:X:>jntsvc?*.class?/OUT:ECHOSvc.exe?/SVCMAIN:EchoSvc??????????"/SERVICENAME:ECHOSvc"。具體的Jntsvc的參數我們可以看一看jntsvc?-?得到,這里的意思大概是:將當前目錄下的所有class文件組裝成一個NT服務進程exe文件,文件名為EchoSvc.exe,服務的啟動入口在echosvc.class中,在注冊表中相應的服務名稱為/Servicename參數指定的EchoSvc。如果有多個多個NT服務需要組裝在一個Exe文件中,還可以在?/Out參數后指定每一個服務展示名稱。/SVCMAIN參數指定服務的入口,所謂入口是指服務啟動之初是從哪一個類的實例開始的。"/SERVICENAME:"參數指定了該服務將以什么名稱出現。這些參數都是jntsvc.exe實用工具需要組裝服務所必須的信息,根據這些信息將編譯后的.class文件按照win32格式要求得到一個可執行文件。

          需要注意的是,這個exe文件的運行必須要有JVM存在,她實際上是通過解釋.class來實現服務提供的。如果需要另外的擴展包,可以通過在/Classpath參數指定另外的擴展包的位置。所以在安裝Java編寫得到的NT服務的機器上必須存在JVM。如果是擁有IE5.x那么不用操心這個問題,IE核心組件已經包括了JVM;但是如果是IE6版本,則需要到MS的網站上下載JVM。如果您講SDK?for?Java安裝在服務器上就更方便了。

          5、如果沒有什么錯誤,您將得到一個可執行文件echosvc.exe。像大多數服務可執行文件一樣,它可以將自己安裝到系統中:?echosvc.exe??install,這一個過程將會往系統注冊表添加一些項目,特別是關于服務的項目,SCM也可以列出這個服務了。我們可以在控制臺下采用DOS?NT服務控制命令Net?start/stop來測試服務是否真像普通服務一樣可以按照標準方式來控制,當然在服務管理器當中啟停該服務更不會有問題。

          ?

          三、Echo服務的樣例

          當系統載入服務進程時,入口是在EchoSvc的構造函數中,我們可以看到此構造函數帶有同一般程序的入口main()類似的參數。
          import?com.ms.service.*?;

          ?public?class?EchoSvc?extends?Service?

          {?????static?Thread?mainSvc=null?;?//定義服務主線程

          ???????public??EchoSvc?(String[]?args)?//構造此服務

          ???????{

          ?????????CheckPoint(1000);????

          ??????????????setRunning(ACCEPT_SHUTDOWN?|?ACCEPT_PAUSE_CONTINUE?|ACCEPT_STOP);?//?該項服務接受的關于服務控制的命令

          ??????????????mainSvc?=?new?Thread((Runnable)?new??MainSvcThread());

          ????????mainSvc.start();?????

          ???????System.out.println(?"The?Echo?Service?Was?Started?Successfully!");//紀錄事件,可以通過事件察看器看到

          ???????}

          }

          CheckPoint是?Service的同步方法,指示系統正改變服務的狀態,需要讓系統等待1秒。這里我們啟動的是一個線程,實際上相當于一個進程,她是服務進程的主線程。在這個線程中我們響應SCM對此服務的控制。大致的表達為:

          public?class?MainSvcThread?implements?Runnable?//實現線程控制

          {?????

          public?static?boolean?STOP?=?false;??//由系統來控制的內部變量,決定著服務進程(線程)的啟動、暫停等

          ??????public?static?boolean?PAUSE?=?false;?

          ??

          ?????????public?void?run()

          ?????????{

          ????????while?(!STOP)

          ?????????????????{??

          ????????????????????????while?(!PAUSE?&&?!STOP)?

          ????????????????????????{

          ??????????????????????????????。。。//此處為服務控制邏輯,下面會充實此處

          ??????????????????????}

          ????????????????try?

          ????????????????{Thread.sleep(5000);//休眠5秒后實現暫停或者停止}?

          ????????????????catch?(InterruptedException?e)?

          {?}

          ??????????????}

          ????????????try?

          ?????????????????????{Thread.sleep(1000);}

          ?????????????????????catch?(InterruptedException?ie)?

          ?????????????????????{}

          ?????????}

          ?????????}??//Run結束??

          }

          在服務邏輯控制當中,我們會具體實現Echo服務。我們的Echo服務監聽2002端口,接收客戶端任何一行輸入,然后加上“Echo:”后返回。如果客戶端輸入一個quit詞組那么服務認為這是客戶關閉此套接字的命令,會自動關閉當前的套接字連接,停止對當前連接的服務。具體的實現(EchoThread.java的代碼):

          public?void?run()

          ???????{

          ????????String?line;

          ????????DataInputStream?in;

          ????????PrintWriter?out;

          ????????boolean?exitflag=false;

          ?

          ???????try

          ???????{

          ????????in=new?DataInputStream(so.getInputStream())?;//獲取套接字的輸入流

          ????????out=new?PrintWriter(new?DataOutputStream(so.getOutputStream()))?;

          ??????out.println("You?have?connected?to?EchoSvc!");??//發送問候

          ????????out.flush();?

          ????????while((line=in.readLine())!=null)?//讀取

          ?????????{

          ???????????????line=line.trim();

          ???????????????if?(line.equalsIgnoreCase("quit")?)

          ???????????????{

          ???????????out.println("ECHO:"?+?line?);

          ?????????????????out.flush();

          return;

          }

          ???????????????else

          ???????????????{

          ???????????????out.println("ECHO:"?+?line?);

          ???????????????out.flush();?

          ???????????????}

          ?????????}

          ????????in.close();

          ????????out.close();

          }

          catch(IOException?ioe)

          ??????????????{}

          }

          Echo服務主要就是將客戶發送的字符回顯給客戶,并加上Echo:的前綴,以表明是從服務器返回的內容。如果客戶輸入“quit”那么表示這是要求服務器停止服務的表現。

          如何調試NT服務進程工程。如果直接將此函數調用來提供客戶端的ECHO套接字服務,邏輯上是沒有什么錯誤,但是就是無法支持多個用戶同時訪問。為了能夠提供多服務,允許同時又多個用戶連接此服務器(這種情況在很多網絡服務都不可少),我們可以將此邏輯在由MainSvcTread創建的線程中實現,而且可以允許多個用戶同時訪問此服務。具體的表達在MainSvcTread的run函數中實現:

          while(ListenThreadCount<maxSocket)?//如果當前啟動的線程數在系統允許的范圍內

          {

          ??????????server=li.accept();?//監聽

          ??????????EchoThread?p=new?EchoThread(server,this);//創建實現該服務的具體邏輯對象,是一個支持線程的類

          ??????????Thread?t=new?Thread(g,(Runnable)p)?;?//將當前線程并入線成組

          ??????????t.start();?//啟動服務線程

          ??????????ListenThreadCount++;?//修改當前線程數量

          ???????}

          參照上面提到的工具Jntsvc.exe可以幫助你講編譯好的.class文件組裝成exe文件,運行此文并加上-install參數可以自動幫助您講些好的服務添加到注冊表中,可以通過服務管理器或者相當的實用程序來如同其他服務一樣來進行控制了。撤除服務采用-uninstall參數。

          本例程采用套接字、多線程實現技術來解釋實現Java編寫NT服務,實際上類似這樣的很多網絡方面的服務都可以按照此規范實現,譬如POP3服務、FTP服務,甚至WWW服務等。我們也接觸過像Tomcat、Jrun等Java應用服務器在NT平臺的啟動往往采用NT服務形式,那么通過此例你也可以嘗試編寫自己的Java服務應用。

          ????最后,如果需要調試NT服務的邏輯,可以采用一個變通的辦法。我們在EchoSvc.java中添加一個Main靜態方法,然后產生一個EchoSvc的實例,這樣就是一個標準的VJ產生的Exe文件,利用Vj的調試功能我們可以排除隱藏的錯誤。一旦調通后,我們注釋掉main靜態方法,編譯后就可以得到一個調試好的NT服務。

          ?

          四、為什么要采用Java編寫NT服務

          比較VC等“原裝”NT服務開發方式而言,Java開發模式可以更快捷,因為幾乎所有得服務框架通過擴展Services類就可以達到,省下不少復雜的細節處理。Java語言提供了豐富的類庫,可以為自己使用,可以提高效率,而且編寫的程序結構清晰容易理解,方便以后維護。

          Java提供的異常處理模式,可以讓我們寫好結構良好,更加安全的代碼。試想如果編寫的服務進程由于采用VC編寫卻忘記對某塊內存的釋放,那么服務啟動后一段時間由于內存泄漏造成服務性能下降,甚至系統崩潰;但是Java本身的語言特性可以使我們不用時刻提防內存管理,可以更加關注服務邏輯本身,是的實現起來更加有效率。

          采用VC如果編寫多線程服務進程,雖然可以實現,但是會相當麻煩。而服務進程多線程幾乎是每一個性能良好的服務必備特征,Java語言本身可以提供這方面良好的支持,同時Java自身對網絡的天然良好支持,使各種網絡套接字編程容易。

          最后,如果不采用其他擴展庫,我們很容易將此服務邏輯實現在其他操作系統上。一個編寫好的NT服務程序,可以在去掉對Ms的相關本地化擴展實現的類引用后,方便移植到其他例如Linux等平臺上,盡可能向Java的“一次編寫、到處可運行”的理想境界靠攏。

          ?

          五、源碼

          /*所附的ZIP文件報含示例的全部工程文件,還有編譯后的NT服務的可執行文件,您可以直接測試此服務exe文件的安裝、服務啟停*/

          /*EchoSvc.java*/

          import?com.ms.service.*?;

          public?class?EchoSvc?extends?Service?

          {

          ???????static?Thread?mainSvc=null?;?//定義主線程

          ?

          ???????public??EchoSvc?(String[]?args)?//構造服務

          ???????{

          ????????????????CheckPoint(1000);????//服務是系統的一部分,作為Log紀錄,可以幫助用戶理解系統故障

          ?????????????????????setRunning(ACCEPT_SHUTDOWN?|?ACCEPT_PAUSE_CONTINUE?|ACCEPT_STOP);

          ?????????????????????mainSvc?=?new?Thread((Runnable)?new??MainSvcThread());

          ????????????mainSvc.start();?????

          ?????????????????????System.out.println(?"The?Echo?Service?Was?Started?Successfully!");

          ???????}

          }

          /*--------------?EchoSvc.java源碼結束-------------------*/

          ?

          /*MainSvcThread.java*/

          import?java.io.*;

          import?java.net.*;

          public?class?MainSvcThread?implements?Runnable?//實現線程控制多線程接口

          {?????/將啟動一組線程來監聽多個服務請求

          ?????????public?static?boolean?STOP?=?false;??//由系統來控制的內部變量,決定著服務進程(線程)的啟動、暫停等

          ??????public?static?boolean?PAUSE?=?false;?

          ?????????public?int?ListenThreadCount=0;??//本服務支持的當前線程數量

          ?????????int?maxSocket=10;??//最大支持的同時連結數

          ?????????int?SvcPort=2002;??//服務監聽的端口

          ?????????

          ?????????public?void?run()

          ?????????{

          ????????try

          ???????????????{

          ???????????while?(!STOP)

          ?????????????????{??

          ????????????????????????while?(!PAUSE?&&?!STOP)?

          ????????????????????????{

          ???????????????????????????????{//創建監聽服務器

          ?????????????????????????????Socket?server;??

          ?????????????????????????????ServerSocket??li=new?ServerSocket(SvcPort);??//創建服務器端套接字

          ?????????????????????????????ThreadGroup?g=new?ThreadGroup("EchoThreads");?//創建一組線程

          ????????????????????System.out.println("Echo?service?starting...");??//記錄在Log中

          ?????????????????????????????while(ListenThreadCount<maxSocket)

          ??????????????????????????????????????{

          ?????????????????????????????????????????????server=li.accept();??//監聽

          ?????????????????????????????????????????????EchoThread?p=new?EchoThread(server,this);?//創建服務單線程

          ?????????????????????????????????????????????Thread?t=new?Thread(g,(Runnable)p)?;?//創建新線程

          ?????????????????????????????????????????????t.start();?//啟動服務線程

          ?????????????????????????????????????????????ListenThreadCount++;?//當前線程的數量

          ??????????????????????????????????????}

          ??????????????????????}

          ???????????????????try?

          ???????????????????{

          ?????????????????????Thread.sleep(5000);//暫停5秒

          ????????????????????}?

          ????????????????????catch?(InterruptedException?e)?

          {???}

          ??????????????}

          ???????????????try?

          ?????????????????????????{

          ???????????????????????????Thread.sleep(1000);

          ?????????????????????????}

          ????????????????????????catch?(InterruptedException?ie)?

          ???????????????????????{??}

          ??????????}

          ??????????????}

          ??????????????catch?(IOException?ioe)

          ??????????????{}?

          ?????????}??//Run結束??

          }

          /*--------------?MainSvcThread.java源碼結束-------------------*/

          ?

          /*EchoThread.java*/

          import?java.io.*;

          import?java.net.*;

          /*實現每一個客戶連接到此NT服務時的服務器端的線程單元邏輯*/

          public?class?EchoThread?implements?Runnable???//實現線程接口

          {

          ????Socket?so=null;//套接字

          ???????MainSvcThread?p;??//一個指向父線程的指針,EchoThread的線程是服務線程的創建的子線程

          ???????public?void?run()

          ??????????????{

          ???????????????String?line;

          ???????????????DataInputStream?in;?//套接字上的輸入流

          ???????????????PrintWriter?out;???//套接字上的輸出流,帶緩沖

          ???????????????boolean?exitflag=false;

          ??????????????try

          ??????????????{

          ???????????????in=new?DataInputStream(so.getInputStream())?;//獲取套接字的輸入流

          ???????????????out=new?PrintWriter(new?DataOutputStream(so.getOutputStream()))?;

          ????????????out.println("You?have?connected?to?EchoSvc!");??//發送問候

          ???????????????out.flush();???//必須刷新緩沖區內的內容

          ?

          ???????????????while((line=in.readLine())!=null?&&?!?exitflag)

          ????????????????{

          ??????????????????????line=line.trim();

          ??????????????????????if?(line.equalsIgnoreCase("quit")?)

          ??????????????????????{//如果是退出命令,則關閉當前套接字上的輸入輸出流

          ????????????????????????????in.close();

          ????????????????out.flush();

          ????????????????????????????out.close();

          p.ListenThreadCount?--;?//主線程的服務線程單元數量控制

          ????????????????????????????return;???//退出當前的服務邏輯線程單元

          ??????????????????????}

          ??????????????????????else

          ??????????????????????{

          ??????????????????????out.println("ECHO:"?+?line?);

          ??????????????????????out.flush();?

          ??????????????????????}

          ?????????????????}

          ??????????in.close();

          ????????????????out.close();

          ????????????????p.ListenThreadCount?--;

          ??????????????}

          ??????????????catch(IOException?ioe)

          ??????????????{}

          ???????}

          ???????

          ??EchoThread(Socket?s,MainSvcThread?parent)

          ???????{

          ??????????????so=s;

          ??????????????p=?parent;

          ???????}

          }

          /*--------------?EchoThread.java源碼結束-------------------*/

          posted on 2006-05-06 15:57 迷途書童 閱讀(297) 評論(0)  編輯  收藏 所屬分類: java應用

          主站蜘蛛池模板: 湄潭县| 金溪县| 巍山| 邯郸县| 香河县| 巫山县| 砚山县| 红桥区| 隆化县| 巴彦淖尔市| 稻城县| 关岭| 乌兰察布市| 海宁市| 灌云县| 苍南县| 英超| 若羌县| 迭部县| 晴隆县| 西平县| 甘孜县| 湘潭县| 清徐县| 冕宁县| 嵊州市| 精河县| 富锦市| 威宁| 芜湖县| 罗江县| 凉城县| 左贡县| 郎溪县| 阳西县| 嘉义市| 长岭县| 十堰市| 金川县| 中方县| 成武县|