原貼地址
http://blog.csdn.net/clearwater21cn/category/99145.aspxQuickServer開發指南(1)- 介紹?
QuickServer是一個免費的開源Java庫,用于快速創建健壯的多線程、多客戶端TCP服務器應用程序。使用QuickServer,用戶可以只集中處理應用程序的邏輯/協議,從而方便的建立功能強大的服務器應用。該程序由Akshathkumar Shetty設計和實現。
??? QuickServer安裝目錄下的example中有演示其功能的例子,最新的例子和文檔可以通過網站
http://www.quickserver.org或
http://quickserver.sourceforge.net獲得。
??? 該指南適用于所有想要學習和使用QuickServer的人,閱讀該指南應具備基本的Java編程知識,基本的網絡和sockets方面的知識也會有所幫助
1.?為什么需要QuickServer???? 無論何種編程語言,socket編程對程序員來說都不是一件容易的事,創建多線程、多客戶端的服務器socket更像一場惡夢了。在每個新的軟件中處理多socket連接,我們都要浪費大把時間編寫大量重復的代碼。QuickServer因而誕生——使用Java創建多線程、多客戶端服務器應用。
2.?基本構造
QuickServer在應用邏輯上為開發者提供了四個類
o?ClientCommandHandler
??? 處理與客戶端的交互——使用字符串命令
o?ClientObjectHandler [可選類]
??? 處理與客戶端的交互——使用對象命令
o?Authenticator [可選類]
??? 客戶端驗證
o?ClientData [可選類]
??? 客戶端數據載體(支持類)
下面的圖表顯示了QuickServer庫的基本構造。QuickServer模塊上七個輻條表示七個方法:
o?java.lang.String info()
o?int getServiceState()
o?boolean initService(java.lang.Object[] config)
o?boolean startService()
o?boolean resumeService()
o?boolean suspendService()
o?boolean stopService()

