UDP(User Datagram Protocol)平痰,即用戶數(shù)據(jù)報(bào)協(xié)議握巢,UDP只提供數(shù)據(jù)的不可靠傳遞靶剑,它一旦把應(yīng)用程序發(fā)給網(wǎng)絡(luò)層的數(shù)據(jù)發(fā)送出去畜侦,就不保留數(shù)據(jù)備份(所以UDP有時(shí)候也被認(rèn)為是不可靠的數(shù)據(jù)報(bào)協(xié)議)。UDP在IP數(shù)據(jù)報(bào)的頭部僅僅加入了復(fù)用和數(shù)據(jù)校驗(yàn)(字段)掐场。
雖然UDP被認(rèn)為是不可靠的往扔,但是可以通過應(yīng)用程序來負(fù)責(zé)傳輸?shù)目煽啃裕鏔TP等都是用了UDP協(xié)議熊户。另外一個(gè)特點(diǎn)是UDP是基于數(shù)據(jù)包萍膛,也就是說數(shù)據(jù)會(huì)被打成包發(fā)送。所以包的大小會(huì)有限制嚷堡,一般認(rèn)為最大是64KB
Java中UDP主要涉及了DatagramPacket和 DatagramSocket兩個(gè)類蝗罗。前者被認(rèn)為是信息的載體,后者被認(rèn)為是收發(fā)的實(shí)體。也就是串塑,DatagramPacket攜帶數(shù)據(jù)沼琉,并通過DatagramSocket收發(fā)。
下面就來實(shí)現(xiàn)一下服務(wù)端與客戶端拟赊。
服務(wù)端:
public class UDPService {
public static final String SERVICE_IP = "127.0.0.1";
public static final int SERVICE_PORT = 10101;
public static final int MAX_BYTES = 2048;
private DatagramSocket service;
public static void main(String[] args) {
UDPService udpService = new UDPService();
udpService.startService(SERVICE_IP,SERVICE_PORT);//啟動(dòng)服務(wù)端
}
private void startService(String ip, int port) {
try {
//包裝IP地址
InetAddress address = InetAddress.getByName(ip);
//創(chuàng)建服務(wù)端的DatagramSocket對(duì)象刺桃,需要傳入地址和端口號(hào)
service = new DatagramSocket(port,address);
byte[] receiveBytes = new byte[MAX_BYTES];
//創(chuàng)建接受信息的包對(duì)象
DatagramPacket receivePacket = new DatagramPacket(receiveBytes,receiveBytes.length);
//開啟一個(gè)死循環(huán)粹淋,不斷接受數(shù)據(jù)
while(true){
try{
//接收數(shù)據(jù)吸祟,程序會(huì)阻塞到這一步,直到收到一個(gè)數(shù)據(jù)包為止
service.receive(receivePacket);
}catch (Exception e){
e.printStackTrace();
}
//解析收到的數(shù)據(jù)
String receiveMsg = new String(receivePacket.getData(),0,receivePacket.getLength());
//解析客戶端地址
InetAddress clientAddress = receivePacket.getAddress();
//解析客戶端端口
int clientPort = receivePacket.getPort();
//組建響應(yīng)信息
String response = "Hello world " + System.currentTimeMillis() + " " + receiveMsg;
byte[] responseBuf = response.getBytes();
//創(chuàng)建響應(yīng)信息的包對(duì)象桃移,由于要發(fā)送到目的地址屋匕,所以要加上目的主機(jī)的地址和端口號(hào)
DatagramPacket responsePacket = new DatagramPacket(responseBuf,responseBuf.length,clientAddress,clientPort);
try{
//發(fā)送數(shù)據(jù)
service.send(responsePacket);
}catch (Exception e){
e.printStackTrace();
}
}
}catch (Exception e){
e.printStackTrace();
}finally {
//關(guān)閉DatagramSocket對(duì)象
if(service!=null){
service.close();
service = null;
}
}
}
}
客戶端:
public class UDPClient {
private DatagramSocket client;
public static void main(String[] args){
UDPClient client = new UDPClient();
Scanner scanner = new Scanner(System.in);
//建立死循環(huán),不斷發(fā)送數(shù)據(jù)
while(true){
String msg = scanner.nextLine();
if("##".equals(msg))
break;
//打印響應(yīng)的數(shù)據(jù)
System.out.println(client.sendAndReceive(UDPService.SERVICE_IP,UDPService.SERVICE_PORT,msg));
}
}
private String sendAndReceive(String ip, int port, String msg) {
String responseMsg = "";
try {
//創(chuàng)建客戶端的DatagramSocket對(duì)象借杰,不必傳入地址和對(duì)象
client = new DatagramSocket();
byte[] sendBytes = msg.getBytes();
//封裝要發(fā)送目標(biāo)的地址
InetAddress address = InetAddress.getByName(ip);
//封裝要發(fā)送的DatagramPacket的對(duì)象过吻,由于要發(fā)送到目的主機(jī),所以要加上地址和端口號(hào)
DatagramPacket sendPacket = new DatagramPacket(sendBytes,sendBytes.length,address,port);
try {
//發(fā)送數(shù)據(jù)
client.send(sendPacket);
}catch (Exception e){
e.printStackTrace();
}
byte[] responseBytes = new byte[UDPService.MAX_BYTES];
//創(chuàng)建響應(yīng)信息的DatagramPacket對(duì)象
DatagramPacket responsePacket = new DatagramPacket(responseBytes,responseBytes.length);
try {
//等待響應(yīng)信息蔗衡,同服務(wù)端一樣纤虽,客戶端也會(huì)在這一步阻塞,直到收到一個(gè)數(shù)據(jù)包
client.receive(responsePacket);
}catch (Exception e){
e.printStackTrace();
}
//解析數(shù)據(jù)包內(nèi)容
responseMsg = new String(responsePacket.getData(),0,responsePacket.getLength());
}catch (Exception e){
e.printStackTrace();
}finally {
//關(guān)閉客戶端
if(client != null){
client.close();
client = null;
}
}
return responseMsg;
}
}
最后介紹一下涉及的幾個(gè)類绞惦。
1. InetAddress
InetAddress類用來代表IP地址逼纸,InetAddress下還有2個(gè)子類:Inet4Address、Inet6Address,它們分別代表IPv4地址和IPv6地址济蝉。
這個(gè)類沒有提供公開的構(gòu)造方法杰刽,而是提供了如下兩個(gè)靜態(tài)方法來獲取InetAddress實(shí)例:
InetAddress getByAddress(byte[] addr)//根據(jù)原始IP地址來獲取對(duì)應(yīng)的InetAddress對(duì)象
InetAddress getByName(String host)//根據(jù)主機(jī)獲取對(duì)應(yīng)的InetAddress對(duì)象
其他一些方法
String getCanonicalHostName()//獲取此 IP 地址的全限定域名。
String getHostAddress()//返回該InetAddress實(shí)例對(duì)應(yīng)的IP地址字符串(以字符串形式)王滤。
String getHostName()//獲取此 IP 地址的主機(jī)名贺嫂。
InetAddress getLocalHost() //獲取本機(jī)IP地址對(duì)應(yīng)的InetAddress實(shí)例.
boolean isReachable(int timeout) //測試是否可以到達(dá)該地址
2. DatagramPacket
有以下幾種構(gòu)造
DatagramPacket(byte[] buf, int length)
DatagramPacket(byte[] buf, int length, InetAddress address, int port)
DatagramPacket(byte[] buf, int offset, int length)
DatagramPacket(byte[] buf, int offset, int length, InetAddress address, int port)
DatagramPacket(byte[] buf, int offset, int length, SocketAddress address)
DatagramPacket(byte[] buf, int length, SocketAddress address)
方法:
InetAddress getAddress() //獲取地址,可能是發(fā)送方的也可能是接收方的
byte[] getData() //獲取數(shù)據(jù)
int getLength() //獲取長度
int getOffset() //獲取偏移量
int getPort() //獲取端口
SocketAddress getSocketAddress() //獲取SocketAddress
void setAddress(InetAddress iaddr) //設(shè)置地址
void setData(byte[] buf) //設(shè)置數(shù)據(jù)
void setData(byte[] buf, int offset, int length) //設(shè)置數(shù)據(jù)雁乡,指定長度與偏移
void setLength(int length) //設(shè)置長度
void setPort(int iport) //設(shè)置端口
void setSocketAddress(SocketAddress address) //設(shè)置SocketAddress
3. DatagramSocket
該類常用的構(gòu)造方法如下:
DatagramSocket() //默認(rèn)本機(jī)ip地址第喳,隨機(jī)選一個(gè)可用端口
DatagramSocket(int port) //默認(rèn)本機(jī)IP地址,指定端口
DatagramSocket(int port, InetAddress laddr) //指定ip和端口
通常第一個(gè)可作為客戶端踱稍,作為服務(wù)端一般需要指定端口墩弯,確保其他客戶端可以發(fā)送到該服務(wù)器.
常用方法:
void send(DatagramPacket p) //發(fā)送一個(gè)數(shù)據(jù)包,數(shù)據(jù)包要包含目的主機(jī)的地址和端口
void receive(DatagramPacket p) //接受一個(gè)數(shù)據(jù)包寞射,數(shù)據(jù)包只要包含一個(gè)byte數(shù)組和長度即可
另外還有一個(gè)connect(InetAddress address,int port)渔工,我們都知道UDP是面向無連接的,這個(gè)connect方法其實(shí)只是指定一個(gè)主機(jī)桥温,相當(dāng)于綁定引矩,但沒有任何實(shí)質(zhì)連接,而接下來都只能從這個(gè)主機(jī)收發(fā)數(shù)據(jù),而且在發(fā)數(shù)據(jù)時(shí)旺韭,DatagramPacket 不必?cái)y帶目的主機(jī)的地址和端口號(hào)氛谜。并且如果DatagramPacket攜帶的主機(jī)地址與端口號(hào)和connect綁定的不一致,會(huì)拋出IllegalArgumentException区端。
另外getInnetAddress和getPort都是返回connect后綁定的主機(jī)信息值漫,若要取到本機(jī)的需要調(diào)用getLocalPort和getLocalAddress。若沒有connect织盼,則getInnetAddress和getPort返回null與-1.