1 / 57

課程參與度之評估方式

課程參與度之評估方式. 上課時必須專心聽講,跟上進度,參與討論 扣分項目 玩線上遊戲一次扣 1 分 玩手機一次扣 1 分 睡覺一次扣 1 分 聊天一次扣 1 分 無法回答老師提出的問題一次扣 1 分 加分項目 主動回答老師的問題一次加 2 分 找出老師程式中的錯誤一次加 1 分 修正老師程式中的錯誤一次加 4 分. 網路程式設計 第十章 TELNET(BBS) 程式設計 -NIO. 鄧姚文. 大綱. 連線並讀取資料 設計回應命令的機制 物件導向設計 TelnetClientNio 類別 Telnet 伺服器. 10-1 連線並讀取資料.

howie
Download Presentation

課程參與度之評估方式

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  1. 課程參與度之評估方式 • 上課時必須專心聽講,跟上進度,參與討論 • 扣分項目 • 玩線上遊戲一次扣1分 • 玩手機一次扣1分 • 睡覺一次扣1分 • 聊天一次扣1分 • 無法回答老師提出的問題一次扣1分 • 加分項目 • 主動回答老師的問題一次加2分 • 找出老師程式中的錯誤一次加1分 • 修正老師程式中的錯誤一次加4分

  2. 網路程式設計第十章 TELNET(BBS)程式設計-NIO 鄧姚文

  3. 大綱 連線並讀取資料 設計回應命令的機制 物件導向設計 TelnetClientNio 類別 Telnet伺服器

  4. 10-1 連線並讀取資料 InetSocketAddressaddr = new InetSocketAddress("localhost", 23); SocketChannelchann = SocketChannel.open(addr); ByteBufferbuf = ByteBuffer.allocate(1024); chann.read(buf); buf.flip(); • 利用InetSocketAddress類別建立物件 • 呼叫SocketChannel的open方法 • 準備緩衝區物件(ByteBuffer) • 資料讀取至緩衝區 • 讀取指標位置移至第一個位置,準備後續循序讀取處理

  5. 10-1 連線並讀取資料 while(buf.hasRemaining()){ byte b = buf.get(); int data = b & 0xFF; System.out.print(data+","); } 利用while迴圈循序讀取緩衝區內的位元組,並顯示於主控台上: 上述程式碼中,由於Java的byte資料型態將第一個位元認定為正負辨認位元,因此筆者將得到的位元組轉為整數型態,以便後續能使用它以辨識是否為命令或資料。完整範例程式碼如下:

  6. 10-1 連線並讀取資料 01 package com.ch10; 02 03 import java.io.IOException; 04 import java.net.InetSocketAddress; 05 import java.nio.ByteBuffer; 06 import java.nio.channels.SocketChannel; 07 08 public class TelnetTester { 09 public static void main(String[] args) throws IOException { 10 InetSocketAddressaddr = new InetSocketAddress( 11 "localhost", 23); 12 SocketChannelchann = SocketChannel.open(addr); 13 ByteBufferbuf = ByteBuffer.allocate(1024); 14 chann.read(buf); 15 System.out.println("緩衝區有效資料個數:" + buf.position()); 16 buf.flip(); 17 while (buf.hasRemaining()) {

  7. 10-1 連線並讀取資料 18 byte b = buf.get(); 19 int data = b & 0xFF; 20 System.out.print(data + ","); 21 } 22 } 23 } ▌執行結果 緩衝區內的有效資料個數:21 255,253,37,255,251,1,255,251,3,255,253,39,255,253,31,255,253,0,255,251,0,

  8. 10-1 連線並讀取資料 • 依照第8章的TELNET協定內容說明以及RFC854規範,解讀後可得知伺服器送來的命令為: • IAC DO AUTH:請執行認證選項。 • IAC WILL ECHO:想要執行回音選項。 • IAC WILL SG:想要執行subnegotiation選項。 • IAC DO NEW-ENV:請執行傳送環境變數選項。 • IAC DO NAWS:請執行客戶端畫面資訊選項。 • IAC DO BINARY:請執行二元制資料傳送選項。 • IAC WILL BINARY:想要執行二元制資料傳送選項。

  9. 10-2 設計回應命令的機制 • ByteBuffersbuf = ByteBuffer.allocate(1024); • sbuf.put((byte)IAC); • sbuf.put((byte)WONT); • sbuf.put((byte)option); • sbuf.flip(); • chann.write(sbuf); • 準備一個傳送專用的緩衝區 • 緩衝區的put方法,將資料放入緩衝中(放入255,252,37三個byte) • 最後呼叫write方法將資料送出

  10. 10-2 設計回應命令的機制 • 回應功能實作 • 設計一個專門處理命令的方法handleCommand,如果傳入的值是255(IAC),即交由該方法處理

  11. 01 public static void handleCommand(SocketChannelchann, 02 ByteBufferbuf) throws IOException { 03 ByteBufferoutBuf = ByteBuffer.allocate(1024); 04 int tone = buf.get() & 0xFF; 05 int option = buf.get() & 0xFF; 06 System.out.println(tone + "," + option); 07 outBuf.clear(); 08 if (tone == DO) { 09 outBuf.put((byte) IAC); 10 outBuf.put((byte) WONT); 11 outBuf.put((byte) option); 12 outBuf.flip(); 13 chann.write(outBuf); 14 System.out.println(" SENT:" + IAC + "," + WONT + "," 15 + option);

  12. 10-2 設計回應命令的機制 • 回應功能實作 • 設計一個專門處理命令的方法handleCommand,如果傳入的值是255(IAC),即交由該方法處理

  13. 16 } else if (tone == WILL) { 17 outBuf.put((byte) IAC); 18 outBuf.put((byte) DONT); 19 outBuf.put((byte) option); 20 outBuf.flip(); 21 chann.write(outBuf); 22 System.out.println(" SENT:" + IAC + "," + DONT + "," 23 + option); 24 } 25 }

  14. 10-2 設計回應命令的機制 ▌程式碼說明 ‧第1-2行,定義handleCommand的規格,接收兩個參數,一是SocketChannel物件chann,另一個是緩衝區物件buf。 ‧第3行,預先準備傳送資料專用緩衝區outBuf。 ‧第4行,讀取IAC後面的語氣值,可能是DO或WILL。 ‧第5行,讀取語氣值後面的選項。 ‧第8-15行,當伺服器傳來請求(DO)執行時,回應否定(WONT)該選項。 ‧第16-24行,當伺服器傳來想要(WILL)執行時,回應拒絕(DONT)該選項。 • 回應功能實作

  15. 10-2 設計回應命令的機制 • 整合測試 • 最後加上部份顯示資料,目的是讓讀者在執行過程中能看到互動情形,最後完成的初階應用程式TelnetTester類別程式碼如下:

  16. 01 package com.ch10; 02 03 import java.io.IOException; 04 import java.net.InetSocketAddress; 05 import java.nio.ByteBuffer; 06 import java.nio.channels.SocketChannel; 07 08 public class TelnetTester { 09 public static finalint IAC = 255; 10 public static finalint WILL = 251; 11 public static finalint WONT = 252;

  17. 10-2 設計回應命令的機制 • 12 public static finalint DO = 253; • public static finalint DONT = 254; • 14 public static finalint TERMINAL_TYPE = 24; • 15 public static finalint SB = 250; • 16 public static finalint SE = 240; • 17 • 18 public static void main(String[] args) throws IOException { • 19 InetSocketAddressaddr = new InetSocketAddress( • 20 "localhost", 23); • 21 SocketChannelchann = SocketChannel.open(addr); • 22 ByteBufferbuf = ByteBuffer.allocate(1024); • 23 chann.read(buf); • 24 System.out.println("緩衝區有效資料個數:" + buf.position()); • 25 buf.flip(); • 26 while (buf.hasRemaining()) { • 27 byte b = buf.get(); • 28 int data = b & 0xFF; • 29 if (data == IAC) { • 30 System.out.println();

  18. 10-2 設計回應命令的機制 31 System.out.print(data + ","); 32 handleCommand(chann, buf); 33 } else { 34 System.out.print((char) data); 35 } 36 } 37 } 38 39 public static void handleCommand(SocketChannelchann, 40 ByteBufferbuf) throws IOException { 41 ByteBufferoutBuf = ByteBuffer.allocate(1024); 42 int tone = buf.get() & 0xFF; 43 int option = buf.get() & 0xFF; 44 System.out.println(tone + "," + option); 45 outBuf.clear(); 46 if (tone == DO) { 47 outBuf.put((byte) IAC); 48 outBuf.put((byte) WONT);

  19. 10-2 設計回應命令的機制 49 outBuf.put((byte) option); 50 outBuf.flip(); 51 chann.write(outBuf); 52 System.out.println(" SENT:" + IAC + "," + WONT + "," 53 + option); 54 } else if (tone == WILL) { 55 outBuf.put((byte) IAC); 56 outBuf.put((byte) DONT); 57 outBuf.put((byte) option); 58 outBuf.flip(); 59 chann.write(outBuf); 60 System.out.println(" SENT:" + IAC + "," + DONT + "," 61 + option); 62 } 63 } 64 }

  20. 10-2 設計回應命令的機制 ▌執行結果 緩衝區有效資料個數:21 255,253,37 SENT:255,252,37 255,251,1 SENT:255,254,1 255,251,3 SENT:255,254,3 255,253,39 SENT:255,252,39 255,253,31 SENT:255,252,31 255,253,0 SENT:255,252,0 255,251,0 SENT:255,254,0

  21. 10-3 物件導向設計TelnetClientNio類別 TelnetClientNio client = new TelnetClientNio("localhost", 23); client.connect(); • 類別規劃 • 設計類別時可先以未來該類別的使用者的觀點開始,如果是個可重覆使用的類別,應該可以用以下方式產生,並進行連線功能:

  22. 10-3 物件導向設計TelnetClientNio類別 public class TelnetClientNio extends Thread{ ... } String host; int port; SocketChannel channel; public TelnetClientNio(String host, int port) { this.host = host; this.port = port; } • 屬性與建構子 • TelnetClientNio類別繼承了java.lang.Thread,目的是為取得使用者在鍵盤輸入的資料: • 屬性 • 建構子

  23. 10-3 物件導向設計TelnetClientNio類別 • 連線並讀取資料-connect方法 • 呼叫後產生SocketChannel物件 • 以while迴圈不斷地讀取伺服器傳來的資料(包括命令) • 判斷傳來的資料是命令時則交由handleCommand方法處理 的資料是命令時 • 若為一般資料則將資料轉為字元並顯示於主控台

  24. 10-3 物件導向設計TelnetClientNio類別 • 連線並讀取資料-connect方法 • 利用上節TelnetTester的程式碼後實作的connect()如下:

  25. 01 public void connect() { 02 try { 03 InetSocketAddressaddr = new InetSocketAddress( 04 "localhost", 23); 05 channel = SocketChannel.open(addr); 06 start(); 07 ByteBufferbuf = ByteBuffer.allocate(1024); 08 while (true) { 09 buf.clear(); 10 channel.read(buf); 11 System.out.println("(緩衝區有效資料個數:" 12 + buf.position() + ")"); 13 if (buf.position() == 0)

  26. 10-3 物件導向設計TelnetClientNio類別 14 break; 15 buf.flip(); 16 while (buf.hasRemaining()) { 17 byte b = buf.get(); 18 int data = b & 0xFF; // 將byte轉為整數 19 if (data == IAC) { 20 handleCommand(buf); 21 } else { 22 System.out.print((char) data); 23 } 24 } 25 } 26 channel.close(); 27 System.exit(0); 28 } catch (IOException e) { 29 e.printStackTrace(); 30 } 31 }

  27. 10-3 物件導向設計TelnetClientNio類別 ▌程式碼說明 ‧第3-4行,產生InetSocketAddress物件addr,目的地是本機localhost的23埠。 ‧第5行,由SocketChannel的靜態方法open取得連線通道物件channel。 ‧第6行,啟動本執行緒,也就是執行run()方法,目的是持續接收使用者在鍵盤上輸入的資料。 ‧第7行,準備緩衝區物件buf,大小為1024個位元組。 ‧第8-25行,以while迴圈持續讀取伺服器端所傳來的資料,並進行命令判斷。 ‧第9行,將緩衝區歸零。 ‧第10行,讀取伺服器端傳來資料,並將資料放置於buf,若資料送達時才執行下一行。 ‧第11-12行,印出讀取到的位元組數量。 ‧第15行,將緩衝區讀取指標移動至第0個位置,等待後續處理。 ‧第16-23行,以while迴圈將緩衝區的位元組一一讀出判斷是否為命令(IAC)。 ‧第19-20行,若資料為IAC,代表後續仍有與命令相關的資料,呼叫專門處理命令的handleCommand方法。 ‧第21-22行,若為一般資料,則轉成字元char後印出。

  28. 10-3 物件導向設計TelnetClientNio類別 • 辨識命令與回應-handleCommand方法 • 當伺服器傳來命令時,全權由handleCommand方法處理 • 解讀IAC、DO/WILL語法與其後的選項(Option)

  29. 10-3 物件導向設計TelnetClientNio類別 辨識命令與回應-handleCommand方法

  30. 01 public void handleCommand(ByteBufferbuf) throws IOException { 02 ByteBufferoutBuf = ByteBuffer.allocate(1024); 03 int tone = buf.get() & 0xFF; 04 int option = buf.get() & 0xFF; 05 outBuf.clear(); 06 if (tone == DO) { 07 outBuf.put((byte) IAC); 08 outBuf.put((byte) WONT); 09 outBuf.put((byte) option); 10 outBuf.flip(); 11 channel.write(outBuf); 12 } else if (tone == WILL) { 13 outBuf.put((byte) IAC); 14 outBuf.put((byte) DONT); 15 outBuf.put((byte) option); 16 outBuf.flip(); 17 channel.write(outBuf); 18 } 19 }

  31. ▌程式碼說明 ‧第1行,handleCommand方法的定義,接送參數為緩衝區buf,此方法可能會拋IOException。 ‧第2行,準備送出資料專用的緩衝區物件outBuf。 ‧第3行,讀取下一個位元組,定義為語意(DO或WILL)tone。 ‧第4行,讀取下一個位元組,定義為選項option。 ‧第5行,將outBuf緩衝區初始化。 ‧第6-11行,當伺服器來是DO 選項時,一律先回絕伺服器的要求。 ‧第12-17行,當伺服器來是WILL 選項時,一律先否定伺服器的請求。

  32. 10-3 物件導向設計TelnetClientNio類別 • 接收使用者鍵盤輸入-run方法 • 執行時可以送出鍵盤上輸入的資料至伺服器 • 繼承了Thread,以執行緒方式設計 • 覆寫了Thread類別的方法run(),在run方法中利用while迴圈持續讀取鍵盤上輸入的資料

  33. 10-3 物件導向設計TelnetClientNio類別 接收使用者鍵盤輸入-run方法

  34. 01 public void run() { 02 ByteBufferoutBuf = ByteBuffer.allocate(1024); 03 BufferedReader in = new BufferedReader( 04 new InputStreamReader(System.in)); 05 while (true) { 06 try { 07 outBuf.clear(); 08 String line = in.readLine(); 09 line = line + "\n\r"; 10 outBuf.put(line.getBytes()); 11 outBuf.flip(); 12 channel.write(outBuf); 13 } catch (IOException e) { 14 e.printStackTrace(); 15 } 16 } 17 }

  35. ▌程式碼說明 ‧第1行,run是覆寫本類別繼承Thread的方法,當本執行緒被執行時(呼叫start()),會執行本方法。 ‧第2行,準備送出資料專用的緩衝區物件outBuf。 ‧第3-4行,與鍵盤建立輸入串流BufferedReader物件in。 ‧第5-16行,以while迴圈持續讀取使用者在鍵盤上輸入的資料。 ‧第7行,清理outBuf緩衝區。 y第8行,當使用者按下Enter鍵時,自鍵盤讀取該字串。 ‧第9行,為該字串最後加上跳行字元"\r\n"。 ‧第10-11行,將字串的位元組放入緩衝區物件outBuf。 ‧第12行,利用通道channel的write方法送出緩衝區資料至伺服器。

  36. 10-3 物件導向設計TelnetClientNio類別 • 實際執行與測試 • 啟動Windows內建的Telnet伺服器後可進行簡單的測試,執行TelnetClientNio後將連線至localhost本機的23埠,讀取伺服器傳來資料並將使用者輸入資料送至伺服器,執行過程如下:

  37. (緩衝區有效資料個數:21) (緩衝區有效資料個數:38) Welcome to Microsoft Telnet Service (緩衝區有效資料個數:9) 伺服器傳來的歡迎訊息 由使用者輸入帳號tom login: tom (緩衝區有效資料個數:25) tom password: aaa123 伺服器傳來剛由用戶端傳送的資料tom 由使用者輸入的密碼

  38. 10-4Telnet伺服器 • 具有帳號登入與驗證帳號的TELNET伺服器 • 使用者帳戶資料,包括了帳號與密碼兩個資訊 • 使用者連線至伺服器時必需輸入帳號與密碼 • 當登入成功後顯示歡迎訊息

  39. 10-4Telnet伺服器 • 類別概況設計 • TelnetServerNio負責傾聽9980埠號並等待連線 • 「ClientHandler」服務每個連上線的用戶

  40. 10-4Telnet伺服器 • TelnetServerNio類別 • 負責傾聽埠號並接收用戶端的連線 • 使用while迴圈傾聽特定埠號 • 當用戶端連線並進入可寫出資料狀態時,即利用ClientHandler類別專門處理用戶端的資料處理 • TelnetServerNio類別就如同醫院的掛號台,當有病患上門時安排他至醫生處看診

  41. 10-4Telnet伺服器 01 package com.ch10; 02 03 import java.io.IOException; 04 import java.net.InetSocketAddress; 05 import java.net.ServerSocket; 06 import java.nio.channels.ClosedChannelException; 07 import java.nio.channels.SelectionKey; 08 import java.nio.channels.Selector; 09 import java.nio.channels.ServerSocketChannel; 10 import java.nio.channels.SocketChannel; 11 import java.util.Iterator; 12 import java.util.Set; 13 14 public class TelnetServerNio { 15 int port = 9980; 16 17 public TelnetServerNio(int port) { TelnetServerNio類別

  42. 10-4Telnet伺服器 18 this.port = port; 19 } 20 21 public void accept() { 22 try { 23 ServerSocketChannelserverChannel = ServerSocketChannel 24 .open(); 25 ServerSocketss = serverChannel.socket(); 26 ss.bind(new InetSocketAddress(port)); 27 serverChannel.configureBlocking(false); 28 Selector selector = Selector.open(); 29 serverChannel.register(selector, SelectionKey.OP_ACCEPT); 30 while (true) { 31 selector.select(); 32 Set keys = selector.selectedKeys(); 33 Iterator it = keys.iterator(); 34 int count = 0; 35 while (it.hasNext()) { 36 SelectionKey key = (SelectionKey) it.next();

  43. 10-4Telnet伺服器 37 it.remove(); 38 if (key.isAcceptable()) { 39 System.out.println("用戶連上線"); 40 ServerSocketChannel server = 41 (ServerSocketChannel) key.channel(); 42 SocketChannel client = server.accept(); 43 client.configureBlocking(false); 44 SelectionKeyclientKey = client.register(selector, 45 electionKey.OP_READ); 46 //指派ClientHandler物件,專門處理用戶端送達的資料 47 ClientHandler handler = 48 new ClientHandler(clientKey); 49 handler.welcomeMessage(); 50 // 將ClientHandler物件附加於clientKey 51 clientKey.attach(handler); 52 } else if (key.isReadable()) { 53 // 由key鍵值中取出附加物件,並轉型為ClientHandler 54 ClientHandler handler = 55 (ClientHandler) key.attachment();

  44. 10-4Telnet伺服器 56 // 處理用戶端送達的資料 57 handler.handleRead(key); 58 } 59 } 60 } 61 } catch (ClosedChannelException e) { 62 e.printStackTrace(); 63 } catch (IOException e) { 64 e.printStackTrace(); 65 } 66 } 67 68 public static void main(String[] args) { 69 new TelnetServerNio(9980).accept(); 70 } 71 }

  45. 10-4Telnet伺服器 ▌程式碼說明 ‧第21-66行,accept()方法,呼叫此方法後,伺服器將持續傾聽9980埠,若用戶端連線成功,則為該用戶端產生一個專屬協定處理物件ClientHandler,針對用戶端傳輸資料進行解讀與服務。 ‧第38-51行,若用戶端連線則為其準備協定處理物件。 ‧第47-48行,產生一個ClientHandler物件。 ‧第49行,呼叫ClientHandler的welcomeMessage()方法,傳送歡迎訊息給用戶端,並提示用戶端進行帳號登入,如下圖: Welcome login: ‧第51行,將用戶協定處理物件附掛於key鍵中。 ‧第52-58行,若用戶端傳來資料(已連線完成),則由ClientHandler的handleRead方法處理資料。 ‧第54-55行,取出key鍵中附掛的ClientHandler物件。 ‧第57行,由ClientHandler物件負責解讀用戶端傳來的資料。

  46. 10-4Telnet伺服器 • ClientHandler用戶協定處理類別 • 專門的協定處理類別 • 專注於用戶端傳入資料的事件(OP_READ) • 幾個重點方法說明 • initAccount():用來測試後續用戶端傳送帳號資料 • welcomeMessage():送出歡迎訊息 • handleCommand(ByteBuffer buf):當用戶端傳來資料為TELNET指令(IAC)時,解析指令並回應否定選項的方法 • handleRead(SelectionKey key):本類別最重要的方法,專門負責辨別與處理用戶端傳來資料

  47. 10-4Telnet伺服器 01 package com.ch10; 02 03 import java.io.IOException; 04 import java.nio.ByteBuffer; 05 import java.nio.channels.SelectionKey; 06 import java.nio.channels.SocketChannel; 07 import java.util.Map; 08 import java.util.TreeMap; 09 10 public class ClientHandler { 11 private Map<String, String> users; 12 SocketChannel channel; 13 ByteBuffer buff = ByteBuffer.allocate(100); 14 public static finalint IAC = 255; 15 public static finalint WILL = 251; 16 public static finalint WONT = 252; • ClientHandler用戶協定處理類別 • 用戶協定處埋ClientHandler類別的整體程式碼如下:

  48. 10-4Telnet伺服器 17 public static finalint DO = 253; 18 public static finalint DONT = 254; 19 public static finalint STAGE_USER = 0; 20 public static finalint STAGE_PASSWORD = 1; 21 public static finalint STAGE_LOGON = 2; 22 int stage = STAGE_USER; 23 String userid = ""; 24 String pw = ""; 25 26 public ClientHandler(SelectionKey key) { 27 channel = (SocketChannel) key.channel(); 28 initAccount(); 29 } 30 31 public void handleRead(SelectionKey key) { 32 try { 33 int count = channel.read(buff); 34 if (count > 0) { 35 buff.flip();

  49. 10-4Telnet伺服器 36 while (buff.position() < count) { 37 int data = buff.get() & 0xFF; 38 if (data == IAC) { 39 handleCommand(buff); 40 } else { 41 switch (stage) { 42 case STAGE_USER: 43 if (data == 10) { 44 send("\n\rpassword:"); 45 stage = STAGE_PASSWORD; 46 userid = userid.substring(0, 47 userid.length() - 1); 48 System.out.println("id="+userid); 49 } else { 50 userid = userid + (char) data; 51 } 52 break; 53 case STAGE_PASSWORD: 54 if (data == 10) {

  50. 10-4Telnet伺服器 55 pw =pw.substring(0,pw.length()-1); 56 System.out.println("pw=" + pw); 57 if (users.get(userid).equals(pw)){ 58 send("\n\rLogin successful"); 59 stage = STAGE_LOGON; 60 } else { 61 send("\n\rLogin failed"); 62 channel.close(); 63 } 64 } else { 65 pw = pw + (char) data; 66 } 67 break; 68 } 69 } 70 } 71 buff.clear(); 72 } 73 } catch (IOException e) {

More Related