JavaEE——网络编程(UDP套接字编程)

文章目录

  • [一、简单理解Socket 套接字](#一、简单理解Socket 套接字)
  • [二、UDP 数据报套接字编程](#二、UDP 数据报套接字编程)
  • [三、编写简单的 UDP 版本服务器客户端](#三、编写简单的 UDP 版本服务器客户端)
  • 四、总结与代码运行结果解释

一、简单理解Socket 套接字

概念: Socket 套接字就是操作系统给应用程序提供的网络编程 API。

我们可以认为 socket api 是和传输层密切相关的。

我们知道,在传输层中,提供了两个最核心的协议,UDP TCP。

因此,socket api 中也提供了两种风格。UDP TCP。

在这里我们简单认识一下 UDP 和 TCP

  • UDP: 无连接 不可靠传输 面向数据报 全双工。
  • TCP: 有连接 可靠传输 面向字节流 全双工。

解释 有连接 / 无连接

例:

打电话就是有连接的,需要建立了才能通信。建立连接需要对方来 "接受"

发短信,就是无连接的,直接发送即可无需接受。

解释 可靠传输 / 不可靠传输

在这里,对于可靠传输 的定义是:发送方的数据到底是不是发送过去了,还是丢了

所以,在这里:

打电话是一个可靠传输。

发短信是一个不可靠传输。
但要注意的是,可靠不可靠,与有没有连接没有任何关系

解释 面向字节流 / 面向数据报

面向字节流: 数据传输和文件读写类似,是"流式"的。
面向数据报: 数据传输以一个个"数据报"为单位。(一个数据报可能为若干个字节,带有一定的格式)。

解释 全双工

即就是一个通信通道,可以双向传输。(既可以发送,也可以接收)

对应的 半双工 就是指只可以单向传输信息。

至于如何传递信息,与用户的 路由器,交换机配置有关。如图:

二、UDP 数据报套接字编程

这里给出了两个类用来操作:

  • DatagramSocket

使用这个类表示一个 socket 对象。

在操作系统中,是将这个 socket 对象当成一个文件来处理的。

对于普通文件 ,对应的硬件设备是 硬盘 。 对于socket 文件 ,对应的硬件设备是 网卡

拥有了 socket 对象就可以与另一台主机进行通信。如果要和多个不同主机交互,就需要创建多个 socket 对象。

DatagramSocket 构造方法:

在这里就可以看出来,本质上不是 进程 和 端口 建立联系,而是进程中的 socket 对象和 端口 建立联系

DatagramSocket 方法:

对于 void receive(DatagramPacket p) 方法:

在此处传入的相当于一个空对象,receive 方法内部,会对参数的空对象进行内容填充。从而构造出结果数据。(构造出一个数据报)

  • DatagramPacket

该套接字 API 表示的是 UDP 中传输的一个报文。构造这个对象可以将指定的具体数据传递进去。

DatagramPacket 构造方法:


DatagramPacket 方法:

三、编写简单的 UDP 版本服务器客户端

文章中详细解释的是其中较为核心的代码,与整体逻辑还有差异,整体代码会在后面罗列。

1. 编写 UDP 版本的回显服务器

注:这里编写的客户端服务器是一个简单 UDP 版本的服务器,称之为:回显服务器。

一个普通的服务器: 收到请求,根据请求计算响应(业务逻辑),返回响应。

回显服务器: 省略了普通服务器的 "根据请求计算响应",这里只是为了演示 socket api 的用法。

创建服务器前的初步准备

  1. 我们要知道,网络通信的本质就是操作网卡
  2. 但是网卡的直接操作十分不便 ,在操作系统内核中就使用了 socket 这样的文件来描述网卡。
  3. 因此要实现网络通信,就必须要先创建出一个 socket 对象

代码如下:

java 复制代码
//这里定义成一个私有属性方便后续直接使用
    private DatagramSocket socket = null;

注:尤其 对于一个服务器 来讲,创建一个 socket 对象的同时,需要让其绑定一个明确的端口号。

因为在服务器在网络传输中处于一个被动的状态,没有一个明确的端口号,客户端就无法寻找到请求的服务器。

java 复制代码
// 这里的 UdpEchoSever 是定义的服务器类名
// 这里的 port 就是一个服务器端口号
    public UdpEchoSever(int port) throws SocketException {
        socket = new DatagramSocket(port);
    }

形象的解释上面需要端口号的原因:

例如:

假设本人现在开了一家面馆,地址在地球村,美利坚1号 ,这里的 "1号" 就相当于端口号

假设本人的小面做的不错,口口相传我都在 "地球村,美利坚1号"。但是,如果我现在通过小推车贩卖小面 ,只是经常在 1号 门口售卖,有时到处跑,此时,客户就很难准确的找到我。

因此,固定的位置就很重要,端口也是如此!
创建服务器的核心原理以及代码

  1. 读取客户端发送过来的请求。

对于 UDP 来说,传输的基本单位是 DatagramPacket

这里要用 receive 方法来接受请求。

这里还需要再次说明一下关键字 receive

这个 receive 方法是一个 输出型参数 ,所以,这里需要先创建出来一个 空白的 DatagramPacket 对象,交给 receive 来填充(填充的是数据来自于网卡)

java 复制代码
            //receive 方法是一个输出型参数,需要先创建好一个空白的 DatagramPacket 对象,交给 receive 方法来填充
        DatagramPacket requestPacket = new DatagramPacket(new byte[4096],4096 );
        socket.receive(requestPacket);

此时,接受过来的 DatagreamPacket 是一个特殊的对象,不方便处理,这里需要将其构造成一个字符串类型。

java 复制代码
String request = new String(requestPacket.getData(),0, requestPacket.getLength());

解释上述字符串转换代码

如下:

我们已知,上面传递下来的元素是存储在数组中。

如上图所示,这里的数组不一定是用满的。因此,要构造字符串,构造的就是呢些使用的部分。 即就是调用 getLength()

方法获取实际长度。(0 ~ getLength() 范围的元素)

  1. 根据需求计算响应 (这里写的是一个回显服务器,所以请求和响应相同)

获取处理后的元素

java 复制代码
String response = process(request);

设计根据需求计算响应方法

java 复制代码
	// 这里就是实现了一个回显
    public String process(String request){
        return request;
    }
  1. 将数据返回给客户端

这里将数据返回给客户端是 服务器 的工作。

因此,这里调用的 send 方法是属于 DatagramSocket 的,但是其发送的内容单元是 DatagramPacket 类型

发送回客户端前也就需要将 packet 构建好。

java 复制代码
   DatagramPacket responsePacket = new DatagramPacket(response.getBytes(),response.getBytes().length,
                  requestPacket.getSocketAddress());
   // 发送组织好的信息
   socket.send(responsePacket);

但是这里构造的相应对象与 接受时获取对象不同 ,这里构造的响应对象不可以使用空的字节数组,而是要使用响应的元素构造

  • 简单分析上述对象构造代码

    要注意的是,这里获取字符的形式 第二种 最好,以字节的形式获取元素很少会出现元素缺失的情况,而字符相比于字节,单位就大了许多,自然风险也高。
  1. 打印一下处理元素时的中间情况
java 复制代码
   System.out.printf("[%s:%d] req: %s; resp: %s\n",requestPacket.getAddress().toString(),requestPacket.getPort()
                    ,request,response);

图示解释各个元素:

回显服务器整体代码罗列

整体展示 UDP 格式的服务器代码:

java 复制代码
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;

//Udp版本的回显服务器
public class UdpEchoSever {
    //网络通信的本质是操作网卡
    //但是网卡的直接操作非常不方便,在操作系统内核中,就使用了 socket 这样的文件来描述网卡
    //因此要实现网络通信,就必须先创建出一个 socket 对象
    private DatagramSocket socket = null;

    //对于服务起来讲,创建 socket 对象同时,需要让其绑定上一个端口号
    //尤其针对服务器,更需要一个准确的端口号
    //因为服务器在网络传输中是处于被动的状态,没有明确地端口号,客户端就无法寻找到请求的服务器
    public UdpEchoSever(int port) throws SocketException {
        socket = new DatagramSocket(port);
    }

    public void start() throws IOException {
        System.out.println("服务器启动");
        //要注意的是服务器不是给一个客户端服务的,需要服务很多的客户端
        while(true){
            //1. 读取客户端发送过来的请求
            //   使用 receive 方法接受请求。
            //receive 方法是一个输出型参数,需要先创建好一个空白的 DatagramPacket 对象,交给 receive 方法来填充
            DatagramPacket requestPacket = new DatagramPacket(new byte[4096],4096 );
            socket.receive(requestPacket);

            //此时 DatagramPacket 是一个特殊的对象,不方便处理,这里可以将数据拿出来,构造成一个字符串
            String request = new String(requestPacket.getData(),0, requestPacket.getLength());

            //2.根据请求计算响应,这里是一个回显服务器,所以请求和响应相同
            String response = process(request);

            //3.将响应数据返回给客户端
            // send 的参数也是 DatagramPacket 需要将这个 packet 构建好
            //      此处构造的响应对象,不可以使用空的字节数组,而是要用响应的元素构造
            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 {
        //这里的端口可以随意设置
        UdpEchoSever sever = new UdpEchoSever(9090);
        sever.start();
    }
}

2. 编写 UDP 版本的回显客户端

对于客户端,我们要知道,就是来和对应的客户端进行通信的。

这里,我们就应该想起前面文章中提到的 网络通信的五元组

如上图所示,下面我们首先来实现基本设置。

回显客户端的基本设置

java 复制代码
    private DatagramSocket socket = null;
    private String severIp = null;
    private int severPort = 0;

//这里是构造方法
    public UdpEchoClient(String severIP,int severPort) throws SocketException {
        //这里不需要设定端口,让操作系统自动分配
        socket = new DatagramSocket();
        this.severIp = severIP;
        this.severPort = severPort;
    }
  • 首先这里构造的 socket 对象,不需要绑定一个固定的显示端口。随机挑选空闲的即可。

  • 其次,这里的
    源IP:127.0.0.1 已知。
    源端口:9090 前面已经设定。
    目的 IP:127.0.0.1 已知(环回IP)。
    目的端口:当前已经随机分配。 已知。

  • 最后使用的协议类型也已经明确。

到这里,基本上已经万事俱备,下面解释后面的操作。

编写客户端核心操作

  1. 从控制台获取要发送的数据

这里的操作比较简单直接展示代码:

java 复制代码
        System.out.println("客户端启动");
        Scanner scanner = new Scanner(System.in);
            //1. 从控制台读取要发送的数据
            //打印提示符
            System.out.println("> ");
            String request = scanner.next();
            if(request.equals("exit")){
                System.out.println("bye");
                break;
            }
  1. 构造成 UDP 请求并发送

这里要注意的是,此处要将信息发送出去,同样要调用 send 方法

前面说过,send 方法中传递的元素类型是 packet 类型。所以仍然需要构造 packet 类型,并且需要将 severIP 和 port 传入。

代码如下:

java 复制代码
 //   上述 IP 地址是一个字符串,需要 InetAddress.getByName 来进行转换
   DatagramPacket requestPacket = new DatagramPacket(request.getBytes(),request.getBytes().length,
           InetAddress.getByName(severIp),severPort);
   socket.send(requestPacket);
  1. 读取服务器的 UDP 响应,并解析
java 复制代码
    DatagramPacket responsePacket = new DatagramPacket(new byte[4096],4096);
    socket.receive(responsePacket);
    String response = new String(responsePacket.getData(),0,requestPacket.getLength());

这里和前面服务器获取元素相同,同样使用 receive 方法将返回的信息填充。

最后转换成 String 类型。

  1. 将解析好的结果显示出来。
java 复制代码
   System.out.println(response);

回显客户端整体代码罗列

整体展示 UDP 格式的客户端代码

java 复制代码
import java.io.IOException;
import java.net.*;
import java.util.Scanner;

//Udp版本的回显客户端
public class UdpEchoClient {
    private DatagramSocket socket = null;
    private String severIp = null;
    private int severPort = 0;

    //一次通信需要两个 IP 两个端口
    //客户端的 IP 是 127.0.0.1 已知
    //客户端的 端口号 是操作系统自动分配 已知
    //要进行传输,服务器的 IP 和 端口号 也需要传递给 客户端
    public UdpEchoClient(String severIP,int severPort) throws SocketException {
        //这里不需要设定端口,让操作系统自动分配
        socket = new DatagramSocket();
        this.severIp = severIP;
        this.severPort = severPort;
    }

    public void start() throws IOException {
        System.out.println("客户端启动");
        Scanner scanner = new Scanner(System.in);
        //这里不只是一个客户端访问服务器
        while(true){
            //1. 从控制台读取要发送的数据
            //打印提示符
            System.out.println("> ");
            String request = scanner.next();
            if(request.equals("exit")){
                System.out.println("bye");
                break;
            }
            //2. 构造成 UDP 请求,并发送
            //   构造这个 Packet 的时候,需要将 severIP 和 port 传入。但是此处的 IP 需要的是一个 32 位的整数形式
            //   上述 IP 地址是一个字符串,需要 InetAddress.getByName 来进行转换
            DatagramPacket requestPacket = new DatagramPacket(request.getBytes(),request.getBytes().length,
                    InetAddress.getByName(severIp),severPort);
            socket.send(requestPacket);
            //3. 读取服务器的 UDP 响应,并解析
            DatagramPacket responsePacket = new DatagramPacket(new byte[4096],4096);
            socket.receive(responsePacket);
            // 将返回回来的信息构造为 String 字符串
            String response = new String(responsePacket.getData(),0,requestPacket.getLength());
            //4. 将解析好的结果显示出来
            System.out.println(response);
        }
    }

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

四、总结与代码运行结果解释

  1. 首先解释运行结果。
  • 启动客户端 / 服务器
  • 运行示例

  1. 总结
    简单说明客户端和服务器之间的相互交流。
  • 说明服务器情况

  • 说明客户端情况

有关 UDP 的使用以及相关工作逻辑到此已经基本解释完毕,文笔浅薄,如有不足之处欢迎指出。

码子不易,您小小的点赞是对我最大的鼓励!!!

相关推荐
网络安全-杰克1 小时前
《网络对抗》—— Web基础
前端·网络
李老头探索2 小时前
TCP-UDP调试工具推荐:Socket通信测试教程(附详细图解)
网络协议·tcp/ip·udp
几维安全2 小时前
出海隐私合规解决方案,一文助力中企合规出海
网络·安全
红米饭配南瓜汤2 小时前
WebRTC服务质量(10)- Pacer机制(02) RoundRobinPacketQueue
网络·音视频·webrtc·媒体
老鑫安全培训3 小时前
从安全角度看 SEH 和 VEH
java·网络·安全·网络安全·系统安全·安全威胁分析
网络安全成叔3 小时前
【网络分析工具】WireShark的使用(超详细)
网络·计算机网络·计算机·wireshark·php
hao_wujing3 小时前
互联网路由架构
网络
忆源3 小时前
工作编码案例--UDP多播 和 本地套接字bind
网络·网络协议·udp
LKID体4 小时前
pathlib:面向对象的文件系统路径
linux·网络·windows
yqcoder4 小时前
HTTP 协议规定的协议头和请求头
网络·网络协议·http