构建一个最简单的UDP服务器和客户端并逐行解析

发布于:2025-04-08 ⋅ 阅读:(37) ⋅ 点赞:(0)

目录

1.服务器

(1)基本概念

(2)代码实现

 3.逐行解析

1)

2)

3)

4)

5)

6)

7)

8)

1. response.getBytes():

2.response.getBytes().length: 

3.requestPacket.getSocketAddress(): 

4.DatagramPacket responsePacket = new DatagramPacket 

9)

10)

11)

2.客户端

(1)基本概念

(2)代码实现

(3)逐行解析

1)

2)

3)

4)

5)

6)

7)

8)

9)

10)

3.结果展示


在 Java 网络编程中,客户端(Client)服务器(Server)是两个核心角色,它们共同构成了网络通信的基本模型(即 C/S 架构)。

以下我会逐行解析构建一个最简单的服务器和客户端的代码。

1.服务器

(1)基本概念

服务器是 接收请求 的一方,被动监听特定端口,处理客户端请求并返回响应。
典型场景:Web 服务器(如 Tomcat)、数据库服务器、游戏服务器等。

(2)代码实现

public class UdpEchServer {
        private DatagramSocket socket=null;
        public UdpEchServer(int port) throws SocketException {
            socket=new DatagramSocket(port);
        }
        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());//数组中有效的长度,避免只收到很少的数据却构建了很大的byte数组
                //2.根据请求,计算相应(一个服务器最关键的逻辑)
                String response=process(request);

            /*3.将响应返回给客户端*/
                DatagramPacket responsePacket = new DatagramPacket(response.getBytes(),response.getBytes().length,requestPacket.getSocketAddress());
                socket.send(responsePacket);
                //4.打印一个日志
                System.out.printf(
                        "[%s:%d] request:%s, response:%s\n",
                        requestPacket.getAddress().toString(), // 客户端IP地址
                        requestPacket.getPort(),               // 客户端端口号
                        request,                               // 请求内容
                        response                               // 响应内容
                );
            }
        }
        /*把根据请求计算响应的逻辑构建成了一个方法   */
        private String process(String request) {
            return request;
        }
 public static void main(String[] args) throws IOException {
        UdpEchoClient client = new UdpEchoClient("127.0.0.1", 9090);
        client.start();
    }
    }

 3.逐行解析

1)

   private DatagramSocket socket=null;

DatagramSock是UDP socket ,用于发送和接受数据包

2)

 public UdpEchServer(int port) throws SocketException {
            socket=new DatagramSocket(port);
        }

指定一个端口号(int port)给服务器使用。因为服务器的端口号不能一直变,所以直接指定固定端口号。

3)

while(true)

因为服务器通常是7*24小时不间断的读取请求的,所以通常会用一个while(true)这样的死循环来保证服务器持续工作。循环一次,就相当于读取一次请求。

4)

 DatagramPacket requestPacket=new DatagramPacket(new byte[4096],4096);

构建出一个用于接受请求数据报的DatagramPacket

这里使用构造函数 DatagramPacket(byte[] buf, int length) 创建了一个 DatagramPacket 对象,其中:

  • byte[] buf 是用于存储接收到的数据的缓冲区。
  • int length 是缓冲区的长度,表示可以接收的最大数据量。

5)

 socket.receive(requestPacket);

 这里将requestPacket作为输出型参数

  • 这一行代码是使用UDP套接字接收数据报的方法调用。receive方法会阻塞服务器进程,直到接收到一个数据报。
  • 一旦接收到数据报,requestPacket将被填充接收到的数据、发送方的IP地址和端口号。

6)

 String request=new String(requestPacket.getData(),0,requestPacket.getLength());

将读取到的二进制数据转换成字符串

  1. requestPacket.getData(): 这个方法调用返回的是接收到的数据包中包含的字节数组。这个数组的大小是固定的(在此例中为4096字节),但其中只有部分是实际接收到的数据,所以还需要指定字节数组的有效长度。

  2. requestPacket.getLength(): 这个方法调用返回的是实际接收到的数据的长度,也就是有效长度。由于UDP数据包的大小是固定的,而实际传输的数据可能较少,因此getLength()的值可能小于getData().length。这个方法确保只处理实际接收到的数据,而不是整个4096字节的数组。

  3. 0: 这是字节数组的偏移量,表示从数组的第0个位置开始读取数据。

  4. new String(...): 这是一个构造函数调用,用于将字节数组转换为字符串。这里使用的是特定范围的字节数组(从偏移量0开始,长度为requestPacket.getLength()来创建字符串,而不是整个数组。

7)

String response=process(request);

这一段代码的含义是根据请求来作出响应,一般来说是一个服务器最关键的逻辑,但是此处的服务器是回显服务器,所以这个逻辑被省略了。

可以response=request,但是此处将做出相应的逻辑构建成了一个process方法,方便后续做其他服务器:

 private String process(String request) {
        return request;
    }

8)

 DatagramPacket responsePacket = new 
DatagramPacket(response.getBytes(),response.getBytes().length,
requestPacket.getSocketAddress());

再次构建一个DatagramPacket对象作为发送给客户端的数据报:responsePacket。

1. response.getBytes():

  • 这个方法将 前面接受reqest后得到的String 类型的 response 转换为 byte[] 类型的数组。
  • getBytes() 方法会根据平台的默认字符集将字符串编码为字节数组。

2.response.getBytes().length

  • 这里调用了 response.getBytes() 方法得到的字节数组的长度。
  • 这个长度表示实际响应数据的总字节数,确保发送的数据报中只包含有效的响应内容。

长度要用response.getBytes().length,因为response是字符串,需要先转成二进制数组,才能获取到数组的长度
不能用response.length,因为response是字符串,字符串的长度是字符的个数,而不是二进制数组的长度

