Java 提供了 DatagramSocket 对象作为基于 UDP 协议的 Socket,使用 DatagramPacket 代表 DatagramSocket 发送、接收的数据报。
1.1 UDP 基础
UDP 协议是英文 User Datagram Protocol 的缩写,即用户数据报协议,主要用来支持那些需要在计算机之间传输数据的网络连接。UDP 协议从问世至今已经被使用了很多年,虽然 UDP 协议目前应用不如 TCP 协议广泛,但 UDP 协议依然是一个非常实用和可行的网络传输层协议。尤其是在一些实时性很强的应用场景中,比如网络游戏、视频会议等,UDP 协议的快速更具有独特的魅力。 UDP 协议是一种面向非连接的协议,面向非连接指的是在正式通信前不必与对方先建立连接,不管对方状态就直接发送。至于对方是否可以接收到这些数据内容,UDP 协议无法控制,因此说 UDP 协议是一种不可靠的协议。UDP 协议适用于一次只传送少量数据、对可靠性要求不高的应用环境。与 TCP 协议一样,UDP 协议直接位于 IP 协议之上。实际上,IP 协议属于 OSI 参考模型的网络层协议,而 UDP 协议和 TCP 协议都属于传输层协议。 因为 UDP 协议是面向非连接的协议,没有建立连接的过程,因此它的通信效率很高;但也正因为如此,它的可靠性不如 TCP 协议。UDP 协议的主要作用是完成网络数据流和数据报之间的转换。在信息的发送端,UDP 协议将网络数据流封装成数据报,然后将数据报发送出去;在信息的接收端,UDP 协议将数据报转换成实际数据内容。
1.2 UDP 通信相关类
1.2.1 DatagramSocket 类
Java 提供了 DatagramSocket 对象作为基于 UDP 协议的 Socket,但是 DatagramSocket 本身只是码头,不能维护状态,不能产生IO流,它的唯一作用就是接收和发送数据报,Java 使用 DatagramPacket 来代表数据报,DatagramSocket 接收和发送的数据都是通过 DatagramPacket 对象完成的。
构造方法
public DatagramSocket()
:创建一个 DatagramSocket 实例,并将该对象绑定到本机默认 IP 地址、本机所有可用端口中随机选择的某个端口。
public DatagramSocket(int prot)
:创建一个 DatagramSocket 实例,并将该对象绑定到本机默认 IP 地址、指定端口。
public DatagramSocket(int port, InetAddress laddr)
:创建一个 DatagramSocket 实例,并将该对象绑定到指定 IP 地址、指定端口。
常用方法
方法名 | 说明 |
---|---|
void receive(DatagramPacket p) | 从该 DatagramSocket 中接收数据报 |
void send(DatagramPacket p) | 从该 DatagramSocket 对象向外发送数据报 |
1.2.2 DatagramPacket 类
使用 DatagramSocket 发送数据报时,DatagramSocket 并不知道将该数据报发送到哪里,而是由 DatagramPacket 决定数据报的目的地。就像码头并不知道每个集装箱的目的地,码头只是将这些集装箱发送出去,而集装箱本身包含了该集装箱的目的地。
构造方法
public DatagramPacket(byte[] buf,int length)
:以一个空数组来创建 DatagramPacket 对象,该对象的作用是接收 DatagramSocket 中的数据。
public DatagramPacket(byte[] buf, int offset, int length)
:以一个空数组来创建 DatagramPacket 对象,并指定接收到的数据放入 buf 数组中时从 offset 开始,最多放 length 个字节。
public DatagramPacket(byte[] buf, int length, InetAddress addr, int port)
:以一个包含数据的数组来创建一个用于发送的 DatagramPacket 对象,创建该 DatagramPacket 对象时还指定了 IP 地址和端口,这就决定了该数据报的目的地。
public DatagramPacket(byte[] buf, int offset, int length, InetAddress address, int port)
:以一个包含数据的数组来创建一个用于发送的 DatagramPacket 对象,指定发送 buf 数组中从 offset 开始,总共 length 个字节。
常用方法
方法名 | 说明 |
---|---|
InetAddress getAddress() | 获取当前 IP |
int getPort() | 获取当前端口号 |
InetAddress.getLocalHost() | 获取本地主机的 InetAddress |
InetAddress.getByName(String host) | 获取指定主机名的 InetAddress |
InetAddress.getByAddress(byte[] addr) | 获取指定 IP 的 InetAddress |
InetAddress.getByAddress(String host, byte[] addr) | 获取指定 主机名 和 IP 的 InetAddress |
1.3 实现 UDP 通信
1.3.1 UDP 通信分析
发送端 ① 创建 DatagramSocket 对象(创建一个码头) ② 创建 DatagramPacket 对象并指定IP和端口号(装箱并集装箱贴目的地和接收港编号) ③ 调用 send() 方法发送(发送集装箱) ④ 释放资源(拆码头) 接收端 ① 创建 DatagramSocket 对象并指定端口号(创建一个码头并编号) ② 创建 DatagramPacket 对象,用于接收数据(拆箱) ③ 调用 receive() 方法接收(接收集装箱) ④ 释放资源(拆码头)
1.3.2 客户端
代码语言:javascript复制public class DemoSend {
public static void main(String[] args) {
DatagramSocket socket = null;
try {
// 创建 DatagramSocket
socket = new DatagramSocket();
// 创建数据
byte[] sendBytes = "你好啊!".getBytes();
// 创建 DatagramPacket
DatagramPacket sendPacket =
new DatagramPacket(sendBytes, sendBytes.length, InetAddress.getByAddress("xiaolaohu.work",
new byte[] { (byte) 47, (byte) 103, 4, (byte) 205 }), 9999);
// 发送数据
socket.send(sendPacket);
// 接受写回数据
// 创建字节数组
byte[] receiveBytes = new byte[1024];
// 创建 DatagramPacket
DatagramPacket receivePacket = new DatagramPacket(receiveBytes, receiveBytes.length);
// 接受数据
socket.receive(packet1);
// 打印到控制台
System.out.println(new String(receivePacket, 0, receiveBytes.length));
} catch (Exception e) {
e.printStackTrace();
} finally {
//释放资源
if (socket != null) {
socket.close();
}
}
}
}
1.3.3 服务端
代码语言:javascript复制public class DemoReceive {
public static void main(String[] args) {
DatagramSocket socket = null;
try {
// 创建 DatagramSocket
socket = new DatagramSocket(9999);
// 创建字节数组
byte[] receiveBytes = new byte[1024];
// 创建 DatagramPacket
DatagramPacket receivePacket = new DatagramPacket(receiveBytes, receiveBytes.length);
// 接受数据
socket.receive(receivePacket);
//打印到控制台
System.out.println(new String(receiveBytes, 0, receiveBytes.length));
// 回写消息
// 创建数据
byte[] sendBytes = "收到了".getBytes();
// 创建 DatagramPacket
DatagramPacket sendPacket =
new DatagramPacket(sendBytes, sendBytes.length, receivePacket.getAddress(), receivePacket.getPort());
// 发送
socket.send(sendPacket);
} catch (IOException e) {
e.printStackTrace();
} finally {
// 释放资源
if (socket != null) {
socket.close();
}
}
}
}
从上述代码可以看出使用 UDP 协议时,实际上并没有明显的服务端和客户端,因为两方都需要先建立一个 DatagramSocket 对象,用来接收或发送数据报,然后使用 DatagramPacket 对象作为传输数据的载体。通常固定 IP 地址、固定端口的 DatagramSocket 对象被称为服务端,因为该 DatagramSocket 可以被动接收数据。