//客户端口
package network;
import java.io.IOException;
import java.net.*;
import java.util.Scanner;
public class UdpEchoClient {
private DatagramSocket socket = null;
private String servsreIp=" ";
private int serverPort = 0;
public UdpEchoClient(String ip,int port) throws SocketException {
//在客户端让其随机分配一个端口
socket = new DatagramSocket();
//由于udp自身不会持有对端的信息,就需要在应程序里,把对端程序记录下来
servsreIp = ip;
serverPort = port;
}
public void start() throws IOException {
System.out.println("客户端启动");
Scanner scanner = new Scanner(System.in);
while(true){
//1:从控制台读取数据,作为为请求
System.out.print("->");
String request = scanner.next();
//2:把请求内容构造成DatagramPacket对象,发给服务器
DatagramPacket requestPacket = new DatagramPacket(request.getBytes(),request.getBytes().length,
InetAddress.getByName(servsreIp),serverPort);
socket.send(requestPacket);
//尝试读取服务器返回的响应
DatagramPacket responsePacket = new DatagramPacket(new byte[4096],4096);
socket.receive(responsePacket);
//把相应转换为字符串,并显示出来
String response = new String (responsePacket.getData(),0,responsePacket.getLength());
System.out.println(response);
}
}
public static void main(String[] args) throws IOException {
UdpEchoClient client = new UdpEchoClient("127.0.0.1",9090);
client.start();
}
}
//服务器端口
package network;
import java.io.IOException;
import java.net.*;
//服务器
public class UdpEchoServer {
//创建一个DatagramSocket 对象,后续操作网卡的基础
private DatagramSocket socket = null;
public UdpEchoServer(int port) throws SocketException {
//手动指定端口
socket = new DatagramSocket(port);
// socket = new DatagramSocket();这是系统自动分配
}
public void start() throws IOException {
System.out.println("服务器启动");
while(true){
//1:读取请求并解析
DatagramPacket requestPacket = new DatagramPacket(new byte[4096],4096);
socket.receive(requestPacket);
String request = new String(requestPacket.getData(),0, requestPacket.getLength());
//2:针对请求来响应
//请求是什么样响应就是什么样
String response = process(request);
//3:把响应写回到客户端
//搞一个响应对象,DatagramPacket
//往DatagramPacket里构造刚才的数据,再通过send返回
DatagramPacket responsePacket = new DatagramPacket(response.getBytes(),response.getBytes().length,
requestPacket.getSocketAddress());// requestPacket.getSocketAddress()是请求中的地址
socket.send(responsePacket);
//4:打印一个日志,把这次数据交互的详情打印出来
System.out.printf("[%s:%d] req=%s,resp=%s
",requestPacket.getAddress().toString(),
requestPacket.getPort(),request,response);
}
}
public String process(String request){
return request;
}
public static void main(String[] args) throws IOException {
UdpEchoServer server = new UdpEchoServer(9090);
server.start();
}
}
这两个代码分别实现了基于 UDP 协议的回声客户端(UdpEchoClient) 和回声服务器(UdpEchoServer),它们配合工作可完成 “客户端发送消息,服务器原样返回” 的 “回声” 功能。下面从 UDP 协议特性、代码结构、交互流程等方面详细介绍:
一、UDP 协议基础
在分析代码前,先明确 UDP(用户数据报协议)的核心特性,这是理解两个程序的基础:
无连接:通信前不需要建立连接(区别于 TCP 的三次握手),客户端直接发送数据,服务器直接接收。
数据报导向:数据以 “数据报(Datagram)” 为单位传输,每个数据报包含完整的源地址和目标地址。
不可靠:不保证数据一定送达,也不保证顺序,可能丢失或重复。
高效:由于无需连接管理和确认机制,传输速度快,适合实时性要求高的场景(如视频、语音)。
二、UdpEchoServer(服务器端)详解
服务器的作用是监听固定端口,接收客户端的请求,处理后返回响应。
1. 核心成员与初始化
java
运行
private DatagramSocket socket = null; // UDP套接字,用于收发数据
// 构造方法:绑定指定端口(服务器必须固定端口,否则客户端无法找到)
public UdpEchoServer(int port) throws SocketException {
socket = new DatagramSocket(port); // 绑定端口,如9090
}
DatagramSocket 是 UDP 通信的核心对象,服务器通过它绑定端口、接收客户端数据、发送响应。
服务器必须手动指定端口(如 9090),否则客户端不知道向哪个端口发送数据。
2. 核心方法 start():服务器主逻辑
服务器通过无限循环持续处理客户端请求,流程分为 4 步:
(1)接收客户端请求
java
运行
// 创建一个空的数据报,用于接收数据(缓冲区4096字节)
DatagramPacket requestPacket = new DatagramPacket(new byte[4096], 4096);
socket.receive(requestPacket); // 阻塞等待客户端发送数据
// 将接收的字节数组转换为字符串(只取有效长度,避免缓冲区空字符)
String request = new String(requestPacket.getData(), 0, requestPacket.getLength());
DatagramPacket 是 UDP 数据的载体,接收时需要一个字节数组作为缓冲区。
socket.receive() 是阻塞方法:如果没有客户端发送数据,服务器会一直卡在这一步,直到收到数据才继续执行。
(2)处理请求(核心业务逻辑)
java
运行
// 调用process方法处理请求,这里是“回声”逻辑(原样返回)
String response = process(request);
// 处理方法:简单返回请求内容(可扩展为复杂逻辑,如计算、查询等)
public String process(String request) {
return request;
}
process 方法是业务逻辑的核心,当前实现 “回声”,若要扩展功能(如服务器返回大写字符串),只需修改此方法。
(3)向客户端发送响应
java
运行
// 构造响应数据报:包含响应内容、客户端地址(从请求中获取)
DatagramPacket responsePacket = new DatagramPacket(
response.getBytes(), // 响应数据的字节数组
response.getBytes().length, // 数据长度
requestPacket.getSocketAddress() // 客户端的地址(IP+端口)
);
socket.send(responsePacket); // 发送响应
由于 UDP 无连接,服务器每次发送响应时,必须明确指定客户端的地址(从接收的请求数据报中获取,requestPacket.getSocketAddress())。
(4)打印日志(调试与监控)
java
运行
// 输出客户端IP、端口、请求和响应内容
System.out.printf("[%s:%d] req=%s, resp=%s
",
requestPacket.getAddress().toString(), // 客户端IP
requestPacket.getPort(), // 客户端端口
request,
response
);
3. 主方法:启动服务器
java
运行
public static void main(String[] args) throws IOException {
UdpEchoServer server = new UdpEchoServer(9090); // 绑定9090端口
server.start(); // 启动服务器,进入循环处理请求
}
服务器启动后会一直运行(无限循环),直到手动终止程序。
三、UdpEchoClient(客户端)详解
客户端的作用是向服务器发送请求,并接收服务器返回的响应。
1. 核心成员与初始化
java
运行
private DatagramSocket socket = null; // UDP套接字
private String serverIp; // 服务器IP地址(如127.0.0.1)
private int serverPort; // 服务器端口(如9090)
// 构造方法:初始化客户端套接字,记录服务器地址
public UdpEchoClient(String ip, int port) throws SocketException {
socket = new DatagramSocket(); // 客户端端口由系统随机分配(无需固定)
serverIp = ip;
serverPort = port;
}
客户端的 DatagramSocket 不指定端口,由系统自动分配一个空闲端口(避免端口冲突)。
客户端需要记录服务器的 IP 和端口(因为 UDP 无连接,每次发送都要指定目标地址)。
2. 核心方法 start():客户端主逻辑
客户端通过循环从控制台读取输入,发送给服务器并接收响应,流程如下:
(1)从控制台读取用户输入(作为请求)
java
运行
Scanner scanner = new Scanner(System.in);
System.out.print("->");
String request = scanner.next(); // 读取用户输入(如"hello")
scanner.next() 读取空格分隔的字符串,若需输入带空格的内容,可改为 scanner.nextLine()。
(2)向服务器发送请求
java
运行
// 构造请求数据报:包含请求内容、服务器IP和端口
DatagramPacket requestPacket = new DatagramPacket(
request.getBytes(), // 请求数据的字节数组
request.getBytes().length, // 数据长度
InetAddress.getByName(serverIp), // 服务器IP解析为InetAddress对象
serverPort // 服务器端口
);
socket.send(requestPacket); // 发送请求到服务器
客户端发送数据时,必须明确指定服务器的 IP(InetAddress.getByName(serverIp))和端口(serverPort)。
(3)接收服务器的响应
java
运行
// 创建空的数据报,用于接收响应(缓冲区4096字节)
DatagramPacket responsePacket = new DatagramPacket(new byte[4096], 4096);
socket.receive(responsePacket); // 阻塞等待服务器响应
// 将响应字节数组转换为字符串
String response = new String(responsePacket.getData(), 0, responsePacket.getLength());
(4)打印响应内容
java
运行
System.out.println(response); // 输出服务器返回的响应(如"hello")
3. 主方法:启动客户端
java
运行
public static void main(String[] args) throws IOException {
// 连接本地服务器(127.0.0.1)的9090端口
UdpEchoClient client = new UdpEchoClient("127.0.0.1", 9090);
client.start(); // 启动客户端,进入交互循环
}
四、客户端与服务器的交互流程
启动服务器:服务器绑定 9090 端口,进入循环等待请求(socket.receive() 阻塞)。
启动客户端:客户端随机分配端口,等待用户输入。
客户端发送请求:用户输入字符串(如 "hi"),客户端将其打包为数据报,发送到 127.0.0.1:9090。
服务器处理请求:
服务器收到数据报,解析出请求内容 "hi"。
调用 process 方法生成响应 "hi"。
将响应打包为数据报,发送回客户端的 IP 和端口。
打印日志 [127.0.0.1:随机端口] req=hi, resp=hi。
客户端接收响应:客户端收到响应 "hi",打印到控制台。
循环交互:客户端继续等待用户输入,重复步骤 3-5,直到手动终止程序。
五、代码特点与可改进点
特点:
简单清晰:用最少的代码实现了 UDP 通信的核心流程,适合初学者理解。
可扩展性:服务器的 process 方法可独立修改,轻松实现除 “回声” 外的功能(如计算、翻译)。
符合 UDP 特性:正确处理了 UDP 无连接的特点(每次通信都指定地址)。
可改进点:
异常处理:当前代码直接抛出异常,实际开发中可添加重试、错误提示等逻辑。
资源释放:DatagramSocket 和 Scanner 需在程序结束时关闭(可使用 try-with-resources)。
输入输出优化:客户端用 nextLine() 支持带空格的输入,服务器日志可更规范。
退出机制:添加退出条件(如客户端输入 "exit" 时退出,服务器接收 "shutdown" 时停止)。
并发处理:服务器可改为多线程,同时处理多个客户端请求(避免单线程阻塞)。
总结
这两个代码是 UDP 通信的经典示例,完整展示了 “客户端发送 - 服务器接收 - 服务器响应 - 客户端接收” 的全流程。通过它们可以理解 UDP 的无连接特性、数据报的使用方式,以及服务器与客户端的角色分工。在此基础上,可扩展实现更复杂的网络应用(如聊天程序、实时数据传输等)。