UDP协议是一种不可靠的网络协议,它在通信实例的两端各建立一个Socket,但在两个Socket之间并没有虚拟链路,这两个Socket只是发送、接收数据报的对象。Java提供了DatagramSocket对象作为基于UDP协议的Socket,使用DatagramPacket代表DatagramSocket发送、接收的数据包。
UDP协议是面向非连接的协议,没有建立连接的过程,因此它的通信效率很高;也正是因为如此,它的可靠性不如TCP协议。
UDP协议和TCP协议的简单比较:
TCP协议:可靠,传输大小无限制,但是需要连接建立时间,差错控制开销大。
UDP协议:不可靠,差错控制开销小,传输大小限制在64KB以下,不需要建立连接。
DatagramSocket与DatagramPacket
Java使用DatagramSocket代表UDP协议的Socket,DatagramSocket唯一作用就是接收和发送数据报。Java使用DatagramPacket来代表数据报,DatagramSocket接收和发送的数据都是通过DatagramPacket对象完成的。
DatagramSocket构造器 | |
---|---|
DatagramSocetk() | 创建一个DatagramSocket实例,并将对象绑定到本机默认IP地址、本机所有可用端口中随机选择的某个端口。 |
DatagramSocket(int port) | 创建一个DataGramSocket实例,并将该对象绑定到本机默认IP地址、指定端口 |
DatagramSocket(int port,InetAddress Iaddr) | 创建一个DatagramSocket实例,并将对象绑定到指定IP地址、指定端口 |
得到DatagramSocket实例之后,可以通过以下方法来接收和发送数据:
DatagramSocket方法 | |
---|---|
receive(DatagramPacket p) | 从该DatagramSocket中接收数据报 |
send(DatagramPacket p) | 以该DatagramSocket对象向外发送数据报 |
DatagramPacket构造器及方法
DatagramPacket的构造器 | |
---|---|
DatagramPacket(byte[] buf,int length) | 以一个空数组来创建DatagramPacket对象,该对象的作用是接收DatagramSocket中的数据 |
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 addr,int port) | 创建一个用于发送的DatagramPacket对象,指定发送buf数组中从offset开始,总共length个字节 |
在接收数据之前,应该采用DatagramPacket的第一个或第三个构造器生成一个DatagramPacket对象,给出接收数据的字节数组及其长度。然后调用DatagramSocket的receive()方法等待数据报的到来,receive()将一直等待(该方法会阻塞调用该方法的线程),直到受到一个数据报为止。
//创建一个接收数据的DatagramPacket对象
DatagramPacket packet = new DatagramPacket(buf, 256);
//接收数据报
socket.receive(packet);
在发送数据之前,调用第二个或第四个构造器创建DatagramPacket对象,此时的字节数组里存放了想发送的数据。除此之外,还要给出完整的目的地址,包括IP地址和端口号。发送数据是通过DatagramSocket的send()方法实现的,send()方法根据数据包的目的地址来寻径以传送数据报。
//创建一个发送数据的DatagramPacket对象
DatagramPacket packet = new Datagrampacket(buf, length, address, port);
//发送数据报
socket.send(packet);
当服务器端接收到一个DatagramPacket对象后,如果想向该数据包的发送者“反馈”一些信息,但由于UDP协议是面向非连接的,所以接收者并不知道每个数据报由谁发送过来,但程序可以调用DatagramPacket的如下三个方法来获取发送者的IP地址和端口。
方法 | |
---|---|
InetAddress getAddress() | 当程序准备发送此数据报时,该方法返回此数据报的目标机器的IP地址;当程序接收到一个数据报时,该方法返回该数据报的发送主机的IP地址 |
int getPort() | 当程序准备发送此数据报时,该方法返回此数据报的目标机器的端口;当程序刚接收到一个数据报时,该方法返回该数据报的发送主机的端口 |
SocketAddress getSocketAddress() | 当程序准备发送此数据报时该方法返回此数据报的目标SocketAddress;当程序刚接收到一个数据报时,该方法返回该数据报的发送主机的SocketAddress |
实例代码:
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;
//定义一个字符串数组,服务器端发送该数组的元素
String[] books = new String[]{"qqq", "www", "eee", "rrr"};
public void init() throws IOException{
try(
//创建DatagramSocket对象
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());
//将接收到的内容转换为字符串后输出
System.out.println(new String(inBuff, 0, inPacket.getLength()));
//从字符串数组中取出一个元素作为发送数据
byte[] sendData = books[i % 4].getBytes();
//以指定的字节数组作为发送数据,以刚接收到的DatagramPacket的源SocketAddress作为
//目标SocketAddress创建DatagramPacket
outPacket = new DatagramPacket(sendData, sendData.length, inPacket.getSocketAddress());
//发送数据
socket.send(outPacket);
}
}
}
public static void main(String[] args) throws IOException{
new UDPServer().init();
}
}
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];
//以指定的字节数组创建准备接收数据的DatagramPacket对象
private DatagramPacket inPacket = new DatagramPacket(inBuff, inBuff.length);
//定义一个用于发送的DatagramPacket对象
private DatagramPacket outPacket = null;
public void init() throws IOException{
try(
//创建一个客户端DatagramSocket,使用随机端口
DatagramSocket socket = new DatagramSocket()){
//初始化发送时用的DatagramSocket,它包含一个长度为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{
new UDPClient().init();
}
}