軟件外,時常需要按照實際環境編寫自己的服務器軟件,以完成特定任務或與特定客戶端軟件實現交互。在實現服務器程序時,為提高程序運行效率,降低用戶等待時間,我們應用了在Java Applet中常見的多線程技術。
一、Java中的服務器程序與多線程
在Java之前,沒有一種主流編程語言能夠提供對高級網絡編程的固有支持。在其他語言環境中,實現網絡程序往往需要深入依賴於操作平台的網絡API的技術中去,而Java提供了對網絡支持的無平台相關性的完整軟件包,使程序員沒有必要為系統網絡支持的細節而煩惱。
Java軟件包內在支持的網絡協議為TCP/IP,也是當今最流行的廣域網/局域網協議。Java有關網絡的類及接口定義在java.net包中。客戶端軟件通常使用java.net包中的核心類Socket與服務器的某個端口建立連接,而服務器程序不同於客戶機,它需要初始化一個端口進行監聽,遇到連接呼叫,才與相應的客戶機建立連接。Java.Net包的ServerSocket類包含了編寫服務器系統所需的一切。下面給出ServerSocket類的部分定義。
Code highlighting produced by Actipro CodeHighlighter (freeware)
http://www.CodeHighlighter.com/
-->1 public class ServerSocket
2 {
3 public ServerSocket(int port)
4 throws IOException ;
5 public Socket accept() throws IOException ;
6 public InetAddress getInetAddress() ;
7 public int getLocalPort() ;
8 public void close() throws IOException ;
9 public synchronized void setSoTimeout (int timeout) throws SocketException ;
10 public synchronized int getSoTimeout() throws IOException ;
11 }
12
ServerSocket構造器是服務器程序運行的基礎,它將參數port指定的端口初始化作為該服務器的端口,監聽客戶機連接請求。Port的范圍是0到65536,但0到1023是標准Internet協議保留端口,而且在Unix主機上,這些端口只有root用戶可以使用。一般自定義的端口號在8000到16000之間。僅初始化了ServerSocket還是遠遠不夠的,它沒有同客戶機交互的套接字(Socket),因此需要調用該類的accept方法接受客戶呼叫。Accept()方法直到有連接請求才返回通信套接字(Socket)的實例。通過這個實例的輸入、輸出流,服務器可以接收用戶指令,並將相應結果回應客戶機。ServerSocket類的getInetAddress和getLocalPort方法可得到該服務器的IP地址和端口。setSoTimeout和getSoTimeout方法分別是設置和得到服務器超時設置,如果服務器在timout設定時間內還未得到accept方法返回的套接字實例,則拋出IOException的異常。
Java的多線程可謂是Java編程的精華之一,運用得當可以極大地改善程序的響應時間,提高程序的並行性。在服務器程序中,由於往往要接收不同客戶機的同時請求或命令,因此可以對每個客戶機的請求生成一個命令處理線程,同時對各用戶的指令作出反應。在一些較復雜的系統中,我們還可以為每個數據庫查詢指令生成單獨的線程,並行對數據庫進行操作。實踐證明,采用多線程設計可以很好的改善系統的響應,並保證用戶指令執行的獨立性。由於Java本身是"線程安全"的,因此有一條編程原則是能夠獨立在一個線程中完成的操作就應該開辟一個新的線程。
Java中實現線程的方式有兩種,一是生成Thread類的子類,並定義該子類自己的run方法,線程的操作在方法run中實現。但我們定義的類一般是其他類的子類,而Java又不允許多重繼承,因此第二種實現線程的方法是實現Runnable接口。通過覆蓋Runnable接口中的run方法實現該線程的功能。本文例子采用第一種方法實現線程。
二、多線程服務器程序舉例
以下是我們在項目中采用的多線程服務器程序的架構,可以在此基礎上對命令進行擴充。本例未涉及數據庫。如果在線程運行中需要根據用戶指令對數據庫進行更新操作,則應注意線程間的同步問題,使同一更新方法一次只能由一個線程調用。這裡我們有兩個類,receiveServer包含啟動代碼(main()),並初始化ServerSocket的實例,在accept方法返回用戶請求後,將返回的套接字(Socket)交給生成的線程類serverThread的實例,直到該用戶結束連接。
Code highlighting produced by Actipro CodeHighlighter (freeware)
http://www.CodeHighlighter.com/
--> 1 //類receiveServer
2 import Java.io.*;
3 import Java.util.*;
4 import Java.Net.*;
5
6 public class receiveServer{
7 final int RECEIVE_PORT=9090; //該服務器的端口號
8 //receiveServer的構造器public receiveServer() {ServerSocket rServer=null;
9 //ServerSocket的實例
10 Socket request=null;
11 //用戶請求的套接字Thread receiveThread=null;
12 try{
13 rServer=new ServerSocket(RECEIVE_PORT);
14 //初始化ServerSocket System.out.println("Welcome to the server!");
15 System.out.println(new Date());
16 System.out.println("The server is ready!");
17 System.out.println("Port: "+RECEIVE_PORT);
18 while(true){ //等待用戶請求 request=rServer.accept(); //接收客戶機連接請求receiveThread=new serverThread(request);
19
20 //生成serverThread的實例
21 receiveThread.start();
22
23 //啟動serverThread線程
24 }
25 }
26 catch(IOException e){
27 System.out.println(e.getMessage()) ;
28 }
29 } public static void main(String args[]){ new receiveServer();
30
31 } //end of main} //end of class//類serverThreadimport Java.io.*;
32
33 import Java.Net.*;
34 class serverThread extends Thread {Socket clIEntRequest;
35 //用戶連接的通信套接字BufferedReader input;
36 //輸入流PrintWriter output;
37 //輸出流
38 public serverThread(Socket s) {
39 //serverThread的構造器 this.clIEntRequest=s;
40 //接收receiveServer傳來的套接字 InputStreamReader reader;
41
42 OutputStreamWriter writer;
43 try{
44 //初始化輸入、輸出流
45 reader=new InputStreamReader(clIEntRequest.getInputStream());
46 writer=new OutputStreamWriter(clIEntRequest.getOutputStream());
47 input=new BufferedReader(reader);
48 output=new PrintWriter(writer,true);
49 }
50 catch(IOException e){ System.out.println(e.getMessage());}
51 output.println("Welcome to the server!");
52 //客戶機連接歡迎詞
53 output.println("Now is: "+new Java.util.Date()+" "+ "Port:"+clIEntRequest.getLocalPort());
54 output.println("What can I do for you?");
55 }
56
57 public void run(){
58 //線程的執行方法
59 String command=null;
60 //用戶指令 String str=null;
61 boolean done=false;
62 while(!done){
63 try{
64 str=input.readLine();
65 //接收客戶機指令
66 }catch(IOException e){
67 System.out.println(e.getMessage());
68 }
69 command=str.trim().toUpperCase();
70
71 if(str==null || command.equals("QUIT")) //命令quit結束本次連接
72 done=true;
73 else if(command.equals("HELP")){
74 //命令help查詢本服務器可接受的命令
75 output.println("query");
76 output.println("quit");
77 output.println("help");
78 }
79 else if(command.startsWith("QUERY")){
80 //命令
81 query output.println("OK to query something!");
82 }//else if …….. //在此可加入服務器的其他指令
83 else if(!command.startsWith("HELP") && !command.startsWith("QUIT") && !command.startsWith("QUERY")){output.println("Command not Found!
84 Please refer to the HELP!"); }
85 }
86
87 //end of while
88
89 try
90 {
91 clIEntRequest.close();
92 //關閉套接字
93 }
94 catch(IOException e){
95 System.out.println(e.getMessage());
96 }
97 command=null;
98 }
99
100 //end of run
啟動該服務器程序後,可用telnet machine port命令連接,其中Machine為本機名或地址,port為程序中指定的端口。也可以編寫特定的客戶機軟件通過TCP的Socket套接字建立連接。
對象池的構造和管理可以按照多種方式實現。最靈活的方式是將池化對象的Class類型在對象池之外指定,即在ObjectPoolFactory類創建對象池時,動態指定該對象池所池化對象的Class類型,其實現代碼如下:
Code highlighting produced by Actipro CodeHighlighter (freeware)
http://www.CodeHighlighter.com/
-->1 . . .
2
3 public ObjectPool createPool(ParameterObject paraObj,Class clsType) {
4
5 return new ObjectPool(paraObj, clsType);
6
7 }
8
9 . . .
10
11
其中,paraObj參數用於指定對象池的特征屬性,clsType參數則指定了該對象池所存放對象的類型。對象池(ObjectPool)創建以後,下面就是利用它來管理對象了,具體實現如下:
Code highlighting produced by Actipro CodeHighlighter (freeware)
http://www.CodeHighlighter.com/
-->1 public class ObjectPool {
2 private ParameterObject paraObj;//該對象池的屬性參數對象
3 private Class clsType;//該對象池中所存放對象的類型
4 private int currentNum = 0; //該對象池當前已創建的對象數目
5 private Object currentObj;//該對象池當前可以借出的對象
6 private Vector pool;//用於存放對象的池
7 public ObjectPool(ParameterObject paraObj, Class clsType) {
8 this.paraObj = paraObj;
9 this.clsType = clsType;
10 pool = new Vector();
11 }
12 public Object getObject() {
13 if (pool.size() <= paraObj.getMinCount()) {
14 if (currentNum <= paraObj.getMaxCount()) {
15 //如果當前池中無對象可用,而且已創建的對象數目小於所限制的最大值,就利用
16 //PoolObjectFactory創建一個新的對象
17 PoolableObjectFactory objFactory =PoolableObjectFactory.getInstance();
18 currentObj = objFactory.create Object (clsType);
19 currentNum++;
20 } else {
21 //如果當前池中無對象可用,而且所創建的對象數目已達到所限制的最大值,
22 //就只能等待其它線程返回對象到池中
23 synchronized (this) {
24 try {
25 wait();
26 } catch (InterruptedException e) {
27 System.out.println(e.getMessage());
28 e.printStackTrace();
29 }
30 currentObj = pool.firstElement();
31 }
32 }
33 } else {
34 //如果當前池中有可用的對象,就直接從池中取出對象
35 currentObj = pool.firstElement();
36 }
37 return currentObj;
38 }
39 public void returnObject(Object obj) {
40 // 確保對象具有正確的類型
41 if (obj.isInstance(clsType)) {
42 pool.addElement(obj);
43 synchronized (this) {
44 notifyAll();
45 }
46 } else {
47 throw new IllegalArgumentException("該對象池不能存放指定的對象類型");
48 }
49 }
50 }
從上述代碼可以看出,ObjectPool利用一個Java.util.Vector作為可擴展的對象池,並通過它的構造函數來指定池化對象的Class類型及對象池的一些屬性。在有對象返回到對象池時,它將檢查對象的類型是否正確。當對象池裡不再有可用對象時,它或者等待已被使用的池化對象返回池中,或者創建一個新的對象實例。不過,新對象實例的創建並不在ObjectPool類中,而是由PoolableObjectFactory類的createObject方法來完成的,具體實現如下:
Code highlighting produced by Actipro CodeHighlighter (freeware)
http://www.CodeHighlighter.com/
-->1 . . .
2 public Object createObject(Class clsType) {
3 Object obj = null;
4 try {
5 obj = clsType.newInstance();
6 } catch (Exception e) {
7 e.printStackTrace();
8 }
9 return obj;
10 }
11 . . .
這樣,通用對象池的實現就算完成了,下面再看看客戶端(ClIEnt)如何來使用它,假定池化對象的Class類型為StringBuffer:
Code highlighting produced by Actipro CodeHighlighter (freeware)
http://www.CodeHighlighter.com/
-->1 . . .
2 //創建對象池工廠
3 ObjectPoolFactory poolFactory = ObjectPoolFactory. getInstance ();
4 //定義所創建對象池的屬性
5 ParameterObject paraObj = new ParameterObject(2,1);
6 //利用對象池工廠,創建一個存放StringBuffer類型對象的對象池
7 ObjectPool pool = poolFactory.createPool(paraObj,String Buffer.class);
8 //從池中取出一個StringBuffer對象
9 StringBuffer buffer = (StringBuffer)pool.getObject();
10 //使用從池中取出的StringBuffer對象
11 buffer.append("hello");
12 System.out.println(buffer.toString());
13 . . .
可以看出,通用對象池使用起來還是很方便的,不僅可以方便地避免頻繁創建對象的開銷,而且通用程度高。但遺憾的是,由於需要使用大量的類型定型(cast)操作,再加上一些對Vector類的同步操作,使得它在某些情況下對性能的改進非常有限,尤其對那些創建周期比較短的對象。
由於通用對象池的管理開銷比較大,某種程度上抵消了重用對象所帶來的大部分優勢。為解決該問題,可以采用專用對象池的方法。即對象池所池化對象的Class類型不是動態指定的,而是預先就已指定。這樣,它在實現上也會較通用對象池簡單些,可以不要ObjectPoolFactory和PoolableObjectFactory類,而將它們的功能直接融合到ObjectPool類,具體如下(假定被池化對象的Class類型仍為StringBuffer,而用省略號表示的地方,表示代碼同通用對象池的實現):
Code highlighting produced by Actipro CodeHighlighter (freeware)
http://www.CodeHighlighter.com/
-->1public class ObjectPool {
2 private ParameterObject paraObj;//該對象池的屬性參數對象
3 private int currentNum = 0; //該對象池當前已創建的對象數目
4 private StringBuffer currentObj;//該對象池當前可以借出的對象
5 private Vector pool;//用於存放對象的池
6 public ObjectPool(ParameterObject paraObj) {
7 this.paraObj = paraObj;
8 pool = new Vector();
9 }
10 public StringBuffer getObject() {
11 if (pool.size() <= paraObj.getMinCount()) {
12 if (currentNum <= paraObj.getMaxCount()) {
13 currentObj = new StringBuffer();
14 currentNum++;
15 }
16 . . .
17 }
18 return currentObj;
19 }
20 public void returnObject(Object obj) {
21 // 確保對象具有正確的類型
22 if (StringBuffer.isInstance(obj)) {
23 . . .
24 }
25 }
結束語
恰當地使用對象池技術,能有效地改善應用程序的性能。目前,對象池技術已得到廣泛的應用,如對於網絡和數據庫連接這類重量級的對象,一般都會采用對象池技術。但在使用對象池技術時也要注意如下問題:
並非任何情況下都適合采用對象池技術。基本上,只在重復生成某種對象的操作成為影響性能的關鍵因素的時候,才適合采用對象池技術。而如果進行池化所能帶來的性能提高並不重要的話,還是不采用對象池化技術為佳,以保持代碼的簡明。
要根據具體情況正確選擇對象池的實現方式。如果是創建一個公用的對象池技術實現包,或需要在程序中動態指定所池化對象的Class類型時,才選擇通用對象池。而大部分情況下,采用專用對象池就可以了。