| 1 UDP套接字?
數(shù)據(jù)報(Datagram)是網(wǎng)絡(luò)層數(shù)據(jù)單元在介質(zhì)上傳輸信息的一種邏輯分組格式,它是一種在網(wǎng)絡(luò)中傳播的、獨立的、自身包含地址信息的消息,它能否到達目的地、到達的時間、到達時內(nèi)容是否會變化不能準確地知道。它的通信雙方是不需要建立連接的,對于一些不需要很高質(zhì)量的應(yīng)用程序來說,數(shù)據(jù)報通信是一個非常好的選擇。還有就是對實時性要求很高的情況,比如在實時音頻和視頻應(yīng)用中,數(shù)據(jù)包的丟失和位置錯亂是靜態(tài)的,是可以被人們所忍受的,但是如果在數(shù)據(jù)包位置錯亂或丟失時要求數(shù)據(jù)包重傳,就是用戶所不能忍受的,這時就可以利用UDP協(xié)議傳輸數(shù)據(jù)包。在Java的java.net包中有兩個類DatagramSocket和DatagramPacket,為應(yīng)用程序中采用數(shù)據(jù)報通信方式進行網(wǎng)絡(luò)通信。?
使用數(shù)據(jù)包方式首先將數(shù)據(jù)打包,Java.net包中的DategramPacket類用來創(chuàng)建數(shù)據(jù)包。 數(shù)據(jù)包有, 一種用來,該數(shù)據(jù)包有要傳遞到的目的地址; 另一種數(shù)據(jù)包用來接收傳遞過來的數(shù)據(jù)包中的數(shù)據(jù)。要創(chuàng)建,通過DatagramPackett類的方法構(gòu)造: public DatagramPacket(byte ibuft[],int ilength) 傳遞數(shù)據(jù)包 public DatagramPacket( byte ibuft[],int ,int ilength) 接收數(shù)據(jù)包
ibuf[]為接受數(shù)據(jù)包的存儲數(shù)據(jù)的緩沖區(qū)的長度,ilength為從傳遞過來的數(shù)據(jù)包中讀取的字節(jié)數(shù)。當采用第一種構(gòu)造方法時,接收到的數(shù)據(jù)從ibuft[0]開始存放,直到整個數(shù)據(jù)包接收完畢或者將ilength的字節(jié)寫入ibuft為止。采用第二種構(gòu)造方法時,接收到的數(shù)據(jù)從ibuft[offset]開始存放。。不過這是RuntimeException,不需要用戶代碼捕獲。示范代碼如下: byte[ ] buffer=new byte[8912]; DatagramPacket datap=new DatagramPacket(buffer ,buffer.length( ));?
創(chuàng)建的構(gòu)造方法為: public DatagramPacket(byt ibuf[],int ilength,InetAddrss iaddr,int port) public DatagramPacket(byt ibuf[],int offset , int ilength,InetAddrss iaddr,int port)?
iaddr為數(shù)據(jù)包要傳遞到的目標地址,port為目標地址的程序接受數(shù)據(jù)包的端口號(即目標地址的計算機上運行的客戶程序是在哪一個端口接收服務(wù)器發(fā)送過來的數(shù)據(jù)包)。ibuf[]為要發(fā)送數(shù)據(jù)的存儲區(qū),以ibuf數(shù)組的offset位置開始填充數(shù)據(jù)包ilength字節(jié),如果沒有offset,則從ibuf數(shù)組的0位置開始填充。以下示范代碼是要發(fā)送一串字符串:?
String s = new String("java networking"); byte[ ] data=s.getbytes(); int port=1024; try{ InetAddress ineta= InetAddress.getByName(" 169.254.0.14"); DatagramPacket datap=new DatagramPacket (data ,data.length( ),ineta,port); } catch(IOException e) { }
數(shù)據(jù)包也是對象,也有操作方法用來,這是很有用的。其方法如下:
public InetAddress getAddress() 如果是發(fā)送數(shù)據(jù)包,則獲得數(shù)據(jù)包要發(fā)送的目標地址,但是如果是接收數(shù)據(jù)包則返回發(fā)送此數(shù)據(jù)包的。
public byte[]getData()?
返回一個字節(jié)數(shù)組,其中是數(shù)據(jù)包的數(shù)據(jù)。如果想把字節(jié)數(shù)組轉(zhuǎn)換成別的類型就要進行轉(zhuǎn)化。如果想轉(zhuǎn)化成String類型,可以進行以下的處理,設(shè)DatagramPacket datap為: String s = new String(datap.getbytes());?
public int getLength() 獲得數(shù)據(jù)包中數(shù)據(jù)的字節(jié)數(shù)。
pubic int getPort( ) 返回數(shù)據(jù)包中的。?
發(fā)送和接收數(shù)據(jù)包還需要發(fā)送和接收數(shù)據(jù)包的,即DatagramSocket對象,DatagramSocket套接字在本地機器端口監(jiān)聽是否有數(shù)據(jù)到達或者將數(shù)據(jù)包發(fā)送出去。其構(gòu)造方法如下。?
public DatagramSocket() 用本地機上任何一個可用的端口創(chuàng)建一個套接字,這個端口號是由系統(tǒng)隨機產(chǎn)生的。使用方法如下:?
try{ DatagramSocket datas=new DatagramSocket( ); //發(fā)送數(shù)據(jù)包 } catch(SocketException e){ }
這種構(gòu)造方法沒有指定端口號,可以用在客戶端。如果構(gòu)造不成功則觸發(fā)SocketException異常。
public DatagramSocket(int port)? 用一個指定的端口號port創(chuàng)建一個套接字。?
當不能創(chuàng)建套接字時就拋出SocketException異常,其原因是指定的或者是試圖的端口,但是又沒有。?
2 實例:利用DatagramSocket查詢端口占用情況?
我們可以利用這個異常探查本地機的端口號有沒有服務(wù)。見示例12-9。 【程序源代碼】?
1 // ==================== Program Description ===================== 2 // 程序名稱:示例12-9: UDPScan.java 3 // 程序目的:熟悉DatagramSocket的基本用法,查詢端口的占用情況 4 //========================================================= 5 import java.net.*; 6? 7 public class UDPScan 8 { 9 public static void main(String args[]) 10 { 11 for (int port=1024;port<=65535;port++) { 12 try { 13 DatagramSocket server=new DatagramSocket(port); 14 server.close(); 15 } 16 catch(SocketException e) { 17 System.out.println("there is a server in port "+port+"."); 18 } 19 } 20 } 21 }
【程序輸出結(jié)果】?
there is a server in port 1026. there is a server in port 1028. there is a server in port 1046. there is a server in port 1900.
【程序注解】 在第11~19行我們用for循環(huán)以端口號為參數(shù)實例化DatagramSocket,其中端口號從1024到65535。如果在實例過程中出錯,會拋出SocketException異常。我們根據(jù)這個異常就可以判斷出哪些端口被占用,哪些還是空閑的。值得一提的是,我們在實例化了DatagramSocket后,調(diào)用了close()關(guān)閉它。作為一種好的作風,應(yīng)該遵循。端口號在1024以下的系統(tǒng)可能會用到,比如HTTP默認為80端口,FTP默認為21端口,等等,所以我們從1024端口開始探查。?
套接字對象也有相應(yīng)的方法,例如發(fā)送數(shù)據(jù)包的方法還有接收數(shù)據(jù)包的方法,介紹如下。
pubic void close() 當我們創(chuàng)建一個套接字后,用該方法關(guān)閉套接字。
public int getLocalPort() 返回本地套接字的正在監(jiān)聽的端口號。
public void receive(DatagramPacket p) 從網(wǎng)絡(luò)上接收數(shù)據(jù)包并將其存儲在DatagramPacket對象p中。p中的數(shù)據(jù)緩沖區(qū)必須足夠大,。接收數(shù)據(jù)出錯時會拋出IOException異常。
public Void Send(DatagramPacket p) 發(fā)送數(shù)據(jù)包,出錯時會發(fā)生IOException異常。?
下面,我們詳細解釋在Java中實現(xiàn)客戶端與服務(wù)器之間數(shù)據(jù)報通信的方法。 應(yīng)用程序的工作流程如下:?
(1)首先要建立數(shù)據(jù)報通信的Socket,我們可以通過創(chuàng)建一個DatagramSocket對象實現(xiàn)它,在Java中DatagramSocket類有如下兩種構(gòu)造方法:
public DatagramSocket() 構(gòu)造一個數(shù)據(jù)報socket,并使其與本地主機任一可用的端口連接。若打不開socket則拋出SocketException異常。
public DatagramSocket(int port) 構(gòu)造一個數(shù)據(jù)報socket,并使其與本地主機指定的端口連接。若打不開socket或socket無法與指定的端口連接則拋出SocketException異常。?
(2)創(chuàng)建一個數(shù)據(jù)報文包,用來實現(xiàn)無連接的包傳送服務(wù)。每個數(shù)據(jù)報文包用DatagramPacket類創(chuàng)建,DatagramPacket對象封裝了數(shù)據(jù)報包數(shù)據(jù)、包長度、目標地址和目標端口。客戶端要發(fā)送數(shù)據(jù)報文包,要調(diào)用DatagramPacket類以如下形式的構(gòu)造函數(shù)創(chuàng)建DatagramPacket對象,將要發(fā)送的數(shù)據(jù)和包文目的地址信息放入對象之中。DatagramPacket(byte bufferedarray[],int length,InetAddress address,int port)即構(gòu)造一個包長度為length的包傳送到指定主機指定端口號上的數(shù)據(jù)報文包,參數(shù)length必須小于等于bufferedarry.length。?
DatagramPacket類提供了4個類獲取信息:
public byte[] getData() 返回一個字節(jié)數(shù)組,包含收到或要發(fā)送的數(shù)據(jù)報中的數(shù)據(jù)。
public int getLength() 返回發(fā)送或接收到的數(shù)據(jù)的長度。
public InetAddress getAddress() 返回一個發(fā)送或接收此數(shù)據(jù)報包文的機器的IP地址。?
public int getPort() 返回發(fā)送或接收數(shù)據(jù)報的遠程主機的端口號。?
(3)創(chuàng)建完DatagramSocket和DatagramPacket對象,就可以發(fā)送數(shù)據(jù)報文包了。發(fā)送是通過調(diào)用,它需要以DatagramPacket對象為參數(shù),將剛才封裝進DatagramPacket對象中的數(shù)據(jù)組成數(shù)據(jù)報發(fā)出。?
(4)當然,我們也可以接收數(shù)據(jù)報文包。為了接收從服務(wù)器返回的結(jié)果數(shù)據(jù)報文包,我們需要創(chuàng)建一個新的DatagramPacket對象,這就需要用到DatagramPacket的另一種構(gòu)造方式DatagramPacket(byte bufferedarray[],int length),即只需指明存放接收的數(shù)據(jù)報的緩沖區(qū)和長度。調(diào)用DatagramSocket對象的報的工作,此時需要將上面創(chuàng)建的DatagramPacket對象作為參數(shù),該方法會一直直到收到一個數(shù)據(jù)報文包,此時DatagramPacket的緩沖區(qū)中包含的就是接收到的數(shù)據(jù),數(shù)據(jù)報文包中也包含發(fā)送者的IP地址,發(fā)送者機器上的端口號等信息。?
(5)處理接收緩沖區(qū)內(nèi)的數(shù)據(jù),獲取服務(wù)結(jié)果。?
(6)當通信完成后,可以使用DatagramSocket對象的close()方法關(guān)閉數(shù)據(jù)報通信Socket。當然,Java會自動關(guān)閉Socket,DatagramSocket和DatagramPacket所占用的。但是作為一種良好的編程習慣,。?
3 實例:利用數(shù)據(jù)報通信的C/S程序 示例12-10給出了一個簡單的利用數(shù)據(jù)報通信的客戶端程序,它能夠完成與服務(wù)器簡單的通信。 【程序源代碼】?
1 // ==================== Program Description =================== 2 // 程序名稱:示例12-10: UDPServer.java 3 // 程序目的:創(chuàng)建UDP服務(wù)器 4 //============================================================= 5 import java.net.*; 6 import java.io.*; 7? 8 public class UDPServer 9 { 10 static public void main(String args[]) 11 { 12 try {? 13 DatagramSocket receiveSocket = new DatagramSocket(5000); 14 byte buf[]=new byte[1000]; 15 DatagramPacket receivePacket=new DatagramPacket(buf,buf.length); 16 System.out.println("startinig to receive packet"); 17 while (true) 18 {? 19 receiveSocket.receive(receivePacket); 20 String name=receivePacket.getAddress().toString(); 21 System.out.println("\n來自主機:"+name+"\n端口:" 22 +receivePacket.getPort()); 23 String s=new String(receivePacket.getData(),0,receivePacket.getLength()); 24 System.out.println("the received data: "+s); 25 } 26 } 27 catch (SocketException e) { 28 e.printStackTrace(); 29 System.exit(1); 30 } 31 catch(IOException e) { 32 System.out.println("網(wǎng)絡(luò)通信出現(xiàn)錯誤,問題在"+e.toString()); 33 } 34 } 35 }
【程序輸出結(jié)果】?
startinig to receive packet 來自主機:/166.111.172.20 端口:3456 the received data: hello! this is the client
【程序注解】 第13行和第15行分別實例化了一個DatagramSocket對象receiveSocket和一個DatagramPacket對象receivePacket,都是通過調(diào)用各自的構(gòu)造函數(shù)實現(xiàn)的,為建立服務(wù)器做好準備。在while這個永久循環(huán)中,receiveSocket這個套接字始終嘗試receive()方法接收DatagramPacket數(shù)據(jù)包,當接收到數(shù)據(jù)包后,就調(diào)用DatagramPacket的一些成員方法顯示一些數(shù)據(jù)包的信息。在程序中調(diào)用了getAddress()獲得地址,getPort()方法獲得客戶端套接字的端口,getData()獲得客戶端傳輸?shù)臄?shù)據(jù)。注意getData( )返回的是字節(jié)數(shù)組,我們把它轉(zhuǎn)化為字符串顯示。在第27~33行我們對程序中發(fā)生的SocketException和IOException異常進行了處理。 示例12-11是UDP客戶端的程序。 【程序源代碼】?
1 // ==================== Program Description =================== 2 // 程序名稱:示例12-11: UDPClient.java 3 // 程序目的:創(chuàng)建UDP客戶端 4 //============================================================= 5 import java.net.*; 6 import java.io.*; 7? 8 public class UDPClient 9 { 10 public static void main(String args[]) 11 { 12 try { 13 DatagramSocket sendSocket=new DatagramSocket(3456); 14 String string="asfdfdfggf"; 15 byte[] databyte=new byte[100]; 16 databyte=string.getBytes(); 17 DatagramPacketsendPacket=new DatagramPacket(databyte,string.length(),? 18 InetAddress.getByName("163.121.139.20"), 5000);? 19 sendSocket.send(sendPacket); 20 System.out.println("send the data: hello ! this is the client"); 21 } 22 catch (SocketException e) { 23 System.out.println("不能打開數(shù)據(jù)報Socket,或數(shù)據(jù)報Socket無法與指定 24 端口連接!"); 25 } 26 catch(IOException ioe) { 27 System.out.println("網(wǎng)絡(luò)通信出現(xiàn)錯誤,問題在"+ioe.toString()); 28 }? 29 } 30 }
【程序輸出結(jié)果】 send the data: hello !this is the clientsend the data: hello !this is the client?
【程序注解】 第13行用DatagramSocket的構(gòu)造函數(shù)實例化一個發(fā)送數(shù)據(jù)的套接字sendSocket。第17~18行實例化了一個DatagramPacket,其中數(shù)據(jù)包要發(fā)往的目的地是163.121.139.20,端口是5000。當構(gòu)造完數(shù)據(jù)包后,就調(diào)用send( )方法將數(shù)據(jù)包發(fā)送出去。?
4 組播套接字?
在Java中,可以用java.net.MulticastSocket類組播數(shù)據(jù)。組播套接字是DatagramSocket的子類,定義如下: public class MulticastSocket extends DatagramSocket?
構(gòu)造方法有兩個: public MulticastSocket ( ) throws SocketException public MulticastSocket (int port ) throws SocketException?
以上兩個方法都是創(chuàng)建組播套接字,第一個方法沒有端口號,第二個指定了端口號。 常用的方法如下:
public void joinGroup(InetAddress address) throws IOException?
建立了MulticastSocket對象后,為了發(fā)送或者接收組播包,必須用joinGroup方法加入一個組播組。若加入的不是組播地址將觸發(fā)IOException異常。
public void leaveGroup(InetAddress address)throws IOException?
如果不想接收組播包了,就調(diào)用leaveGroup方法。程序就發(fā)信息到組播路由器,通知它向此用戶發(fā)送數(shù)據(jù)。若想離開的地址不是組播地址就觸發(fā)IOException異常。
public void send(DatagramPacket packet, byte, ttl) throws IOExceptin 發(fā)送組播包的方法與DatagramSocket發(fā)送數(shù)據(jù)相似。其中ttl是生存時間,大小在0~255之間。
public void receive(DatagramPacket p) 與DatagramSocket的接收方法沒有差別。
public void setTimeToLive(int ttl )throws IOException 設(shè)置套接字發(fā)出的組播包中的默認ttl數(shù)值。
public int getTimeToLive( ) throws IOException 返回ttl數(shù)值。?
使用組播套接字發(fā)送數(shù)據(jù)的過程是首先用MulticastSocket()構(gòu)造器創(chuàng)建MulticastSocket類,然后利用MulticastSocket類的joinGroup()方法加入一個組播組,之后創(chuàng)建DatagramPacket數(shù)據(jù)包,最后調(diào)用MulticastSocket類的send()方法發(fā)送組播包。 發(fā)送組播包的代碼如下:?
try { InetAddress address = InetAddress.getByName (www.mmm.net) ; byte[ ] data=" java networking"; int port =5000; DatagramPacket datap =new DatagramSocket? (data ,data.length( ),address,port); MulticastSocket muls =new MulticastSocket ( ); muls.send(datap ); } catch(IOException ie) {? }
使用組播套接字接收數(shù)據(jù)的過程是首先用MulticastSocket()構(gòu)造器創(chuàng)建MulticastSocket類,然后利用MulticastSocket類的joinGroup()方法加入一個組播組,之后用receive()方法接收組播包。我們發(fā)現(xiàn)其過程與UDP包的過程很相似,區(qū)別是要加入一個組播組。?
5 實例:組播套接字C/S程序?
下面的程序示例12-12說明了組播套接字的基本用法。 【程序源代碼】?
1 // ==================== Program Description =====================? 2 // 程序名稱:示例12-12: MulticastServer.java 3 // 程序目的:創(chuàng)建一個組播服務(wù)器 4 //========================================================== 5 import java.io.*; 6 import java.net.*; 7 import java.util.*; 8? 9 class QuoteServerThread extends Thread? 10 { 11 protected DatagramSocket socket = null; 12 protected BufferedReader in = null; 13 protected boolean moreQuotes = true; 14? 15 public QuoteServerThread() throws IOException { 16 this("QuoteServerThread"); 17 } 18? 19 public QuoteServerThread(String name) throws IOException { 20 super(name); 21 socket = new DatagramSocket(4445); 22? 23 try { 24 in = new BufferedReader(new FileReader("one-liners.txt")); 25 } catch (FileNotFoundException e) { 26 System.err.println("Could not open quote file.? Serving time instead."); 27 } 28 } 29? 30 public void run() { 31 while (moreQuotes) { 32 try { 33 byte[] buf = new byte[256]; 34? 35 // 獲取請求 36 DatagramPacket packet = new DatagramPacket(buf, buf.length); 37 socket.receive(packet); 38? 39 // 進行響應(yīng) 40 String dString = null; 41 if (in == null) 42 dString = new Date().toString(); 43 else 44 dString = getNextQuote(); 45 buf = dString.getBytes(); 46? 47 // 向用戶發(fā)送響應(yīng) 48 InetAddress address = packet.getAddress(); 49 int port = packet.getPort(); 50 packet = new DatagramPacket(buf, buf.length, address, port); 51 socket.send(packet); 52 } 53 catch (IOException e) { 54 e.printStackTrace(); 55 moreQuotes = false; 56 } 57 } 58 socket.close(); 59 } 60? 61 protected String getNextQuote() { 62 String returnValue = null; 63 try { 64 if ((returnValue = in.readLine()) == null) { 65 in.close(); 66 moreQuotes = false; 67 returnValue = "No more quotes. Goodbye."; 68 } 69 } catch (IOException e) { 70 returnValue = "IOException occurred in server."; 71 } 72 return returnValue; 73 } 74 } 75? 76 class MulticastServerThread extends QuoteServerThread? 77 { 78 private long FIVE_SECONDS = 5000; 79? 80 public MulticastServerThread() throws IOException { 81 super("MulticastServerThread"); 82 } 83? 84 public void run() { 85 while (moreQuotes) { 86 try { 87 byte[] buf = new byte[256]; 88? 89 // 構(gòu)造引用 90 String dString = null; 91 if (in == null) 92 dString = new Date().toString(); 93 else 94 dString = getNextQuote(); 95 buf = dString.getBytes(); 96? 97 // 發(fā)送 98 InetAddress group = InetAddress.getByName("136.122.133.1"); 99 DatagramPacket packet =new DatagramPacket(buf,buf.length,group,? 100 4446); 101 socket.send(packet); 102? 103 // 休眠 104 try { 105 sleep((long)(Math.random() * FIVE_SECONDS)); 106 }? 107 catch (InterruptedException e) { } 108 }? 109 catch (IOException e) { 110 e.printStackTrace(); 111 moreQuotes = false; 112 } 113 } 114 socket.close(); 115 } 116 } 117? 118 public class MulticastServer { 119 public static void main(String[] args) throws java.io.IOException { 120 new MulticastServerThread().start(); 121 } 122 }
【程序注解】 服務(wù)器程序由3個類組成:QuoteServerThread,MulticastServerThread和MulticastServer。它們的關(guān)系是:QuoteServerThread繼承自線程類,而MulticastServerThread類繼承自類QuoteServerThread。這個程序主要的部分在QuoteServerThread和MulticastServerThread。QuoteServerThread類有兩個構(gòu)造函數(shù),其中在構(gòu)造函數(shù)QuoteServerThread(String name)中,初始化了DatagramSocket套接字并打開了文件one-liners.txt,在這個文件中存有服務(wù)器發(fā)送的字符串。?
在QuoteServerThread類的run()函數(shù)中,服務(wù)器端套接字接收來自客戶端的數(shù)據(jù)包,并從文件中讀取數(shù)據(jù),把信息發(fā)給客戶端。?
MulticastServerThread類中重載了run( )方法,實現(xiàn)的功能基本相同,在發(fā)完服務(wù)器的信息后,用sleep( )函數(shù)停止處理了一個隨機的時間。?
在MultiServer類中,用 new MulticastServerThread().start()開始服務(wù)器線程。我們現(xiàn)在只是關(guān)注其基本思想。?
示例12-13是UDP組播的客戶端程序。 【程序源代碼】?
1 // ==================== Program Description =====================? 2 // 程序名稱:示例12-13: MulticastClient.java 3 // 程序目的:UDP組播客戶端 4 //====================>7��:示例1D==================================== 5 import java.io.*; 6 import java.net.*; 7 import java.util.*; 8? 9 public class MulticastClient? 10 { 11 public static void main(String[] args) throws IOException? 12 { 13 MulticastSocket socket = new MulticastSocket(4446); 14 InetAddress address = InetAddress.getByName("136.122.133.1"); 15 socket.joinGroup(address); 16 DatagramPacket packet; 17? 18 for (int i = 0; i < 5; i++)? 19 { 20 byte[] buf = new byte[256]; 21 packet = new DatagramPacket(buf, buf.length); 22 socket.receive(packet); 23 String received = new String(packet.getData()); 24 System.out.println("Quote of the Moment: " + received); 25 } 26? 27 socket.leaveGroup(address); 28 socket.close(); 29 } 30 }
【程序輸出結(jié)果】?
Quote of the Moment: Give me ambiguity or give me something else. Quote of the ME7��:示例1oment: I.R.S.: We've got what it takes to take what you've got! Quote of the Moment: We are born naked, wet and hungry. Then things get worse. Quote of the Moment: Make it idiot proof and someone will make a better idiot. Quote of the Moment: He who laughs last thinks slowest!
【程序注解】 在客戶端的main()方法中,第13行實例化了一個MulticastSocket對象socket,然后用join()方法加入了組播組136.122.133.1。在for循環(huán)中接收了5個數(shù)據(jù)包,并把數(shù)據(jù)包中的內(nèi)容顯示出來(第18~25行)。最后在第27行離開組播組(leaveGroup()),第28行關(guān) |