1. 程式人生 > >Java網路程式設計:UDP通訊

Java網路程式設計:UDP通訊

網路通訊的方式除了TCP方式以外,還有一種實現的方式就是UDP方式。UDP(User Datagram Protocol),中文意思是使用者資料報協議,方式類似於發簡訊息,是一種物美價廉的通訊方式,使用該種方式無需建立專用的虛擬連線,由於無需建立專用的連線,所以對於伺服器的壓力要比TCP小很多,所以也是一種常見的網路程式設計方式。但是使用該種方式最大的不足是傳輸不可靠,當然也不是說經常丟失,就像大家發簡訊息一樣,理論上存在收不到的可能,這種可能性可能是1%,反正比較小,但是由於這種可能的存在,所以平時我們都覺得重要的事情還是打個電話吧(類似TCP方式),一般的事情才發簡訊息(類似UDP方式

)。網路程式設計中也是這樣,必須要求可靠傳輸的資訊一般使用TCP方式實現,一般的資料才使用UDP方式實現。

         UDP方式的網路程式設計也在Java語言中獲得了良好的支援,由於其在傳輸資料的過程中不需要建立專用的連線等特點,所以在Java API中設計的實現結構和TCP方式不太一樣。當然,需要使用的類還是包含在java.net包中。

Java API中,實現UDP方式的程式設計,包含客戶端網路程式設計和伺服器端網路程式設計,主要由兩個類實現,分別是:

DatagramSocket

DatagramSocket類實現網路連線,包括客戶端網路連線和伺服器端網路連線。雖然

UDP方式的網路通訊不需要建立專用的網路連線,但是畢竟還是需要傳送和接收資料,DatagramSocket實現的就是傳送資料時的發射器,以及接收資料時的監聽器的角色。類比於TCP中的網路連線,該類既可以用於實現客戶端連線,也可以用於實現伺服器端連線。

DatagramPacket

DatagramPacket類實現對於網路中傳輸的資料封裝,也就是說,該類的物件代表網路中交換的資料。在UDP方式的網路程式設計中,無論是需要傳送的資料還是需要接收的資料,都必須被處理成DatagramPacket型別的物件,該物件中包含傳送到的地址、傳送到的埠號以及傳送的內容等。其實DatagramPacket

類的作用類似於現實中的信件,在信件中包含信件傳送到的地址以及接收人,還有傳送的內容等,郵局只需要按照地址傳遞即可。在接收資料時,接收到的資料也必須被處理成DatagramPacket型別的物件,在該物件中包含傳送方的地址、埠號等資訊,也包含資料的內容。和TCP方式的網路傳輸相比,IO程式設計在UDP方式的網路程式設計中變得不是必須的內容,結構也要比TCP方式的網路程式設計簡單一些。

下面介紹一下UDP方式的網路程式設計中,客戶端和伺服器端的實現步驟,以及通過基礎的示例演示UDP方式的網路程式設計在Java語言中的實現方式。

         UDP方式的網路程式設計,程式設計的步驟和TCP方式類似,只是使用的類和方法存在比較大的區別,下面首先介紹一下UDP方式的網路程式設計客戶端實現過程。

UDP客戶端程式設計涉及的步驟也是4個部分:建立連線、傳送資料、接收資料和關閉連線。

首先介紹UDP方式的網路程式設計中建立連線的實現。其中UDP方式的建立連線和TCP方式不同,只需要建立一個連線物件即可,不需要指定伺服器的IP和埠號碼。實現的程式碼為:

                   DatagramSocket ds = new DatagramSocket();

這樣就建立了一個客戶端連線,該客戶端連線使用系統隨機分配的一個本地計算機的未用埠號。在該連線中,不指定伺服器端的IP和埠,所以UDP方式的網路連線更像一個發射器,而不是一個具體的連線。

當然,可以通過制定連線使用的埠號來建立客戶端連線。

                   DatagramSocket ds = new DatagramSocket(5000);

這樣就是使用本地計算機的5000號埠建立了一個連線。一般在建立客戶端連線時沒有必要指定埠號碼。

