Datagram(資料報)是一種盡力而為的傳送資料的方式,它隻是把資料的目的地記錄在資料包中,然後就直接放在網絡上,系統不保證資料是否能安全送到,或者什麼時候可以送到,也就是說它并不保證傳送品質。
1 UDP套接字
資料報(Datagram)是網絡層資料單元在媒體上傳輸資訊的一種邏輯分組格式,它是一種在網絡中傳播的、獨立的、自身包含位址資訊的消息,它能否到達目的地、到達的時間、到達時内容是否會變化不能準确地知道。它的通信雙方是不需要建立連接配接的,對于一些不需要很高品質的應用程式來說,資料報通信是一個非常好的選擇。還有就是對實時性要求很高的情況,比如在實時音頻和視訊應用中,資料包的丢失和位置錯亂是靜态的,是可以被人們所忍受的,但是如果在資料包位置錯亂或丢失時要求資料包重傳,就是使用者所不能忍受的,這時就可以利用UDP協定傳輸資料包。在Java的java.net包中有兩個類DatagramSocket和DatagramPacket,為應用程式中采用資料報通信方式進行網絡通信。
使用資料包方式首先将資料打包,Java.net包中的DategramPacket類用來建立資料包。資料包有兩種,一種用來傳遞資料包,該資料包有要傳遞到的目的位址;另一種資料包用來接收傳遞過來的資料包中的資料。要建立接收的資料包,通過DatagramPackett類的方法構造:
public DatagramPacket(byte ibuft[],int ilength)
public DatagramPacket( byte ibuft[],int offset ,int ilength)
ibuf[]為接受資料包的存儲資料的緩沖區的長度,ilength為從傳遞過來的資料包中讀取的位元組數。當采用第一種構造方法時,接收到的資料從ibuft[0]開始存放,直到整個資料包接收完畢或者将ilength的位元組寫入ibuft為止。采用第二種構造方法時,接收到的資料從ibuft[offset]開始存放。如果資料包長度超出了ilength,則觸發IllegalArgument-Exception。不過這是RuntimeException,不需要使用者代碼捕獲。示範代碼如下:
byte[ ] buffer=new byte[8912];
DatagramPacket datap=new DatagramPacket(buffer ,buffer.length( ));
建立發送資料包的構造方法為:
public DatagramPacket(byt ibuf[],int ilength,InetAddrss iaddr,int port)
public DatagramPacket(byt ibuf[],int offset , int ilength,InetAddrss iaddr,int port)
iaddr為資料包要傳遞到的目标位址,port為目标位址的程式接受資料包的端口号(即目标位址的計算機上運作的客戶程式是在哪一個端口接收伺服器發送過來的資料包)。ibuf[]為要發送資料的存儲區,以ibuf數組的offset位置開始填充資料包ilength位元組,如果沒有offset,則從ibuf數組的0位置開始填充。以下示範代碼是要發送一串字元串:
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) {
}
資料包也是對象,也有操作方法用來擷取資料包的資訊,這是很有用的。其方法如下:
public InetAddress getAddress() 如果是發送資料包,則獲得資料包要發送的目标位址,但是如果是接收資料包則傳回發送此資料包的源位址。
public byte[]getData()
傳回一個位元組數組,其中是資料包的資料。如果想把位元組數組轉換成别的類型就要進行轉化。如果想轉化成String類型,可以進行以下的處理,設DatagramPacket datap為:
String s = new String(datap.getbytes());
public int getLength() 獲得資料包中資料的位元組數。
pubic int getPort( ) 傳回資料包中的目标位址的主機端口号。
發送和接收資料包還需要發送和接收資料包的套接字,即DatagramSocket對象,DatagramSocket套接字在本地機器端口監聽是否有資料到達或者将資料包發送出去。其構造方法如下。
public DatagramSocket() 用本地機上任何一個可用的端口建立一個套接字,這個端口号是由系統随機産生的。使用方法如下:
try{
DatagramSocket datas=new DatagramSocket( );
//發送資料包
}
catch(SocketException e){
}
這種構造方法沒有指定端口号,可以用在用戶端。如果構造不成功則觸發SocketException異常。
public DatagramSocket(int port)
用一個指定的端口号port建立一個套接字。
當不能建立套接字時就抛出SocketException異常,其原因是指定的端口已被占用或者是試圖連接配接低于1024的端口,但是又沒有具備權限。
2 執行個體:利用DatagramSocket查詢端口占用情況
我們可以利用這個異常探查本地機的端口号有沒有服務。見示例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 }
【程式輸出結果】
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循環以端口号為參數執行個體化DatagramSocket,其中端口号從1024到65535。如果在執行個體過程中出錯,會抛出SocketException異常。我們根據這個異常就可以判斷出哪些端口被占用,哪些還是空閑的。值得一提的是,我們在執行個體化了DatagramSocket後,調用了close()關閉它。作為一種好的作風,應該遵循。端口号在1024以下的系統可能會用到,比如HTTP預設為80端口,FTP預設為21端口,等等,是以我們從1024端口開始探查。
套接字對象也有相應的方法,例如發送資料包的方法還有接收資料包的方法,介紹如下。
pubic void close() 當我們建立一個套接字後,用該方法關閉套接字。
public int getLocalPort() 傳回本地套接字的正在監聽的端口号。
public void receive(DatagramPacket p) 從網絡上接收資料包并将其存儲在DatagramPacket對象p中。p中的資料緩沖區必須足夠大,receive()把盡可能多的資料存放在p中,如果裝不下,就把其餘的部分丢棄。接收資料出錯時會抛出IOException異常。
public Void Send(DatagramPacket p) 發送資料包,出錯時會發生IOException異常。
下面,我們詳細解釋在Java中實作用戶端與伺服器之間資料報通信的方法。
應用程式的工作流程如下:
(1)首先要建立資料報通信的Socket,我們可以通過建立一個DatagramSocket對象實作它,在Java中DatagramSocket類有如下兩種構造方法:
public DatagramSocket() 構造一個資料報socket,并使其與本地主機任一可用的端口連接配接。若打不開socket則抛出SocketException異常。
public DatagramSocket(int port) 構造一個資料報socket,并使其與本地主機指定的端口連接配接。若打不開socket或socket無法與指定的端口連接配接則抛出SocketException異常。
(2)建立一個資料封包包,用來實作無連接配接的包傳送服務。每個資料封包包用DatagramPacket類建立,DatagramPacket對象封裝了資料報包資料、包長度、目标位址和目标端口。用戶端要發送資料封包包,要調用DatagramPacket類以如下形式的構造函數建立DatagramPacket對象,将要發送的資料和封包目的位址資訊放入對象之中。DatagramPacket(byte bufferedarray[],int length,InetAddress address,int port)即構造一個包長度為length的包傳送到指定主機指定端口号上的資料封包包,參數length必須小于等于bufferedarry.length。
DatagramPacket類提供了4個類擷取資訊:
public byte[] getData() 傳回一個位元組數組,包含收到或要發送的資料報中的資料。
public int getLength() 傳回發送或接收到的資料的長度。
public InetAddress getAddress() 傳回一個發送或接收此資料報封包的機器的IP位址。
public int getPort() 傳回發送或接收資料報的遠端主機的端口号。
(3)建立完DatagramSocket和DatagramPacket對象,就可以發送資料封包包了。發送是通過調用DatagramSocket對象的send方法實作,它需要以DatagramPacket對象為參數,将剛才封裝進DatagramPacket對象中的資料組成資料報發出。
(4)當然,我們也可以接收資料封包包。為了接收從伺服器傳回的結果資料封包包,我們需要建立一個新的DatagramPacket對象,這就需要用到DatagramPacket的另一種構造方式DatagramPacket(byte bufferedarray[],int length),即隻需指明存放接收的資料報的緩沖區和長度。調用DatagramSocket對象的receive()方法完成接收資料報的工作,此時需要将上面建立的DatagramPacket對象作為參數,該方法會一直阻塞直到收到一個資料封包包,此時DatagramPacket的緩沖區中包含的就是接收到的資料,資料封包包中也包含發送者的IP位址,發送者機器上的端口号等資訊。
(5)處理接收緩沖區内的資料,擷取服務結果。
(6)當通信完成後,可以使用DatagramSocket對象的close()方法關閉資料報通信Socket。當然,Java會自動關閉Socket,釋放DatagramSocket和DatagramPacket所占用的資源。但是作為一種良好的程式設計習慣,還是要顯式地予以關閉。
3 執行個體:利用資料報通信的C/S程式
示例12-10給出了一個簡單的利用資料報通信的用戶端程式,它能夠完成與伺服器簡單的通信。
【程式源代碼】
1 // ==================== Program Description ===================
2 // 程式名稱:示例12-10: UDPServer.java
3 // 程式目的:建立UDP伺服器
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("網絡通信出現錯誤,問題在"+e.toString());
33 }
34 }
35 }
【程式輸出結果】
startinig to receive packet
來自主機:/166.111.172.20
端口:3456
the received data: hello! this is the client
【程式注解】
第13行和第15行分别執行個體化了一個DatagramSocket對象receiveSocket和一個DatagramPacket對象receivePacket,都是通過調用各自的構造函數實作的,為建立伺服器做好準備。在while這個永久循環中,receiveSocket這個套接字始終嘗試receive()方法接收DatagramPacket資料包,當接收到資料包後,就調用DatagramPacket的一些成員方法顯示一些資料包的資訊。在程式中調用了getAddress()獲得位址,getPort()方法獲得用戶端套接字的端口,getData()獲得用戶端傳輸的資料。注意getData( )傳回的是位元組數組,我們把它轉化為字元串顯示。在第27~33行我們對程式中發生的SocketException和IOException異常進行了處理。
示例12-11是UDP用戶端的程式。
【程式源代碼】
1 // ==================== Program Description ===================
2 // 程式名稱:示例12-11: UDPClient.java
3 // 程式目的:建立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("不能打開資料報Socket,或資料報Socket無法與指定
24 端口連接配接!");
25 }
26 catch(IOException ioe) {
27 System.out.println("網絡通信出現錯誤,問題在"+ioe.toString());
28 }
29 }
30 }
【程式輸出結果】
send the data: hello !this is the clientsend the data: hello !this is the client
【程式注解】
第13行用DatagramSocket的構造函數執行個體化一個發送資料的套接字sendSocket。第17~18行執行個體化了一個DatagramPacket,其中資料包要發往的目的地是163.121.139.20,端口是5000。當構造完資料包後,就調用send( )方法将資料包發送出去。
4 多點傳播套接字
在Java中,可以用java.net.MulticastSocket類多點傳播資料。多點傳播套接字是DatagramSocket的子類,定義如下:
public class MulticastSocket extends DatagramSocket
構造方法有兩個:
public MulticastSocket ( ) throws SocketException
public MulticastSocket (int port ) throws SocketException
以上兩個方法都是建立多點傳播套接字,第一個方法沒有端口号,第二個指定了端口号。
常用的方法如下:
public void joinGroup(InetAddress address) throws IOException
建立了MulticastSocket對象後,為了發送或者接收多點傳播包,必須用joinGroup方法加入一個多點傳播組。若加入的不是多點傳播位址将觸發IOException異常。
public void leaveGroup(InetAddress address)throws IOException
如果不想接收多點傳播包了,就調用leaveGroup方法。程式就發資訊到多點傳播路由器,通知它向此使用者發送資料。若想離開的位址不是多點傳播位址就觸發IOException異常。
public void send(DatagramPacket packet, byte, ttl) throws IOExceptin
發送多點傳播包的方法與DatagramSocket發送資料相似。其中ttl是生存時間,大小在0~255之間。
public void receive(DatagramPacket p) 與DatagramSocket的接收方法沒有差别。
public void setTimeToLive(int ttl )throws IOException 設定套接字發出的多點傳播包中的預設ttl數值。
public int getTimeToLive( ) throws IOException 傳回ttl數值。
使用多點傳播套接字發送資料的過程是首先用MulticastSocket()構造器建立MulticastSocket類,然後利用MulticastSocket類的joinGroup()方法加入一個多點傳播組,之後建立DatagramPacket資料包,最後調用MulticastSocket類的send()方法發送多點傳播包。
發送多點傳播包的代碼如下:
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) {
}
使用多點傳播套接字接收資料的過程是首先用MulticastSocket()構造器建立MulticastSocket類,然後利用MulticastSocket類的joinGroup()方法加入一個多點傳播組,之後用receive()方法接收多點傳播包。我們發現其過程與UDP包的過程很相似,差別是要加入一個多點傳播組。
5 執行個體:多點傳播套接字C/S程式
下面的程式示例12-12說明了多點傳播套接字的基本用法。
【程式源代碼】
1 // ==================== Program Description =====================
2 // 程式名稱:示例12-12: MulticastServer.java
3 // 程式目的:建立一個多點傳播伺服器
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 // 進行響應
40 String dString = null;
41 if (in == null)
42 dString = new Date().toString();
43 else
44 dString = getNextQuote();
45 buf = dString.getBytes();
46
47 // 向使用者發送響應
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 // 構造引用
90 String dString = null;
91 if (in == null)
92 dString = new Date().toString();
93 else
94 dString = getNextQuote();
95 buf = dString.getBytes();
96
97 // 發送
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 }
【程式注解】
伺服器程式由3個類組成:QuoteServerThread,MulticastServerThread和MulticastServer。它們的關系是:QuoteServerThread繼承自線程類,而MulticastServerThread類繼承自類QuoteServerThread。這個程式主要的部分在QuoteServerThread和MulticastServerThread。QuoteServerThread類有兩個構造函數,其中在構造函數QuoteServerThread(String name)中,初始化了DatagramSocket套接字并打開了檔案one-liners.txt,在這個檔案中存有伺服器發送的字元串。
在QuoteServerThread類的run()函數中,伺服器端套接字接收來自用戶端的資料包,并從檔案中讀取資料,把資訊發給用戶端。
MulticastServerThread類中重載了run( )方法,實作的功能基本相同,在發完伺服器的資訊後,用sleep( )函數停止處理了一個随機的時間。
在MultiServer類中,用 new MulticastServerThread().start()開始伺服器線程。我們現在隻是關注其基本思想。
示例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 }
【程式輸出結果】
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循環中接收了5個資料包,并把資料包中的内容顯示出來(第18~25行)。最後在第27行離開多點傳播組(leaveGroup()),第28行關