udp協(xié)議源碼詳解
在進(jìn)行UDP協(xié)議的使用中,我們通常會(huì)借助其他語言工具來完成工作。那么今天我們主要介紹一下Java下的UDP協(xié)議的使用。首先我們來了解一下UDP協(xié)議的基本概念。UDP協(xié)議的全稱是用戶數(shù)據(jù)報(bào),在網(wǎng)絡(luò)中它與TCP協(xié)議一樣用于處理數(shù)據(jù)包?在OSI模型中,在第四層??傳輸層,處于IP協(xié)議的上一層?UDP有不提供數(shù)據(jù)報(bào)分組?組裝和不能對(duì)數(shù)據(jù)包的排序的缺點(diǎn),也就是說,當(dāng)報(bào)文發(fā)送之后,是無法得知其是否安全完整到達(dá)的?
為什么要使用UDP在選擇使用協(xié)議的時(shí)候,選擇UDP必須要謹(jǐn)慎?在網(wǎng)絡(luò)質(zhì)量令人不十分滿意的環(huán)境下,UDP協(xié)議數(shù)據(jù)包丟失會(huì)比較嚴(yán)重?但是由于UDP的特性:它不屬于連接型協(xié)議,因而具有資源消耗小,處理速度快的優(yōu)點(diǎn),所以通常音頻?視頻和普通數(shù)據(jù)在傳送時(shí)使用UDP較多,因?yàn)樗鼈兗词古紶杹G失一兩個(gè)數(shù)據(jù)包,也不會(huì)對(duì)接收結(jié)果產(chǎn)生太大影響?比如我們聊天用的ICQ和OICQ就是使用的UDP協(xié)議?
一、使用DatagramSocket發(fā)送、接收數(shù)據(jù)原理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ì)象完成的。
1. 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ā)送出去,而集裝箱本身包含了該集裝箱的目的地。
2. 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地址和端口。