【JavaEE初阶 — 网络编程】Socket 套接字 & UDP数据报套接字编程

news2024/11/28 5:52:02

      c96f743646e841f8bb30b2d242197f2f.gif

ddb5ae16fc92401ea95b48766cb03d96.jpeg692a78aa0ec843629a817408c97a8b84.gif


    1. Socket套接字    


    1.1 概念    


Socket 套接字,是由系统提供用于网络通信的技术,是基于TCP / IP协议的网络通信的基本操作单元。基于 Socket 套接字的网络程序开发就是网络编程。


    1.2 分类     


Socket套接字主要针对传输层协议划分为如下三类:


  • 对于字节流来说,可以简单的理解为,传输数据是基于IO流,流式数据的特征就是在IO流没有关闭的情况下,是无边界的数据,可以多次发送,也可以分开多次接收。 

  • 对于数据报来说,可以简单的理解为,传输数据是一块一块的,发送一块数据假如100个字节,必须一次发送,接收也必须一次接收100个字节,而不能分100次,每次接收1个字节。

     流套接字和数据报套接字的特点     

流套接字数据报套接字

使⽤传输层TCP协议

使用传输层UDP协议

有连接

无连接

可靠传输不可靠传输
面向字节流面向数据报
有接收缓冲区,也有发送缓冲区

有接收缓冲区,无发送缓冲区

大小不限大小受限:一次最多传输64k

    1.3 套接字通信模型     


    1.3.1 Java数据报套接字通信模型    


  • 对于 UDP 协议来说,具有无连接,面向数据报的特征,即每次都是没有建立连接,并且一次发送全部数据报,一次接收全部的数据报。

  • Java中使用 UDP 协议通信,主要基于 DatagramSocket 类来创建数据报套接字,并使用DatagramPacket 作为发送或接收的 UDP 数据报。

     对于一次发送及接收UDP数据报的流程如下    

以上只是一次发送端的UDP数据报发送,及接收端的数据报接收,并没有返回的数据。也就是只有请求,没有响应。


    对于一个服务端来说,重要的是提供多个客户端的请求处理及响应,流程如下    


    1.3.2 Java流套接字通信模型     



    1.4 Socket编程注意事项     



     2. UDP数据报套接字编程    


    2.1 API 介绍    


    2.1.1  DatagramSocket    


    概念    


计算机中的文件,通常是一个广义的概念,文件IO特指的是硬盘上的文件,是狭义的文件,除此之外,文件还可以代指一些硬件设备;


Socket 在计算机编程中,也可以认为是一种特殊的文件,打开 Socket 文件,也会在文件描述表中分配一个表项,来表示这个文件;

这样的文件特指网卡这样的硬件设备

对于网卡这样的硬件设备,在操作系统中就被抽象成 Socket 文件;这样的设定,主要是为了方便操作网卡;


直接操作网卡,需要往网卡的寄存器上写一些特定的数据,不好操作;操作系统管理一些硬件设备,是抽象成文件统一管理的;把操作网卡,转化成操作Socket文件,此时 Socket 文件,就相当于网卡的 “遥控器" ;


所以 DategramSocket ,就是一个用来表示网卡的文件,通过 DategramSocket 来操作网卡,只是加了一个 Dategram 前缀,意思就是基于UDP协议进行网络通信

DatagramSocket 是UDP Socket,用于发送和接收UDP数据报。 


    构造方法    


方法签名
 
方法说明
 
  DatagramSocket()  
 
创建一个UDP数据报套接字的Socket,绑定到本机任意一个随机端口(一般用于客户端)
 
  DatagramSocket(int port)  
 
创建一个UDP数据报套接字的Socket,绑定到本机指定的端口(一般用于服务端)

    方法     


方法签名
 
方法说明
 
void receive(DatagramPacket p)
 

读操作,从此套接字接收数据报

(如果没有接收到数据报,该方法会阻塞等待)

void send(DatagramPacket p)
 

写操作,从此套接字发送数据报包

(不会阻塞等待,直接发送)

void close()
 
关闭此数据报套接字

DatagramSocket 类的 receive() & sand() 的参数类型,都是 DatagramPacket ;

一个UDP数据包,就通过 DatagramPacket 对象来进行体现的,在进行receive() 或者 send(),都是按照 DatagramPacket 这样的数据包为定位进行接收的;


    2.1.2 DatagramPacket    


    定义    


