
?UDP協(xié)議是一種不可靠的網(wǎng)絡(luò)協(xié)議,之所以說這種協(xié)議不可靠,是因為它在通信實例的兩端各建立一個Socket,但這兩個Socket之間并沒有虛擬鏈路。這兩個Socket只是發(fā)送、接收數(shù)據(jù)報的對象。Java 提供了DatagramSocket對象作為基于UDP協(xié)議的Socket,而使用DatagramPacket代表DatagramSocket發(fā)送、接收的數(shù)據(jù)報。本小節(jié)將詳細(xì)講解基于UDP協(xié)議的網(wǎng)絡(luò)編程技術(shù)。
UDP是User Datagram Protocol的縮寫,意為“用戶數(shù)據(jù)報協(xié)議”。UDP協(xié)議主要用來支持那些需要在計算機之間傳輸數(shù)據(jù)的網(wǎng)絡(luò)連接,雖然UDP協(xié)議目前應(yīng)用不如TCP協(xié)議廣泛,但UDP協(xié)議依然是一個非常實用和可行的網(wǎng)絡(luò)傳輸層協(xié)議,尤其是在一些實時性很強的應(yīng)用場景中,比如網(wǎng)絡(luò)游戲、視頻會議等。?
(資料圖片僅供參考)
UDP協(xié)議是一種面向非連接的協(xié)議,面向非連接指的是在正式通信前不必與對方先建立連接,不管對方狀態(tài)就直接發(fā)送。至于對方是否可以接收到這些數(shù)據(jù)內(nèi)容,UDP協(xié)議無法控制,因此說UDP協(xié)議是一種不可靠的協(xié)議。UDP協(xié)議適用于一次只傳送少量數(shù)據(jù)、對可靠性要求不高的應(yīng)用環(huán)境。?
與前面介紹的TCP協(xié)議一樣,UDP協(xié)議直接建立在IP協(xié)議之上。實際上,UDP協(xié)議和TCP協(xié)議都屬于傳輸層協(xié)議。正因為UDP協(xié)議是面向非連接的協(xié)議,沒有建立連接的過程,因此它的通信效率很高,但也正因為如此,它的可靠性不如TCP協(xié)議。?
UDP協(xié)議的主要作用是完成網(wǎng)絡(luò)數(shù)據(jù)流和數(shù)據(jù)報之間的轉(zhuǎn)換:在信息的發(fā)送端,UDP協(xié)議將網(wǎng)絡(luò)數(shù)據(jù)流封裝成數(shù)據(jù)報,然后將數(shù)據(jù)報發(fā)送出去,而在信息的接收端,UDP協(xié)議將數(shù)據(jù)報轉(zhuǎn)換成實際數(shù)據(jù)內(nèi)容。?
讀者可以把UDP協(xié)議下的DatagramSocket想象成手機,把DatagramPacket想像成短信,也就是說使用DatagramSocket發(fā)送DatagramPacket,而無論對發(fā)是否做好接收準(zhǔn)備,我們都可以用手機發(fā)短信。把UDP協(xié)議和TCP協(xié)議做個簡單的對比,可以發(fā)現(xiàn):TCP協(xié)議可靠,傳輸大小無限制,但是需要連接建立時間,差錯控制開銷大。而UDP協(xié)議不可靠,差錯控制開銷較小,傳輸大小限制在64KB以下,不需要建立連接。?
之前講過:DatagramSocket就如同是手機,而DatagramPacket如同是短信。DatagramSocket不產(chǎn)生IO流,它唯一的作用就是收發(fā)DatagramPacket。DatagramSocket的構(gòu)造方法如表15-7所示。?
表15-7 DatagramSocket的構(gòu)造方法?
方法? | 功能? |
DatagramSocket()? | 創(chuàng)建一個DatagramSocket 實例,并將該對象綁定到本機默認(rèn)IP地址、本機所有可用端口中隨機選擇的某個端口? |
DatagramSocket(int prot)? | 創(chuàng)建一個DatagramSocket實例,并將該對象綁定到本機默認(rèn)IP地址和指定端口? |
DatagramSocket(int port, InetAddress laddr)? | 創(chuàng)建一個DatagramSocket實例,并將該對象綁定到指定的IP地址和端口號? |
從表15-7可以看出:DatagramSocket只是定義了自己在哪個IP地址的計算機以及自己接收數(shù)據(jù)報的端口號,并沒有定義數(shù)據(jù)報要發(fā)到哪個IP地址以及發(fā)給哪個端口號。實際上,數(shù)據(jù)報到底要發(fā)送到哪里是由數(shù)據(jù)報,也就是DatagramPacket自身所決定的。DatagramPacket的構(gòu)造方法如表15-8所示。?
表15-8 DatagramPacket的構(gòu)造方法?
方法? | 功能? |
DatagramPacket(byte[] buf,int length)? | 以一個空數(shù)組來創(chuàng)建DatagramPacket對象,該對象的作用是接收DatagramSocket中的數(shù)據(jù)。? |
DatagramPacket(byte[] buf, int length, InetAddress addr, int port)? | 以一個包含數(shù)據(jù)的數(shù)組來創(chuàng)建DatagramPacket對象,創(chuàng)建該DatagramPacket對象時指定了IP地址和端口,IP地址和端口決定了該數(shù)據(jù)報的目的地? |
DatagramPacket(byte[] buf, int offset, int length)? | 以一個空數(shù)組來創(chuàng)建DatagramPacket對象,并? 指定接收到的數(shù)據(jù)放入buf數(shù)組中時從下標(biāo)offset開始,最多放length個字節(jié)? |
DatagramPacket(byte[] buf, int offset, int length, InetAddress address, int port)? | 創(chuàng)建一個用于發(fā)送的DatagramPacket對象,指定發(fā)送buf數(shù)組中從offset開始,總共length個字節(jié)? |
DatagramSocket發(fā)送和接收數(shù)據(jù)報的方法分別是send()和received(),它們都以DatagramPacket類型的對象作為參數(shù)。需要強調(diào):使用UDP協(xié)議時,實際上并沒有明顯的服務(wù)器端和客戶端,因為雙方都需要先建立一個DatagramSocket 對象,用來接收或發(fā)送數(shù)據(jù)報,然后使用DatagramPacket對象作為傳輸數(shù)據(jù)的載體。但通常固定IP 地址、固定端口的DatagramSocket對象所在的程序被稱為服務(wù)器,這是因為該DatagramSocket有固定的IP地址,其他DatagramSocket可以準(zhǔn)確的找到它。?
發(fā)送數(shù)據(jù)時,應(yīng)調(diào)用表15-8中第二個或第四個構(gòu)造方法創(chuàng)建DatagramPacket 對象,此時的字節(jié)數(shù)組里存放了想發(fā)送的數(shù)據(jù)。除此之外,還要給出完整的目的地址,包括IP 地址和端口號。發(fā)送數(shù)據(jù)是通過DatagramSocket的send()方法實現(xiàn)的,send0方法根據(jù)數(shù)據(jù)報的目的地址來尋徑以傳送數(shù)據(jù)報。?
接收數(shù)據(jù)時,應(yīng)該調(diào)用表15-8中第一個或第三個構(gòu)造方法創(chuàng)建DatagramPacket 對象,并給出接收數(shù)據(jù)的字節(jié)數(shù)組及其長度。然后調(diào)用DatagramSocket的receive()方法等待數(shù)據(jù)報的到來,receive()方法將一直等待,直到收到一個數(shù)據(jù)報為止。?
可以看到,創(chuàng)建DatagramPacket對象時,必須傳入一個字節(jié)數(shù)組,而這個數(shù)組的長度決定了該DatagramPacket能放多少數(shù)據(jù)。DatagramPacket提供了一個getData()方法,這個方法能夠返回Datagram Packet對象中封裝的字節(jié)數(shù)組。此外,程序員通過DatagramPacket類的getLength()方法可以獲得字節(jié)數(shù)組中有效字節(jié)的長度。?
當(dāng)DatagramSocket收到一個DatagramPacket對象后,可以向該數(shù)據(jù)報的發(fā)送者回復(fù)信息,但由于UDP協(xié)議是面向非連接的,所以接收者并不知道每個數(shù)據(jù)報由誰發(fā)送過來的,此時可以使用表15-9所示方法來獲取發(fā)送者的IP地址和端口。?
表15-9 獲取DatagramPacket的IP地址和端口號的方法?
方法? | 功能? |
InetAddress getAddress()? | 當(dāng)程序準(zhǔn)備發(fā)送此數(shù)據(jù)報時,該方法返回此數(shù)據(jù)報的目標(biāo)機器的IP地址,當(dāng)程序剛接收到一個數(shù)據(jù)報時,該方法返回該數(shù)據(jù)報的發(fā)送主機的IP地址? |
int getPort()? | 當(dāng)程序準(zhǔn)備發(fā)送此數(shù)據(jù)報時,該方法返回此數(shù)據(jù)報的目標(biāo)機器的端口號,當(dāng)程序剛接收到一個數(shù)據(jù)報時,該方法返回該數(shù)據(jù)報的發(fā)送主機的端口號? |
SocketAddress getSocketAddress()? | 當(dāng)程序準(zhǔn)備發(fā)送此數(shù)據(jù)報時,該方法返回此數(shù)據(jù)報的目標(biāo)SocketAddress,當(dāng)程序剛接收到一個數(shù)據(jù)報時,該方法返回該數(shù)據(jù)報的發(fā)送主機的SocketAddress? |
表15-9中第三個方法的返回值是一個SocketAddress對象,該對象中封裝了一個InetAddress對象和一個代表端口的整數(shù),所以使用SocketAddress 對象可以同時代表IP地址和端口。下面的【例15_06】展示了如何使用DatagramSocket收發(fā)消息。?
【例15_06 使用DatagramSocket收發(fā)數(shù)據(jù)】
Exam15_06.java?
import java.net.*;//接收消息的線程class ReceiveThread extends Thread { public void run() { try { DatagramSocket inSocket = new DatagramSocket(4567); for (int i=1;i<=3;i++) { byte[] in = new byte[1024]; DatagramPacket inPack = new DatagramPacket(in,in.length); inSocket.receive(inPack); String message = new String(in,0,inPack.getLength()); System.out.println(message); } inSocket.close(); }catch (Exception e){ e.printStackTrace(); } }}//發(fā)送消息的線程class SendThread extends Thread{ public void run() { try { DatagramSocket outSocket = new DatagramSocket(); String message1 = "網(wǎng)絡(luò)編程"; String message2 = "是Java語言中很重要的模塊"; String message3 = "雖然有難度,但我會努力"; byte[] out1 = message1.getBytes(); byte[] out2 = message2.getBytes(); byte[] out3 = message3.getBytes(); //創(chuàng)建三個數(shù)據(jù)報,它們代表要被發(fā)送的消息,數(shù)據(jù)包指定了消息要發(fā)送到哪個IP地址和端口號 DatagramPacket outPack1 = new DatagramPacket(out1,out1.length,InetAddress.getLocalHost(),4567); DatagramPacket outPack2 = new DatagramPacket(out2,out2.length,InetAddress.getLocalHost(),4567); DatagramPacket outPack3 = new DatagramPacket(out3,out3.length,InetAddress.getLocalHost(),4567); //發(fā)送消息 outSocket.send(outPack1); outSocket.send(outPack2); outSocket.send(outPack3); outSocket.close(); }catch (Exception e){ e.printStackTrace(); } }}public class Exam15_06 { public static void main(String[] args) { //創(chuàng)建并啟動線程 new ReceiveThread().start(); new SendThread().start(); }}
【例15_06】中創(chuàng)建了兩個線程,分別是接收消息的ReceiveThread和發(fā)送消息的SendThread。ReceiveThread的run()方法中創(chuàng)建了用于接收消息的inSocket,并規(guī)定接收消息的端口號是4567。當(dāng)接收到消息后,調(diào)用DatagramPacket類的getLength()方法可以獲得字節(jié)數(shù)組中有效字節(jié)的長度,并以數(shù)組中有效字節(jié)部分創(chuàng)建消息字符串。?
在SendThread線程的run()方法中創(chuàng)建了用于發(fā)送消息的outSocket,并且使用它連續(xù)發(fā)送了三條長短不一的消息。【例15_06】的運行結(jié)果如圖15-7所示。?
圖15-7【例15_06】運行結(jié)果?
DatagramSocket只允許數(shù)據(jù)報發(fā)送給指定的目標(biāo)地址,而MulticastSocket可以將數(shù)據(jù)報以廣播方式發(fā)送到多個地址的計算機。如果想要使用多點廣播,則需要讓一個數(shù)據(jù)報指定一組目標(biāo)主機地址,當(dāng)數(shù)據(jù)報發(fā)出后,整個組的所有主機都能收到該數(shù)據(jù)報。IP多點廣播實現(xiàn)了將單一信息發(fā)送到多個接收者的廣播,實現(xiàn)多點廣播的原理是:設(shè)置一組特殊網(wǎng)絡(luò)地址作為多點廣播地址,每一個多點廣播地址都被看做一個組,當(dāng)客戶端需要發(fā)送、接收廣播信息時,加入到該組即可。IP協(xié)議為多點廣播提供了這批特殊的IP地址,這些IP地址的范圍是224.0.0.0~239.255.255.255。多點廣播原理如圖15-8 所示。?
圖15-8 多點廣播原理圖?
從圖17-8 中可以看出:MulticastSocket 類是實現(xiàn)多點廣播的關(guān)鍵,當(dāng)MulticastSocket 把一個數(shù)據(jù)報發(fā)送到多點廣播IP地址時,該數(shù)據(jù)報將被自動廣播到加入該地址的所有MulticastSocket。?
MulticastSocket既可以將數(shù)據(jù)報發(fā)送到多點廣播地址,也可以接收其他主機的廣播信息。MulticastSocket有點像DatagramSocket,事實上MulticastSocket是DatagramSocket的一個子類,也就是說,MulticastSocket 是特殊的DatagramSocket。當(dāng)要發(fā)送一個數(shù)據(jù)報時,可以使用隨機端口創(chuàng)建MulticastSocket,也可以在指定端口創(chuàng)建MulticastSocket。MulticastSocket 提供了三個構(gòu)造方法,如表15-10所示。?
表15-10 MulticastSocket的構(gòu)造方法?
方法? | 功能? |
MulticastSocket()? | 使用本機默認(rèn)地址、隨機端口來創(chuàng)建對象? |
MulticastSocket(int portNumber)? | 使用本機默認(rèn)地址、指定端口來創(chuàng)建對象? |
MulticastSocket(SocketAddress bindaddr)? | 使用本機指定IP地址來創(chuàng)建對象? |
如果創(chuàng)建僅用于發(fā)送數(shù)據(jù)報的MulticastSocket 對象,則使用默認(rèn)地址、隨機端口即可。但如果創(chuàng)建接收數(shù)據(jù)報的MulticastSocket對象,則該MulticastSocket對象必須具有指定端口,否則發(fā)送方無法確定發(fā)送數(shù)據(jù)報的目標(biāo)端口。創(chuàng)建MulticastSocket 對象后,還需要將該MulticastSocket 加入到指定的多點廣播地址,程序員需要調(diào)用MulticastSocket類的joinGroup()方法把對象加入指定組,如果要讓對象脫離一個組,則使用leaveGroup()方法實現(xiàn)。?
在某些系統(tǒng)中,可能有多個網(wǎng)絡(luò)接口。這可能會給多點廣播帶來問題,這時候程序需要在一個指定的網(wǎng)絡(luò)接口上監(jiān)聽,通過調(diào)用setInterface()方法可以強制MulticastSocket使用指定的網(wǎng)絡(luò)接口,也可以使用getInterface()方法查詢MulticastSocket監(jiān)聽的網(wǎng)絡(luò)接口。?
MulticastSocket用于發(fā)送、接收數(shù)據(jù)報的方法與DatagramSocket完全一樣。但MulticastSocket比DatagramSocket多了一個setTimeToLive(int t)方法,該ttl參數(shù)用于設(shè)置數(shù)據(jù)報最多可以跨過多少個網(wǎng)絡(luò),當(dāng)ttl的值為0時,指定數(shù)據(jù)報應(yīng)停留在本地主機,當(dāng)ttl 的值為1時,指定數(shù)據(jù)報發(fā)送到本地局域網(wǎng),當(dāng)t的值為32時,意味著只能發(fā)送到本站點的網(wǎng)絡(luò)上,當(dāng)ttl的值為64時,意味著數(shù)據(jù)報應(yīng)保留在本地區(qū);當(dāng)ttl的值為128時,意味著數(shù)據(jù)報應(yīng)保留在本大洲,當(dāng)ttl的值為255時,意味著數(shù)據(jù)報可發(fā)送到所有地方;在默認(rèn)情況下,該ttl的值為1。?
使用MulticastSocket 進(jìn)行多點廣播時所有的通信實體都是平等的,它們都將自己的數(shù)據(jù)報發(fā)送到多點廣播IP地址,并使用MulticastSocket接收其他人發(fā)送的廣播數(shù)據(jù)報。下面【例15_07】展示了使用MulticastSocket實現(xiàn)多點廣播。?
【例15_07多點廣播】
Exam15_07.java
import java.net.*;public class Exam15_07 { public static void main(String[] args) throws Exception { String IP = "239.255.255.254";//廣播地址 InetAddress broadcastAddress = InetAddress.getByName (IP) ; //創(chuàng)建兩個接收消息的MulticastSocket MulticastSocket receiveSocket1 = new MulticastSocket (4325) ; MulticastSocket receiveSocket2 = new MulticastSocket (4325) ; //創(chuàng)建一個發(fā)送廣播消息的MulticastSocket MulticastSocket sendSocket = new MulticastSocket () ; byte[] in1 = new byte[1024]; DatagramPacket inPack1 = new DatagramPacket(in1,in1.length); byte[] in2 = new byte[1024]; DatagramPacket inPack2 = new DatagramPacket(in2,in2.length); sendSocket.setLoopbackMode(false);//設(shè)置消息不會發(fā)送給自己 //把Socket加入廣播地址 receiveSocket1.joinGroup(broadcastAddress); receiveSocket2.joinGroup(broadcastAddress); sendSocket.joinGroup(broadcastAddress); String message = "這是一條多點廣播的消息"; byte[] out = message.getBytes(); //要發(fā)送的數(shù)據(jù)報 DatagramPacket outPack = new DatagramPacket(out,out.length,broadcastAddress,4325); sendSocket.send(outPack);//發(fā)送 receiveSocket1.receive(inPack1);//接收數(shù)據(jù)報 receiveSocket2.receive(inPack2);//接收數(shù)據(jù)報 String messageReceive1 = new String(in1,0,inPack1.getLength()); System.out.println(messageReceive1);//打印接收的消息 String messageReceive2 = new String(in2,0,inPack2.getLength()); System.out.println(messageReceive2);//打印接收的消息 }}
由于IPv6不支持多點廣播,所以需要在運行多點廣播程序之前手動設(shè)置一個虛擬機參數(shù)才能保證程序運行成功。設(shè)置虛擬機參數(shù)的方式是:首先找到IDEA的“Run”菜單,展開菜單后單擊“Eidt Configurations”菜單項,出現(xiàn)如圖15-9所示界面。?
圖15-9 設(shè)置虛擬機參數(shù)界面?
打開這個界面后,先檢查類名稱,如果類名稱不是當(dāng)前程序,將會導(dǎo)致參數(shù)設(shè)置到其他程序上。檢查類名稱后,單擊右上角方框中的“Modify options”超鏈接,在彈出的菜單中選擇“Add VM options”菜單項,之后在參數(shù)文本框中填入“-Djava.net.preferIPv4Stack=true”,如圖15-10所示。?
圖15-10 填入?yún)?shù)?
填入以上參數(shù)后,單擊“OK”按鈕即可完成配置,之后就能正確的運行程序。【例15_07】的運行結(jié)果如圖15-11所示。?
圖15-11【例15_07】運行結(jié)果?
從圖15-11可以看出:一個MulticastSocket發(fā)出消息后,在同一個廣播地址的所有MulticastSocket都能收到消息。
本文字版教程還配有更詳細(xì)的視頻講解,小伙伴們可以??點擊這里??觀看。
標(biāo)簽: 構(gòu)造方法 廣播地址 發(fā)送數(shù)據(jù)