請注意: 做好習題請到 e3.nctu.edu.tw 本題討論版依照規定 post,
且 Subject: LAB08 from 學號姓名
報告的內容是心得, 其他包括Running Script 與帶Line numbers程式碼當夾檔副件
LAB08 身分證驗證程式 網路版 (簡易網路程式)
Due: 2011/04/24 Sunday 23:59 (提醒2011/04/18期中考!)
(考量大家期中考;本題 Due 延期到2011/04/30 Saturday 23:59)
Description:
本題是要把 LAB04 的 Java Application 改成網路版(用你寫版本來改),
配合我給的 TCP Server 範例 HaLa.java 稍作修改, 搭配執行,
以便讓全世界的台灣人可以用 telnet 命令連進來 check 其身分證,
或是生出一個可用的身分證號碼 :-)
Purpose: 藉由修改 HaLa.java 搭配 ID check 的 Java Application 成為
網路版的 ID check 程式, 了解網路程式TCP Server 在 Java有多簡單:-)
(HaLa.java 是一支典型的 TCP Server 程式; 以後很有用!:)
Hint:
( 其實這題很簡單 :-! 大部份同學應該 10 分鐘內可以改完:-)
(1) 用 fc (或 diff) 比較 HaLa.java 與 HaLaBat.java (在network_BATNUM/)
研究改兩檔案之差異! (fc 是 file compare工具, diff 是 Unix 系統的工具)
(2) 研究 HaLaBat.java 如何搭配 Bat6OK.java, 並須實際測試後寫心得在報告裡;
注意是這次給的 Bat6OK.java, 與以前給的 Bat6ok.java (小寫ok)略有差異!
再參考 HaLaBat.java, 改為可配合你 LAB04 寫的 ID check Java application
你可直接改 HaLa.java, 或 copy 到 別的檔名(但記得裡面也要配合改!)
(3) 把 LAB04 的ID check程式拿來多寫一個 constructor 給你新 HaLa.java 用
這樣就大功告成了! (參考新的 Bat6OK.java 裡面的 Constructor)
注意這樣你改後的 LAB04 之 ID check 程式配合 HaLa.java 就成網路版!
不過它仍可以當作單機版獨立執行!
因為新寫的 Constructor 只給你新的 HaLa.java用
參考所給範例:
http://www.cs.nctu.edu.tw/~tsaiwn/oop/03_Labs/Lab08/
(a.) 人透過網路與電腦簡易對談的程式 HaLa.java
先分開測試, HaLa.java 執行後要從別的電腦 telnet 進去測試!
抓 HaLa.java
(b.) 在 LAB04 就看過的 BATNUM 之 C 版與 Java 版
抓 bat6ok.c
抓 單機版 bat6ok.java
注意該 bat6ok.java (小寫 ok)單機版還不能搭配HaLa.java 喔!
(c.) 從 (b.) 之 Java 版改為網路版的 BATNUM,
可在本目錄之下的子目錄 network_BATNUM 之下找到.
網路版 BATNUM
** 注意因為Bat6OK.java有同名檔案 bat6ok.java, 要注意自己放不同目錄!
(註: Unix 上大小寫不同就是不同檔案, 但 Windows 上同名但大小寫不同是沒用的!)
* 仔細研究 network_BATNUM 下 範例,
研究該 Bat6OK.java 與本目錄下之前版本叫 bat6ok.java (注意大小寫)差異,
注意名稱雖幾乎相同(Unix上算不同), 但內容有些些不同!
並研究 HaLaBat.java 相對於 HaLa.java 到底改了哪幾行?
(HaLaBat.java 是用來配合 Bat6OK.java)
* 再測試並研究 network_BATNUM 下檔案, 編譯後並測試看看!
javac HaLaBat.java
java HaLaBat
* 研究如何把 HaLaBat.java 與 Bat6OK.java 結何成網路版 BATNUM?
(各只改一點點!)
(想不出或看不懂就找助教或同學討論:)
* 其實之前給大家的 bat.jar 裡面有個會先印出你的 IP 的 netBat.zip
在演習課時助教也 Demo 過讓大家連進來玩過 ! (batnet.zip)
這次順便再附上, 請看目錄內有個 new_netBat 子目錄, 就在裡面啦!
(執行: java -jar netBat.zip )
*** 注意 network_BATNUM 下的 Bat6OK.java 雖與此目錄的 bat6ok.java不同,
但 在 network_BATNUM 之下的 Bat6OK.java 仍可單獨執行!!!
You are the
1 //HaLa.java -- by tsaiwn@csie.nctu.edu.tw
2 //javac HaLa.java
3 //java HaLa
4 //then you can use "telnet hostName 3388" to connect this program
5 import java.io.*;
6 import java.net.*;
7 import java.util.*;
8 import java.lang.*; // NOT necessary
9 class HaLa { // should be same as the File name, 建議該用大寫開頭 HaLa
10 ServerSocket ssk; // 用來等待連線
11 Socket ccc; // 有client連線進來可生出 Socket 小洞
12 int portNumber = 3388; // Listening TCP port
13 PrintStream cout = System.out; // 取個與 C++ 同名的物件變數
14 PrintStream cerr = System.err;
15 public static void main(String[ ] xxx) throws Exception {
16 // ^^^ 宣稱若出問題會報告馬政府 !
17 new HaLa( ); // 要求升出 HaLa 物件並立刻執行 HaLa( ) 建構子
18 }
19 HaLa( ) { // 這就是 class HaLa 的 constructor 建構子
20 cout.println("Server started ... at port "+ portNumber);
21 try {
22 ssk = new ServerSocket( portNumber );
23 while(38 == 38) { // true
24 ccc = ssk.accept( ); // 等待連線後取得連線暫稱 ccc
25 Brother b = new Brother(ccc); // 請一個小弟, 並把連線交給小弟
26 b.start( ); // 叫小弟開始處理該連線
27 }
28 } catch(Exception e) {
29 cerr.println("Cannot startup ChatServer at port "+ portNumber+"!");
30 cerr.println("Maybe port "+portNumber+" has been used?\nByebye.");
31 }
32
33 } // HaLa( )
34 } // class HaLa
35 ////////
36 class Brother extends Thread { // 表示這 class Brother 是一個 Thread 執行緒
37 Socket mySock; // 用來記住"老大"傳來的資料, 方便這class內其他 function 用
38 Date now;
39 private String myName;
40 private String remoteAddr; // 連著我的對方 IP address
41 private BufferedReader in;
42 private PrintWriter out;
43 private PrintStream cout = System.out; // cout 只是簡寫而已
44 Brother(Socket x) {
45 mySock = x;
46 now = new Date( );
47 try {
48 remoteAddr = mySock.getInetAddress().getHostAddress();
49 // 然後取得InputStream並包成 BufferedReader 方便 readLine()
50 in = new BufferedReader(
51 new InputStreamReader(mySock.getInputStream()) );
52 // 再取得 OutputStream 並包成 PrintWriter
53 out = new PrintWriter(
54 new OutputStreamWriter(mySock.getOutputStream()), true );
55 cout.println("Friend from " + remoteAddr + " at " + now);
56 } catch(Exception e) {
57 // say something
58 }
59 }
60 public void run( ) { // 規定 Thread 內一定要這樣寫才有用
61 // 接著, 可以 與 連線者對話
62 try {
63 while(true) {
64 out.print("Say: "); out.flush( );
65 String s = in.readLine( );
66 if(s.equals("/quit")) break;
67 int k = (int) (5*Math.random( ));
68 if(k < 1 ) out.println(" You said " + s);
69 else if(k < 3 )out.println(" Really?");
70 else out.println(" Ohh .. my God !");
71 }
72 out.println("Bye!");
73 try { mySock.close( ); }catch(Exception e) { }
74 }catch(Exception e) { }
75 cout.println("leaving ... " + remoteAddr);
76 } // run( )
77 } // class Brother
1 //idcheck.c -- by tsaiwn@csie.nctu.edu.tw
2 //gcc idcheck.c
3 //寫個程式可以檢查身分證號碼是否正確
4 //Extra credit: 輸入 999 則產生一個可用的合法身分證號碼
5 ////////////
6 #include <stdio.h>
7 #include <ctype.h>
8 void generateID( ), squeeze(char*); // 宣告兩個 function
9 int testID(char*); // 查驗身分證各種可能的錯誤 並傳回錯誤代碼
10 void printError(int code); // print error message according code
11
12 // 注意 Java 不需要宣告, 因為在 class 內沒有先後關係
13
14 int main( ) {
15 static char id[99]; // 夠'長吧 :-) 身分證才 10 碼啦
16 int code = 0; // 用來記住錯誤代碼
17 while(38 == 38) { // for( ;; ) {
18 printf("請輸入身分證號碼 Input ID: ");
19 fgets(id, sizeof(id), stdin); // 整列讀入 到 id
20 // check EOF
21 if(feof(stdin)) break; //EOF == ^D in Unix; ^Z on DOS/Window
22 squeeze(id); // 把所有空白都去掉 white space
23 //printf("strlen(id)=%d\n", strlen(id));
24 if(strcmp(id, "-1") == 0) break; //Java 用 id.equals("-1")
25 if(strcmp(id, ".") == 0) break;
26 if(strcmp(id, "quit") == 0) break;
27 if(id[1] == 'U') break; // QUIT ? // Java 用 id.charAt(1)==
28 if(strcmp(id, "999") == 0) {generateID( ); continue; }
29 code = testID(id); // 取得錯誤代號, 0 表示無錯 :-)
30 printf(" ID %s is ", id);
31 if(code==0) printf(" OK.\n"); // 印出說這號碼正確
32 else {
33 printf(" Error ID! Reason(s):\n");
34 }//if
35 printError(code); // 依據 code 印訊息
36 }// while(
37 printf("\r\nThank you and bye bye!\n");
38 return 0;
39 }//main(
40
41 //關於 squeeze(char*) 這好用的 function, 因為 C 程式庫沒有,自己寫:
42 //注意 NewLine 也算 white space, 所以這函數也會把尾巴的 '\n' 拿掉!
43 // 因為 fgets 讀入的資料尾巴有 NewLine; C++ 的 getline 則沒有 NewLine!
44 void squeeze(char*p) { // 擠掉所有的 white space; Java 要如何做呢?
45 char*p2 = p;
46 if(*p == 0)return; // NULL terminated, 一開始就字串結束: 空字串
47 while(*p2 !=0) { // white space 請看 K&R課本第二章與附錄 B
48 if(isspace(*p2)) { ++p2; continue; } // 丟掉 white space
49 *p = *p2;
50 p++; ++p2; // advance one char
51 }//while
52 *p = *p2; // 0 == '\0' == NULL
53 }// squeeze(
54
55 // 關於 int testID(char* id) : 依據身分證規則查看 id 傳回錯誤代碼
56 int yy[ ]={ 10,11,12,13,14,15,16,17, 34, //ABCDEFGH I
57 18,19,20,21,22, 35, //JKLMN O
58 23,24,25,26,27,28, //PQRSTU
59 29,32,30,31, 33 }; //VWXY Z
60 int checkSum(char * id){ // 幫忙算 checkSum 給 testID(id) 用
61 int sum, i; // 因編碼沒完全照字母順序, 用算的要很多 if(...
62 int ynum;
63 // 用查表法 table look up 查出字母對應的兩位數較簡單直覺 !
64 // 先建個表 int yy[ ] = { 10, 11, 12, 13, ...}; // 照規定 AB..
65 // 然後 Let i = id[0]字母減去 'A' 得到 0..25
66 // 再查出 yy[i] 拿來用: ynum = yy[i]; // 10..O是35..Z不是35 !!
67 i = id[0] - 'A';
68 ynum = yy[i];
69 sum = ynum/10 + 9* (ynum%10); // weight 1, 9, [876543211]
70 for(i=1; i<=8; ++i) sum += (id[i] - '0') *(9-i); // 87654321
71 sum += (id[9] - '0') ; // *1 檢查碼 weight 也是 1
72 return sum; // 我只負責算出 checksum
73 }//checkSum(
74
75 int testID(char* id) { //傳回錯誤代碼, 可用 bitwise "&" 運算找出
76 int i, ans = 0, sum=0; // sum 用來算 weighted check sum
77 id[0] = toupper(id[0]); // 轉為大寫
78 if(!isalpha(id[0])) ans = ans + 1; // 1 號錯 ans = ans | 1;
79 if(id[1] != '1' && id[1] != '2') ans += 2; // 2 號錯 男生女生?
80 if(strlen(id) < 10) ans += 4; // 太短
81 if(strlen(id) > 10) ans += 8; // 太長
82 for(i=1; i<=9; ++i) if(!isdigit(id[i])) ans = ans | 16; // 非數字
83 if((ans&16) != 0) return ans; // 有非數字不用再算 check sum 啦
84 if(ans != 0) return ans; // 有任何錯就..就不用再算 check sum 啦
85 sum = checkSum(id); // 假設沒有其他怪字就算出 check sum
86 if(sum%10 != 0) ans |= 32; // 必須除以 10 除得盡才對
87 return ans;
88 }// testID(
89
90 char what[ ][88]={ "對啦!!這是合法的身份證字號", //訊息0
91 "ㄟ..第一個字必須是字母啦!", // 訊息1
92 "你是第三性嗎?", // 訊息2
93 "太短了!不足碼唷!!", // 訊息3
94 "怎麼會有這麼多碼!!", // 訊息4
95 "打錯啦!!應該是數字喔!!", // 訊息5
96 "神秘數字算出來是錯的??" // 訊息6
97 }; // do NOT forget the ";"
98 // String what[ ] = { ... }; // in Java
99 void printError(int code) { // print all errors found use bitwise and
100 int i, yy[ ] = {0, 1, 2, 4, 8, 16, 32, 64, 128};
101 if(code == 0) { printf("%s\n", what[0]); return; }
102 for(i=1; i <= 6; ++i)if((code&yy[i]) != 0)printf("%s\n", what[i]);
103 }// printError(
104
105 void generateID( ) {
106 char id[11]={ 0 }; // 會全放 0 (NULL) == '\0' == 0
107 int i;
108 id[0] = 'A' + rand( ) % 26; // 'A' .. 'Z'
109 id[1] = '1' + rand( ) % 2; // '1' .. '2'
110 for(i=2; i<=8; ++i) {
111 // 用亂數生出 id[2] .. id[8]
112 id[i] = '0' + rand( )%10; // '0' .. '9'
113 } //
114 /// id[9] 是檢查碼, 要算, 可先塞 '0' 偷叫 checkSum( )再調整
115 id[9] = '0';
116 i = checkSum(id); // 借用 i 來存 checkSum
117 i = i % 10;
118 if(i != 0) id[9] = '0' + (10-i); // 更正檢查碼
119 printf(" Good ID: %s\n", id); // legal ID now
120 }// generateID(