DatagramPacket 是UDP Socket发送和接收的数据报。 


    构造方法    


 UDP 数据包的载荷数据,可以通过构造方法来指定

方法签名
 
方法说明
 
  DatagramPacket(byte[] buf, int length)  
 
  • 构造一个 DatagramPacket 用来接收数据报;
  • 接收的数据保存在字节数组(参数buf)中;
  • 接收的指定长度(第二个参数length);

  DatagramPacket  

  (byte[] buf, int offset,int  length,       SocketAddress address)  

  • 构造一个 DatagramPacket 用来发送数据报;
  • 发送的数据为字节数组(参数buf)中;
  • 从0到指定长度(第二个参数length);
  • address指定目的主机的IP和端口号

  DatagramPacket  

  (byte[] buf, int offset,int  length, 

 InetAddress address, int port)  

  • 构造一个 DatagramPacket 用来发送数据报;
  • 发送的数据为字节数组(参数buf)中;
  • 从0到指定长度(第二个参数length);
  • address,port 分别指定目的主机的IP和端口号

    方法    


方法签名
 
方法说明
 
 InetAddress getAddress() 
 
从接收的数据报中,获取发送端主机IP地址;或从发送的数据报中,获取接收端主机IP地址
 
 int getPort() 
 
从接收的数据报中,获取发送端主机的端口号;或从发送的数据报中,获取接收端主机端口号
 
 byte[] getData() 
 
获取数据报中的数据

构造UDP发送的数据报时,需要传入SocketAddress,该对象可以使用InetSocketAddress 来创建; 


    2.1.3  InetSocketAddress ( SocketAddress的子类 )     


    构造方法    


方法签名
 
方法说明
 
  InetSocketAddress(lnetAddress addr,int port)  
 
创建一个Socket地址,包含IP地址和端口号

    2.2 实现回显服务器      


     2.2.1  实现原理    


  • 客户端给服务器发一个数据的操作,称为请求;
  • 服务器返回一个数据的操作,称为响应

  • 一个真实的服务器,请求和响应一般是不一样的,但是为了展示上述 API 的用法,就先不去管服务器中其他复杂的逻辑,写一个最简单的回显服务器(请求是什么,响应就是什么)

     2.2.2  代码实现    


    UDP Echo Server    

    (1) 构造一个 socket 对象代表网卡文件    


  • 输出 socket 文件内容,等于从网卡中读取数据;
  • 输入 socket 文件内容,等于向网卡内发送数据;


    (2) 实现启动服务器的 start()     


对于服务器来说,客户端啥时候发请求,发多少个请求,我们无法预测;
因此服务器中通常都需要有一个死循环,持续不断的尝试读取客户端的请求数据~~ 

在主循环中,需要实现的逻辑:

  1. 读取请求并解析;
  2. 根据请求,计算响应。(服务器最关键的逻辑),但是此处写的是回显服务器。这个环节相当于省略了;
  3. 把响应返回给客户端;

