|
用DatagramSocket發(fā)送、接收數(shù)據(jù)(1)
Java使用DatagramSocket代表UDP協(xié)議的Socket,DatagramSocket本身只是碼頭,不維護(hù)狀態(tài),不能產(chǎn)生IO 流,它的唯一作用就是接收和發(fā)送數(shù)據(jù)報(bào),Java使用DatagramPacket來代表數(shù)據(jù)報(bào),DatagramSocket接收和發(fā)送的數(shù)據(jù)都是通過 DatagramPacket對(duì)象完成的。 先看一下DatagramSocket的構(gòu)造器。 DatagramSocket():創(chuàng)建一個(gè)DatagramSocket實(shí)例,并將該對(duì)象綁定到本機(jī)默認(rèn)IP地址、本機(jī)所有可用端口中隨機(jī)選擇的某個(gè)端口。 DatagramSocket(int prot):創(chuàng)建一個(gè)DatagramSocket實(shí)例,并將該對(duì)象綁定到本機(jī)默認(rèn)IP地址、指定端口。 DatagramSocket(int port, InetAddress laddr):創(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ù)組及其長(zhǎng)度。然后調(diào)用 DatagramSocket 的receive()方法等待數(shù)據(jù)報(bào)的到來,receive()將一直等待(該方法會(huì)阻塞調(diào)用該方法的線程),直到收到一個(gè)數(shù)據(jù)報(bào)為止。如下代碼所示:
在發(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)。如下代碼所示:
使用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ù)組的長(zhǎng)度決定了該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
上面程序中的粗體字代碼就是使用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ù)組中)??蛻舳顺绦虼a如下。
17.4.2 使用DatagramSocket發(fā)送、接收數(shù)據(jù)(3) 程序清單:codes\17\17.4\UdpClient.java
上面程序中的粗體字代碼同樣也是使用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)閉客戶端。
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í)例。 下面是例子
|
|
|