??? 與QuickServer模塊相連接的四個組件中只有ClientCommandHandler是必須的。
??? QuickServerConfig對象由initService()方法構建。它實現了QuickServer,在讀取XML配置后,QuickServerConfig用于QuickServer配置。
??? ClientHandler線程對象用于客戶端緩沖池。可選的ClientData類與ClientHandler類關聯,ClientHandler對象容器參考ClientCommandHandler,ClientObjectHandler(可選),Authenticator(可選)對象包含在QuickServer主函數中。
??? 注意:上圖中并未顯示QSAdminServer,它是圖中QuickServer的組成部分。
3.?主要特點
o?創建多線程、多客戶端TCP服務器應用程序
o?支持安全服務的創建:SSL, TLS
o?清楚的分離服務、協議、驗證邏輯
o?GUI圖形界面遠程管理支持
o?Command Shell對服務器的本地管理
o?無須斷開客戶端連接的重啟或延遲服務
o?為線程的再利用和大多數的使用對象建立緩沖池
o?完全的日志支持(Java構建)
o?支持發送和接收字符串、字節、二進制、序列化Java對象
o?在同樣的xml中支持能夠存貯指定應用數據的XML配置
o?支持通過IP地址限制服務
o?支持基于XML的JDBC映射
o?支持服務配置模式
o?支持從xml加載/重新加載用于jar包
o?在QuickServer中添加處理hooks
o?指定允許的最大客戶端連接數
o?在通常的TCP連接上支持談判安全連接
o?支持鑒別和查詢客戶端
o?附帶典型例子——FTPServer, CmdServer,EchoWebServer, ChatServer
4.?1.4版的新功能
o?為QuickServer添加安全模式:SSL, TLS
o?添加SecureManagerLoader管理安全模式
o?在通常的TCP連接上添加談判安全連接
o?添加初始化服務hooks
o?為通信添加二進制模式
o?為QsAdminServer通信添加QSAdminAPI
o?為QuickServer 添加findAllClientByKey
o?添加ConnectionLostException類
o?改進ClientHandler、安全配置
o?新例子——XmlAdder:一個簡單的xml服務,可添加兩個整數
o?新例子——PipeServer:一個簡單的重定向服務
QuickServer開發指南(2)- 安裝
1. 運行環境
QuickServer 1.2以上的版本需要(其實在偶看來一個1.4版以上JDK足矣):
? 推薦1.4版以上Java虛擬機,最低1.3版(未經測試).
? Java Logging API(下列之一)
o java.util.logging包 [JDK 1.4版自帶]
o Lumberjack庫 [http://javalogging.sourceforge.net/]
? XML 解析器 (下列之一)
o SAX (面向XML 2.0的API) [JDK 1.4版自帶]
o JAXP (面向XML解析的Java API) 1.1 [JDK 1.4版自帶]
o Xerces [http://xml.apache.org/xerces2-j]
o Crimson [http://xml.apache.org/crimson]
? Jakarta公共組件{Digester, Pool}
o 這些產品包含在Apache開發的軟件中(http://www.apache.org/)。Jar包都在以下的庫中:BeanUtils, Collections, Logging. [http://jakarta.apache.org/commons/components.html]. Apache軟件許可證在文件“apache_license.txt”中。
2. 安裝
??? 目前最新的1.4.1版QuickServer可在http://www.quickserver.org/download.html下載。安裝QuickServer,假設安裝路徑為$INSTALL_PATH。
??? 在CLASSPATH中添加"$INSTALL_PATH\dist\QuickServer.jar",在PATH中添加"$INSTALL_PATH\bin"。
??? 另外測試socket的通訊軟件推薦SockTest,在http://www.ddost.com/soft/sockettest 可下載到最新版本。Windows自帶的telnet也可以進行測試。
QuickServer開發指南(3)- 構建EchoServer?
學習怎樣使用QuickServer庫的一個好的方法是學習它提供的例子。在QuickServer安裝路徑下的examples文件夾里有許多典型的例子。
??? 下面的章節里我們模仿其中的一個例子EchoServer來構建一個服務器。EchoServer是一個簡單的TCP服務器,主要功能是將用戶發送的字符串加上前綴"Echo :"后返回。雖然這個例子可用性不強,但它是一個對QuickServer所有特點的一個很好的示范。我們從構建一個最基本的服務器開始,以后慢慢給它添加新的功能。
1.?代碼
??? 首先實現EchoServer最基本的功能:將用戶發送的字符串加上前綴"Echo :"后返回。
??? 在本地創建一個文件夾存放需要的代碼,如在c:\projects\中建立echoserver文件夾,然后創建一個類EchoServer.java:
01 package echoserver; 02 03 import org.quickserver.net.*; 04 import org.quickserver.net.server.*; 05 06 import java.io.*;
07 08 public class EchoServer { 09 public static void main(String s[]) { 10 QuickServer myServer = 11 new QuickServer("echoserver.EchoCommandHandler"); 12 myServer.setPort(4123); 13 myServer.setName("EchoServer v 1.0"); 14 try { 15 myServer.startServer(); 16 } catch(AppException e){ 17 System.err.println("Error in server : "+e); 18 } 19 } 20 } |
??? 在第10行和第11行定義了一個QuickServer對象myServer,通過一個String對象"echoserver.EchoCommandHandler"聲明了要加載的類,這個類面向所有客戶端做命令處理器,實現了org.quickserver.net.server.ClientCommandHandler接口,我們即將創建。
??? 第12行設置了一個服務器端口用來做監聽,然后設置整個應用的名字(第13行)。最后啟動服務(第15行)。
??? 接下來為EchoServer創建一個實現org.quickserver.net.server.ClientCommandHandler接口的類EchoCommandHandler.java,用來處理服務器發送的命令。
01 // EchoCommandHandler.java 02 package echoserver; 03 04 import java.net.*; 05 import java.io.*; 06 import org.quickserver.net.server.ClientCommandHandler; 07 import org.quickserver.net.server.ClientHandler; 08 09 public class EchoCommandHandler implements ClientCommandHandler { 10 11 public void gotConnected(ClientHandler handler) 12 throws SocketTimeoutException, IOException { 13 handler.sendClientMsg("+++++++++++++++++++++++++++++++"); 14 handler.sendClientMsg("| Welcome to EchoServer v 1.3 |"); 15 handler.sendClientMsg("| Send 'Quit' to exit |"); 16 handler.sendClientMsg("+++++++++++++++++++++++++++++++"); 17 } 18 public void lostConnection(ClientHandler handler) 19 throws IOException { 20 handler.sendSystemMsg("Connection lost : " + 21 handler.getSocket().getInetAddress()); 22 } 23 public void closingConnection(ClientHandler handler)
24 throws IOException { 25 handler.sendSystemMsg("Closing connection : " + 26 handler.getSocket().getInetAddress()); 27 } 28 29 public void handleCommand(ClientHandler handler, String command) 30 throws SocketTimeoutException, IOException { 31 if(command.equals("Quit")) { 32 handler.sendClientMsg("Bye ;-)"); 33 handler.closeConnection(); 34 } else { 35 handler.sendClientMsg("Echo : "+command); 36 } 37 } 38 } |
??? 根據QuickServer的要求,這個類必須實現ClientCommandHandler接口。
??? 當客戶端建立一個連接(11行),gotConnected()方法被調用。在這個方法里面,我們給客戶端發送歡迎文本(13-16行),這些文本使用通過ClientHandler的sendClientMsg()方法發送給客戶端。我們也會使用ClientHandler的sendSystemMessage()方法顯示客戶端連接的InetAddress(20-21,25-26行)。
??? handlerCommand()方法是ClientCommandHandler接口的核心方法,因為服務器接收客戶端發送的任何命令時都要調用該方法。在我們對這個方法的實現中,我們會檢查命令是否為"Quit"(31行),如果是,我們將發送一些提示文本表示服務器即將關閉連接,然后關閉連接(33行)。否則,將命令加上前綴"Echo :"返回給用戶。
2.?運行和測試
o?運行命令提示符程序(cmd.exe)
o?進入代碼所在文件夾根目錄,如c:\projects
o?編譯代碼? javac echoserver\*.java
o?若無編譯錯誤,運行服務器:
??? set classpath=%classpath%;d:\QuickServer\dist\QuickServer.jar;.\(類所在文件夾)
??? java echoserver.EchoServer
o?您將會看到如下信息:

o?測試我們的服務器是否可以正常工作。再運行一個cmd程序,進入SocketTest.jar所在目錄,鍵入java -jar sockettest.jar命令,彈出一個窗口。在IP Address中輸入"127.0.0.1",在Port里輸入"4123",點擊"Connect"按鈕,將看到窗口中顯示如下圖的信息。

??? 若使用telnet,可鍵入命令:open localhost 4123
??? 在Message中輸入一些字符串,點擊"Send"按鈕,瀏覽器將會返回一個加了前綴"Echo :"的字符串。發送"Quit",服務器斷開連接。
QuickServer開發指南(4)- 添加認證
現在我們給剛剛創建的服務器添加認證功能。
??? 查看org.quickserver.net.server.QuickServer的文檔(docs文件夾下)你可以注意到里面有一個方法
??? public void setAuthenticator(java.lang.String authenticator)
??? 閱讀文檔可知此方法中的authenticator字符串是實現org.quickserver.net.server.Authenticator接口的方法的全名。
??? Authenticator接口有兩個實現:
??? org.quickserver.net.server.QuickAuthenticator:這個類用來驗證連接QuickServer的客戶端。它只用一個實例處理所有的QuickServer驗證。(推薦)
??? org.quickserver.net.server.ServerAuthenticator:這個類同樣用來驗證連接QuickServer的客戶端,但對每一個驗證的處理都會創建一個實例。
??? 接下來給EchoServer加驗證功能。簡單點,客戶端輸入的用戶名和密碼一致就算驗證通過。
??? 首先,在同樣的文件夾里創建一個驗證類:EchoServerQuickAuthenticator
01 package echoserver;
02 03 import org.quickserver.net.server.*; 04 import java.io.*; 05 06 public class EchoServerQuickAuthenticator extends QuickAuthenticator { 07 08 public boolean askAuthorisation(ClientHandler clientHandler) 09 throws IOException { 10 String username = askStringInput(clientHandler, "User Name :"); 11 String password = askStringInput(clientHandler, "Password :"); 12 13 if(username==null || password ==null) 14 return false; 15 16 if(username.equals(password)) { 17 sendString(clientHandler, "Auth OK"); 18 return true; 19 } else { 20 sendString(clientHandler, "Auth Failed"); 21 return false; 22 } 23 } 24 } |
??? 這個類擴展了org.quickserver.net.server.QuickAuthenticator(第6行),在askAuthorisation()方法中(8行),通過askStringInput()方法要求客戶端輸入用戶名和密碼,并讀入客戶端輸入的信息(10-11行)。這個方法繼承自QuickAuthenticator。如果用戶名與密碼相等,發送正確信息并返回"true"(16-18行),否則發送錯誤信息并返回"false"(20-21行)。
??? 接下來我們要告訴QuickServer使用我們新創建的驗證類來做驗證器。修改前一章創建的EchoServer.java文件,代碼如下(粗體為修改的代碼):
01 package echoserver; 02 03 import com.ddost.net.*; 04 import com.ddost.net.server.*; 05 06 import java.io.*; 07 08 public class EchoServer { 09 10 public static void main(String s[]) { 11 12 QuickServer myServer = 13 new QuickServer("echoserver.EchoCommandHandler"); 14myServer.setAuthenticator(
15"echoserver.EchoServerQuickAuthenticator"); 16 myServer.setPort(4123); 17 myServer.setName("EchoServer v 1.0"); 18try { 19 myServer.startServer(); 20 } catch(AppException e){ 21 System.err.println("Error in server : "+e); 22 } 23 } 24 } |
??? OK,將修改好的文件編譯,按照前一章講述的方法運行程序。這次當我們點擊"Connect"時,瀏覽器會要求我們輸入用戶名和密碼。如果輸入的用戶名和密碼一致就可以登錄。如果輸入錯誤五次以上,瀏覽器會提示"-ERR Max Auth Try Reached"并自動斷開連接。這個次數和提示信息可以通過QuickServer類的setMaxAuthTry() 和 setMaxAuthTryMsg()修改。
??? 有時在驗證過程中我們可能需要中途退出而不是等待驗證結束,這時輸入"Quit"是不起作用的。我們可以這樣修改代碼,有兩個方法:
??? 一是從EchoServerQuickAuthenticator類中的askAuthorisation()方法拋出一個org.quickserver.net.AppException異常,代碼如下:
??? String username = askStringInput(clientHandler, "User Name :");
??? if (username != null &&
??????? username.equalsIgnoreCase("QUIT")) {
????? sendString(clientHandler, "Logged out.");
????? throw new AppException("Quit");
}
??? 或者參考ClientHandler,關閉連接,代碼如下:
??? String username = askStringInput(clientHandler, "User Name :");
??? if (username != null &&
??????? username.equalsIgnoreCase("QUIT")) {
????? sendString(clientHandler, "Logged out.");
????? clientHandler.closeConnection();
????? return false;
}
??? ClientHandler對象能夠提供很多客戶端連接的有用信息,如IP地址。更多信息請參考API文檔。
注意:
??? o 不要在驗證器類中存貯任何客戶端相關信息,如果需要,必須存放在ClientData類中--下一章將講解該部分內容。
??? o 必須確認askAuthorisation()方法是線程安全的。
QuickServer開發指南(5)- 客戶數據
既然不能在ClientCommandHandler和ServerAuthenticator類中保存客戶數據,我們使用ClientData類的handleCommand()或askAuthorisation()方法來存儲所有的客戶端信息。
??? 示范一下這個特點有什么用。還是以EchoServer為例,當用戶發送"Hello"時,我們給他一個問候。如果用戶再發送"Hello",我們提醒他已經發了n次"Hello"。接下來定義ClientData類來存儲用戶名以及他向服務器發送"Hello"的次數。
1.?代碼
1.?在EchoServer中創建一個EchoServerData類
01 //---- EchoServerData.java ---- 02?package echoserver; 03 04 import org.quickserver.net.server.*; 05 import java.io.*; 06
07 public class EchoServerData implements ClientData { 08 private int helloCount; 09 private String username; 10 11 public void setHelloCount(int count) { 12 helloCount = count; 13 } 14 public int getHelloCount() { 15 return helloCount; 16 } 17 18 public void setUsername(String username) { 19 this.username = username; 20 } 21 public String getUsername() { 22 return username; 23 } 24 } 25 //--- end of code --- |
2.?告訴QuickServer用這個EchoServerData來做為它的ClientData類。
??? 修改前面創建的EchoServer.java,代碼如下:
01 package echoserver; 02 03 import org.quickserver.net.*; 04 import org.quickserver.net.server.*; 05 06 import java.io.*; 07 08 public class EchoServer { 09 public static void main(String s[]) { 10 11 String cmd = "echoserver.EchoCommandHandler"; 12 String auth = "echoserver.EchoServerQuickAuthenticator"; 13 String data = "echoserver.EchoServerData"; 14 15 QuickServer myServer = new QuickServer(cmd); 16 myServer.setAuthenticator(auth); 17 myServer.setClientData(data); 18 19 myServer.setPort(4123); 20 myServer.setName("Echo Server v 1.0"); 21 try { 22 myServer.startServer(); 23 } catch(AppException e){
24 System.out.println("Error in server : "+e); 25 } 26 } 27 } |
??? 上面的代碼中,我們將配置信息寫入String對象來設置QuickServer。???
3.?修改Authenticator類,也就是EchoServerAuthenticator類,讓它在ClientData對象中存儲用戶名。下面是修改后的代碼:
01 package echoserver; 02 03 import org.quickserver.net.server.*; 04 import java.io.*; 05 06 public class EchoServerQuickAuthenticator extends QuickAuthenticator { 07 08 public boolean askAuthorisation(ClientHandler clientHandler) 09 throws IOException { 10 String username = askStringInput(clientHandler, "User Name :"); 11 if(username!=null && username.equalsIgnoreCase("QUIT")) { 12 sendString(clientHandler, "Logged out."); 13 //close the connection 14 clientHandler.closeConnection(); 15 return false; 16 } 17 18 String password = askStringInput(clientHandler, "Password :"); 19 20 if(username==null || password ==null) 21 return false; 22 23 if(username.equals(password)) { 24 sendString(clientHandler, "Auth OK"); 25 //store the username in ClientData 26 EchoServerData data = (EchoServerData)clientHandler.getClientData(); 27 data.setUsername(username); 28 return true; 29 } else { 30 sendString(clientHandler, "Auth Failed"); 31 return false; 32 } 33 } 34 } |
4.?修改ClientCommandHandler實現類EchoCommandHandler。如果用戶發送"Hello",給他一個問候。如果他發送多次"Hello",告訴他已經發送了n次"Hello"。下面是修改后的代碼:
01 // EchoCommandHandler.java 02 package echoserver; 03 04 import java.net.*; 05 import java.io.*; 06 import org.quickserver.net.server.ClientCommandHandler; 07 import org.quickserver.net.server.ClientHandler; 08 09 public class EchoCommandHandler implements ClientCommandHandler { 10 11 public void gotConnected(ClientHandler handler) 12 throws SocketTimeoutException, IOException { 13 handler.sendClientMsg("+++++++++++++++++++++++++++++++"); 14 handler.sendClientMsg("| Welcome to EchoServer v 1.0 |"); 15 handler.sendClientMsg("| Note: Password = Username |"); 16 handler.sendClientMsg("| Send 'Quit' to exit |"); 17 handler.sendClientMsg("+++++++++++++++++++++++++++++++"); 18 } 19 public void lostConnection(ClientHandler handler) 20 throws IOException { 21 handler.sendSystemMsg("Connection lost : " + 22 handler.getSocket().getInetAddress()); 23 } 24 public void closingConnection(ClientHandler handler) 25 throws IOException { 26 handler.sendSystemMsg("Closing connection : " + 27 handler.getSocket().getInetAddress()); 28 } 29 30 public void handleCommand(ClientHandler handler, String command) 31 throws SocketTimeoutException, IOException { 32 if(command.equals("Quit")) { 33 handler.sendClientMsg("Bye ;-)"); 34 handler.closeConnection(); 35 } if(command.equalsIgnoreCase("hello")) { 36 EchoServerData data = (EchoServerData) handler.getClientData(); 37 data.setHelloCount(data.getHelloCount()+1); 38 if(data.getHelloCount()==1) { 39 handler.sendClientMsg("Hello "+data.getUsername()); 40 } else { 41 handler.sendClientMsg("You told Hello "+data.getHelloCount()+
42 " times. "); 43 } 44 } else { 45 handler.sendClientMsg("Echo : "+command); 46 } 47 } 48 } |
5.?編譯改好的程序,運行,使用SocketTest測試。登錄后,發送"Hello",系統會給一個問候,再次發送"Hello",它將告訴你發送了多少次"Hello"。
?
4.2?創建ClientData池
??? 現在我們知道ClientData可以正常工作了。但是對每一個連接QuickServer的客戶端都要創建一個新的ClientData對象,可能會造成性能上的瓶頸,尤其對性能要求較高的服務器來說。
??? 我們可以創建一個ClientData池對象,無論客戶端什么時候進行連接,都使用同一個對象。首先實現下面的接口:
??? org.quickserver.util.pool.PoolableObject
??? 查找QuickServer API文檔可以發現PoolableObject只有兩個必須實現的方法:
??? org.apache.commons.pool.PoolableObjectFactory屬于通常的工廠方法。
??? isPoolable()判斷對象是否可以成為池對象。
PoolableObjectFactory
org.apache.commons.pool.PoolableObjectFactory接口包含了以下方法:
o?void activateObject(Object obj):重新初始化一個實例。
o?void destroyObject(Object obj):銷毀一個不再需要的實例。
o?Object makeObject():創建一個實例。
o?void passivateObject(Object obj):禁止初始化一個實例。
o?boolean validateObject(Object obj):確定一個實例是否安全。
??? 我們可以擴展一個基于無操作的實現來創建可"池"化的對象:
??? org.apache.commons.pool.BasePoolableObjectFactory
??? 這個類只有一個抽象方法makeObject()和一個validateObject()方法,它只返回true。
??? 我們來創建一個EchoServerPoolableData類。
01 //---- EchoServerPoolableData.java ---- 02 package echoserver; 03 04 import org.quickserver.net.server.*; 05 import java.io.*; 06 07 public class EchoServerPoolableData 08 extends EchoServerData 09 implements org.apache.commons.pool.PoolableObjectFactory { 10 11 public void activateObject(Object obj) { 12 } 13 public void destroyObject(Object obj) { 14 if(obj==null) return; 15 passivateObject(obj); 16 obj = null; 17 } 18 public Object makeObject() { 19 return new EchoServerPoolableData(); 20 } 21 public void passivateObject(Object obj) { 22 EchoServerPoolableData pd = (EchoServerPoolableData)obj; 23 pd.setHelloCount(0); 24 pd.setUsername(null); 25 } 26 public boolean validateObject(Object obj) { 27 if(obj==null) 28 return false; 29 else 30 return true; 31 } 32 } 33 //--- end of code --- |
??? 這個類擴展了我們的EchoServerData,然后我們實現了org.apache.commons.pool.BasePoolableObjectFactory,這個實現是簡單的不需要解釋了。
??? 現在我們需要告訴QuickServer使用這個類來代替原來的ClientData類。
??????? myServer.setClientData("echoserver.EchoServerPoolableData");
??? 編譯修改的程序,可能會報如下錯誤:
??????? package org.apache.commons.pool does not exist。
??? 這是因為編譯器不知道這個類。可以在環境變量中添加D:\QuickServer\dist\commons-pool.jar包,并在運行時
??? set classpath=%classpath%;d:\QuickServer\dist\QuickServer.jar; d:\QuickServer\dist\commons-pool.jar;.\(類所在文件夾)即可。
posted on 2006-12-04 19:45
OMG 閱讀(1129)
評論(0) 編輯 收藏 所屬分類:
Soket