循环每执行一次,就相当于处理了一次请求,处理请求的过程,典型的服务器也是上面这三个步骤


    (3) 读取请求    


  • 服务器调用 receive() 对客户端发送的请求进行读取,等 receive() 方法执行完毕,参数里面对象的数据就是读取的结果:


      创建一个 DatagramPacket 对象,用于接收请求     


  • 创建 DatagramSocket 对象的时候,需要指定一个字节数组,并且传入接收的指定长度:

  • DatagramPacket 表示个UDP数据报。此处传入的字节数组,就保存 UDP的载荷部分;

     读取网卡中的数据包     


  • 把 DatagramPacket 对象(对象为全0)传给 receive();
     
  •  receive() 就会在方法内部把从响应数据报中读到的数据,填充到 requestPacket 这个引用指向的对象中;
  • receive() 执行完毕,参数里面的对象数据,就是需要从网卡中读取的数据(请求)

  • 这个过程就相当于我们在食堂打饭,把空的盘子(requestPacket )交给打饭阿姨( receive() ),阿姨会把打好饭(读到的数据)的盘子还给我们;

     (4) 解析请求    


  • 当前 UDP 载荷,是 DatagramSocket 对象的字节数组,存放着读取到的数据;
  • 这些读取到的数据是二进制数据,为了方便后续处理,我们把读取到的二进制数据转换成字符串形式;


  • 通过字节数组构造一个 String 对象,是构造 String 对象的一个典型做法;


  • 上述操作,表示拿到了一个数据报中的字节数组,把整个字节数组传给String对象,并且指定字节数组有效部分的范围,调用相应的构造方法,构成一个字符串;


    (5) 根据请求计算响应     


  • 这是服务器最关键的逻辑,但是此处写的是回显服务器,这个环节就相当于省略了;

  • 根据解析数据报,得到的请求 request,计算出响应 response 的操作,封装成一个方法,可以起到解耦合的作用;
  • 后续要写别的服务器,只需要修改 process() 的内容即可 ;

    (6) 把计算好的响应返回给客户端    


  • 如何根据响应 response 构造 DatagramSocket 对象呢?


  • 首先,需要拿出对响应数据报进行解析操作时,创建的字符串 request 里面的字节数组:


  • 要传入字节数组的长度,而不是使用字符串的长度,因为字符串的单位是字符,而我们要使用字节的个数,来作为当前 responsePacket 数据包的参数


   (6) 指定目的IP&目的端口     


  • 发送的响应数据报 responsePacket 是没有明确标注有发送的目的IP&目的端口的,要想正确地返回响应,就必须给响应数据报显式地标注目的IP & 目的端口;
  •  对于服务器返回响应的目的IP&目的端口,就是接到客户端请求的源IP&源端口:


  •  所以,通过调用 DatagramSocket 类中的 getSocketAddress(),该方法返回一个 InerSocketAddress 对象;


  • 这个对象包含了目标IP&目标端口号都在报头中,而不是不是在载荷中
  • 将这个对象作为参数,传给DatagramPacket对象,调用对应的构造方法;


  • 所以,将 getScketAddress() 方法返回的对象,作为参数来调用对应的构造方法,实例出的 responsePacket,就会显式地标注响应数据报的目标IP&目标端口:


     (7) 发送响应给客户端       

  • 服务器需要调用 send() 方法,把创建好的响应数据报作为响应,返回给客户端:

  •  send() 的构造方法

  •  所以,我们把刚刚构建好的响应数据报返回给客户端:

    (8) 打印日志来记录客户端/服务器交互的过程    



     (9) 判断当前 socket 对象(文件)是否需要关闭     


  • 文件是否需要关闭,考虑的是这个文件对象的生命周期是怎样的,此处的 socket 对象会自始至终伴随整个UDP服务器;
  • 只要服务器运行,就随时可能会从客户端中读数据,如果提前关闭 socket对象,那么UDP服务器继续运行也没有意义,所以socket对象,不能在服务器运行的过程中关闭;
  • 服务器关闭(进程结束),也不需要手动调用close(),因为进程结束时就会自动释放 PCB 的文件描述符表中的所有资源,
  • 所以当前socket文件不手动调用 close(),也是完全没问题的,因为socket的生命周期本来就需要跟随整个进程的;
  • 如果是有请求级别的文件对象,给一个请求,创建一个对象,就需要确保处理完毕之前,关闭对象。
  • 所以需要结合实际情况来确认一个对象的生命周期,通过生命周期,来决定对象是否应该关闭;

    (10) 补充    


  • DatagramPacket这个类说是一个UDP数据报,其实也包含了一些源信息;
  • 这个类有接收IP&端口号的属性,在通过 receive() 填充好DatagramPacket对象后,可以直接从对象中取出 UDP 数据包的来源(源IP和源端口)

  • 所以在将DatagramPacket这个UDP数据包,作为 receive() 方法的输入型参数时,不只是把UDP中的数据读进去了,还把IP&端口号等信息也读进去了;

  • 拿到请求数据包的源IP和源端口,就可以作为参数传给DatagramPacket对象,调用相应的构造方法;
  • UDP协议虽然没有保存发送请求数据包的地址信息,但是通过调用的构造方法,根据获取到的源IP和源端口,可以显式地指定响应数据包要发送的目的IP和目的端口 ;

    UDP Echo Client    

在客户端构造方法中传入的参数,就不能只有一个端口号了,要指定的是,当前客户端要连哪一个服务器,所以构造方法传要访问的服务器IP和端口号


UDP.本身不保存对端的信息,所以我们在代码中保存一下 



对比服务器调用DatagramSocket的构造方法,服务器调用的是不传端口号的构造方法:

