Welcome to Yumao′s Blog.
T-ELTS Project Day04
====================
不開口, 沒有人知道你想什麼
不去做, 任何想法只在腦海里游泳
不邁出腳步, 永遠找不到你前進的方向
1 窗口的居中處理
2 考試時間提醒與超時交卷
3 設置登錄界面的默認按鈕
4 更新考試界面按鈕狀態
5 方法調用實現, Utils.java類
6 無持續狀態連接協議實現
7 代理模式 實現 ExamService接口, login() 方法部分代碼
8 網絡代理層 工作原理
9 軟件實現建議
1 窗口的居中處理
private void center(Window win){
Toolkit toolkit = Toolkit.getDefaultToolkit();
Dimension screen = toolkit.getScreenSize();
int x = (screen.width - win.getWidth()) / 2;
int y = (screen.height - win.getHeight())/2;
win.setLocation(x, y);
}
public void show(){
center(welcomeWindow);
welcomeWindow.setVisible(true);
final Timer timer = new Timer();
timer.schedule(new TimerTask(){
public void run() {
welcomeWindow.setVisible(false);
center(loginFrame);
loginFrame.setVisible(true);
timer.cancel();
}
}, 2000);
}
2 考試時間提醒與超時交卷
1) 更新控制器 start方法添加: startTimer();
2) 更新控制器 添加
private Timer timer;
private void startTimer(){
timer = new Timer();
int timeout = examInfo.getTimeLimit()*60*1000;
final long end = System.currentTimeMillis() + timeout;
timer.schedule(new TimerTask(){
public void run() {
//show 是需要顯示的剩餘時間
long show = end - System.currentTimeMillis();
examFrame.updateTime(show);
}
}, 0, 1000);
timer.schedule(new TimerTask(){
public void run() {
gameOver();//提前交卷!
}
}, timeout);
}
public void gameOver() {
try {
timer.cancel();
int idx = currentQuestion.getQuestionIndex();
List userAnswers =
examFrame.getUserAnswers();
examService.saveUserAnswers(idx, userAnswers);
int score = examService.examOver();
JOptionPane.showMessageDialog(
examFrame, "你的分數:"+score);
examFrame.setVisible(false);
menuFrame.setVisible(true);
} catch (Exception e) {
e.printStackTrace();
JOptionPane.showMessageDialog(
examFrame, e.getMessage());
}
}
public void send() {
int val = JOptionPane.showConfirmDialog(examFrame, "交嗎?");
if (val != JOptionPane.YES_OPTION) {
return;
}
gameOver();
}
3) 更新examFrame 添加顯示時間方法
private JLabel timer;
public void updateTime(long show) {
long h = show/1000/60/60;
long m = show/1000/60%60;
long s = show/1000%60;
if(m<=5 && h==0){
timer.setForeground(Color.red);
}
timer.setText(h+":"+m+":"+s);
}
3 設置登錄界面的默認按鈕
更新LoginFrame方法: createBtnPane() 添加:
getRootPane().setDefaultButton(login);
4 更新考試界面按鈕狀態:
1)添加
private JButton next;
private JButton prev;
private void updateButton(int count , int index){
prev.setEnabled(index!=0);
next.setEnabled(index!=count-1);
}
2) 修改方法updateView() 增加:
updateButton(examInfo.getQuestionCount(),
questionInfo.getQuestionIndex());
3) 將next, prev 引用引用到界面按鈕控件對象.
5 方法調用實現, Utils.java類
1) 反射方法調用
public static Response call(
Object obj, Request request){
Response res = new Response();
Class cls = obj.getClass();
try {
Method m = cls.getDeclaredMethod(
request.getMethod(), request.getTypes());
Object val = m.invoke(obj, request.getParams());
res.setValue(val);
return res;
} catch (InvocationTargetException e) {
e.printStackTrace();
//方法執行異常
Exception ex = (Exception) e.getTargetException();
res.setException(ex);
return res;
}catch(Exception e){
e.printStackTrace();
res.setException(e);
return res;
}
}
方法調用測試:
public class MethodCallTest {
public static void main(String[] args) {
Request request =
new Request("charAt",
new Class[]{int.class},
new Object[]{2});
Response res = Utils.call("ABCDE", request);
System.out.println(res);//C
ExamServiceImpl service = new ExamServiceImpl();
Config config = new Config("client.properties");
EntityContext entityContext = new EntityContext(config);
service.setEntityContext(entityContext);
//可以任意調用 service 對象的任何方法
request = new Request("login",
new Class[]{int.class, String.class},
new Object[]{1001, "1234"});
Response r = Utils.call(service, request);
System.out.println(r);
}
}
2) 請求與反饋對象序列化 與 遠程調用
將Request和Response 對象利用網絡流傳輸, 就可以實現網絡遠程
方法調用
6 無持續狀態連接協議實現
1) 無連接協議原理: 建立連接-發送請求-接收響應-斷開連接
優點: 充分重用服務器的網絡服務能力,
缺點: 不能保持持久連接狀態, 不能記住是否已經連接
客戶端: 建立連接-發送請求-接收響應-斷開連接
com.tarena.test.ClientDemo.java
服務器: 建立連接-接收請求-處理-發送響應-斷開連接
com.tarena.elts.net.ExamServer.java
2) 無連接協議的狀態保持:
狀態保持目的: 識別是否已經來過
採用令牌機制實現: 每次發送請求都帶着令牌發送, 服務器檢查
令牌是否可用, 如果不可用創建新的令牌, 服務器每次發送響應
都包含令牌信息. 服務器通過比較令牌是否在以登記的集合中,來
確定是那個客戶
3) 網絡服務端實現:
/** 考試應用服務器 */
public class ExamServer {
private Config config;
/** 服務列表, 每個SID對應一個 ExamService 實例,
* 每個客戶對應一個SID */
Map serviceMap =
new HashMap();
private EntityContext entityContext;
public void setEntityContext(EntityContext entityContext) {
this.entityContext = entityContext;
}
public ExamServer() {
}
public void setConfig(Config config) {
this.config = config;
}
public void start(){
int port = config.getInt("ServerPort");
try {
ServerSocket ss = new ServerSocket(port);
while(true){
//等待客戶端的連接
Socket s = ss.accept();
new Service(s).start();
}
} catch (IOException e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
class Service extends Thread{
Socket s;
public Service(Socket s) {
this.s = s;
}
@Override
public void run() {
try {
// 接收請求
ObjectInputStream in =
new ObjectInputStream(s.getInputStream());
Request req = (Request)in.readObject();
//處理-
//String obj = "ABCD";
//根據用戶請求req, 找到對應SessionID 的考試服務對象
//一個考試服務對象對應於一個考試客戶端(一個考生)
//如果請求req 中 沒有SessionID getService()方法會
//創建新的SessionID和ExamService 實力
ExamService service = getService(req);
Response res = Utils.call(service, req);
res.setSessionID(req.getSessionID());
//發送響應
ObjectOutputStream out =
new ObjectOutputStream(s.getOutputStream());
out.writeObject(res);
//斷開連接
s.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
private ExamService getService(Request req) {
String sid = req.getSessionID();
if(sid==null){//如果沒有SID 就創建新的
sid = UUID.randomUUID().toString();
req.setSessionID(sid);
}
ExamService service = serviceMap.get(sid);
if(service==null){//第一次訪問服務, 需要創建新的service
ExamServiceImpl serviceImpl =
new ExamServiceImpl();
serviceImpl.setEntityContext(entityContext);
serviceMap.put(sid, serviceImpl);
service = serviceImpl;
}
return service;
}
/** 啟動服務器 */
public static void main(String[] args) {
ExamServer server = new ExamServer();
Config config = new Config("server.properties");
EntityContext entityContext =
new EntityContext(config);
server.setConfig(config);
server.setEntityContext(entityContext);
server.start();
}
}
4) 網絡服務器啟動代碼:
public static void main(String[] args) {
ExamServer server = new ExamServer();
Config config = new Config("server.properties");
EntityContext entityContext =
new EntityContext(config);
server.setConfig(config);
server.setEntityContext(entityContext);
server.start();
}
}
5) 網絡服務端測試:
public class ExamServerTest {
public static void main(String[] args)
throws Exception {
//建立連接
Socket socket = new Socket("localhost", 9091);
//建立建立-發送請求-接收響應-斷開連接
ObjectOutputStream out =
new ObjectOutputStream(socket.getOutputStream());
ObjectInputStream in =
new ObjectInputStream(socket.getInputStream());
Request request = new Request("login",
new Class[]{int.class, String.class},
new Object[]{1001, "12345"});
out.writeObject(request);//發送請求
Response r = (Response)in.readObject(); //接收響應
socket.close();// 斷開連接;
System.out.println(request);
System.out.println(r);//人名
}
}
7 代理模式 實現 ExamService接口, login() 方法部分代碼
1) login() 方法部分代碼
public User login(int id, String pwd) throws IdOrPwdException {
Request req = new Request();
req.setMethod("login");
req.setTypes(new Class[] { int.class, String.class });
req.setParams(new Object[] { id, pwd });
Response r = call(req);
if (r.isSuccess()) {
return (User) r.getValue();
} else if (r.getException() instanceof IdOrPwdException) {
throw (IdOrPwdException) r.getException();
} else {
throw new RuntimeException(r.getException());
}
}
2) 遠程調用工具方法, 在Utils.java中:
/**
* 遠程方法調用
* @param host 遠程主機
* @param port 端口號
* @param req 方法請求
* @return 運行結果
*/
public static Response remoteCall( String host,
int port, Request req){
try{
Socket s = new Socket(host, port);//建立連接
//發送請求
ObjectOutputStream out =
new ObjectOutputStream(s.getOutputStream());
System.out.println("remoteCall request:"+req);
out.writeObject(req);
//獲取響應
ObjectInputStream in=
new ObjectInputStream(s.getInputStream());
Response res = (Response)in.readObject();
System.out.println("remoteCall Response:"+res);
return res;
}catch(Exception e){
e.printStackTrace();
throw new RuntimeException(e);
}
}
2) 網絡客戶端啟動代碼, 使用 服務網絡代理, 替換業務層
public class ClientMain {
public static void main(String[] args) {
//配置文件
Config config = new Config("client.properties");
//界面層= 視圖+控制器
//視圖
LoginFrame loginFrame = new LoginFrame();
MenuFrame menuFrame = new MenuFrame();
ExamFrame examFrame = new ExamFrame();
WelcomeWindow welcomeWindow = new WelcomeWindow();
//控制器
ClientContext clientContext = new ClientContext();
//業務模型
// ExamServiceImpl examService = new ExamServiceImpl();
ExamServiceAgent examService = new ExamServiceAgent();
//組裝 界面層 業務層 和 數據層
loginFrame.setClientContext(clientContext);
menuFrame.setClientContext(clientContext);
examFrame.setClientContext(clientContext);
clientContext.setExamFrame(examFrame);
clientContext.setLoginFrame(loginFrame);
clientContext.setMenuFrame(menuFrame);
clientContext.setWelcomeWindow(welcomeWindow);
//插接 examService 到 表現層控制器
clientContext.setExamService(examService);
examService.setConfig(config);
//啟動軟件
clientContext.show();
}
}
8 網絡代理層 工作原理:
1) 客戶端業務請求到網絡代理客戶端
2) 網絡代理將請求通過網絡發送到服務器
3) 服務器接收請求通過令牌識別客戶端, 找到合適的業務層實例
4) 服務器利用反射調用業務層實例的業務方法.
5) 服務器將業務執行結果發送給客戶端代理
6) 客戶端代理將結果返回給客戶端界面
9 軟件實現建議
1) 完整實現桌面版為主要目標
2) 實現網絡代理功能為擴展目標
3) 以測試驅動開發, 步步為營, 逐步遞歸達到目的
4) 堅持編碼->測試->Debug, 苦盡甘來.
5) 完整嘗試重新實現一遍, 意猶未盡.