UDP Client 和 UDP Server 的实现分析与解读
UDP(User Datagram Protocol,用户数据报协议)是一种无连接的网络协议,相比于TCP,它不保证数据包的可靠送达和顺序。因此,UDP在网络应用中,尤其是对实时性要求较高、容错能力强的应用场景中,得到了广泛的应用。本文将结合 UdpClient
和 UdpServer
这两个示例程序,分析它们的实现及相关的注释内容,帮助大家更好地理解UDP的使用。
1. UDP Client(客户端)
代码分析
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;
/**
* Description://UDP的特点是无连接
* 就是通信双方,不保存对方的信息(IP+端口号)
* DatagramSocket和DatagramPacket是UDP中的两个重点api
* Date: 2025-02-20
* Time: 21:48
*/
public class UdpServer {
private DatagramSocket socket = null;
public UdpServer(int port) throws SocketException {
//对于服务器这来说, 需要在socket对象创建的时候就制定一个端口号,作为构造方法的参数
//后续服务器开始运行的时候,操作系统就会把端口号和进程关系起来
//在调用这个构造方法的过程中,jvm就会调用系统中的socket api,完成端口号-进程的关联
//也就是"绑定端口号"
//对于一个系统来说,同一时刻,一个端口号,只能绑定一个进程
//但是一个进程可以绑定多个端口号,类似手机号一个人有多个
//如果有多个进程,先绑定之后,后来的想要绑定的进程就会失效
socket = new DatagramSocket(port);
}
public void start() throws IOException {
System.out.println("UDP 服务器 Started");
//对于服务器来说,主要的工作就是时刻准备好处理客户端传来的请求
//所以这里要通过一个死循环,不停的处理来自客户端的请求
while (true) {
//接下来就是服务器开发三板斧
//1. 读取客户端的请求并且解析
//receive 是从网卡上获取数据
//但是调用receive的时候网卡上不一定有数据,如果没有收到数据,
// receive就会一直阻塞,直到等待真正收到数据为止
//和inputStream.read(buffer)一样receive也是通过输出型参数获取到网卡上收到数据的
//receive 的参数是DatagramPacket
//dataframerequestPacket 的自身需要存储数据,但是存储数据有多大,需要外部指定:
DatagramPacket requestPacket = new DatagramPacket(new byte[1024], 1024);
socket.receive(requestPacket);
//上述收到的数据,是二进制byte[] 的形式体现的,后续代码如果要进行打印之类的处理操作
//需要转化成字符串才好处理
//这个方法是用来构造String对象
//问题为什么要用getData()
String request = new String(requestPacket.getData(), 0, requestPacket.getLength());
//2. 根据请求计算响应
//此处请求就是响应,所以
String response = process(request);
//3. 把响应写回到客户端
//此处response.getBytes().length 获取的是字节数组的长度
//而不是response.length,这个方法获得的是字符串中字符的个数
//如果输入的是中文,字符个数不等于字节的个数,比如,utf-8一个中文对应3个字节
//因为UDP无连接,所以在send的时候就需要在send的数据包中把要对端的信息写进去,这样才能把信息返回
//所以要用两个方法分别返回address和port
DatagramPacket responsePacket = new DatagramPacket(response.getBytes(), response.getBytes().length
, requestPacket.getAddress(), requestPacket.getPort());
socket.send(responsePacket);
//把日志打印
System.out.printf("[%s:%d] req = %s , resp %s\n", requestPacket.getAddress(),requestPacket.getPort(),
request,response);
}
}
private String process(String request) {
request = request.replace(" ", "");
return request;
}
public static void main(String[] args) throws IOException {
UdpServer server = new UdpServer(9090);
server.start();
}
}
代码和思想分析
Socket的创建:客户端在创建
DatagramSocket
时,不指定端口号。因为客户端是主动发送数据的一方,它只需要通过一个临时的端口与服务器通信。若指定端口号,可能会发生端口冲突(即目标端口已被其他进程占用)。这是UDP协议的特性之一,避免了不必要的冲突。构造DatagramPacket:客户端构造了一个
DatagramPacket
对象,里面封装了待发送的数据(来自控制台输入),以及目标IP地址和端口号。InetAddress.getByName(serverIP)
将目标服务器的IP地址转换为InetAddress
类型。发送数据包:通过
socket.send(requestPacket)
方法将数据包发送给服务器。由于UDP是无连接的协议,发送数据时不需要建立正式的连接。接收数据包:客户端通过
socket.receive(responsePacket)
接收服务器的响应,接收到的数据为字节数组,之后将其转换为字符串并打印。循环操作:客户端的工作是持续不断地从用户获取输入,发送数据,并接收服务器响应,直到用户退出程序。
2. UDP Server(服务器端)
代码分析
import java.io.IOException;
import java.net.*;
import java.util.Scanner;
/**
* Description:
* Date: 2025-02-20
* Time: 21:49
*/
public class UdpClient {
DatagramSocket socket = null;
private String serverIP;
private int serverPort;
//客户端这一边创建socket不要制定端口号,客户端是主动的一方
//不需要服务器来找他,而且如果制定了a端口号,如果服务器的a端口此时正在执行其他进程,这样就会产生冲突
public UdpClient(String serverIP,int serverPort) throws SocketException {
socket = new DatagramSocket();
this.serverIP = serverIP;
this.serverPort = serverPort;
}
public void start() throws IOException {
System.out.println("启动客户端");
Scanner scanner = new Scanner(System.in);
while(true){
//1. 从控制台读取到用户的输入
System.out.println("--> ");
String request = scanner.next();
//2. 构造出一个UDP请求,发送给服务器
//此处是给服务器发送数据,发送数据的时候,UDP数据报里面就需要有带有目标的IP和端口
//接受数据的时候,构造的UDP数据报,就是一个空的数据报
//此处要使用InetAdress来包裹IP,因为这里的serverIP是二进制的格式,
// 但是这个DatagramPacket构造方法只接受整数类型的IP地址,所以要包裹
DatagramPacket requestPacket = new DatagramPacket(request.getBytes(),request.getBytes().length,
InetAddress.getByName(this.serverIP),this.serverPort);
//发送给服务器
socket.send(requestPacket);
//3. 从服务器读取到响应
DatagramPacket responsePacket = new DatagramPacket(new byte[1024], 1024);
socket.receive(responsePacket);
//4. 把响应打印
String response = new String(responsePacket.getData(), 0, responsePacket.getLength());
System.out.println(response);
}
}
public static void main(String[] args) throws IOException {
UdpClient udpClient = new UdpClient("127.0.0.1",9090);
udpClient.start();
}
}
代码和思想分析
Socket的创建与端口绑定:与客户端不同,服务器在创建
DatagramSocket
时需要指定端口号。这个端口号与服务器进程绑定,操作系统会将该端口与服务器进程关联。若同一端口已被其他进程占用,新的进程将无法绑定该端口。接收数据:服务器通过
socket.receive(requestPacket)
接收客户端发送的UDP数据包。receive()
方法是阻塞的,直到数据包到达。数据包的内容是二进制数据,接收后将其转换为字符串进行处理。处理请求:服务器通过
process()
方法处理客户端的请求。在这个示例中,处理逻辑非常简单——去除字符串中的空格。实际应用中,服务器可能会根据不同的业务需求对请求进行更加复杂的处理。响应客户端:服务器处理完请求后,通过
DatagramPacket
将响应数据返回给客户端。发送的数据包需要包含客户端的地址和端口号,UDP协议依赖这些信息来正确地将数据返回给请求方。打印日志:每次接收请求并发送响应后,服务器都会打印日志,显示请求的来源地址、端口,以及请求和响应的内容。
3. 总结与思考
通过这两个示例,我们可以更好地理解UDP协议的工作方式:
无连接特性:UDP不需要像TCP那样建立连接,客户端和服务器通过
DatagramSocket
和DatagramPacket
直接发送和接收数据。它是无连接的,这使得它在实时通信和网络游戏等场景中非常有用。高效性:由于没有连接的建立和拆除过程,UDP的通信速度通常比TCP更快,适用于对时延敏感的应用。然而,UDP不保证数据的可靠性,发送的数据包可能会丢失或乱序。
数据报格式:UDP使用数据报(Datagram)进行通信,每个数据包都包含目标IP和端口信息。客户端发送数据时,数据包需要包含目标服务器的IP和端口;服务器响应数据时,数据包需要包含客户端的IP和端口。
API的使用:
DatagramSocket
和DatagramPacket
是UDP通信的核心API,前者用于发送和接收数据,后者用于封装数据及其目的地址。
4. 代码中的关键语法和思想
客户端不指定端口:客户端在创建
DatagramSocket
时不指定端口号,这是因为它是主动发送数据的一方。若指定端口,可能会导致端口冲突。阻塞式接收:服务器端使用
socket.receive()
接收数据包,这个方法会阻塞直到接收到数据包,因此可以通过死循环处理不断到来的客户端请求。字节数组与字符串的转换:在处理UDP数据时,收到的都是字节数据,使用
getData()
获取字节数组,再根据需要将其转换为字符串。特别注意getLength()
方法,它返回的是有效数据的长度,不是字节数组的总长度。
以上就是UDP客户端和服务器端代码的整体分析和注释解读。通过这些示例,我们可以清晰地看到UDP协议的基本使用方法,以及其在不同场景中的应用。