使用DatagramSocket发送、接收数据(Socket之UDP套接字)
2019獨(dú)角獸企業(yè)重金招聘Python工程師標(biāo)準(zhǔn)>>>
創(chuàng)建一個(gè)DatagramSocket實(shí)例,并將該對(duì)象綁定到指定IP地址、指定端口。
通過上面三個(gè)構(gòu)造器中的任意一個(gè)構(gòu)造器即可創(chuàng)建一個(gè)DatagramSocket實(shí)例,通常在創(chuàng)建服務(wù)器時(shí),創(chuàng)建指定端口的DatagramSocket實(shí)例--這樣保證其他客戶端可以將數(shù)據(jù)發(fā)送到該服務(wù)器。一旦得到了DatagramSocket實(shí)例之后,就可以通過如下兩個(gè)方法來接收和發(fā)送數(shù)據(jù)。
receive(DatagramPacket p):從該DatagramSocket中接收數(shù)據(jù)報(bào)。
send(DatagramPacket p):以該DatagramSocket對(duì)象向外發(fā)送數(shù)據(jù)報(bào)。
從上面兩個(gè)方法可以看出,使用DatagramSocket發(fā)送數(shù)據(jù)報(bào)時(shí),DatagramSocket并不知道將該數(shù)據(jù)報(bào)發(fā)送到哪里,而是由DatagramPacket自身決定數(shù)據(jù)報(bào)的目的地。就像碼頭并不知道每個(gè)集裝箱的目的地,碼頭只是將這些集裝箱發(fā)送出去,而集裝箱本身包含了該集裝箱的目的地。
下面看一下DatagramPacket的構(gòu)造器。
DatagramPacket(byte[] buf,int length):以一個(gè)空數(shù)組來創(chuàng)建DatagramPacket對(duì)象,該對(duì)象的作用是接收DatagramSocket中的數(shù)據(jù)。
DatagramPacket(byte[] buf, int length, InetAddress addr, int port):以一個(gè)包含數(shù)據(jù)的數(shù)組來創(chuàng)建DatagramPacket對(duì)象,創(chuàng)建該DatagramPacket對(duì)象時(shí)還指定了IP地址和端口--這就決定了該數(shù)據(jù)報(bào)的目的地。
DatagramPacket(byte[] buf, int offset, int length):以一個(gè)空數(shù)組來創(chuàng)建DatagramPacket對(duì)象,并指定接收到的數(shù)據(jù)放入buf數(shù)組中時(shí)從offset開始,最多放length個(gè)字節(jié)。
DatagramPacket(byte[] buf, int offset, int length, InetAddress address, int port):創(chuàng)建一個(gè)用于發(fā)送的DatagramPacket對(duì)象,指定發(fā)送buf數(shù)組中從offset開始,總共length個(gè)字節(jié)。
當(dāng)Client/Server程序使用UDP協(xié)議時(shí),實(shí)際上并沒有明顯的服務(wù)器端和客戶端,因?yàn)閮煞蕉夹枰冉⒁粋€(gè)DatagramSocket對(duì)象,用來接收或發(fā)送數(shù)據(jù)報(bào),然后使用DatagramPacket對(duì)象作為傳輸數(shù)據(jù)的載體。通常固定IP地址、固定端口的DatagramSocket對(duì)象所在的程序被稱為服務(wù)器,因?yàn)樵揇atagramSocket可以主動(dòng)接收客戶端數(shù)據(jù)。
在接收數(shù)據(jù)之前,應(yīng)該采用上面的第一個(gè)或第三個(gè)構(gòu)造器生成一個(gè)DatagramPacket對(duì)象,給出接收數(shù)據(jù)的字節(jié)數(shù)組及其長度。然后調(diào)用DatagramSocket 的receive()方法等待數(shù)據(jù)報(bào)的到來,receive()將一直等待(該方法會(huì)阻塞調(diào)用該方法的線程),直到收到一個(gè)數(shù)據(jù)報(bào)為止。如下代碼所示:
?
// 創(chuàng)建一個(gè)接收數(shù)據(jù)的DatagramPacket對(duì)象 DatagramPacket packet=new DatagramPacket(buf, 256); // 接收數(shù)據(jù)報(bào) socket.receive(packet);?
在發(fā)送數(shù)據(jù)之前,調(diào)用第二個(gè)或第四個(gè)構(gòu)造器創(chuàng)建DatagramPacket對(duì)象,此時(shí)的字節(jié)數(shù)組里存放了想發(fā)送的數(shù)據(jù)。除此之外,還要給出完整的目的地址,包括IP地址和端口號(hào)。發(fā)送數(shù)據(jù)是通過DatagramSocket的send()方法實(shí)現(xiàn)的,send()方法根據(jù)數(shù)據(jù)報(bào)的目的地址來尋徑以傳送數(shù)據(jù)報(bào)。如下代碼所示:
// 創(chuàng)建一個(gè)發(fā)送數(shù)據(jù)的DatagramPacket對(duì)象 DatagramPacket packet = new DatagramPacket(buf, length, address, port); // 發(fā)送數(shù)據(jù)報(bào) socket.send(packet);使用DatagramPacket接收數(shù)據(jù)時(shí),會(huì)感覺DatagramPacket設(shè)計(jì)得過于煩瑣。開發(fā)者只關(guān)心該DatagramPacket能放多少數(shù)據(jù),而DatagramPacket是否采用字節(jié)數(shù)組來存儲(chǔ)數(shù)據(jù)完全不想關(guān)心。但Java要求創(chuàng)建接收數(shù)據(jù)用的DatagramPacket時(shí),必須傳入一個(gè)空的字節(jié)數(shù)組,該數(shù)組的長度決定了該DatagramPacket能放多少數(shù)據(jù),這實(shí)際上暴露了DatagramPacket的實(shí)現(xiàn)細(xì)節(jié)。接著DatagramPacket又提供了一個(gè)getData()方法,該方法又可以返回Datagram Packet對(duì)象里封裝的字節(jié)數(shù)組,該方法更顯得有些多余--如果程序需要獲取DatagramPacket里封裝的字節(jié)數(shù)組,直接訪問傳給 DatagramPacket構(gòu)造器的字節(jié)數(shù)組實(shí)參即可,無須調(diào)用該方法。
當(dāng)服務(wù)器端(也可以是客戶端)接收到一個(gè)DatagramPacket對(duì)象后,如果想向該數(shù)據(jù)報(bào)的發(fā)送者"反饋"一些信息,但由于UDP協(xié)議是面向非連接的,所以接收者并不知道每個(gè)數(shù)據(jù)報(bào)由誰發(fā)送過來,但程序可以調(diào)用DatagramPacket的如下3個(gè)方法來獲取發(fā)送者的IP地址和端口。
InetAddress getAddress():當(dāng)程序準(zhǔn)備發(fā)送此數(shù)據(jù)報(bào)時(shí),該方法返回此數(shù)據(jù)報(bào)的目標(biāo)機(jī)器的IP地址;當(dāng)程序剛接收到一個(gè)數(shù)據(jù)報(bào)時(shí),該方法返回該數(shù)據(jù)報(bào)的發(fā)送主機(jī)的IP地址。
int getPort():當(dāng)程序準(zhǔn)備發(fā)送此數(shù)據(jù)報(bào)時(shí),該方法返回此數(shù)據(jù)報(bào)的目標(biāo)機(jī)器的端口;當(dāng)程序剛接收到一個(gè)數(shù)據(jù)報(bào)時(shí),該方法返回該數(shù)據(jù)報(bào)的發(fā)送主機(jī)的端口。
SocketAddress getSocketAddress():當(dāng)程序準(zhǔn)備發(fā)送此數(shù)據(jù)報(bào)時(shí),該方法返回此數(shù)據(jù)報(bào)的目標(biāo)SocketAddress;當(dāng)程序剛接收到一個(gè)數(shù)據(jù)報(bào)時(shí),該方法返回該數(shù)據(jù)報(bào)的發(fā)送主機(jī)的SocketAddress。
getSocketAddress()方法的返回值是一個(gè)SocketAddress對(duì)象,該對(duì)象實(shí)際上就是一個(gè)IP地址和一個(gè)端口號(hào)。也就是說,SocketAddress對(duì)象封裝了一個(gè)InetAddress對(duì)象和一個(gè)代表端口的整數(shù),所以使用SocketAddress對(duì)象可以同時(shí)代表IP地址和端口。
?
http://book.51cto.com/art/201203/322542.htm
17.4.2 使用DatagramSocket發(fā)送、接收數(shù)據(jù)(2)
下面程序使用DatagramSocket實(shí)現(xiàn)了Server/Client結(jié)構(gòu)的網(wǎng)絡(luò)通信。本程序的服務(wù)器端使用循環(huán)1000次來讀取DatagramSocket中的數(shù)據(jù)報(bào),每當(dāng)讀取到內(nèi)容之后便向該數(shù)據(jù)報(bào)的發(fā)送者送回一條信息。服務(wù)器端程序代碼如下。
程序清單:codes\17\17.4\UdpServer.java
public class UdpServer{public static final int PORT = 30000;// 定義每個(gè)數(shù)據(jù)報(bào)的最大大小為4KBprivate static final int DATA_LEN = 4096;// 定義接收網(wǎng)絡(luò)數(shù)據(jù)的字節(jié)數(shù)組????byte[] inBuff = new byte[DATA_LEN];// 以指定字節(jié)數(shù)組創(chuàng)建準(zhǔn)備接收數(shù)據(jù)的DatagramPacket對(duì)象private DatagramPacket inPacket =new DatagramPacket(inBuff , inBuff.length);// 定義一個(gè)用于發(fā)送的DatagramPacket對(duì)象private DatagramPacket outPacket;// 定義一個(gè)字符串?dāng)?shù)組,服務(wù)器端發(fā)送該數(shù)組的元素String[] books = new String[]{"瘋狂Java講義","輕量級(jí)Java EE企業(yè)應(yīng)用實(shí)戰(zhàn)","瘋狂Android講義","瘋狂Ajax講義"};public void init()throws IOException{try(// 創(chuàng)建DatagramSocket對(duì)象DatagramSocket socket = new DatagramSocket(PORT)){// 采用循環(huán)接收數(shù)據(jù)for (int i = 0; i < 1000 ; i++ ){// 讀取Socket中的數(shù)據(jù),讀到的數(shù)據(jù)放入inPacket封裝的數(shù)組里socket.receive(inPacket);// 判斷inPacket.getData()和inBuff是否是同一個(gè)數(shù)組System.out.println(inBuff == inPacket.getData());// 將接收到的內(nèi)容轉(zhuǎn)換成字符串后輸出System.out.println(new String(inBuff, 0 , inPacket.getLength()));// 從字符串?dāng)?shù)組中取出一個(gè)元素作為發(fā)送數(shù)據(jù)byte[] sendData = books[i % 4].getBytes();// 以指定的字節(jié)數(shù)組作為發(fā)送數(shù)據(jù),以剛接收到的DatagramPacket的// 源SocketAddress作為目標(biāo)SocketAddress創(chuàng)建DatagramPacketoutPacket = new DatagramPacket(sendData, sendData.length , inPacket.getSocketAddress());// 發(fā)送數(shù)據(jù)socket.send(outPacket);}}}public static void main(String[] args)throws IOException{new UdpServer().init();} }上面程序中的粗體字代碼就是使用DatagramSocket發(fā)送、接收DatagramPacket的關(guān)鍵代碼,該程序可以接收1000個(gè)客戶端發(fā)送過來的數(shù)據(jù)。
客戶端程序代碼也與此類似,客戶端采用循環(huán)不斷地讀取用戶鍵盤輸入,每當(dāng)讀取到用戶輸入的內(nèi)容后就將該內(nèi)容封裝成DatagramPacket數(shù)據(jù)報(bào),再將該數(shù)據(jù)報(bào)發(fā)送出去;接著把DatagramSocket中的數(shù)據(jù)讀入接收用的DatagramPacket中(實(shí)際上是讀入該DatagramPacket所封裝的字節(jié)數(shù)組中)。客戶端程序代碼如下。
?
17.4.2 使用DatagramSocket發(fā)送、接收數(shù)據(jù)(3)
程序清單:codes\17\17.4\UdpClient.java
?
public class UdpClient{????// 定義發(fā)送數(shù)據(jù)報(bào)的目的地public static final int DEST_PORT = 30000;public static final String DEST_IP = "127.0.0.1";// 定義每個(gè)數(shù)據(jù)報(bào)的最大大小為4KBprivate static final int DATA_LEN = 4096;// 定義接收網(wǎng)絡(luò)數(shù)據(jù)的字節(jié)數(shù)組byte[] inBuff = new byte[DATA_LEN];// 以指定的字節(jié)數(shù)組創(chuàng)建準(zhǔn)備接收數(shù)據(jù)的DatagramPacket對(duì)象private DatagramPacket inPacket =new DatagramPacket(inBuff , inBuff.length);// 定義一個(gè)用于發(fā)送的DatagramPacket對(duì)象private DatagramPacket outPacket = null;public void init()throws IOException{try(// 創(chuàng)建一個(gè)客戶端DatagramSocket,使用隨機(jī)端口DatagramSocket socket = new DatagramSocket()){// 初始化發(fā)送用的DatagramSocket,它包含一個(gè)長度為0的字節(jié)數(shù)組outPacket = new DatagramPacket(new byte[0] , 0, InetAddress.getByName(DEST_IP) , DEST_PORT);// 創(chuàng)建鍵盤輸入流Scanner scan = new Scanner(System.in);// 不斷地讀取鍵盤輸入while(scan.hasNextLine()){// 將鍵盤輸入的一行字符串轉(zhuǎn)換成字節(jié)數(shù)組byte[] buff = scan.nextLine().getBytes();// 設(shè)置發(fā)送用的DatagramPacket中的字節(jié)數(shù)據(jù)outPacket.setData(buff);// 發(fā)送數(shù)據(jù)報(bào)socket.send(outPacket);// 讀取Socket中的數(shù)據(jù),讀到的數(shù)據(jù)放在inPacket所封裝的字節(jié)數(shù)組中socket.receive(inPacket);System.out.println(new String(inBuff , 0, inPacket.getLength()));}}}public static void main(String[] args)throws IOException{new UdpClient().init();} }上面程序中的粗體字代碼同樣也是使用DatagramSocket發(fā)送、接收DatagramPacket的關(guān)鍵代碼,這些代碼與服務(wù)器端代碼基本相似。而客戶端與服務(wù)器端的唯一區(qū)別在于:服務(wù)器端的IP地址、端口是固定的,所以客戶端可以直接將該數(shù)據(jù)報(bào)發(fā)送給服務(wù)器端,而服務(wù)器端則需要根據(jù)接收到的數(shù)據(jù)報(bào)來決定"反饋"數(shù)據(jù)報(bào)的目的地。
讀者可能會(huì)發(fā)現(xiàn),使用DatagramSocket進(jìn)行網(wǎng)絡(luò)通信時(shí),服務(wù)器端無須也無法保存每個(gè)客戶端的狀態(tài),客戶端把數(shù)據(jù)報(bào)發(fā)送到服務(wù)器端后,完全有可能立即退出。但不管客戶端是否退出,服務(wù)器端都無法知道客戶端的狀態(tài)。
當(dāng)使用UDP協(xié)議時(shí),如果想讓一個(gè)客戶端發(fā)送的聊天信息被轉(zhuǎn)發(fā)到其他所有的客戶端則比較困難,可以考慮在服務(wù)器端使用Set集合來保存所有的客戶端信息,每當(dāng)接收到一個(gè)客戶端的數(shù)據(jù)報(bào)之后,程序檢查該數(shù)據(jù)報(bào)的源SocketAddress是否在Set集合中,如果不在就將該SocketAddress添加到該Set集合中。這樣又涉及一個(gè)問題:可能有些客戶端發(fā)送一個(gè)數(shù)據(jù)報(bào)之后永久性地退出了程序,但服務(wù)器端還將該客戶端的SocketAddress保存在Set集合中……總之,這種方式需要處理的問題比較多,編程比較煩瑣。幸好Java為UDP協(xié)議提供了MulticastSocket類,通過該類可以輕松地實(shí)現(xiàn)多點(diǎn)廣播。
?
Socket之UDP套接字
UDP套接字:UDP套接字的使用是通過DatagramPacket類和DatagramSocket類,客戶端和服務(wù)器端都是用DatagramPacket類來接收數(shù)據(jù),使用DatagramSocket類來發(fā)送數(shù)據(jù)。
UDP客戶端:也是主要執(zhí)行三個(gè)步驟。
1.創(chuàng)建DatagramSocket實(shí)例;
2.使用DatagramSocket類的send()和receive()方法發(fā)送和接收DatagramPacket實(shí)例;
3.最后使用DatagramSocket類的close()方法銷毀該套接字。
下面是例子,它主要執(zhí)行三個(gè)步驟,
1.向服務(wù)器發(fā)送信息;
2.在receive()方法上最多阻塞等待3秒鐘,在超時(shí)前若沒有收到響應(yīng),則重發(fā)請(qǐng)求(最多重發(fā)5次);
3.關(guān)閉客戶端。
//UDPEchoClientTimeout.java ?
import java.net.DatagramSocket; ?
import java.net.DatagramPacket; ?
import java.net.InetAddress; ?
import java.io.IOException; ?
import java.io.InterruptedIOException; ?
??
public class UDPEchoClientTimeout { ?
??
? ? private static final int TIMEOUT = 3000; ? // 設(shè)置超時(shí)為3秒 ?
? ? private static final int MAXTRIES = 5; ? ? // 最大重發(fā)次數(shù)5次 ?
? ? ??
? ? public static void main(String[] args) throws IOException { ?
? ? ??
? ? ? ? if ((args.length < 2) || (args.length > 3)) { // Test for correct # of args ?
? ? ? ? ? throw new IllegalArgumentException("Parameter(s): <Server> <Word> [<Port>]"); ?
? ? ? ? } ?
? ? ? ? InetAddress serverAddress = InetAddress.getByName(args[0]); // 服務(wù)器地址 ?
? ? ? ? // Convert the argument String to bytes using the default encoding ?
? ? ? ? //發(fā)送的信息 ?
? ? ? ? byte[] bytesToSend = args[1].getBytes(); ?
? ? ??
? ? ? ? int servPort = (args.length == 3) ? Integer.parseInt(args[2]) : 7; ?
? ? ??
? ? ? ? DatagramSocket socket = new DatagramSocket(); ?
? ? ??
? ? ? ? socket.setSoTimeout(TIMEOUT); // 設(shè)置阻塞時(shí)間 ?
? ? ??
? ? ? ? DatagramPacket sendPacket = new DatagramPacket(bytesToSend, // 相當(dāng)于將發(fā)送的信息打包 ?
? ? ? ? ? ? bytesToSend.length, serverAddress, servPort); ?
? ? ??
? ? ? ? DatagramPacket receivePacket = ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?// 相當(dāng)于空的接收包 ?
? ? ? ? ? ? new DatagramPacket(new byte[bytesToSend.length], bytesToSend.length); ?
? ? ??
? ? ? ? int tries = 0; ? ? ?// Packets may be lost, so we have to keep trying ?
? ? ? ? boolean receivedResponse = false; ?
? ? ? ? do { ?
? ? ? ? ? socket.send(sendPacket); ? ? ? ? ?// 發(fā)送信息 ?
? ? ? ? ? try { ?
? ? ? ? ? ? socket.receive(receivePacket); // 接收信息 ?
? ? ??
? ? ? ? ? ? if (!receivePacket.getAddress().equals(serverAddress)) {// Check source ?
? ? ? ? ? ? ? throw new IOException("Received packet from an unknown source"); ?
? ? ? ? ? ? } ?
? ? ? ? ? ? receivedResponse = true; ?
? ? ? ? ? } catch (InterruptedIOException e) { // 當(dāng)receive不到信息或者receive時(shí)間超過3秒時(shí),就向服務(wù)器重發(fā)請(qǐng)求 ?
? ? ? ? ? ? tries += 1; ?
? ? ? ? ? ? System.out.println("Timed out, " + (MAXTRIES - tries) + " more tries..."); ?
? ? ? ? ? } ?
? ? ? ? } while ((!receivedResponse) && (tries < MAXTRIES)); ?
? ? ??
? ? ? ? if (receivedResponse) { ?
? ? ? ? ? System.out.println("Received: " + new String(receivePacket.getData())); ?
? ? ? ? } else { ?
? ? ? ? ? System.out.println("No response -- giving up."); ?
? ? ? ? } ?
? ? ? ? socket.close(); ?
? ? } ?
} ?
例子只是簡單的向指定的服務(wù)器發(fā)送信息,并將發(fā)送的信息由服務(wù)器返回給指定客戶端。
UDP服務(wù)器端:典型的UDP服務(wù)器要執(zhí)行三個(gè)步驟,
1.創(chuàng)建一個(gè)指定了本地端口的DatagramSocket實(shí)例;
2.使用DatagramSocket的receive()方法接收一個(gè)來自客戶端的DatagramPacket實(shí)例,而這個(gè)DatagramPacket實(shí)例在客戶端創(chuàng)建時(shí)就包含了客戶端的地址,這樣我們就知道回復(fù)信息要發(fā)送到哪里了;
3.使用DatagramSocket類的send()和receive()方法來發(fā)送和接收DatagramPacket實(shí)例。
下面是例子
//UDPEchoServer.java??
import?java.io.IOException;??
import?java.net.DatagramPacket;??
import?java.net.DatagramSocket;??
??
public?class?UDPEchoServer?{??
??
????private?static?final?int?ECHOMAX?=?255;?//?發(fā)送或接收的信息最大字節(jié)數(shù)??
??????
????public?static?void?main(String[]?args)?throws?IOException?{??
??????
????????if?(args.length?!=?1)?{?//?Test?for?correct?argument?list??
??????????throw?new?IllegalArgumentException("Parameter(s):?<Port>");??
????????}??
??????
????????int?servPort?=?Integer.parseInt(args[0]);??
??????
????????DatagramSocket?socket?=?new?DatagramSocket(servPort);??
????????DatagramPacket?packet?=?new?DatagramPacket(new?byte[ECHOMAX],?ECHOMAX);??
??????
????????while?(true)?{?//?不斷接收來自客戶端的信息及作出相應(yīng)的相應(yīng)??
??????????socket.receive(packet);?//?Receive?packet?from?client??
??????????System.out.println("Handling?client?at?"?+?packet.getAddress().getHostAddress()?+?"?on?port?"?+?packet.getPort());??
??????????socket.send(packet);?//?將客戶端發(fā)送來的信息返回給客戶端??
??????????packet.setLength(ECHOMAX);???
?????????//?重置packet的內(nèi)部長度,因?yàn)樘幚砹私邮盏降男畔⒑?#xff0c;數(shù)據(jù)包的內(nèi)部長度將被?????????????????????????????????????????????????????????
?????????//設(shè)置為剛處理過的信息的長度,而這個(gè)長度可能比緩沖區(qū)的原始長度還要短,??
?????????//如果不重置,而且接收到的新信息長于這個(gè)內(nèi)部長度,則超出長度的部分將會(huì)被截?cái)?#xff0c;所以這點(diǎn)必須注意到。??
????????}??
????????/*?NOT?REACHED?*/??
????}??
}??
例子只是簡單地將客戶端發(fā)送過來的信息再回復(fù)給客戶端,服務(wù)器端會(huì)不斷地receive來自客戶端的信息,如果receive不到任何客戶端請(qǐng)求,則將會(huì)進(jìn)入阻塞狀態(tài),直到receive到有客戶端請(qǐng)求位置。
轉(zhuǎn)載于:https://my.oschina.net/u/2971326/blog/896959
總結(jié)
以上是生活随笔為你收集整理的使用DatagramSocket发送、接收数据(Socket之UDP套接字)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: oracle imp使用
- 下一篇: Json 不同语言的使用