3.requestPacket.getSocketAddress()

  • 这个方法用于获取发送请求的数据报(requestPacket)的源地址(SocketAddress)。
  • SocketAddress 包含了客户端的 IP 地址和端口号信息,这样服务器就能知道将响应数据发送回哪个客户端。

 

4.DatagramPacket responsePacket = new DatagramPacket 

  • 这里创建了一个新的 DatagramPacket 对象,命名为 responsePacket
  • 构造函数 DatagramPacket(byte[] buf, int length, SocketAddress address) 的参数分别为:
    • byte[] buf: 要发送的数据的字节数组。
    • int length: 数据的有效长度。
    • SocketAddress address: 目标地址,即要发送到的客户端地址。

9)

  socket.send(responsePacket);

发送数据包给客户端,由于前面使用了requestPacket.getSocketAddress()方法将源ip、源端口号都传给了requestPacket,所以此处UDP已经知道了该发送给谁。

10)

    System.out.printf(
                    "[%s:%d] request:%s, response:%s\n",
                    requestPacket.getAddress().toString(), // 客户端IP地址
                    requestPacket.getPort(),               // 客户端端口号
                    request,                               // 请求内容
                    response                               // 响应内容
            );

打印一个日志,方便调试和监控。

11)

 public static void main(String[] args) throws IOException {
        UdpEchoClient client = new UdpEchoClient("127.0.0.1", 9090);
        client.start();
    }

 在main方法中实例化服务器,并指定了服务器的端口号是9090

2.客户端

(1)基本概念

客户端是 发起请求 的一方,主动连接服务器并发送请求,然后等待服务器的响应。
典型场景:浏览器、移动应用、桌面程序等。

(2)代码实现

public class UdpEchoClient {
    private DatagramSocket socket = null;
    //UDP本身不保存对方信息,在自己的代码中保存一下
    private String sercerIp;
    private int serverPort;

    //和服务器不同,客户端的构造方法不能只有一个端口号,还要有ip地址
    public UdpEchoClient(String sercerIp, int serverPort) throws SocketException {
        this.sercerIp = sercerIp;
        this.serverPort = serverPort;
        //创建一个socket,不能传入端口号,因为端口号会有竞争,让系统随机分配一个空闲的即可
        socket = new DatagramSocket();
    }

    //启动客户端
    public void start() throws IOException {
        Scanner sc = new Scanner(System.in);
        while (true) {
            //1.读取用户输入的数据
            System.out.println("请输入要发送的数据");
            String request = sc.next();
            //2.将请求发送给服务器,构建一个DatagramPacket对象,发送给服务器
            DatagramPacket packet = new DatagramPacket(request.getBytes(), request.getBytes().length, InetAddress.getByName(sercerIp), serverPort);
            //3.发送数据
            socket.send(packet);
            //4.接收服务器的响应数据
            DatagramPacket responsePacket = new DatagramPacket(new byte[4096], 4096);
            socket.receive(responsePacket);
            //5.从数据包中获取响应数据,打印出来
            String response = new String(responsePacket.getData(), 0, responsePacket.getLength());
            System.out.println("response:" + response);
        }
    }

    public static void main(String[] args) throws IOException {
        UdpEchoClient client = new UdpEchoClient("127.0.0.1", 9090);
        client.start();
    }
}

(3)逐行解析

1)

  private DatagramSocket socket = null;

创建一个socket用来接收和发送数据

2)

  private String sercerIp;
    private int serverPort;

定义String类型的ip地址和int 类型的端口号。

3)

 public UdpEchoClient(String sercerIp, int serverPort) throws SocketException {
        this.sercerIp = sercerIp;
        this.serverPort = serverPort;
        //创建一个socket,不能传入端口号,因为端口号会有竞争,让系统随机分配一个空闲的即可
        socket = new DatagramSocket();
    }

创建客户端的构造方法。与服务器不同的是,参数不止有端口号,还有ip地址,因为客户端要知道服务器的ip地址和端口号以便发送,而服务器只需要一个端口号来监听、接受客户端发送来的数据。

4)

  //1.读取用户输入的数据
            System.out.println("请输入要发送的数据");
            String request = sc.next();

读取用户输入的数据。

5)

DatagramPacket packet = new 
DatagramPacket(request.getBytes(), request.getBytes().length, 
InetAddress.getByName(sercerIp), serverPort);

将请求发送给服务器:构建一个DatagramPacket对象来发送。

1.request.getBytes()

来将字符串request中的字节数组取出来。

2.request.getBytes().length

指定字节数组的长度

3.InetAddress.getByName(sercerIp), serverPort

传入服务器的ip地址和端口号,其中ip地址是字符串,需要用getByname()方法来包装一下。

6)

 socket.send(packet);

 发送数据给服务器。

7)

 DatagramPacket responsePacket = new DatagramPacket(new byte[4096], 4096);

构建一个DatagramPacket对象来作为服务器的响应数据

8)

 socket.receive(responsePacket);

responsePacket作为输出型参数,得到了响应数据包里面的内容。

9)

 String response = new String(responsePacket.getData(), 0, responsePacket.getLength());
            System.out.println("response:" + response);

用getData()方法得到的字节数组构建一个字符串作为响应数据,指定了字节数组的长度为【0,字节数组的长度)。

打印日志。

10)

 public static void main(String[] args) throws IOException {
        UdpEchoClient client = new UdpEchoClient("127.0.0.1", 9090);
        client.start();
    }

在main方法中指定服务器的ip地址是“127.0.0.1”,这是一个环回ip,表示本机。指定服务器的端口号9090,与服务器的端口号对应。

3.结果展示

服务器: 

客户端: