【JavaEE】_基于UDP实现网络通信

目录

[1. 服务器](#1. 服务器)

[1.1 实现逻辑](#1.1 实现逻辑)

[1.2 代码](#1.2 代码)

[1.3 部分代码解释](#1.3 部分代码解释)

[2. 客户端](#2. 客户端)

[2.1 实现逻辑](#2.1 实现逻辑)

[2.2 代码](#2.2 代码)

[2.3 客户端部分代码解释](#2.3 客户端部分代码解释)

[3. 程序运行结果](#3. 程序运行结果)

[4. 服务器客户端交互逻辑](#4. 服务器客户端交互逻辑)


此篇内容为实现UDP版本的回显服务器echo server;

普通服务器:收到请求,根据请求计算响应,返回响应;

回显服务器:忽略计算,直接将收到的请求作为响应返回;

如需实现其他功能,修改响应计算方法process内容即可);

具体实现代码如下:

1. 服务器

1.1 实现逻辑

对于网络通信的服务器需要进行的工作为:

1. 读取请求并解析;

2. 根据请求计算响应;

3. 把响应写回客户端;

4. 打印交互详细信息;

1.2 代码

java 复制代码
package TestDemo1;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;

public class UDPEchoServer {
    // 创建一个DatagramSocket对象,作为后续操作网卡的基础
     private DatagramSocket socket = null;
     public UDPEchoServer(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);
             // DatagramPacket对象用于承载从网卡读到的数据,收到数据时需要创建一个内存空间保存这个数据,
             // DatagramPacket内部不能自行分配内存空间,需要程序员手动创建
             socket.receive(requestPacket);
             // 完成receive之后,数据以二进制形式存储在DatagramPacket中
             // 需要将二进制数据转成字符串
             String request = new String(requestPacket.getData(),0, requestPacket.getLength());
             // 取0~requestPacket.getLength()区间内的字节构成一个String对象;

             //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] req=%s, resp=%s\n",
                     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);
        // 可以在1024~65535(临时端口)中任意选取端口号
        server.start();
    }
}

1.3 部分代码解释

  1. 为方便计算请求,将请求的DatagramPacket对象构造成字符串时只需获取DatagramPacket对象中实际有效的部分数据,requestPacket.getLength()获取到的就是收到数据的真实长度而非4096这个最大长度:
  1. 已经了解过UDP本身是无连接的,故而对于每一次通信过程,在构造数据报时都需要指定数据报要发给谁;

(1)对于构造的responsePacket对象有3个参数:

java 复制代码
DatagramPacket responsePacket = new DatagramPacket(                   
response.getBytes(),
response.getBytes().length,
requestPacket.getSocketAddress());

response.length()获取到的是响应对象字符串的字符个数,

response.getBytes().length获取到的是响应对象字符串的字节个数;

requestPacket.getSocketAddress()获取到的是请求发送方(即客户端)的IP与端口号

(2)注意response.getBytes.length(字节个数)与response.getlength()(字符个数)的区别:

如果response字符串都是英文字符则二者相等,如果包含中文则二者不同;

在进行网络传输时,必然是需要通过字节为单位进行通信的。

  1. while(true)会使程序处于快速循环的状态,每循环一次就处理一次请求响应,

当客户端发出请求时,recerive就能顺利读取请求,客户端没有发出请求时,receive就会阻塞

如果客户端发出的请求过多,可以使用多线程冲动调动计算机硬件资源,也可以再多开机器,但多开机器又会涉及到分布式问题;

  1. 使用格式化输出Packet的IP与端口号:
  1. 前文已经提及socket也是一个文件,但在上文代码中并未进行close操作,却没有造成文件资源泄漏的原因是:

socket是文件描述符表中的一个表项,每次打开一个文件就会占用一个位置,文件描述符在pcb上,是跟随进程的。在上文代码中创建的socket对象在整个程序运行过程中都需要使用,不可以提前关闭,当socket不需要使用时,即代表程序结束了,进程结束了,文件描述符表也销毁了,伴随着销毁都被系统自动回收了。故而不会造成文件资源泄露问题

只有代码中频繁打开文件但不关闭,在一个进程的运行过程中,不断积累打开的文件,逐渐消耗掉文件描述符表中的内容,最后消耗殆尽,才会造成泄露。

对于生命周期很短的进程,无需考虑泄露,在客户端方一般来说影响不大。

2. 客户端

2.1 实现逻辑

对于网络通信的客户端,需要进行的工作是:

1. 从控制台读取数据作为客户端发出的请求;

2. 将请求字符串request构造成请求requestPacket对象,发送给服务器;

3. 尝试读取服务器返回的响应;

4. 将响应responsePacket对象构造成响应response字符串,显示出来;

2.2 代码

java 复制代码
package TestDemo1;

import java.io.IOException;
import java.net.*;
import java.util.Scanner;

public class UDPEchoClient {
    private DatagramSocket socket = null;
    private String serverIp = "";
    private int serverPort = 0;
    public UDPEchoClient(String ip, int port) throws SocketException {
        // 客户端的socket对象需令系统自动分配
        socket = new DatagramSocket();
        // UDP本身不持有对端信息,需要在应用程序中记录对端信息(IP与端口)
        serverIp = ip;
        serverPort = port;
    }
    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. 将请求字符串request构造成请求requestPacket对象,发送给服务器
            DatagramPacket requestPacket = new DatagramPacket(
                    request.getBytes(),request.getBytes().length,
                    InetAddress.getByName(serverIp), serverPort);
            socket.send(requestPacket);

            // 3. 尝试读取服务器返回的响应
            DatagramPacket responsePacket = new DatagramPacket(new byte[4096], 4096);
            socket.receive(responsePacket);

            // 4. 将响应responsePacket对象转换为响应字符串response,显示出来
            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();
    }
}

2.3 客户端部分代码解释

1.DatagramPacket的三个构造方法

第一种:只指定字节数组缓冲区,用于服务器接收请求与客户端接收响应时使用

第二种:指定字节数组缓冲区与InetAddress对象(同时包含IP与端口),用于服务器向客户端发回响应时使用:

第三种:指定字节数组缓冲区,同时指定IP与端口号

  1. 客户端对服务器发出请求的过程:

对于服务器端口必须是确定的:程序员可以手动分配空闲端口给当前服务器使用即可,

代码为:

java 复制代码
socket = new DatagramSocket(port);

客户端一般都采取系统分配的方式:也可以指定端口,但不推荐。一方面,因为指定的端口可能被其他进程占用,如果被占用就会产生端口号冲突,运行就会抛出异常,提示绑定端口失败;另一方面,如果客户端出现了端口冲突,让客户手动解决也是不现实的。

代码为:

java 复制代码
socket = new Datagramocket();
  1. 端口与进程的关系:

端口号用于标识或区分一个进程,因此在同一台主机上,不允许一个端口同时被多个进程使用;

但是一个进程可以绑定多个端口;

socket和端口是一一对应的,进程与socket是一对多的

3. 程序运行结果

首先启动EchoServer,再启动EchoClient,在客户端输入请求字符串后查看运行结果:

客户端与服务器通信成功。

4. 服务器客户端交互逻辑

  1. 服务器先启动,启动后进入循环,执行到receive处阻塞;

  2. 客户端开始启动后,进入循环执行scanner.next(),在此处阻塞。

当客户在控制台输入内容后,next返回作为请求,继而构造请求数据并发送给服务器;

  1. 客户端发送数据后,

服务器从receive中返回,解析请求构造字符串,执行process操作计算响应,构造响应后执行send发回给客户端;

客户端执行到receive处等待服务器的响应;

  1. 客户端获取到从服务器返回的数据后,从receive中返回,继而显示响应内容;

  2. 服务器完成一次循环后又执行到receive处,客户端完成依次循环后又执行到scanner.next处,二者均进入阻塞状态;

注意:当服务器程序在普通私有ip计算机上运行时,若不在一个局域网中,无法实现跨主机访问。

如果服务器程序在特殊的计算机:云服务器上,就拥有了公有ip,可以实现跨主机访问。

此部分内容后续详解。

相关推荐
Abladol-aj14 分钟前
并发和并行的基础知识
java·linux·windows
清水白石00814 分钟前
从一个“支付状态不一致“的bug,看大型分布式系统的“隐藏杀机“
java·数据库·bug
吾日三省吾码6 小时前
JVM 性能调优
java
弗拉唐7 小时前
springBoot,mp,ssm整合案例
java·spring boot·mybatis
oi777 小时前
使用itextpdf进行pdf模版填充中文文本时部分字不显示问题
java·服务器
少说多做3437 小时前
Android 不同情况下使用 runOnUiThread
android·java
知兀8 小时前
Java的方法、基本和引用数据类型
java·笔记·黑马程序员
蓝黑20208 小时前
IntelliJ IDEA常用快捷键
java·ide·intellij-idea
Ysjt | 深8 小时前
C++多线程编程入门教程(优质版)
java·开发语言·jvm·c++