因为客户端访问服务器,访问的目的 IP&目的端口,都是都是服务器的源IP&源端口;
服务器返回响应给客户端,目的IP 是客户端所在的主机 IP;目的端口,则随机使用一个操作系统分配的空闲端口;


   为什么客户端不推荐使用固定端口,而服务器推荐呢?  

     以餐饮店的老板和顾客交互的情景为例子:   


     对于老板(Server)    


  • 老板在选好一个地方后,就需要派发传单来宣传店铺,传单上有着餐饮店详细的地址(服务器源IP和源端口号)和菜谱(响应);
  • 对于这个餐饮店的地址(服务器IP&端口号),是必须要指定一个地方的(调用指定端口号的 DatagramSocket 的构造方法);
  • 如果餐厅地址不固定,就会影响顾客正常来店铺吃饭(客户端对服务器发出请求);


     对于顾客(Client)   


  • 当顾客光顾餐饮店的时候(客户端对服务器发出请求),会对老板点菜(服务器对客户端发送的请求作出响应);
  • 点菜后,需要找一个位置(服务器作出响应的目标IP&端口号)坐下,等待老板做菜(解析请求,计算响应);
  • 顾客在等待的过程中,会找一个空闲的位置坐下,这个位置是随机的,而不是指定的;(调用不指定端口号的构造方法,服务器返回响应时,随机使用一个操作系统分配的空闲端口;
  • 如果顾客要指定一个位置等待,就可能需要等待这个位置的另一位顾客用餐(不同应用程序竞争同一个端口号)


     总结:    


  • 为了能更好地接收客户端请求,需要指定服务器的IP和端口号;
  • 对于服务器,端口号是区分同一个主机不同的应用程序的同一时刻,不能有两个程序使用同一个端口(操作系统中,一个程序尝试关联一个非空闲的端口,就会关联失败)

实现 start() 方法


从控制台读取用户输入的内容


把请求发送给服务器,需要构造DatagramPacket对象,构造过程中,不光要构造载荷,还要设置服务器的IP和端口号 

需要把serverIp转换一下,通过InetAddress类提供的静态方法getByName,通过这样的方法,把字符串中的serverIp,转换成InetAddress对象


发送数据报请求给服务器



接收服务器响应


从服务器读取的数据( respose 中的二进制内容)进行读取解析,将读取到的数据转换成一个字符串,并且打印出来该字符串;


嵌套循环并设置循环结束条件


    Server & client 的交互过程    


    补充    


  • 127.0.0.1 是一个特殊的IP,是一个环回IP,表示当前主机;无论主机的 IP真实是什么,都可以使用127.0.0.1代替(类似于this);
  • 由于此时,客户端和服务器在同一个主机上,就可以使用127.0.0.1来访问,如果是不同主机就需要填写其他的IP了.

同时启动这两个程序的执行结果:


    UDP Dict Server    

编写⼀个英译汉的服务器,只需要继承 UdpEchoServer ,重写 process() 即可;



    字典服务器和客户端交互过程   


    同时启动进程,查看程序执行结果    


 这个报错说明当前端口被占用了,换一个端口即可;


    2.2.3  完整代码    


    UDP Echo Server     

package network;

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

public class UdpEchoServer {

    private DatagramSocket socket ;

    public UdpEchoServer(int port) throws SocketException {

        socket = new DatagramSocket(port);
    }

    public void start() throws IOException {
        System.out.println("启动服务器");

        while (true){

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

            socket.receive(requestPacket);

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

            String response = process(request);



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

            socket.send(responsePacket);

            System.out.printf("[%s:%d] rep: %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(9092);
        server.start();
    }
}

    UDP Echo Client     

package network;

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

public class UdpEchoClient {
    private DatagramSocket socket = null;

    private String serverIp;

    private int serverPort;

    public UdpEchoClient(String serverIp, int serverPort) throws SocketException {
        this.serverIp = serverIp;
        this.serverPort = serverPort;

        socket = new DatagramSocket();
    }

    public void start() throws IOException {

        Scanner scanner = new Scanner(System.in);

        while (true){
            System.out.println("请输入要发送的内容");

            if(!scanner.hasNext()){
                break;
            }
            String request = scanner.nextLine();

            DatagramPacket requestPacket = new DatagramPacket(request.getBytes(),request.getBytes().length,
                    InetAddress.getByName(serverIp) , serverPort);

            socket.send(requestPacket);

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

            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", 9092);
        client.start();
    }
}

    UDP Dict Server     

package network;

import java.io.IOException;
import java.net.SocketException;
import java.util.HashMap;

public class UdpDictServer extends UdpEchoServer{

    private HashMap<String,String> dict = new HashMap<>();

    public UdpDictServer(int port) throws SocketException {
        super(port);

        //初始化词典
        dict.put("小狗","dog");
        dict.put("小猫","cat");
        dict.put("小鸭","rabbit");
        dict.put("小雷","handsome guy");

    }
    @Override
    public String process(String request){
        //查字典
        return dict.getOrDefault(request,"未找到该词条");
    }

    public static void main(String[] args) throws IOException {
        UdpDictServer server = new UdpDictServer(9092);
        server.start();
    }
}

        c96f743646e841f8bb30b2d242197f2f.gif

692a78aa0ec843629a817408c97a8b84.gif

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2248857.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

熔断限流:业务实现自我保护

服务端-限流 服务端主要是通过限流来进行自我保护&#xff0c;实现限流时要考虑到应用和IP级别&#xff0c;方便在服务治理的时候&#xff0c;对部分访问量特别大的应用进行合理的限流&#xff1b;服务端的限流阈值配置都是作用于单机的&#xff0c;而在有些场景下&#xff0c…

linux系统误操作,设置nofile值超过限制,导致无法登录,permission denied

1.问题描述&#xff08;虚拟机复现&#xff09; 在k8s集群运行某些服务时&#xff0c;对文件描述符要求比较大&#xff0c;在提高这个值前未查询这个值的限制&#xff0c;最后设置了一个超过限制的值导致登录被拒绝 [roottest4 ~]# tail -3 /etc/security/limits.conf * sof…

从零开始配置Qt+VsCode环境

从零开始配置QtVsCode环境 文章目录 从零开始配置QtVsCode环境写在前面扩展安装及配置Qt Configure配置 VsCode创建Qt工程VsCodeQMakeMinGwVsCodeQMakeMsvcVsCodeCMakeMinGwVsCodeCMakeMsvcQtCreatorQMakeMinGw->VsCodeQtCreatorQMakeMsvc->VsCodeQtCreatorCMakeMinGw-&g…

如何借助AI生成PPT,让创作轻松又高效

PPT是现代职场中不可或缺的表达工具&#xff0c;但同时也可能是令人抓狂的时间杀手。几页幻灯片的制作&#xff0c;常常需要花费数小时调整字体、配色与排版。AI的飞速发展为我们带来了革新——AI生成PPT的技术不仅让制作流程大大简化&#xff0c;还重新定义了效率与创意的关系…

【Linux】Make/Makefile

这个3/4行的语法和1/2行是一样的。也是依赖关系和依赖方法。 make命令扫描makefile文件时&#xff0c;从上向下扫描&#xff0c;默认形成一个目标文件。 指定make clean的时候才回去执行对应的清除。 为什么要给我们的clean.PHONY:clean声明它是伪目标呢&#xff1f; PHONY类…

HarmonyOS:@Provide装饰器和@Consume装饰器:与后代组件双向同步

一、前言 Provide和Consume&#xff0c;应用于与后代组件的双向数据同步&#xff0c;应用于状态数据在多个层级之间传递的场景。不同于上文提到的父子组件之间通过命名参数机制传递&#xff0c;Provide和Consume摆脱参数传递机制的束缚&#xff0c;实现跨层级传递。 其中Provi…

如何做好一份技术文档?

打造出色技术文档的艺术 在当今技术驱动的世界中&#xff0c;技术文档扮演着至关重要的角色。它不仅是工程师和开发人员之间交流的桥梁&#xff0c;更是产品和技术成功的隐形推手。一份优秀的技术文档宛如一张精准的航海图&#xff0c;能够引导读者穿越技术的迷雾&#xff0c;…

泰山众筹怎样吸引用户参与

泰山众筹项目要吸引用户参与&#xff0c;需要采取一系列策略来增强项目的吸引力、提高用户信任度&#xff0c;并激发用户的参与热情。以下是一些建议&#xff1a; 1. 明确项目价值与愿景 展示独特性&#xff1a;明确泰山众筹项目的独特卖点&#xff0c;如创新性、社会影响力或…

抓包之验证content-length响应头的作用

写在前面 根据http协议的规范&#xff0c;content-length响应头用来标记固定长度响应信息长度&#xff0c;http客户端&#xff0c;比如浏览器也会解析这个字段来进行数据的解析。 1&#xff1a;测试 1.1&#xff1a;content-length等于实际内容匹配时 使用python脚本testco…

T3 TensorFlow入门实战——天气识别

&#x1f368; 本文為&#x1f517;365天深度學習訓練營 中的學習紀錄博客&#x1f356; 原作者&#xff1a;K同学啊 | 接輔導、項目定制 一、前期准备 1. 导入数据 # Import the required libraries import numpy as np import os,PIL,pathlib import matplotlib.pyplot as …

✨系统设计时应时刻考虑设计模式基础原则

目录 &#x1f4ab;单一职责原则 (Single Responsibility Principle, SRP)&#x1f4ab;开放-封闭原则 (Open-Closed Principle, OCP)&#x1f4ab;依赖倒转原则 (Dependency Inversion Principle, DIP)&#x1f4ab;里氏代换原则 (Liskov Substitution Principle, LSP)&#x…

fatal error in include chain (rtthread.h):rtconfig.h file not found

项目搜索这个文件 rtconfig 找到后将其复制粘贴到 你的目录\Keil\ARM\ARMCC\include 应该还有cJSON&#xff0c;rtthread.h和 等也复制粘贴下

【回文数组——另类递推】

题目 代码 #include <bits/stdc.h> using namespace std; using ll long long; const int N 1e510; int a[N], b[N]; int main() {int n;cin >> n;for(int i 1; i < n; i)cin >> a[i];for(int i 1; i < n / 2; i)b[i] a[i] - a[n1-i];ll ans 0;…

SQL基础入门—— 简单查询与条件筛选

在SQL中&#xff0c;查询是从数据库中获取数据的核心操作&#xff0c;而条件筛选是查询中不可或缺的一部分。通过使用条件筛选&#xff0c;我们可以精准地从大量数据中提取我们需要的信息。本节将详细讲解如何使用SQL进行简单查询与条件筛选&#xff0c;包含常见的条件运算符和…

反向代理模块

1 概念 1.1 反向代理概念 反向代理是指以代理服务器来接收客户端的请求&#xff0c;然后将请求转发给内部网络上的服务器&#xff0c;将从服务器上得到的结果返回给客户端&#xff0c;此时代理服务器对外表现为一个反向代理服务器。 对于客户端来说&#xff0c;反向代理就相当于…

英语知识网站:Spring Boot技术构建

6系统测试 6.1概念和意义 测试的定义&#xff1a;程序测试是为了发现错误而执行程序的过程。测试(Testing)的任务与目的可以描述为&#xff1a; 目的&#xff1a;发现程序的错误&#xff1b; 任务&#xff1a;通过在计算机上执行程序&#xff0c;暴露程序中潜在的错误。 另一个…

TMS FNC UI Pack 5.4.0 for Delphi 12

TMS FNC UI Pack是适用于 Delphi 和 C Builder 的多功能 UI 控件的综合集合&#xff0c;提供跨 VCL、FMX、LCL 和 TMS WEB Core 等平台的强大功能。这个统一的组件集包括基本工具&#xff0c;如网格、规划器、树视图、功能区和丰富的编辑器&#xff0c;确保兼容性和简化的开发。…

【AIGC】国内AI工具复现GPTs效果详解

博客主页&#xff1a; [小ᶻ☡꙳ᵃⁱᵍᶜ꙳] 本文专栏: AIGC | GPTs应用实例 文章目录 &#x1f4af;前言&#x1f4af;本文所要复现的GPTs介绍&#x1f4af;GPTs指令作为提示词在ChatGPT实现类似效果&#x1f4af;国内AI工具复现GPTs效果可能出现的问题解决方法解决后的效…

网络原理(一):应用层自定义协议的信息组织格式 HTTP 前置知识

目录 1. 应用层 2. 自定义协议 2.1 根据需求 > 明确传输信息 2.2 约定好信息组织的格式 2.2.1 行文本 2.2.2 xml 2.2.3 json 2.2.4 protobuf 3. HTTP 协议 3.1 特点 4. 抓包工具 1. 应用层 在前面的博客中, 我们了解了 TCP/IP 五层协议模型: 应用层传输层网络层…

【es6】原生js在页面上画矩形及删除的实现方法

画一个矩形&#xff0c;可以选中高亮&#xff0c;删除自己效果的实现&#xff0c;后期会丰富下细节&#xff0c;拖动及拖动调整矩形大小 实现效果 代码实现 class Draw {constructor() {this.x 0this.y 0this.disX 0this.disY 0this.startX 0this.startY 0this.mouseDo…