400 likes | 619 Views
課程參與度之評估方式. 上課時必須專心聽講,跟上進度,參與討論 扣分項目 玩線上遊戲一次扣 1 分 玩手機一次扣 1 分 睡覺一次扣 1 分 聊天一次扣 1 分 無法回答老師提出的問題一次扣 1 分 加分項目 主動回答老師的問題一次加 2 分 找出老師程式中的錯誤一次加 1 分 修正老師程式中的錯誤一次加 4 分. 網路程式設計 第九章 Java NIO. 鄧姚文. 大綱. NIO 常用類別 緩衝區 Buffer NIO 客戶端程式 NIO 伺服器. 前言. Java NIO
E N D
課程參與度之評估方式 • 上課時必須專心聽講,跟上進度,參與討論 • 扣分項目 • 玩線上遊戲一次扣1分 • 玩手機一次扣1分 • 睡覺一次扣1分 • 聊天一次扣1分 • 無法回答老師提出的問題一次扣1分 • 加分項目 • 主動回答老師的問題一次加2分 • 找出老師程式中的錯誤一次加1分 • 修正老師程式中的錯誤一次加4分
大綱 NIO常用類別 緩衝區Buffer NIO客戶端程式 NIO伺服器
前言 • JavaNIO • Java語言原本的輸出入(Input/Output)是java.io套件 • JDK自從1.4版開始有了新的處理方式-NIO(New I/O) • NIO以「非等待式」或稱「非堵塞式」(non-blocking)的方式進行資料的接收 • 一種「觀察並通知」的機制 • 當需要讀取資料時,不用讓程式痴痴地等待資料的到來到達或到來 • NIO並不是要「取代」原java.io的功能,而是補充
前言 • NIO的套件 • java.nio.channels.* • 定義了各類通道(Channels) • java.nio.channels.spi.* • java.nio.channels套件中類別設計的「服務提供者」類別。 • java.nio.charset.* • 定義在轉換Unicode字元與位元組資料之間的傳換類別,或稱為解碼器與編碼器類別。 • java.nio.charset.spi.* • 類別CharsetProvider是java.nio.charset套件類的服務提供者類別。
9-1NIO常用類別 • InetSocketAddress • 代表了一個主機的資訊 • Channel • NIO使用Channel來產生與遠端主機或檔案的通道 • SocketChannel • 適用於TCP協定的資料讀取與傳送。 • SocketServerChannel • 用於TCP協定的伺服器,傾聽本機的埠號,等待外來的連線。 • DatagramChannel • 用於UDP協定的封包傳送。
9-1NIO常用類別 InetSocketAddressaddr = new InetSocketAddress("ptt.cc", 23); • InetSocketAddress • 代表了一個主機的資訊 • 常用的建構子有: • public InetSocketAddress(InetAddress addr, int port):由傳入值addr位址物件再加上port埠號值而生成物件。 • public InetSocketAddress(String host, int port):傳入主機名稱或IP位址的字串與port埠號,例如產生ptt.cc與port 23:
9-1NIO常用類別 InetSocketAddressaddr = new InetSocketAddress("ptt.cc", 23);SocketChannelchann = SocketChannel.open(addr); • Channel • SocketChannel:適用於TCP協定的資料讀取與傳送。 • SocketServerChannel:用於TCP協定的伺服器,傾聽本機的埠號,等待外來的連線。 • DatagramChannel:用於UDP協定的封包傳送。 • 通常使用TCP協定的SocketChannel類別,並呼叫SocketChannel.open(主機位址)方法得到與主機的連線通道物件: • open方法將建立連線通道,若失敗則會拋出輸出入例外,因此需預先處理IOException。
9-1NIO常用類別 • Channel • NIO對資料的讀取與傳送都需要使用到另一個要角「緩衝區Buffer」,讀取資料時使用read方法,由通道的另一端將資料讀取至緩衝區內。傳送資料時先將資料放在緩衝區中,再呼叫Channel的write方法,以送出資料: • SocketChannel類的的read與write方法規格如下: • public int read(ByteBuffer buf):讀取遠端資料並放置於緩衝區(buf)中。 • public int write(ByteBuffer):先將欲傳送出的資料放在緩衝區中,再傳送至遠端主機。
9-2 緩衝區Buffer • 緩衝區類別 • 常用的緩衝區類別有七種
9-2 緩衝區Buffer • ByteBuffer buf = ByteBuffer.allocateDirect(20); • ByteBufferbuf = ByteBuffer.allocate(20); • 緩衝區的分類 • 直接緩衝區 • 可跳過JVM的處理,直接對應到作業系統的記憶體來存取緩衝區資料 • 適合較大檔案 • 間接緩衝區 • 由JVM負責所有的容量分配與存取動作 • 使用上會耗費JVM本身所分配的記憶體 • 實務設計常採用間接緩衝區來處理
9-2 緩衝區Buffer ByteBufferbuf = ByteBuffer.allocate(10); byte[] bb = "abcxyz".getBytes(); buf.put(bb); • 將資料放入緩衝區 • 使用put方法將資料放入緩衝區 • ByteBuffer put(byte[] src) • 將一個byte陣列的資料放入緩衝區 • buf的內容將成為:
ByteBuffer put(byte[] src, int offset, int length) • 將byte陣列中特定範圍的元素放入緩衝區,從陣列的offset位置開始取得length個元素 • ByteBuffer put(ByteBuffer src) • 將另一個ByteBuffer的內容放到目前的ByteBuffer中
9-2 緩衝區Buffer ByteBufferbuf = ByteBuffer.allocate(10); byte[] bb = "abcxyz".getBytes(); buf.put(bb, 4, 2); • 將資料放入緩衝區 • ByteBuffer put(byte[] src, int offset, int length) • 將byte陣列中特定範圍的元素放入緩衝區,從陣列的offset位置開始取得length個元素 • buf的內容將成為: • ByteBuffer put(ByteBuffer src) • 將另一個ByteBuffer的內容放到目前的ByteBuffer中
9-2 緩衝區Buffer ByteBufferbuf = ByteBuffer.allocate(10); byte[] bb = "abcxyz".getBytes(); buf.put(bb); ByteBuffer buf2 = ByteBuffer.allocate(10); buf2.put(buf); • 將資料放入緩衝區 • ByteBuffer put(ByteBuffer src) • 將另一個ByteBuffer的內容放到目前的ByteBuffer中 • buf2的內容將成為:
9-2 緩衝區Buffer ByteBufferbuf = ByteBuffer.allocate(10); System.out.println("容量:"+buf.capacity()); ▌程式片段執行結果 容量:10 • 操作緩衝區 • 一個緩衝區有四項重要資訊 • 容量(capacity) • 緩衝區可容納的最大資料個數
9-2 緩衝區Buffer buf.put("abcxyz".getBytes()); System.out.println("位置:"+buf.position()); ▌程式片段執行結果 位置:6 • 操作緩衝區 • 位置(position) • 下一個資料被讀取或被寫入的位置 • 緩衝區未放置任何資料時: • 放入6個字元時: • 如右圖
9-2 緩衝區Buffer buf.put("abcxyz".getBytes()); System.out.println("位置:"+buf.position()); System.out.println("限制:"+buf.limit()); ▌程式片段執行結果 位置:6限制:10 buf.put("abcxyz".getBytes()); buf.mark(); • 操作緩衝區 • 限制(limit) • 緩衝區中可用資料的最大索引值 • 標記(mark) • 提供設計者依需求自行設定一個標記位置,供後續處理 • 放入6個字元,並標記:
9-2 緩衝區Buffer buf.put("GH".getBytes()); • 操作緩衝區 • 標記(mark) • 緩衝區標記(M)如下圖所示: • 接著再放入2個字元: • 緩衝區的位置(P)與標記(M)如下圖所示:
9-2 緩衝區Buffer buf.reset(); • 操作緩衝區 • 標記(mark) • 呼叫reset(),使緩衝區的位置值回到先前標記的位置: • 緩衝區的位置(P)與標記(M)如下圖:
9-2 緩衝區Buffer • ByteBufferbuf = ByteBuffer.allocate(10); • buf.put(“abcxyz”.getBytes()); • buf.flip(); • System.out.println(“容量:”+buf.capacity()); • System.out.println(“限制:”+buf.limit()); • System.out.println("位置:"+buf.position()); ▌執行結果 容量:10 限制:6 位置:0 • 從緩衝區中取出資料 • 使用flip()方法,將限制值(limit,有效資料的位置)修改為目前的位置值,再將位置值設定回0,可從頭讀取緩衝區內的資料
9-2 緩衝區Buffer byte[] bb = new byte[buf.limit()]; buf.get(bb); System.out.println(new String(bb)); while(buf.hasRemaining()){ System.out.print((char)buf.get()); } • 從緩衝區中取出資料 • 使用flip()方法後,緩衝區將如下圖: • 再使用get方法將資料讀出: • 或是使用while迴圈並配合緩衝區的hasRemaining()方法將資料循序讀出:
9-2 緩衝區Buffer • 緩衝區的方法 • public final Buffer clear() • clear方法可將位置值設定為0,且將限制值設定為最大可容納的長度,使用clear方法通常是為了讓緩衝區可以重新放新的資料。 • public final Buffer rewind() • 可讓緩衝區再被讀取一次。 • public final boolean hasRemaining() • 緩衝區內是否還有未讀取的資料,回傳布林值。 • public final int remaining() • 回傳目前位置值與限制值之間的資料個數。
9-3NIO客戶端程式 01 package com.ch09; 02 import java.io.IOException; 03 import java.net.InetSocketAddress; 04 import java.nio.ByteBuffer; 05 import java.nio.channels.SocketChannel; 06 • 以NIO方式連線至遠端主機的埠號並讀取資料,需經過四個步驟: • Step1 建立與遠端主機的通道。 • Step2 準備緩衝區。 • Step3 讀取遠端主機的資料至緩衝區。 • Step4 列印緩衝區內的資料。 • 本例將連線至一FTP站(j.snpy.org,埠號21):
9-3NIO客戶端程式 07 public class NioFtpClient { 08 public static void main(String[] args) throws IOException { 09 InetSocketAddressaddr = 10 new InetSocketAddress("j.snpy.org", 21); 11 SocketChannelchann = SocketChannel.open(addr); 12 //準備緩衝區 13 ByteBufferbuf = ByteBuffer.allocate(1024); 14 //讀取資料 15 chann.read(buf); 16 System.out.println("緩衝區有效資料個數:"+buf.position()); 17 //將緩衝區的位置設定為0,準備一一讀出並列印 18 buf.flip(); 19 while(buf.hasRemaining()){ 20 System.out.print((char)buf.get()); 21 } 22 } 23 } 本例將連線至一FTP站(j.snpy.org,埠號21):
9-3NIO客戶端程式 ▌程式碼說明 ‧第11-13行,先產生InetSocketAddress位址物件,再以位址物件產生連線至該主機的SocketChannel。 ‧第13行,準備緩衝區物件buf,共1024個位置大小的空間。 ‧第17行,開始讀取由遠端主機傳來的資料,並放在buf中。 ‧第20行,呼叫flip()方法,先將緩衝區的位置值設為0,限制值設為有效資料的後一位置值。 ‧第21至23行,利用迴圈將所有有效資料一一印出。 ▌以下為執行結果,最後一行是FTP伺服器所傳來的資料 緩衝區內的有效資料個數:20 220 (vsFTPd 2.0.7) 本例將連線至一FTP站(j.snpy.org,埠號21):
9-3NIO客戶端程式 • 傳送資料 • 在讀取了伺服器傳送的資料後,可進行FTP命令的傳送,將“USER anonymous\n”字串資料傳送至FTP伺服器進行帳號驗證:
22 //命令傳送,帳號認證 23 buf.clear(); 24 buf.put("USER anonymous\n".getBytes()); 25 buf.flip(); 26 chann.write(buf); 27 buf.clear(); 28 //再次讀取資料 29 chann.read(buf); 30 //將緩衝區的位置設定為0,準備一一讀出並列印 31 buf.flip(); 32 while(buf.hasRemaining()){ 33 System.out.print((char)buf.get()); 34 }
9-3NIO客戶端程式 • 緩衝區內的有效資料個數:20 • 220 (vsFTPd 2.0.7) • 331 Please specify the password. ▌程式碼說明 ‧第23行,將緩衝區的位置值歸零,以便後續可再放新的資料。 ‧第24行,將認證字串放入緩衝區。 ‧第25行,將緩衝區的位置值設回資料前端,以便後續將緩衝區資料傳送出去。 ‧第26行,傳送緩衝區資料至FTP伺服器端。 ‧第27行,清除緩衝區。 ‧第32行至34行,讀取FTP伺服器傳來的下一個回應訊息。 • 傳送資料 • 因為傳送了認證命令,伺服器會再傳送回應訊息,要求再傳送密碼:
9-4NIO伺服器 伺服器程式時更能夠顯出其優勢 使用NIO設計伺服器程式可以讓主程式(main執行緒)處理多個客戶端的需求,而不需要為每個客戶端產生個別的執行緒 一個客戶端的服務包含兩個通道(Channels),一是等待客戶端連線的「連線通道」ServerSocketChannel,另一個則是專門處理資料接收與傳送的「傳輸通道」SocketChannel
9-4NIO伺服器 • 「選擇器」Selector • 選擇器可容納需要觀察的通道,如連線通道與傳輸通道 • 各個通道使用register()方法把自己「註冊」(或登記)在選擇器中 • 有任何已註冊的動作(SelectionKey)發生時,可由選擇器的selectedKey()方法得到一個已發生的動作集合
9-4NIO伺服器 • 處理模式 • 使用ServerSocketChannel,一開始是傾聽並接受連線 • 待接收客戶端的連線之後,再轉換為資料傳輸的SocketChannel
9-4NIO伺服器 • 處理步驟 • 建立ServerSocketChannel • 註冊動作ACCEPT至選擇器selector • 準備監視迴圈 • 檢查所有發生的動作鍵值 • 產生該連線的SocketChannel • 利用資料傳輸通道送出資料
9-4NIO伺服器 01 package com.ch09; 02 03 import java.io.IOException; 04 import java.net.InetSocketAddress; 05 import java.net.ServerSocket; 06 import java.nio.ByteBuffer; 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 NioSimpleServer { 15 public static void main(String[] args) throws IOException { 16 ServerSocketChannelserverChannel = 17 ServerSocketChannel.open(); 18 ServerSocketss = serverChannel.socket();
9-4NIO伺服器 19 ss.bind(new InetSocketAddress(9950)); 20 serverChannel.configureBlocking(false); 21 Selector selector = Selector.open(); 22 serverChannel.register(selector, SelectionKey.OP_ACCEPT); 23 while (true) { 24 selector.select(); 25 Set keys = selector.selectedKeys(); 26 Iterator it = keys.iterator(); 27 while (it.hasNext()) { 28 SelectionKey key = (SelectionKey) it.next(); 29 it.remove(); 30 if (key.isAcceptable()) { 31 System.out.println("client connected"); 32 ServerSocketChannel server = 33 (ServerSocketChannel) key.channel(); 34 SocketChannel client = server.accept(); 35 client.configureBlocking(false); 36 SelectionKeyclientKey =
9-4NIO伺服器 37 client.register(selector, SelectionKey.OP_WRITE); 38 } else if (key.isWritable()) { 39 SocketChannel client = (SocketChannel) key.channel(); 40 ByteBuffer buff = ByteBuffer.allocate(10); 41 buff.put("ABC".getBytes()); 42 buff.flip(); 43 client.write(buff); 44 client.close(); 45 } 46 } 47 } 48 } 49 } ▌程式碼說明 ‧第16-17行,利用ServerSocketChannel的靜態方法open()產生serverChannel物件。
9-4NIO伺服器 ‧第18行,得到ServerSocket物件ss。 ‧第19行,將伺服器通道傾聽的埠號綁定為9950。 ‧第20行,通道設定為Non-blocking模式。 ‧第21行,呼叫Selector類別的靜態方法open()產生selector物件。 ‧第22行,將伺服器通道的接收連線事件註冊至selector選擇器,以便日後用戶端連線時可以得到通知。 ‧第23-47行,持續監視(傾聽)迴圈。 ‧第24行,呼叫選擇器的select()方法,進行等待註冊事件的發生,當有事件發生時,會進行下一行的處理。 ‧第25行,事件發生(可能是有客戶端連線),利用選擇器的selectedKeys()方法取得所有事件的鍵值,並放入Set集合物件keys。 ‧第26行,取得集合內的所有元素it。 ‧第27-46行,利用while迴圈走訪Iterator集合內的每一個元素。 ‧第28-29行,得到元素內的SelectionKey物件key,再自集合中移除該元素。 ‧第30-37行,若key值是剛連上線的事件,則印出訊息並取得該連線的通道物件client,再註冊其資料寫入的事件至選擇器中。 ‧第38-45行,若是資料寫入事件發生,即取得通道物件client,並以緩衝區方式,送出"ABC"字串至用戶端,最後關閉連線。
9-4NIO伺服器 telnet localhost 9950 • 完整程式碼與解說 • 執行與測試 • 執行本類別後,會持續傾聽9950埠號,接著使用Windows系統內附的telnet程式即可測試本伺服器運作,讀者可開啟「/開始/程式集/附屬應用程式/命令提示字元」,再輸入: • 即可收到伺服器回應的"ABC"字串,如下圖:
本章結束 Q&A討論時間