自定址通訊端(Datagram Sockets)
,因為使用流通訊端的每個串連均要花費一定的時間,要減少這種開銷,網路API提供了第二種通訊端:自定址通訊端(datagram socket),自定址使用UDP發送定址資訊(從客戶程式到服務程式或從服務程式到客戶程式),不同的是可以通過自定址通訊端發送多IP資訊包,自定址資訊包含在自定址包中,此外自定址包又包含在IP包內,這就將定址資訊長度限制在60000位元組內。圖2顯示了位於IP包內的自定址包的自定址資訊。
與TCP保證資訊到達資訊目的地的方式不同,UDP提供了另外一種方法,如果自定址資訊包沒有到達目的地,,那麼UDP也不會請求寄件者重新發送自定址包,這是因為UDP在每一個自定址包中包含了錯誤偵測資訊,在每個自定址包到達目的地之後UDP只進行簡單的錯誤檢查,如果檢測失敗,UDP將拋棄這個自定址包,也不會從寄件者那裡重新請求替代者,這與通過郵局發送信件相似,發信人在發信之前不需要與收信人建立串連,同樣也不能保證信件能到達收信人那裡
自定址通訊端工作包括下面三個類:DatagramPacket, DatagramSocket,和 MulticastSocket。DatagramPacket對象描繪了自定址包的地址資訊,DatagramSocket表示客戶程式和服務程式自定址通訊端,MulticastSocket描繪了能進行多點傳送的自定址通訊端,這三個類均位於java.net包內。
DatagramPacket類
在使用自定址包之前,你需要首先熟悉DatagramPacket類,地址資訊和自定址包以位元組數組的方式同時壓縮入這個類建立的對象中
DatagramPacket有數個建構函式,即使這些建構函式的形式不同,但通常情況下他們都有兩個共同的參數:byte [] buffer 和 int length,buffer參數包含了一個對儲存自定址資料包資訊的位元組數組的引用,length表示位元組數組的長度。
最簡單的建構函式是DatagramPacket(byte [] buffer, int length),這個建構函式確定了自定址資料包數組和數組的長度,但沒有任何自定址資料包的地址和連接埠資訊,這些資訊可以後面通過調用方法setAddress(InetAddress addr)和setPort(int port)添加上,下面的代碼示範了這些函數和方法。
byte [] buffer = new byte [100]; DatagramPacket dgp = new DatagramPacket (buffer, buffer.length); InetAddress ia = InetAddress.getByName ("www.disney.com"); dgp.setAddress (ia); dgp.setPort (6000); // Send datagram packet to port 6000. |
如果你更喜歡在調用建構函式的時候同時包括地址和連接埠號碼,可以使用DatagramPacket(byte [] buffer, int length, InetAddress addr, int port)函數,下面的代碼示範了另外一種選擇。
byte [] buffer = new byte [100]; InetAddress ia = InetAddress.getByName ("www.disney.com"); DatagramPacket dgp = new DatagramPacket (buffer, buffer.length, ia, 6000); |
有時候在建立了DatagramPacket對象後想改變位元組數組和他的長度,這時可以通過調用setData(byte [] buffer) 和 setLength(int length)方法來實現。在任何時候都可以通過調用getData() 來得到位元組數組的引用,通過調用getLength()來獲得位元組數組的長度。下面的代碼示範了這些方法:
byte [] buffer2 = new byte [256]; dgp.setData (buffer2); dgp.setLength (buffer2.length); |
關於DatagramPacket的更多資訊請參考SDK文檔。
DatagramSocket類
DatagramSocket類在用戶端建立自定址通訊端與伺服器端進行通訊串連,並發送和接受自定址通訊端。雖然有多個建構函式可供選擇,但我發現建立用戶端自定址通訊端最便利的選擇是DatagramSocket()函數,而伺服器端則是DatagramSocket(int port)函數,如果未能建立自定址通訊端或綁定自定址通訊端到本地連接埠,那麼這兩個函數都將拋出一個SocketException對象,一旦程式建立了DatagramSocket對象,那麼程式分別調用send(DatagramPacket dgp)和 receive(DatagramPacket dgp)來發送和接收自定址資料包,
List4顯示的DGSClient原始碼示範了如何建立自定址通訊端以及如何通過通訊端處理髮送和接收資訊
Listing 4: DGSClient.java // DGSClient.javaimport java.io.*; import java.net.*; class DGSClient { public static void main (String [] args) { String host = "localhost"; // If user specifies a command-line argument, that argument // represents the host name. if (args.length == 1) host = args [0]; DatagramSocket s = null; try { // Create a datagram socket bound to an arbitrary port. s = new DatagramSocket (); // Create a byte array that will hold the data portion of a // datagram packet's message. That message originates as a // String object, which gets converted to a sequence of // bytes when String's getBytes() method is called. The // conversion uses the platform's default character set. byte [] buffer; buffer = new String ("Send me a datagram").getBytes (); // Convert the name of the host to an InetAddress object. // That object contains the IP address of the host and is // used by DatagramPacket. InetAddress ia = InetAddress.getByName (host); // Create a DatagramPacket object that encapsulates a // reference to the byte array and destination address // information. The destination address consists of the // host's IP address (as stored in the InetAddress object) // and port number 10000 -- the port on which the server // program listens. DatagramPacket dgp = new DatagramPacket (buffer, buffer.length, ia, 10000); // Send the datagram packet over the socket. s.send (dgp); // Create a byte array to hold the response from the server. // program. byte [] buffer2 = new byte [100]; // Create a DatagramPacket object that specifies a buffer // to hold the server program's response, the IP address of // the server program's computer, and port number 10000. dgp = new DatagramPacket (buffer2, buffer.length, ia, 10000); // Receive a datagram packet over the socket. s.receive (dgp); // Print the data returned from the server program and stored // in the datagram packet. System.out.println (new String (dgp.getData ())); } catch (IOException e) { System.out.println (e.toString ()); } finally { if (s != null) s.close (); } } } |
DGSClient由建立一個綁定任意本地(用戶端)連接埠好的DatagramSocket對象開始,然後裝入帶有文本資訊的數組buffer和描述伺服器主機IP地址的InetAddress子類對象的引用,接下來,DGSClient建立了一個DatagramPacket對象,該對象加入了帶文本資訊的緩衝器的引用,InetAddress子類對象的引用,以及服務連接埠號碼10000, DatagramPacket的自定址資料包通過方法sent()發送給伺服器程式,於是一個包含服務程式響應的新的DatagramPacket對象被建立,receive()得到響應的自定址資料包,然後自定址資料包的getData()方法返回該自定址資料包的一個引用,最後關閉DatagramSocket。
DGSServer服務程式補充了DGSClient的不足,List5是DGSServer的原始碼:
Listing 5: DGSServer.java // DGSServer.javaimport java.io.*; import java.net.*; class DGSServer { public static void main (String [] args) throws IOException { System.out.println ("Server starting .../n"); // Create a datagram socket bound to port 10000. Datagram // packets sent from client programs arrive at this port. DatagramSocket s = new DatagramSocket (10000); // Create a byte array to hold data contents of datagram // packet. byte [] data = new byte [100]; // Create a DatagramPacket object that encapsulates a reference // to the byte array and destination address information. The // DatagramPacket object is not initialized to an address // because it obtains that address from the client program. DatagramPacket dgp = new DatagramPacket (data, data.length); // Enter an infinite loop. Press Ctrl+C to terminate program. while (true) { // Receive a datagram packet from the client program. s.receive (dgp); // Display contents of datagram packet. System.out.println (new String (data)); // Echo datagram packet back to client program. s.send (dgp); } } } |
DGSServer建立了一個綁定連接埠10000的自定址通訊端,然後建立一個位元組數組容納自定址資訊,並建立自定址包,下一步,DGSServer進入一個無限迴圈中以接收自定址資料包、顯示內容並將響應返回用戶端,自定址套接沒有關閉,因為迴圈是無限的。
在編譯DGSServer 和DGSClient的原始碼後,由輸入java DGSServer開始運行DGSServer,然後在同一主機上輸入Java DGSClient開始運行DGSClient,如果DGSServer與DGSClient運行於不同主機,在輸入時注意要在命令列加上服務程式的主機名稱或IP地址,如:java DGSClient www.yesky.com
多點傳送和MulticastSocket類
前面的例子顯示了伺服器程式線程發送單一的訊息(通過流通訊端或自定址通訊端)給唯一的用戶端程式,這種行為被稱為單點傳送(unicasting),多數情況都不適合於單點傳送,比如,搖滾歌手舉辦一場音樂會將通過互連網進行播放,畫面和聲音的品質依賴於傳輸速度,伺服器程式要傳送大約10億位元組的資料給用戶端程式,使用單點傳送,那麼每個客戶程式都要要複製一份資料,如果,互連網上有10000個用戶端要收看這個音樂會,那麼伺服器程式通過Internet要傳送10000G的資料,這必然導致網路阻塞,降低網路的傳輸速度。
如果伺服器程式要將同一資訊發送給多個用戶端,那麼伺服器程式和客戶程式可以利用多點傳送(multicasting)方式進行通訊。多點傳送就是服務程式對專用的多點傳送組的IP地址和連接埠發送一系列自定址資料包,通過加入操作IP地址被多點傳送Socket註冊,通過這個點客戶程式可以接收發送給組的自定址包(同樣客戶程式也可以給這個組發送自定址包),一旦客戶程式讀完所有要讀的自定址資料包,那麼可以通過離開組操作離開多點傳送組。
注意:IP地址224.0.0.1 到 239.255.255.255(包括)均為保留的多點傳送組地址。
網路API通過MulticastSocket類和MulticastSocket,以及一些輔助類(比如NetworkInterface)支援多點傳送,當一個客戶程式要加入多點傳送組時,就建立一個MulticastSocket對象。MulticastSocket(int port)建構函式允許應用程式指定連接埠(通過port參數)接收自定址包,連接埠必須與服務程式的連接埠號碼相匹配,要加入多點傳送組,客戶程式調用兩個joinGroup()方法中的一個,同樣要離開傳送組,也要調用兩個leaveGroup()方法中的一個。
由於MulticastSocket擴充了DatagramSocket類,一個MulticastSocket對象就有權訪問DatagramSocket方法。
List6是MCClient的原始碼,這段代碼示範了一個用戶端加入多點傳送組的例子。
Listing 6: MCClient.java // MCClient.javaimport java.io.*; import java.net.*; class MCClient { public static void main (String [] args) throws IOException { // Create a MulticastSocket bound to local port 10000. All // multicast packets from the server program are received // on that port. MulticastSocket s = new MulticastSocket (10000); // Obtain an InetAddress object that contains the multicast // group address 231.0.0.1. The InetAddress object is used by // DatagramPacket. InetAddress group = InetAddress.getByName ("231.0.0.1"); // Join the multicast group so that datagram packets can be // received. s.joinGroup (group); // Read several datagram packets from the server program. for (int i = 0; i < 10; i++) { // No line will exceed 256 bytes. byte [] buffer = new byte [256]; // The DatagramPacket object needs no addressing // information because the socket contains the address. DatagramPacket dgp = new DatagramPacket (buffer, buffer.length); // Receive a datagram packet. s.receive (dgp); // Create a second byte array with a length that matches // the length of the sent data. byte [] buffer2 = new byte [dgp.getLength ()]; // Copy the sent data to the second byte array. System.arraycopy (dgp.getData (), 0, buffer2, 0, dgp.getLength ()); // Print the contents of the second byte array. (Try // printing the contents of buffer. You will soon see why // buffer2 is used.) System.out.println (new String (buffer2)); } // Leave the multicast group. s.leaveGroup (group); // Close the socket. s.close (); } } |
MCClient建立了一個綁定連接埠號碼10000的MulticastSocket對象,接下來他獲得了一個InetAddress子類對象,該子類對象包含多點傳送組的IP地址231.0.0.0,然後通過joinGroup(InetAddress addr)方法加入多點傳送組中,接下來MCClient接收10個自定址包,同時輸出他們的內容,然後使用leaveGroup(InetAddress addr)方法離開傳送組,最後關閉通訊端。
也許你對使用兩個位元組數組buffer 和 buffer2感到奇怪,當接收到一個自定址包後,getData()方法返回一個引用,自定址包的長度是256個位元組,如果要輸出所有資料,在輸出完實際資料後會有很多空格,這顯然是不合理的,所以我們必須去掉這些空格,因此我們建立一個小的位元組數組buffer2,buffer2的實際長度就是資料的實際長度,通過調用DatagramPacket's getLength()方法來得到這個長度。從buffer 到 buffer2快速複製getLength()的長度的方法是調用System.arraycopy()方法。
List7 MCServer的原始碼顯示了服務程式是怎樣工作的。
Listing 7: MCServer.java // MCServer.javaimport java.io.*; import java.net.*; class MCServer { public static void main (String[] args) throws IOException { System.out.println ("Server starting.../n"); // Create a MulticastSocket not bound to any port. MulticastSocket s = new MulticastSocket (); // Because MulticastSocket subclasses DatagramSocket, it is // legal to replace MulticastSocket s = new MulticastSocket (); // with the following line. // DatagramSocket s = new DatagramSocket (); // Obtain an InetAddress object that contains the multicast // group address 231.0.0.1. The InetAddress object is used by // DatagramPacket. InetAddress group = InetAddress.getByName ("231.0.0.1"); // Create a DatagramPacket object that encapsulates a reference // to a byte array (later) and destination address // information. The destination address consists of the // multicast group address (as stored in the InetAddress object) // and port number 10000 -- the port to which multicast datagram // packets are sent. (Note: The dummy array is used to prevent a // NullPointerException object being thrown from the // DatagramPacket constructor.) byte [] dummy = new byte [0]; DatagramPacket dgp = new DatagramPacket (dummy, 0, group, 10000); // Send 30000 Strings to the port. for (int i = 0; i < 30000; i++) { // Create an array of bytes from a String. The platform's // default character set is used to convert from Unicode // characters to bytes. byte [] buffer = ("Video line " + i).getBytes (); // Establish the byte array as the datagram packet's // buffer. dgp.setData (buffer); // Establish the byte array's length as the length of the // datagram packet's buffer. dgp.setLength (buffer.length); // Send the datagram to all members of the multicast group // that listen on port 10000. s.send (dgp); } // Close the socket. s.close (); } } |
MCServer建立了一個MulticastSocket對象,由於他是DatagramPacket對象的一部分,所以他沒有綁定連接埠號碼,DatagramPacket有多點傳送組的IP地址(231.0.0.0),一旦建立DatagramPacket對象,MCServer就進入一個發送30000條的文本的迴圈中,對文本的每一行均要建立一個位元組數組,他們的引用均儲存在前面建立的DatagramPacket對象中,通過send()方法,自定址包發送給所有的群組成員。
在編譯了MCServer 和 MCClient後,通過輸入java MCServer開始運行MCServer,最後再運行一個或多個MCClient。
結論
本文通過研究通訊端揭示了Java的網路API的應用方法,我們介紹了套接自的慨念和通訊端的組成,以及流通訊端和自定址通訊端,以及如何使用InetAddress, Socket, ServerSocket, DatagramPacket, DatagramSocket和MulticastSocket類。在完成本文後就可以編寫基本的底層通訊程式。