1、UDP协议基础
1. UDP是什么?
UDP(User Datagram Protocol,用户数据报协议)是传输层的核心协议之一,与TCP并列。它的主要特点是:
-
无连接:通信前不需要建立连接(知道对端的IP和端口号就直接进行传输,不需要建立连接)
-
不可靠:不保证数据包的顺序、完整性或可达性(没有确认机制,没有重传机制;如果因为网络故障该段无法发到对方,UDP协议层也不会给应用层返回任何错误信息)
-
大小受限:⼀次最多传输64k(UDP协议首部中有⼀个16位的最大长度.也就是说⼀个UDP能传输的数据最大长度是 64K(包含UDP首部))
-
轻量级:头部开销小(仅8字节)
-
高效:没有TCP的握手、确认和重传机制
2. UDP报文结构
UDP数据包(称为数据报)由头部和数据部分组成:
-
源端口号(2字节):发送方端口
-
目标端口号(2字节):接收方端口
-
数据报长度(2字节):头部+数据的长度
-
校验和(2字节):错误检测(可选)如果校验和出错则直接丢弃
2、UDP的核心特性
1. 无连接通信:UDP不需要预先建立连接,直接发送数据报。这类似于寄信,不需要确认收件人是否在家。
2. 不可靠传输:UDP不提供:数据包确认机制、丢失重传机制、数据包排序功能
3.面向数据报:每个UDP数据报都是独立的,这与TCP的字节流模式不同
4.支持广播和多播:UDP可以向多个主机同时发送数据:
-
单播 :一对一
-
广播 :一对所有
-
多播 :一对一组
3、基于UDP的应用层协议
- NFS:网络文件系统
- TFTP:简单文件传输协议
- DHCP:动态主机配置协议
- BOOTP:启动协议(用于无盘设备启动)
- DNS:域名解析协议
当然,也包括我们自己写UDP程序时自定义的应用层协议。
4、Java中的UDP编程
主要使用了两个类 :
1、DatagramSocket :用于发送和接收数据报的套接字
// 创建UDP套接字(绑定随机端口)
DatagramSocket socket = new DatagramSocket();// 创建绑定特定端口的套接字
DatagramSocket serverSocket = new DatagramSocket(8080);
2、DatagramPacket :表示UDP数据报的容器
// 创建接收数据包
byte[] buffer = new byte[1024];
DatagramPacket packet = new DatagramPacket(buffer, buffer.length);// 创建发送数据包
String message = "Hello UDP";
byte[] data = message.getBytes();
DatagramPacket sendPacket = new DatagramPacket(
data,
data.length,
InetAddress.getByName("localhost"),
8080
);
5、完整Java UDP示例
1.Echoserver:UDP服务器,接收客户端消息并原样返回
package Network.UDP;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;public class Echoserver {//创建一个socket对象private DatagramSocket socket;//构造方法,初始化socket对象public Echoserver(int port) throws SocketException {socket = new DatagramSocket(port);}//启动服务器,完成主要的业务逻辑public void start() throws IOException {System.out.println("服务器启动!");while(true) {//1.接收客户端的请求并解析//1)创建一个字节数组(DatagramPacket 对象),用于存储接收到的数据DatagramPacket reqPacket = new DatagramPacket(new byte[4096],4096);//2)通过receive读取网卡的数据,如果网卡没有收到数据,就会阻塞等待socket.receive(reqPacket);//3)把DATagramPacket中的数据解析成字符串,只需要从DatagramPacket取到有效的树即可String request = new String(reqPacket.getData(),0,reqPacket.getLength());//2.根据请求计算响应String response = process(request);//3.把响应写回客户端//1)把响应字符串转成字节数组,并封装成DatagramPacket对象DatagramPacket resPacket = new DatagramPacket(response.getBytes(),response.getBytes().length,reqPacket.getSocketAddress());//2)通过socket把DatagramPacket对象发送出去 (把DatagramPacket写回到客户端)socket.send(resPacket);//4.打印日志System.out.printf("[%s:%d] req: %s,resp: %s\n",reqPacket.getAddress(),reqPacket.getPort(),request,response);}}//由于是“回显服务器”,所以响应和请求是一样的,直接返回请求即可public String process(String request) {return request;}public static void main(String[] args) throws IOException {Echoserver server = new Echoserver(9090);server.start(); //回显服务器完成}
}
代码详细解析:
①创建 EchoServer 服务端的类,定义成员变量
-
DatagramSocket:UDP通信的核心类,用于发送和接收数据报
-
封装在类中作为成员变量,整个生命周期内有效
②构造方法
-
创建绑定到指定端口的 DatagramSocket(可能会抛出异常,如:该端口已被占用)
-
port 是服务器自身绑定的监听端口号,等待客户端发送数据,客户端需要知道服务器的这个端口号才能向其发送消息。
客户端通过该端口找到服务器,而客户端的端口由系统动态分配。
这种设计体现了 UDP 无连接的特性,服务器只需关注自身绑定端口即可接收所有客户端消息。
③核心业务逻辑(start方法)
- while 无限循环接收来自客户端的数据,这里可以根据自己的需求更改接收次数
- 创建缓冲区:分配一个4096字节(字节大小自行定义不溢出就行)的空数组,存储接收到的数据
- 阻塞等待数据报到达(数据此时已存入 reqPacket 的字节数组中)如果 reqPacket 没有接收到客户端发来的数据这里会阻塞等待
- 声明 String 类型的 request 变量,将 reqPacket 中的数据解析为字符串
- getData() :获取字节数组(可能包含多余的空字节)
- getLength() :获取实际有效数据长度(避免解析无效字节)
在网络编程中,将响应(
response
)转成字节数组(byte[]
)再发送是必要的步骤,这与计算机底层的数据传输机制和网络协议的特性密切相关。
- process() :返回响应后的数据,由于整段代码实现的是 Echo(回显) 服务器代码,响应内容=请求内容,所以方法中直接返回请求即可
- soclet.send() :将数据发送回客户端
附上发送回客户端的终端代码
- 打印日志:打印客户端信息和通信内容
④主方法
-
new Echoserver(9090) :调用构造函数,创建绑定到
9090
端口的DatagramSocket
(UDP套接字) -
server.start() :启动服务器循环,等待客户端数据报
2.EchoClient:UDP客户端,发送消息并显示服务器返回的响应
package Network.UDP;import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.util.Scanner;public class EchoClient {private DatagramSocket socket = null;private String serverIp;private int serverPort;public EchoClient(String serverIp, int serverPort) throws IOException { socket = new DatagramSocket();this.serverIp = serverIp;this.serverPort = serverPort;} //启动客户端,发送数据,接收数据,关闭连接public void start() throws IOException {Scanner scanner = new Scanner(System.in);System.out.println("客户端启动!");while(true) {//1.从控制台读取要发送的数据内容(用字符串表示)System.out.print(">");String request = scanner.nextLine(); //2.构造成UDP请求,并发送.不光要填内容,还要填服务器的地址和端口号DatagramPacket reqPacket = new DatagramPacket(request.getBytes(),request.getBytes().length,InetAddress.getByName(serverIp),serverPort); socket.send(reqPacket);//3.接收服务器响应的数据DatagramPacket resPacket = new DatagramPacket(new byte[4096], 4096);socket.receive(resPacket);String response = new String(resPacket.getData(),0,resPacket.getLength());//4.把响应的数据解析并打印System.out.println(response);}}public static void main(String[] args) throws IOException {EchoClient echoclient = new EchoClient("127.0.0.1",9090);echoclient.start();}
}
详细代码解析:
①创建 EchoClient 客户端的类,定义成员变量
-
socket :客户端UDP套接字
-
serverIp/serverPort :服务器地址信息
②构造方法
- DatagramSocket ( ) 为客户端随机分配一个端口号
- 保存服务器地址信息
③核心业务逻辑(start方法)
- request 用户输入要发送的数据
- reqPacket 封装要发送的数据
- request.getBytes() :将用户输入的字符串转换为字节数组
- request.getBytes().length :获取字节数组的长度,表示要发送的数据大小
- InetAddress.getByName(serverIp) :通过服务器的IP地址获取对应的 InetAddress 对象,表示数据要发送到的目标地址
- serverPort :服务器的端口号,表示数据要发送到的端口
- socket.send() :调用 send 方法将数据报发送到指定的目标地址和端口
- resPacket() :接收服务器发回的响应数据,socket.receive() 是一个阻塞操作,直到接收到数据才会继续执行
- response :提取响应内容,将接收到的字节数据转换为字符串
④主方法
- 创建客户端实例,连接本地服务器的9090端口
- start 启动客户端
6、UDP编程注意事项
1.数据报大小限制
- UDP数据报最大长度理论为65535字节
- 实际受MTU限制(通常1500字节)
- 建议保持数据报在1472字节以内(IPv4)
2.数据边界问题
- 每个 receive() 调用接收一个完整的数据报
- 不会出现TCP的“粘包”问题
3.错误处理
- 网络不可达
- 端口未监听
- 数据包丢失
4.安全性考虑
- UDP易受DDoS攻击
- 应考虑实现应用层的验证机制