420 likes | 593 Views
網路程式設計 Network Programming Ch.6 伺服器. 陳冠中 國立高雄應用科技大學. 章節大綱. 前言. 6-2 ServerSocket 相關範例. 6-1 ServerSocket 類別. 6-3 UDP 應用程式. 備註:可依進度點選小節. 前言.
E N D
網路程式設計Network ProgrammingCh.6 伺服器 陳冠中 國立高雄應用科技大學 2012 網路程式設計
章節大綱 前言 6-2ServerSocket相關範例 6-1ServerSocket類別 6-3UDP應用程式 備註:可依進度點選小節
前言 • 網際網路上最多的應用是Clinet/Server模式,Client代表客戶端應用程式,Server代表伺服端應用程式。伺服端應用程式實作了特定的訊協定並在伺服器主機上固定傾聽一個port,等待客戶端的連線,當實作了某通訊協定的客戶端程式連上後,進行該協定的對話與功能的實現。 • 最常見的Client/Server應用就屬http網頁通訊協定了,伺服器上有個常駐執行的應用程式,在伺服器上傾聽80 port,並等待http客戶端應用程式連線,而實作http協定的客戶端就是常用的網頁瀏覽器,如IE. Firefox.Chrome等。其他常用的Client/Server應用有ftp檔案傳輸協定, Telnet遠端登入協定(BBS),除了常見的用外,也有許多為了某些客製化的需求而實作的通訊協定,如銀行業在各分行之間資料傳遞,或網路主機的校時等。 2012 網路程式設計
前言 • 銀行總行與分行間 • Client/Server模式 • Server代表伺服端應用程式 • 網頁服務(HTTP) • BBS(TELNET)
前言 • 伺服端應用程式是Client/Server模式中很重要的一員,缺少它則整個協定將無法正常運作,本章將介紹如何利用Java語言所提供的類別,實作出簡單的伺服器應用程式,再利用之前章節所學的網路套件寫出與伺服端配合的程式,連線至伺服器並測試運作是否正常。除了設計伺服端應用程式外,測試整個通訊協定也是重要的工作,目的是驗證兩者間的傳輸是否正確。 2012 網路程式設計
6-1ServerSocket類別 • 利用java.net.ServerSocket類別,可以很快速地建立伺服器應用程式,使用建構子ServerSocket()即可產生物件,再綁定一個傾聽port,即可讓程式固定傾聽主機上的port。ServerSocket類別有四種建構子,讓使用者可決定適合需求的建構子。而它也有幾個常用的方法(methods),將在本節詳細介紹。 • 在使用網路類型的類別時,通常會設計成抛出某些例外物件,因為網路的環境無法百分之百確定物件會運作正常,可能在執行建構子或方法時,出現port被占用或輸出入錯誤等例外情形,常見的例外有: • BindException:當ServerSocket要傾聽的port被其他應用程式占用,或其資源尚未被釋放,此時會抛出port資料被占用的例外。 • IOException:當連線建立的過程,或是在通道資料流傳遞過程中有可能出現斷線, 網路不通等情形時,會抛出輸出入例外。 2012 網路程式設計
6-1ServerSocket類別 • ServerSocket類別的四個建構子分別為: • public ServerSocket():無需參數即建立ServerSocket物件的建構子,預設會抛出IOException,由於物件都必需要指定一個port,因此會在後續程式碼中呼叫bind方法,以指定port 。Bind方法需要一個SocketAddress物件,方法使用如下: • ServerSocket ss = new ServerSocket(); • SocketAddress bbs = new SocketAddress(23); • ss.bind(bbs);
6-1ServerSocket類別 • ServerSocket類別的四個建構子分別為: • public ServerSocket(int port):建立物件時直接指定傾聽埠號,會抛出BindException與IOException,以下片段程式碼可以建立一個傾聽8888port的物件: • 除了可以指定port並建立物件外,當port指定為0時,則不指定特定port,可以向OS取得一個可用的port來使用,以下程式利用getLocalPort方法得到被分配的port: • 執行結果則因人因時而有所不同,以下是執行結果 • ServerSocketss = newServerSocket(8888); • ServerSocketss = newServerSocket(0); • System.out.println(ss.getLocalPort()); • 50461
6-1ServerSocket類別 • ServerSocket類別的四個建構子分別為: • public ServerSocket(int port, int queueLength):建立物件時直接指定傾聽埠號,若有主機擁有多張網卡與IP位址時,ServerSocket通常都會傾聽所有的位址,當多個客戶端同時要求連線時,設定等待駐列最多可使用的個數。建立一個傾聽8888 ,並設定20個等待駐列: • ServerSocketss = newServerSocket(8888,20);
6-1ServerSocket類別 • ServerSocket類別的四個建構子分別為: • public ServerSocket(int port, int queueLength, InetAddress bindAddress):此建構子除了建立物件時直接指定傾聽埠號與等待駐列個數外,若主機擁有多個IP位址(多張網路卡)時,可指定傾聽的IP位址。IP位址則以java.net.inetAddress物件為傳入值。如指定聽傾聽主機的IP位址192.168.1.10與port 8888,等待駐列5個: • 當指定的IP位址錯誤或不存在時,會抛出BindException • ServerSocketss = newServerSocket(8888,5, InetAddress.getByName(“192.168.1.10”));
6-1ServerSocket類別 • 方法 • public void bind(SocketAddress ep):指定傾聽埠號,此方法會檢查主機的port是否被占用,若已被占用而無法使用時會抛出BindException (如p6-5所示) • public int getLocalPort():取得ServerSocket物件所指定的埠號值,傳回整數值 執行結果: • ServerSocketss = newServerSocket(8888); • System.out.println(“占用port:”+ss.getLocalPort()); • 占用port: 8888
6-1ServerSocket類別 • 方法 • public InetAddress getInetAddress():取得ServerSocket物件所綁定的IP位址,傳回位址物件 執行結果: • 若使用未指定綁定IP位址的建構子時,代表本機的所有IP位址0.0.0.0: • ServerSocket ss = new ServerSocket(8888,5, InetAddress.getByName(“192.168.1.10”)); • System.out.println(“綁定IP:”+ss.getInetAddress().getHostAddress()); • 綁定IP: 192.168.1.10
6-1ServerSocket類別 • 方法 • public InetAddress getInetAddress():取得ServerSocket物件所綁定的IP位址,傳回位址物件 執行結果: • 若使用未指定綁定IP位址的建構子時,代表本機的所有IP位址0.0.0.0: • 執行結果: • ServerSocket ss = new ServerSocket(8888,5, InetAddress.getByName(“192.168.1.10”)); • System.out.println(“綁定IP:”+ss.getInetAddress().getHostAddress()); • 綁定IP: 192.168.1.10 • 綁定IP: 0.0.0.0
6-1ServerSocket類別 • 方法 • Socket accept():開始依照所指定的port進行傾聽,程式碼執行至此方法時,將會停頓在此,直到有客戶端連線後,才會傳回Socket件供後續處理: 執行結果: • Public void close()關閉ServerSocket物件,釋放傾聽的port資源占用 • ServerSocket ss = new ServerSocket(8888); • System.out.println(“開始傾聽…”); • Socket socket = ss.accept(); • System.out.println(“已有客戶端連線”); • 開始傾聽…
6-1-3 伺服器的輸出入串流 • 伺服器使用accept()方法時,會停頓等待客戶端連線,當客戶端連線後該方法會回傳一Socket物件。在前一章Java網路套件中的客戶端程式設計,範例以Socket物件連線至某伺服器後,曾利用Socket物件的getInputStream與getOutputStream方法得到輸入與輸出資料流物件,伺服器程式設計在這個階段也是相同的,可以由Socket物件取得輸出入串流: • ServerSocket ss = new ServerSocket(8888); • Socket socket = ss.accept(); • //連線後,取得輸出入串流 • OutputStream out = socket.getOutputStream(); • InputStream in = socket.getInputStream();
6-1-3 伺服器的輸出入串流 • 或將輸入串流轉接為BufferedReader,輸出串流轉接為PrintWriter: • ServerSocket ss = new ServerSocket(8888); • Socket socket = ss.accept(); • //連線後,取得輸出入串流 • OutputStream rawout = socket.getOutputStream(); • InputStream rawin = socket.getInputStream(); • PrintWriter out = new PrintWriter(rawOut); • BufferedReader in = new BufferedReader(new InputStreamReader(rawIn));
6-2ServerSocket相關範例 • 設計伺服器應用程式時,要考量到幾個要點: • 支援多客戶端連線 大部份的伺服器應用程式都支援多人連線使用,當有一個客戶端連線後,必需馬上準備傾聽下一個客戶端的連線,因此,大都將ServerSocket物件的accept方法置於while迴圈內,讓它可以不斷的接收連線的要求,程式架構如下: • ServerSocketss = newServerSocket(8888); • While(true){ • Socket socket = ss.accept(); • ….. • }
6-2ServerSocket相關範例 • 設計伺服器應用程式時,要考量到幾個要點: • 連線等待負荷 若伺服器應用程式需要在很短時間內接受大量的客戶端連線,而且往往集中在某一特定時間,此時應考慮加大ServerSocket物件的等待駐列的值,例如加大為100,這樣才不會因為作業系統的限制而使得客戶端產生連線超出時間限定,而出現連結錯誤的情形: • ServerSocketss = newServerSocket(8888,100);
6-2ServerSocket相關範例 • 設計伺服器應用程式時,要考量到幾個要點: • 多執行緒 支援多人的伺服器應為每個連線指派不同的執行緒,以分開處理個別連線的協定。 • 伺服器資源負荷 當伺服器的連線客戶數量一多,產生的執行緒必定隨著增加,對硬體資源的使用量將明顯增高,例如記憶體與中央處理器等資源,所以應該考量到硬體是否能負荷伺服器應用程式對資源的耗用。
6-2-1 偵測本機被占用的port - PortScanner • 本範例為PortScanner設計一個方法,該方法執行時,會由port 1-65536開始建立ServerSocket,如果該port已有程式占用,則當建立物件時會抛出IOException,再以catch區塊印出該port資訊:
6-2ServerSocket相關範例 01 package com.ch06; 02 03 import java.io.IOException; 04 import java.net.ServerSocket; 05 06 public class PortScanner { 07 public void scan(){ 08 for (int i=1; i< 65535; i++){ 09 try { 10 ServerSocket ss = new ServerSocket(i); 11 } catch (IOException e) { 12 System.out.println("port "+ i + "被占用中"); 13 } 14 } 15 } 16 17 public static void main(String[] args) { 18 PortScanner pserver = new PortScanner(); 19 pserver.scan(); 20 } 21 } • 偵測本機被佔用的埠號-PortScanner
6-2ServerSocket相關範例 ▌執行結果 port 21被占用中 port 80被占用中 port 135被占用中 port 445被占用中 port 554被占用中 • 偵測本機被佔用的埠號-PortScanner
6-2-2 報時伺服器-TimeServer • 本範例設計一個會回報目前日期與時間的伺服器,時間字串以跳行字元為結束並傳遞給客戶端後關閉連線,主要是以TCP協定實作:
6-2ServerSocket相關範例 01 package com.ch06; 02 03 import java.io.IOException; 04 import java.io.PrintWriter; 05 import java.net.ServerSocket; 06 import java.net.Socket; 07 import java.util.Calendar; 08 import java.util.Date; 09 10 public class TimeServer { 11 public TimeServer() { 12 } 13 14 public void report() { 15 try { 16 ServerSocketss = new ServerSocket(8886); 17 Date date = Calendar.getInstance().getTime(); 18 Socket socket = ss.accept(); • 報時伺服器-TimeServer
6-2ServerSocket相關範例 19 PrintWriter out = new PrintWriter( 20 socket.getOutputStream()); 21 out.println(date); 22 out.flush(); 23 out.close(); 24 socket.close(); 25 ss.close(); 26 } catch (IOException e) { 27 System.out.println("輸出入錯誤"); 28 } 29 } 30 31 public static void main(String[] args) { 32 TimeServer tserver = new TimeServer(); 33 tserver.report(); 34 } 35 } • 報時伺服器-TimeServer
6-2ServerSocket相關範例 執行此程式後,可使用Windows系統內建的telnet程式連線至localhost的8886埠號測試: C:\> telnet localhost 8886 Thu Apr 15 01:29:11 CST 2010 遺失與主機的連線。 C:\> • 報時伺服器-TimeServer 伺服器傳來的字串
6-2ServerSocket相關範例 01 package com.ch06; 02 03 import java.io.*; 04 import java.net.ServerSocket; 05 import java.net.Socket; 06 07 public class EchoServer { 08 public void echo() { 09 try { 10 ServerSocket ss = new ServerSocket(8885); 11 Socket socket = ss.accept(); 12 // 連線後,取得輸出入串流 13 OutputStream rawOut = socket.getOutputStream(); 14 InputStream rawIn = socket.getInputStream(); 15 PrintWriter out = new PrintWriter(rawOut); 16 BufferedReader in = new BufferedReader( 17 new InputStreamReader(rawIn)); 18 // 等待客戶端送來字串 • 回音伺服器-EchoServer
6-2ServerSocket相關範例 19 String data = in.readLine(); 20 System.out.println("收到:" + data); 21 // 將傳來的資料回送給客戶端 22 out.println(data); 23 out.flush(); 24 System.out.println("送出:" + data); 25 // 關閉連線資源 26 in.close(); 27 out.close(); 28 socket.close(); 29 ss.close(); 30 } catch (IOException e) { 31 System.out.println("輸出入錯誤"); 32 } 33 } 34 35 public static void main(String[] args) { • 回音伺服器-EchoServer
6-2ServerSocket相關範例 36 EchoServer eserver = new EchoServer(); 37 eserver.echo(); 38 } 39 } • 回音伺服器-EchoServer 執行此程式後,可使用Windows系統內建的telnet程式連線至localhost的8885埠號測試,輸入hello後按下Enter鍵即可得到伺服器回傳回來: C:\> telnet localhost 888 Hello 遺失與主機的連線。 C:\> ▌EchoServer執行的結果 收到:hello 送出:hello
6-2ServerSocket相關範例 01 package com.ch06; 02 03 import java.io.*; 04 import java.net.ServerSocket; 05 import java.net.Socket; 06 07 public class CounterServer { 08 public static int count = 0; 09 10 public void startCount() { 11 while (true) { 12 try { 13 ServerSocket ss = new ServerSocket(8884); 14 Socket socket = ss.accept(); 15 count++; 16 System.out.println("第" + count + "個客戶連線成功"); 17 OutputStream rawOut = socket.getOutputStream(); 18 PrintWriter out = new PrintWriter(rawOut); • 計算客戶端連線次數-CounterServer
6-2ServerSocket相關範例 19 out.println("您是第" + count + "個客戶端"); 20 out.flush(); 21 out.close(); 22 socket.close(); 23 ss.close(); 24 } catch (IOException e) { 25 System.out.println("輸出入錯誤"); 26 } 27 } 28 } 29 30 public static 31 CounterServer cserver = new CounterServer(); 32 cserver.startCount(); 33 } 34 } • 計算客戶端連線次數-CounterServer
6-2ServerSocket相關範例 執行CounterServer類別後,可使用Windows系統內建的telnet程式連線至localhost的8884埠號測試,多執行幾次,可看出持續計次的效果: C:\> telnet localhost 8884 您是第1個客戶端 遺失與主機的連線。 C:\> telnet localhost 8884 您是第2個客戶端 遺失與主機的連線。 C:\> telnet localhost 8884 您是第3個客戶端 遺失與主機的連線。 C:\> • 計算客戶端連線次數-CounterServer
6-3UDP應用程式 • DatagramPacket • 建立UDP封包 • 定義了UDP封包的格式與資訊 • 對方(伺服端)的IP位址 • 埠號 • 欲傳送的資料
6-3UDP應用程式 • DatagramPacket • 建構子 • public DatagramPacket(byte[] data, int length, InetAddress dest, int port) • public DatagramPacket(byte[] data, int offset, int length, InetAddress dest, int port) • public DatagramPacket(byte[] data, int length, SocketAddress dest) • public DatagramPacket(byte[] data, int length, SocketAddress dest) • public DatagramPacket(byte[] buffer, int length) • PublicDatagramPacket(byte[]butter,intoffset,intlength)
6-3UDP應用程式 • DatagramPacket • DatagramPacket方法 • public byte[] getData():取得UDP封包內的資料 • public InetAddress getAddress():得到UDP封包的IP位址物件 • public int getPort():得到UDP封包的埠號值 • public int getLength():得到UDP封包的資料長度 • public void setData(byte[] data):設定封包內的資料,傳入一個位元組陣列。 • public void setAddress(InetAddress addr):設定封包內的位址,傳入一個位址物件。 • public void setPort(int port):設定封包內的埠號值,傳入一整數。 • public void setLength(int length):定封包的資料個數(長度),傳入一個整數。
6-3UDP應用程式 • DatagramSocket • 傳送UDP封包 • 接收時,需要占用一個本機的埠號 • 傳送時,時不需指令埠號
6-3UDP應用程式 • DatagramSocket • 建構子 • DatagramSocket() • DatagramSocket(int port) • DatagramSocket(int port, InetAddress addr)
6-3UDP應用程式 • DatagramSocket • DatagramSocket方法 • void connect(InetAddress addr, int port):依傳入的位址與埠號參數與遠端主機建立連線,可用來傳送UDP封包至特定遠端主機。 • void disconnect():切斷連線。 • DatagramChannel getChannel():得到NIO的UDP封包頻道。 • void receive(DatagramPacket p):執行到此方法時會開始等待外來封包到達 • void send(DatagramPacket p):依照p封包內所事先定義的位址與埠號,將p封包傳送至遠端主機。
6-3UDP應用程式 • UDPClient/Server範例 • 送出UDP封包(客戶端)-UDPClient 01 package com.ch06; 02 03 import java.io.IOException; 04 import java.net.DatagramPacket; 05 import java.net.DatagramSocket; 06 import java.net.InetAddress; 07 08 public class UDPClient { 09 public static void main(String[] args) throws IOException { 10 InetAddress addr = InetAddress.getLocalHost(); 11 String data = "ABC"; 12 byte[] buf = data.getBytes(); 13 DatagramPacket pkt = new DatagramPacket(buf, buf.length, 14 addr, 9950); 15 DatagramSocket ds = new DatagramSocket(); 16 ds.send(pkt); 17 } 18 }
6-3UDP應用程式 • UDPClient/Server範例 • 接收UDP封包(伺服端)-UDPServer 01 package com.ch06; 02 03 import java.io.IOException; 04 import java.net.DatagramPacket; 05 import java.net.DatagramSocket; 06 07 public class UDPServer { 08 public static void main(String[] args) throws IOException { 09 byte[] buffer = new byte[10]; 10 DatagramPacket pkt = new DatagramPacket(buffer, 10); 11 DatagramSocket ds = new DatagramSocket(9950); 12 System.out.println("正在等待埠號:" + ds.getLocalPort()); 13 ds.receive(pkt); 14 System.out.println("已收到UDP封包,封包內容:"); 15 for (int i = 0; i < buffer.length; i++) 16 System.out.print(buffer[i]); 17 } 18 }
client • Server client 2012 網路程式設計
http://ppt.cc/CMh~ Any Question? 2012 網路程式設計