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和portDatagramPacket 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协议的基本使用方法,以及其在不同场景中的应用。