接著,介紹一下UDP客戶端程式設計中傳送資料的實現。在UDP方式的網路程式設計中,IO技術不是必須的,在傳送資料時,需要將需要傳送的資料內容首先轉換為byte陣列,然後將資料內容、伺服器IP和伺服器埠號一起構造成一個DatagramPacket型別的物件,這樣資料的準備就完成了,傳送時呼叫網路連線物件中的send方法傳送該物件即可。例如將字串“Hello”傳送到IP127.0.0.1,埠號是10001的伺服器,則實現傳送資料的程式碼如下:

 String s = “Hello”;

                   String host = “127.0.0.1”;

                   int port = 10001;

                  //將傳送的內容轉換為byte陣列

                   byte[] b = s.getBytes();

                   //將伺服器IP轉換為InetAddress物件

                   InetAddress server = InetAddress.getByName(host);

                   //構造傳送的資料包物件

                   DatagramPacket sendDp = new DatagramPacket(b,b.length,server,port);

                   //傳送資料

                   ds.send(sendDp);

在該示例程式碼中,不管傳送的資料內容是什麼,都需要轉換為byte陣列,然後將伺服器端的IP地址構造成InetAddress型別的物件,在準備完成以後,將這些資訊構造成一個DatagramPacket型別的物件,在UDP程式設計中,傳送的資料內容、伺服器端的IP和埠號,都包含在DatagramPacket物件中。在準備完成以後,呼叫連線物件dssend方法把DatagramPacket物件傳送出去即可。

按照UDP協議的約定,在進行資料傳輸時,系統只是盡全力傳輸資料,但是並不保證資料一定被正確傳輸,如果資料在傳輸過程中丟失,那就丟失了。

         UDP方式在進行網路通訊時,也遵循“請求-響應”模型,在傳送資料完成以後,就可以接收伺服器端的反饋資料了。

下面介紹一下UDP客戶端程式設計中接收資料的實現。當資料傳送出去以後,就可以接收伺服器端的反饋資訊了。接收資料在Java語言中的實現是這樣的:首先構造一個數據緩衝陣列,該陣列用於儲存接收的伺服器端反饋資料,該陣列的長度必須大於或等於伺服器端反饋的實際有效資料的長度。然後以該緩衝陣列為基礎構造一個DatagramPacket資料包物件,最後呼叫連線物件的receive方法接收資料即可。接收到的伺服器端反饋資料儲存在DatagramPacket型別的物件內部。實現接收資料以及顯示伺服器端反饋內容的示例程式碼如下:

                   //構造緩衝陣列

                   byte[] data = new byte[1024];

                   //構造資料包物件

                   DatagramPacket received = new DatagramPacket(data,data.length);

                   //接收資料

                   ds.receive(receiveDp);

                   //輸出資料內容

                   byte[] b = receiveDp.getData(); //獲得緩衝陣列

                   int len = receiveDp.getLength(); //獲得有效資料長度

                   String s = new String(b,0,len);

                   System.out.println(s);

在該程式碼中,首先構造緩衝陣列data,這裡設定的長度1024是預估的接收到的資料長度,要求該長度必須大於或等於接收到的資料長度,然後以該緩衝陣列為基礎,構造資料包物件,使用連線物件dsreceive方法接收反饋資料,由於在Java語言中,除String以外的其它物件都是按照地址傳遞,所以在receive方法內部可以改變資料包物件receiveDp的內容,這裡的receiveDp的功能和返回值類似。資料接收到以後,只需要從資料包物件中讀取出來就可以了,使用DatagramPacket物件中的getData方法可以獲得資料包物件的緩衝區陣列,但是緩衝區陣列的長度一般大於有效資料的長度,換句話說,也就是緩衝區陣列中只有一部分資料是反饋資料,所以需要使用DatagramPacket物件中的getLength方法獲得有效資料的長度,則有效資料就是緩衝陣列中的前有效資料長度個內容,這些才是真正的伺服器端反饋的資料的內容。

         UDP方式客戶端網路程式設計的最後一個步驟就是關閉連線。雖然UDP方式不建立專用的虛擬連線,但是連線物件還是需要佔用系統資源,所以在使用完成以後必須關閉連線。關閉連線使用連線物件中的close方法即可,實現的程式碼如下:

                   ds.close();

需要說明的是,和TCP建立連線的方式不同,UDP方式的同一個網路連線物件,可以傳送到達不同伺服器端IP或埠的資料包,這點是TCP方式無法做到的。

