JavaEE 编写Java程序,实现一个简单的echo程序(网络编程UDP实践练习)

发布于:2025-02-27 ⋅ 阅读:(14) ⋅ 点赞:(0)

UDP Client 和 UDP Server 的实现分析与解读

UDP(User Datagram Protocol,用户数据报协议)是一种无连接的网络协议,相比于TCP,它不保证数据包的可靠送达和顺序。因此,UDP在网络应用中,尤其是对实时性要求较高、容错能力强的应用场景中,得到了广泛的应用。本文将结合 UdpClientUdpServer 这两个示例程序,分析它们的实现及相关的注释内容,帮助大家更好地理解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();
    }
}
代码和思想分析
  1. Socket的创建:客户端在创建DatagramSocket时,不指定端口号。因为客户端是主动发送数据的一方,它只需要通过一个临时的端口与服务器通信。若指定端口号,可能会发生端口冲突(即目标端口已被其他进程占用)。这是UDP协议的特性之一,避免了不必要的冲突。

  2. 构造DatagramPacket:客户端构造了一个DatagramPacket对象,里面封装了待发送的数据(来自控制台输入),以及目标IP地址和端口号。InetAddress.getByName(serverIP)将目标服务器的IP地址转换为InetAddress类型。

  3. 发送数据包:通过socket.send(requestPacket)方法将数据包发送给服务器。由于UDP是无连接的协议,发送数据时不需要建立正式的连接。

  4. 接收数据包:客户端通过socket.receive(responsePacket)接收服务器的响应,接收到的数据为字节数组,之后将其转换为字符串并打印。

  5. 循环操作:客户端的工作是持续不断地从用户获取输入,发送数据,并接收服务器响应,直到用户退出程序。

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();
    }


}
代码和思想分析
  1. Socket的创建与端口绑定:与客户端不同,服务器在创建DatagramSocket时需要指定端口号。这个端口号与服务器进程绑定,操作系统会将该端口与服务器进程关联。若同一端口已被其他进程占用,新的进程将无法绑定该端口。

  2. 接收数据:服务器通过socket.receive(requestPacket)接收客户端发送的UDP数据包。receive()方法是阻塞的,直到数据包到达。数据包的内容是二进制数据,接收后将其转换为字符串进行处理。

  3. 处理请求:服务器通过process()方法处理客户端的请求。在这个示例中,处理逻辑非常简单——去除字符串中的空格。实际应用中,服务器可能会根据不同的业务需求对请求进行更加复杂的处理。

  4. 响应客户端:服务器处理完请求后,通过DatagramPacket将响应数据返回给客户端。发送的数据包需要包含客户端的地址和端口号,UDP协议依赖这些信息来正确地将数据返回给请求方。

  5. 打印日志:每次接收请求并发送响应后,服务器都会打印日志,显示请求的来源地址、端口,以及请求和响应的内容。

3. 总结与思考

通过这两个示例,我们可以更好地理解UDP协议的工作方式:

  • 无连接特性:UDP不需要像TCP那样建立连接,客户端和服务器通过DatagramSocketDatagramPacket直接发送和接收数据。它是无连接的,这使得它在实时通信和网络游戏等场景中非常有用。

  • 高效性:由于没有连接的建立和拆除过程,UDP的通信速度通常比TCP更快,适用于对时延敏感的应用。然而,UDP不保证数据的可靠性,发送的数据包可能会丢失或乱序。

  • 数据报格式:UDP使用数据报(Datagram)进行通信,每个数据包都包含目标IP和端口信息。客户端发送数据时,数据包需要包含目标服务器的IP和端口;服务器响应数据时,数据包需要包含客户端的IP和端口。

  • API的使用DatagramSocketDatagramPacket是UDP通信的核心API,前者用于发送和接收数据,后者用于封装数据及其目的地址。

4. 代码中的关键语法和思想
  • 客户端不指定端口:客户端在创建DatagramSocket时不指定端口号,这是因为它是主动发送数据的一方。若指定端口,可能会导致端口冲突。

  • 阻塞式接收:服务器端使用socket.receive()接收数据包,这个方法会阻塞直到接收到数据包,因此可以通过死循环处理不断到来的客户端请求。

  • 字节数组与字符串的转换:在处理UDP数据时,收到的都是字节数据,使用getData()获取字节数组,再根据需要将其转换为字符串。特别注意getLength()方法,它返回的是有效数据的长度,不是字节数组的总长度。

以上就是UDP客户端和服务器端代码的整体分析和注释解读。通过这些示例,我们可以清晰地看到UDP协议的基本使用方法,以及其在不同场景中的应用。