焦點速看:第十五章《網(wǎng)絡(luò)編程》第4節(jié):基于UDP協(xié)議的網(wǎng)絡(luò)編程

2023-01-02 22:30:29 來源:51CTO博客

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

15.4.1協(xié)議簡介

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以下,不需要建立連接。?

15.4.2使用DatagramSocket發(fā)送和接收數(shù)據(jù)

之前講過: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é)果?

15.4.3使用MulticastSocket實現(xiàn)多點廣播

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

上一篇:全球要聞:第十六章《正則表達(dá)式》第1節(jié):正則表達(dá)式入門
下一篇:C語言--指針1