介紹完了UDP方式客戶端網路程式設計的基礎知識以後,下面再來介紹一下UDP方式伺服器端網路程式設計的基礎知識。

         UDP方式網路程式設計的伺服器端實現和TCP方式的伺服器端實現類似,也是伺服器端監聽某個埠,然後獲得資料包,進行邏輯處理以後將處理以後的結果反饋給客戶端,最後關閉網路連線,下面依次進行介紹。

首先UDP方式伺服器端網路程式設計需要建立一個連線,該連線監聽某個埠,實現的程式碼為:

           DatagramSocket ds = new DatagramSocket(10010);

由於伺服器端的埠需要固定,所以一般在建立伺服器端連線時,都指定埠號。例如該示例程式碼中指定10010埠為伺服器端使用的埠號,客戶端端在連線伺服器端時連線該埠號即可。

接著伺服器端就開始接收客戶端傳送過來的資料,其接收的方法和客戶端接收的方法一直,其中receive方法的作用類似於TCP方式中accept方法的作用,該方法也是一個阻塞方法,其作用是接收資料。

接收到客戶端傳送過來的資料以後,伺服器端對該資料進行邏輯處理,然後將處理以後的結果再發送給客戶端,在這裡傳送時就比客戶端要麻煩一些,因為伺服器端需要獲得客戶端的IP和客戶端使用的埠號,這個都可以從接收到的資料包中獲得。示例程式碼如下:

     //獲得客戶端的IP

     InetAddress clientIP = receiveDp.getAddress();

         //獲得客戶端的埠號

         Int clientPort = receiveDp.getPort();

使用以上程式碼,就可以從接收到的資料包物件receiveDp中獲得客戶端的IP地址和客戶端的埠號,這樣就可以在伺服器端中將處理以後的資料構造成資料包物件,然後將處理以後的資料內容反饋給客戶端了。

最後,當伺服器端實現完成以後,關閉伺服器端連線,實現的方式為呼叫連線物件的close方法,示例程式碼如下:

         ds.close();

介紹完了UDP方式下的客戶端程式設計和伺服器端程式設計的基礎知識以後,下面通過一個簡單的示例演示UDP網路程式設計的基本使用。

該示例的功能是實現將客戶端程式的系統時間傳送給伺服器端,伺服器端接收到時間以後,向客戶端反饋字串“OK”。實現該功能的客戶端程式碼如下所示:

  1. package udp;  
  2. import java.net.*;  
  3. import java.util.*;  
  4. /** 
  5.  * 簡單的UDP客戶端,實現向伺服器端發生系統時間功能 
  6.  */
  7. publicclass SimpleUDPClient {  
  8.             publicstaticvoid main(String[] args) {  
  9.                      DatagramSocket ds = null//連線物件
  10.                      DatagramPacket sendDp; //傳送資料包物件
  11.                 DatagramPacket receiveDp; //接收資料包物件
  12.                      String serverHost = "127.0.0.1"//伺服器IP
  13.                 int serverPort = 10010//伺服器埠號
  14.                      try{  
  15.                         //建立連線
  16.                         ds = new DatagramSocket();  
  17.                         //初始化傳送資料
  18.                         Date d = new Date(); //當前時間
  19.                         String content = d.toString(); //轉換為字串
  20.                         byte[] data = content.getBytes();  
  21.                         //初始化傳送包物件
  22.                         InetAddress address = InetAddress.getByName(serverHost);  
  23.                         sendDp = new DatagramPacket(data,data.length,address,serverPort);  
  24.                         //傳送
  25.                         ds.send(sendDp);  
  26.                         //初始化接收資料
  27.                         byte[] b = newbyte[1024];  
  28.                         receiveDp = new DatagramPacket(b,b.length);  
  29.                         //接收
  30.                         ds.receive(receiveDp);  
  31.                         //讀取反饋內容,並輸出
  32.                         byte[] response = receiveDp.getData();  
  33.                         int len = receiveDp.getLength();  
  34.                         String s = new String(response,0,len);  
  35.                         System.out.println("伺服器端反饋為:" + s);  
  36.                 }catch(Exception e){  
  37.                         e.printStackTrace();  
  38.                 }finally{  
  39.                         try{  
  40.                            //關閉連線
  41.                            ds.close();  
  42.                         }catch(Exception e){}  
  43.                 }  
  44.             }  
  45.         }  

