Welcome to Yumao′s Blog.
T-ELTS Project Day04
, 2012年03月26日 , Java Language , 评论 在〈T-ELTS Project Day04〉中留言功能已關閉 ,

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) 完整嘗試重新實現一遍, 意猶未盡.
关键字:,

评论已关闭