UDP协议即用户数据报协议是一种面向无连接的协议,由于不需要建立连接,它的通信效率高,实时性好,同时可靠性相对于TCP协议较低。UDP协议的主要作用是完成网络数据流和数据报之间的转换--在信息的发送端,UDP协议将网络数据流封装成数据报,然后将数据报发送出去;在信息的接收端,UDP协议将数据报转换成实际数据内容。UDP在客户端和服务端之间没有虚拟链路,两端各建立一个Socket只是用于发送、接收数据报的对象。Java提供了DatagramSocket对象作为基于UDP协议的Socket,使用DatagramPacket代表DatagramSocket发送、接收数据的数据报。
1.DatagramSocket类
DatagramSocket不维护状态,不能产生IO流,它的唯一作用就是接收和发送数据报。通过构造器创建UDP的Socket。以下是DatagramSocket的构造器。
DatagramSocket():创建一个DatagramSocket实例,并将该对象绑定到本机默认IP地址、本机所有可用端口中随机选择某个端口。
DatagramSocket(int port):创建一个DatagramSocket实例,并将该对象绑定在本机默认IP地址、指定端口。
DatagramSocket(int port,InetAddress laddr):创建一个DatagramSocket实例,并将该对象绑定到指定IP地址、指定端口。
DatagramSocket实例有两个方法:
receive(DatagramPacket p):从该DatagramSocket中接收数据报
send(DatagramPacket p):以该DatagramSocket向外发送数据报
注意:由于DatagramSocket本身没有状态,所以不知道数据报发往何处,而是由DatagramPacket自身决定数据报发往何处。
2.DatagramPacket类
以下是DatagramPacket的构造器:
DatagramPacket(byte[] buf,int length):以一个空数组来创建DatagramPacket对象,该对象的作用是接收DatagramPacket中的数据。
DatagramPacket(byte[] buf,int length,InetAddress addr,int port):以一个包含数据的数组来创建DatagramPacket对象,创建该DatagramPacket对象时还指定了IP地址和端口,决定了该数据报的目的地。
DatagramPacket(byte[] buf,int offset,int length):以一个空数组来创建DatagramPacket对象,并指定接收到的数据放入buf数组中时从offset开始,最多放length个字节。
DatagramPacket(byte[] buf,int offset,int length,InetAddress address,int port):创建一个用于发送的DatagramPacket对象,指定发送buf数组中从offset开始,总共length个字节。
注意:在接收数据之前,应该采用上面的第一个或第三个构造器生成一个DatagramPacket对象,给出接收数据的字节数组及其长度。然后调用DatagramSocket的receive()方法等待数据报的到来,receive()将一直等待,直到收到一个数据报为止。在发送数据之前,调用第二个或第四个构造器创建DatagramPacket对象,此时的字节数组里存放了想发送的数据。除此之外,还要给出完整的目的地址。发送数据通过DatagramS的send()方法实现的。
3.示例
这个例子主要是通过客户端控制台输入信息发送请求查询结果,而服务端接收到信息之后,响应结果并发送给客户端。这个过程中,DatagramPacket是传递信息的介质,并且数据报本身确定了目标地址。
服务端:
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;
import java.util.HashMap;
import java.util.Map;
public class UdpServer {
public static final int PORT = 30000;
//定义每个数据报大小为4KB
private static final int DATA_LEN = 4096;
//定义接收网络数组的字节数组
byte[] inBuff = new byte[DATA_LEN];
//以指定字节数组创建准备接收数据的DatagramPacket对象
private DatagramPacket inPacket = new DatagramPacket(inBuff, inBuff.length);
//定义一个用于发送的DatagramPacket对象
private DatagramPacket outPacket;
//定义一个map,接收到客户端查询字符之后,可在map中查找是否有相应结果
HashMap<String, String> map = new HashMap<>();
{
map.put("最喜欢的水果", "苹果");
map.put("最喜欢的蔬菜", "西蓝花");
map.put("最不喜欢的蔬菜", "土豆");
map.put("最不喜欢的水果", "榴莲");
map.put("最喜欢的动物", "狗");
map.put("最不喜欢的动物", "蛇");
map.put("最喜欢的画家", "齐白石");
map.put("最喜欢的城市", "杭州");
}
public void init() throws IOException {
try(DatagramSocket socket = new DatagramSocket(PORT)){
//采用循环接收数据
for(int i=0;i<1000;i++) {
//读取socket中的数据,读到的数据放入inPacket封装的数组里
socket.receive(inPacket);
//判断inPacket.getData()和inBuff是否是同一个数组
System.out.println(inBuff==inPacket.getData());
//将接收到的数据转换为字符串用于后续查找
String string = new String(inBuff,0,inPacket.getLength());
//从map中查找结果
String anString = null;
if(map.containsKey(string))
anString ="服务端:"+map.get(string);
else
anString="服务端:您要找的信息不存在!";
byte[] sendData = anString.getBytes();
//创建发送DatagramPacket对象
outPacket = new DatagramPacket(sendData, sendData.length, inPacket.getSocketAddress());
socket.send(outPacket);
}
}
}
public static void main(String[] args) throws IOException {
// TODO Auto-generated method stub
new UdpServer().init();
}
}
客户端:
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
import java.util.Scanner;
public class UdpClient {
//定义发送数据报的目的地
public static final int DEST_PORT = 30000;
public static final String DEST_IP = "127.0.0.1";
//定义每个数据报大小最大为4KB
private static final int DATA_LEN = 4096;
//定义接收网络数据的字节数组
byte[] inBUff = new byte[DATA_LEN];
//以指定的字节数组创建准备接收数据的DatagramP对象
private DatagramPacket inPacket = new DatagramPacket(inBUff, inBUff.length);
//定义一个用于发送的DatagramPacket对象
private DatagramPacket outPacket = null;
public void init() throws IOException {
try(
//创建一个客户端DatagramSocket,随机端口
DatagramSocket socket = new DatagramSocket()){
//初始化发送用的DatagramPacket,它包含一个长度为0的字节数组
outPacket = new DatagramPacket(new byte[0], 0, InetAddress.getByName(DEST_IP), DEST_PORT);
//创建键盘输入流
Scanner scan = new Scanner(System.in);
//不断地读取键盘输入
while(scan.hasNextLine()) {
//将键盘输入的一行字符串转换成字节数组
byte[] buff = scan.nextLine().getBytes();
//设置发送用的DatagramPacket中的字节数据
outPacket.setData(buff);
socket.send(outPacket);
//读取socket中的数据,读到的数据放在inPacket锁封装的字节数组中
socket.receive(inPacket);
System.out.println(new String(inBUff,0,inPacket.getLength()));
}
}
}
public static void main(String[] args) throws IOException {
// TODO Auto-generated method stub
new UdpClient().init();
}
}
运行结果:
注意:在基于UDP编程中,需要注意的是:
1.Socket没有状态,不知道数据报发往什么位置,仅提供receive、send两个方法
2.发送的数据报本身绑定了目标地址
3.服务端的DatagramSocket对象初始化时需要指定IP和端口,而客户端可以是随机端口