在該示例程式碼中,首先建立UDP方式的網路連線,然後獲得當前系統時間,這裡獲得的系統時間是客戶端程式執行的本地計算機的時間,然後將時間字串以及伺服器端的IP和埠,構造成傳送資料包物件,呼叫連線物件dssend方法傳送出去。在資料傳送出去以後,構造接收資料的資料包物件,呼叫連線物件dsreceive方法接收伺服器端的反饋,並輸出在控制檯。最後在finally語句塊中關閉客戶端網路連線。

和下面將要介紹的伺服器端一起執行時,客戶端程式的輸出結果為:

伺服器端反饋為:OK

下面是該示例程式的伺服器端程式碼實現:

  1. package udp;  
  2.         import java.net.*;  
  3.         /** 
  4.          * 簡單UDP伺服器端,實現功能是輸出客戶端傳送資料, 
  5.            並反饋字串“OK"給客戶端 
  6.          */
  7.         publicclass SimpleUDPServer {  
  8.             publicstaticvoid main(String[] args) {  
  9.                      DatagramSocket ds = null//連線物件
  10.                      DatagramPacket sendDp; //傳送資料包物件
  11.                      DatagramPacket receiveDp; //接收資料包物件
  12.                      finalint PORT = 10010//埠
  13.                                                try{  
  14.                         //建立連線,監聽埠
  15.                         ds = new DatagramSocket(PORT);  
  16.                        System.out.println("伺服器端已啟動:");  
  17.                         //初始化接收資料
  18.                         byte[] b = newbyte[1024];  
  19.                         receiveDp = new DatagramPacket(b,b.length);  
  20.                         //接收
  21.                         ds.receive(receiveDp);  
  22.                         //讀取反饋內容,並輸出
  23.                         InetAddress clientIP = receiveDp.getAddress();  
  24.                         int clientPort = receiveDp.getPort();  
  25.                         byte[] data = receiveDp.getData();  
  26.                         int len = receiveDp.getLength();  
  27.                         System.out.println("客戶端IP:" + clientIP.getHostAddress());  
  28.                         System.out.println("客戶端埠:" + clientPort);  
  29.                         System.out.println("客戶端傳送內容:" + new String(data,0,len));  
  30.                         //傳送反饋
  31.                         String response = "OK";  
  32.                         byte[] bData = response.getBytes();  
  33.                         sendDp = new DatagramPacket(bData,bData.length,clientIP,clientPort);  
  34.                         //傳送
  35.                         ds.send(sendDp);  
  36.                                                }catch(Exception e){  
  37.                         e.printStackTrace();  
  38.                                                }finally{  
  39.                         try{  
  40.                            //關閉連線
  41.                            ds.close();  
  42.                         }catch(Exception e){}  
  43.                                                }  
  44.             }  
  45.         }  

在該伺服器端實現中,首先監聽10010號埠,和TCP方式的網路程式設計類似,伺服器端的receive方法是阻塞方法,如果客戶端不傳送資料,則程式會在該方法處阻塞。當客戶端傳送資料到達伺服器端時,則接收客戶端傳送過來的資料,然後將客戶端傳送的資料內容讀取出來,並在伺服器端程式中列印客戶端的相關資訊,從客戶端傳送過來的資料包中可以讀取出客戶端的IP以及客戶端埠號,將反饋資料字串“OK”傳送給客戶端,最後關閉伺服器端連線,釋放佔用的系統資源,完成程式功能示例。

和前面TCP方式中的網路程式設計類似,這個示例也僅僅是網路程式設計的功能示例,也存在前面介紹的客戶端無法進行多次資料交換,以及伺服器端不支援多個客戶端的問題,這兩個問題也需要對於程式碼進行處理才可以很方便的進行解決。

在解決該問題以前,需要特別指出的是UDP方式的網路程式設計由於不建立虛擬的連線,所以在實際使用時和TCP方式存在很多的不同,最大的一個不同就是“無狀態”。該特點指每次伺服器端都收到資訊,但是這些資訊和連線無關,換句話說,也就是伺服器端只是從資訊是無法識別出是誰傳送的,這樣就要求傳送資訊時的內容需要多一些,這個在後續的示例中可以看到。

