Java網絡編程基礎(三) Datagram類使用方法

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)。

JAVA網絡編程基礎
  摘 要:Java語言是Internet上最熱門的編程語言,本文針對  Java的網絡功能,對Java從網絡上獲取圖象、聲音、  HTML文檔及文本文件等編程方法作了初步的介紹,同時介紹了動態獲取網絡上資源的方法作了介紹。文...查看完整版>>JAVA網絡編程基礎
 
Java動畫編程基礎
  基本技術:  在Java中實現動畫有很多種辦法,但它們實現的基本原理是一樣的,即在  屏幕上畫出一系列的幀來造成運動的感覺。  我們先構造一個程序的框架,再慢慢擴展,使之功能比較齊備。    使用線程:  ...查看完整版>>Java動畫編程基礎
 
Java動畫編程基礎
  基本技術:  在Java中實現動畫有很多種辦法,但它們實現的基本原理是一樣的,即在  屏幕上畫出一系列的幀來造成運動的感覺。  我們先構造一個程序的框架,再慢慢擴展,使之功能比較齊備。  使用線程:  爲了...查看完整版>>Java動畫編程基礎
 
Java中的異步網絡編程
該文章對編寫客戶服務器應用的Java程序員有所幫助,可以解決程序在對方出現故障的時候繼續穩定運行.目前java平台已經廣泛應用于各類客戶/服務器系統中,在實際編程中,往往需要網絡的異步處理。比如客戶程序,假如客戶...查看完整版>>Java中的異步網絡編程
 
JAVA教程第八講Java網絡編程
8.1.3兩類傳輸協議:TCP;UDP  盡管TCP/IP協議的名稱中只有TCP這個協議名,但是在TCP/IP的傳輸層同時存在TCP和UDP兩個協議。  TCP是Tranfer Control Protocol的簡稱,是一種面向連接的保證可靠傳輸的協議。通過T...查看完整版>>JAVA教程第八講Java網絡編程
 
利用Socket進行Java網絡編程
  Socket是網絡上運行的兩個程序間雙向通訊的一端,它既可以接受請求,也可以發送請求,利用它可以較爲方便的編寫網絡上數據的傳遞。在Java中,有專門的Socket類來處理用戶的請求和響應。利用Socket類的方法,就可...查看完整版>>利用Socket進行Java網絡編程
 
Java中的異步網絡編程
  該文章對編寫客戶服務器應用的java程序員有所幫助,可以解決程序在對方出現故障的時候繼續穩定運行.  目前java平台已經廣泛應用于各類客戶/服務器系統中,在實際編程中,往往需要網絡的異步處理。比如客戶程序,...查看完整版>>Java中的異步網絡編程
 
Java網絡編程,有助于解決實際問題,
Java開發網絡軟件非常方便和強大,Java的這種力量來源于他獨有的一套強大的用于網絡的 API,這些API是一系列的類和接口,均位于包java.net和javax.net中。在這篇文章中我們將介紹套接字(Socket)慨念,同時以實例說明...查看完整版>>Java網絡編程,有助于解決實際問題,
 
Java中的異步網絡編程
該文章對編寫客戶服務器應用的java程序員有所幫助,可以解決程序在對方出現故障的時候繼續穩定運行.  目前java平台已經廣泛應用于各類客戶/服務器系統中,在實際編程中,往往需要網絡的異步處理。比如客戶程序,如果...查看完整版>>Java中的異步網絡編程
 
 
回到王朝網路移動版首頁