下面是實現客戶端多次傳送以及伺服器端支援多個數據包同時處理的程式結構,實現的原理和TCP方式類似,在客戶端將資料的傳送和接收放入迴圈中,而伺服器端則將接收到的每個資料包啟動一個專門的執行緒進行處理。實現的程式碼如下:

  1. package udp;  
  2.     import java.net.*;  
  3.     import java.util.*;  
  4.     /** 
  5.      * 簡單的UDP客戶端,實現向伺服器端發生系統時間功能 
  6.      * 該程式傳送3次資料到伺服器端 
  7.      */
  8.     publicclass MulUDPClient {  
  9.                   publicstaticvoid main(String[] args) {  
  10.                  DatagramSocket ds = null//連線物件
  11.                                      DatagramPacket sendDp; //傳送資料包物件
  12.                                      DatagramPacket receiveDp; //接收資料包物件
  13.                                      String serverHost = "127.0.0.1"//伺服器IP
  14.                                      int serverPort = 10012//伺服器埠號
  15.                                      try{  
  16.                     //建立連線
  17.                     ds = new DatagramSocket();  
  18.                     //初始化
  19.                               InetAddress address = InetAddress.getByName(serverHost);  
  20.                     byte[] b = newbyte[1024];  
  21.                     receiveDp = new DatagramPacket(b,b.length);  
  22.                     System.out.println("客戶端準備完成");  
  23.                     //迴圈30次,每次間隔0.01秒
  24.                     for(int i = 0;i < 30;i++){  
  25.                                                         //初始化傳送資料
  26.                                                         Date d = new Date(); //當前時間
  27.                                                         String content = d.toString(); //轉換為字串
  28.                                                         byte[] data = content.getBytes();  
  29.                                                         //初始化傳送包物件
  30.                                                         sendDp = new DatagramPacket(data,data.length,address, serverPort);  
  31.                                                         //傳送
  32.                                                         ds.send(sendDp);  
  33.                                                         //延遲
  34.                                                         Thread.sleep(10);  
  35.                                                         //接收
  36.                                                         ds.receive(receiveDp);  
  37.                                                         //讀取反饋內容,並輸出
  38.                                                         byte[] response = receiveDp.getData();  
  39.                                                         int len = receiveDp.getLength();  
  40.                                                         String s = new String(response,0,len);  
  41.                                                         System.out.println("伺服器端反饋為:" + s);  
  42.                      }  
  43.                  }catch(Exception e){  
  44.                      e.printStackTrace();  
  45.                  }finally{  
  46.                      try{  
  47.                                                         //關閉連線
  48.                                                         ds.close();  
  49.                      }catch(Exception e){}  
  50.                  }  
  51.          }  
  52.      }  

在該示例中,將和伺服器端進行資料交換的邏輯寫在一個for迴圈的內部,這樣就可以實現和伺服器端的多次交換了,考慮到伺服器端的響應速度,在每次傳送之間加入0.01秒的時間間隔。最後當資料交換完成以後關閉連線,結束程式。

實現該邏輯的伺服器端程式程式碼如下:

  1. package udp;  
  2. import java.net.*;  
  3. /** 
  4. * 可以併發處理資料包的伺服器端 
  5. * 功能為:顯示客戶端傳送的內容,並向客戶端反饋字串“OK” 
  6. */
  7. publicclass MulUDPServer {  
  8. publicstaticvoid main(String[] args) {  
  9. DatagramSocket ds = null//連線物件
  10. DatagramPacket receiveDp; //接收資料包物件
  11. finalint PORT = 10012//埠
  12. byte[] b = newbyte[1024];  
  13. receiveDp = new DatagramPacket(b,b.length);  
  14. try{  
  15. //建立連線,監聽埠
  16. ds = new DatagramSocket(PORT);  
  17. System.out.println("伺服器端已啟動:");  
  18. while(true){  
  19. //接收
  20. ds.receive(receiveDp);  
  21. //啟動執行緒處理資料包
  22. new LogicThread(ds,receiveDp);  
  23. }  
  24. }catch(Exception e){  
  25.          e.printStackTrace();  
  26. }finally{  
  27. try{  
  28. //關閉連線
  29. ds.close();  
  30. }catch(Exce