【网络编程二】UDP与TCP协议你学会了吗~

news2024/11/25 3:00:24

目录

🌟需要知道

1、什么是网络编程?

2、怎么进行网络编程? 

3、TCP与UDP的区别? (面试题)

🌟一、UDP

🌈1、UDP数据报套接字编程

🌈2、实现一个简单的UDP回显服务器与客户端

🌟二、TCP

🌈1、TCP流套接字编程

🌈2、实现一个简单的TCP回显服务器与客户端


🌟需要知道

1、什么是网络编程?

        网络编程,指的是网络上的主机通过不同的进程以程序的方式来实现网络通信(网络数据传输)。也可以是同一个主机的不同进程。比如MySQL的服务端和客户端,在开发环境中一般都是同一台主机上运行的两个不同的程序。

        (1)客户端:服务的使用方-->请求:一般是客户端主动发起,表示目的;

        (2)服务器:服务的提供方-->响应:一般是服务器根据客户端的请求计算出来的结果。

2、怎么进行网络编程? 

        针对网络编程,操作系统提供了用于网络编程的技术,称为Socket套接字,是系统提供的专门用于实现网络编程的一套API。

        应用程序在应用层,操作系统工作在传输层,Socket套接字就是在传输层对应用层提供的API支持。(Java对每种操作系统都做了进一步的封装,JDK中提供的API是我们学习的目的)。传输层最知名的协议就是TCP和UDP。

3、TCP与UDP的区别? (面试题)

面试题1:TCP与UDP的区别?

TCP 流套接字UDP 数据报套接字
TCP即Transmission Control  Protocol传输控制协议,传输层协议。UDP即User Datagram Protocol用户数据报协议。传输层协议
有连接无连接
可靠传输不可靠传输
面向字节流面向数据报
全双工(有接收缓冲区,也有发送缓冲区)全双工(有接收缓冲区,也有发送缓冲区)
大小不限大小受限,一次最多传输64K
传输数据是基于IO流,没有边界多次发送,多次接收传输数据是一个整体,不能分开发送。

                      

举个栗子🌰

(1)有无连接:

        TCP相当于打电话:接收方必须要接通电话之后双方才可以通信;

        UDP相当于发短信:不需要对方开机都可以进行;

(2)可靠传输:

        如果数据包在传输过程中丢了,那么TCP会有重传机制,UDP则是丢了就丢了。

(3)面向字节流:

        TCP打电话的时候是说一个字对方听见一个字;

        UDP发短信是发一条条信息,是一整段的,对方收到一整条信息之后才可以阅读;

(4)全双工(有发送缓冲区也有接收缓冲区):

        可以打电话也可以接电话,可以发短信也可以收短信。

(5)大小不限:

        打电话的时候时间不受限制;

        发短信的时候 字数有限制。                                                                                              


🌟一、UDP

🌈1、UDP数据报套接字编程

🍅DatagramSocket API:DatagramSocket 是UDP Socket,用于发送和接收UDP数据报。
(1)DatagramSocket 构造方法

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

(2)DatagramSocket 普通方法

方法名说明
void receive(DatagramPacket p)从此套接字接收数据报(如果没有接收到数据报,该方法会阻塞等待)
void send(DatagramPacket p)从此套接字发送数据报包(不会阻塞等待,直接发送)
void close()关闭此数据报套接字

🍅DatagramPacket API:DatagramPacket 是UPD Socket接收和发送的数据报。存放消息的具体内容。

(1)DatagramPacket 构造方法

方法名说明
DatagramPacket(byte[] buf,int length)构造一个DatagramPacket用来接收数据报,接收的数保存在字节数组(第一个参数buf中),接收指定长度(第二个参数length中)
DatagramPacket(byte[] buf,int offset,int length,Socket Address address)                                           构造一个DatagramPacket用来发送数据报,发送的数据为字节数组(第一个参数buf中),从0到指定长度(第二个参数是Length)。address指定目的主机的IP和端口号

(2)DatagramPacket方法

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

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

🍅InetSocketAddress API

InetSocketAddress ( SocketAddress 的子类 )构造方法:

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

🌈2、实现一个简单的UDP回显服务器与客户端

回显:指的就是没有特殊的业务,客户端发送什么服务器就返回什么。

(1)客户端实现:

public class UDPEchoClient {
    //定义一个用于客户端的DatagramSocket
    private DatagramSocket client;
    //定义服务器的端口号
    private String serverIP;
    //定义服务器的IP地址
    private int port;
    // 定义SocketAddress地址
    private SocketAddress address;

    /**
     * 构造方法:指定服务器的IP和端口号
     * @param serverIP
     * @param port
     */
    public UDPEchoClient(String serverIP,int port) throws SocketException {
        this.client = new DatagramSocket();
        this.serverIP = serverIP;
        this.port = port;
        this.address = new InetSocketAddress(serverIP,port);
    }
    public void clientStart() throws IOException {
        System.out.println("客户端已经启动");
        //循环接收用户输入
        Scanner sc = new Scanner(System.in);
        while (true){
            System.out.println("->");
            /**
             * 获取用户输入的内容并发送给服务器;然后获取服务器返回的内容并解析
             */
            String userRequest = sc.nextLine();
            //1、将用户输入的请求包装成DatagramPacket
            DatagramPacket userRequestPacket = new DatagramPacket(userRequest.getBytes(StandardCharsets.UTF_8),
                    userRequest.getBytes().length,
                    address);
            //2、发送数据
            client.send(userRequestPacket);
            //3、接收响应
            DatagramPacket respondPacket = new DatagramPacket(new byte[1024],1024);
            //4、在receive方法中填充响应数据
            client.receive(respondPacket);
            //5、解析响应数据
            String respond = new String(respondPacket.getData(),0,respondPacket.getLength(),"UTF-8");
            //打印日志
            System.out.printf("request: %s, response: %s.\n", userRequest, respond);
        }
    }

    public static void main(String[] args) throws IOException {
        UDPEchoClient client = new UDPEchoClient("127.0.0.1",9999);
        //启动客户端
        client.clientStart();
    }
}

(2)服务端实现版本1

实现功能:客户端输入一个值,服务器接收并将该值原封不动的返回。

public class UDPEchoServer {
    //定义一个用于服务器端的DatagramSocket
    private DatagramSocket server;
    /**
     * 构造方法:完成服务器的初始化
     * @param port
     */
    public UDPEchoServer(int port) throws Exception {
        //1、如果端口不合法,则提示错误
        if(port < 1024 || port>65535){
            throw new Exception("端口号必须在1024-65535之间");
        }
        //2、初始化服务器端的UDP服务
        this.server = new DatagramSocket(port);
    }

    /**
     * 服务器接收发来的消息
     */
    public void serverStart() throws IOException {
        System.out.println("服务器已经启动");
        //循环接收用户请求
        while (true){
            /**
             * 接收客户端发来的数据:创建DatagramPacket里面用来存放的接收来的消息体-> receive接收DatagramPacket ->解析数据
             */
            //1、创建一个用于接收请求数据的DatagramPacket
            DatagramPacket requestPacket = new DatagramPacket(new byte[1024], 1024);
            //2、接收请求,将真实的内容填充到requestPacket
            server.receive(requestPacket);
            //3、从requestPacket中获取数据
            String request = new String(requestPacket.getData(),0,requestPacket.getLength(),"UTF-8");
            //4、根据获取到的请求发出响应
            String respond = processor(request);
            /**
             * 发送响应:也要进行数据的封装
             */
            //5、将发出的响应也封装到DatagramPacket中:requestPacket.getSocketAddress()是从请求中获取到的IP地址
            DatagramPacket respondPacket = new DatagramPacket(respond.getBytes(StandardCharsets.UTF_8),respond.getBytes().length,requestPacket.getSocketAddress());
            //6、发送数据
            server.send(respondPacket);
            //打印日志
            System.out.printf("[%s:%d] request: %s,respond: %s.\n",
                    requestPacket.getAddress().toString(),
                    requestPacket.getPort(),
                    request,
                    respond);
        }
    }
    /**
     * 服务器对发来的消息做出反应
     * @param request
     * @return
     */
    public String processor(String request) {
        return request;
    }

    public static void main(String[] args) throws Exception {
        UDPEchoServer server = new UDPEchoServer(9999);
        server.serverStart();
    }
}

(2)服务端实现版本2:字典服务器

        实现功能:客户端输入一个值,如果map集合中存在该值,则服务器返回该值对应的value值;如果不存在该值,服务器返回“查无此值”。

public class UDPDictServer extends UDPEchoServer {
    //定义一个map集合
    private Map<String,String> map = new HashMap<>();
    /**
     * 构造方法:完成服务器的初始化
     * @param port
     */
    public UDPDictServer(int port) throws Exception {
        super(port);
        //初始化字典内容
        map.put("dog","小狗");
        map.put("cat","小猫");
        map.put("pig","小猪");
        map.put("rabbit","兔纸");
        map.put("tiger","小脑斧");
    }

    /**
     * 重写 处理请求的方法:如果不存在key值,就返回默认值
     * @param request
     * @return
     */
    @Override
    public String processor(String request) {
        return map.getOrDefault(request,"查无此词");
    }

    public static void main(String[] args) throws Exception {
        UDPDictServer server = new UDPDictServer(9999);
        server.serverStart();
    }
}

问题:如果需要允许多个客户端同时运行:

结果:

 

 


🌟二、TCP

🌈1、TCP流套接字编程

🍅ServerSocket API 是创建TCP服务端Socket的API。

(1)ServerSocket 构造方法

方法签名方法说明
ServerSocket(int port)创建一个服务端流套接字Socket,并制定到指定端口

(2)ServerSocket 方法

方法签名方法说明
Socket accept()开始监听指定端口(创建时绑定的端口),有客户端连接后,返回一个服务端Socket对象,并基于该Scoket建立与客户端的连接,否则阻塞等待。(网络通讯过程中对数据进行了封装,InputStream输入流从网卡缓冲区读取数据,OutputStream输出流,将数据写到网卡缓冲区,相当于发送数据。)
void close()关闭此套接字

🍅Socket API: Socket 是客户端Socket,或服务端中接收到客户端建立连接(accept方法)的请求后,返回的服务端Socket。
        不管是客户端还是服务端Socket,都是双方建立连接以后,保存的对端信息,及用来与对方收发数据的。

(1)Socket 构造方法

方法签名方法说明
Socket(String host, int port)创建一个客户端流套接字Socket,并与对应IP的主机上,对应端口的进程建立连接(通过IP和端口可以确定网络上的主机,进程)

(2)Socket 方法

方法签名方法说明
InetAddress getInetAddress()返回套接字所连接的地址
InputStream getInputStream()返回此套接字的输入流
OutputStream getOutputStream()                返回此套接字的输出流

🌈2、实现一个简单的TCP回显服务器与客户端

(1)服务器端代码

public class TCPEchoServer {
    //声明一个用于服务端的Socket对象
    private ServerSocket server;

    /**
     * 通过指定端口号来实例化服务器
     * @param port 端口号
     * @throws IOException
     */
    public TCPEchoServer(int port) throws IOException {
        //端口的合法性校验
        if(port > 65535 || port < 1024){
            throw new RuntimeException("端口号要在1024-65535之间");
        }
        //实例化ServerSocket并制定端口
        this.server = new ServerSocket(port);
    }

    /**
     * 服务器开始工作
     * @throws IOException
     */
    public void start() throws IOException {
        System.out.println("服务器启动成功");
        //1、循环接收客户端的输入
        while (true){
            //accept():如果有客户端连接就返回一个Socket对象,没有的话就阻塞等待
            Socket clientSocket = server.accept();
            //2、服务器对接收到的客户端数据进行处理
            processConnections(clientSocket);
        }
    }

    /**
     * 服务器对接收到的客户端数据进行处理
     * @param clientSocket 客户端数据
     */
    private void processConnections(Socket clientSocket) throws IOException {
        //打印日志:也可以使用printf方式打印
        String clientInfo = MessageFormat.format("[{0}:{1}] 客户端已经上线",clientSocket.getInetAddress(),clientSocket.getPort());
        System.out.println(clientInfo);
        //1、处理数据之前要先获取一下输入输出流
        try(InputStream inputStream = clientSocket.getInputStream();
            OutputStream outputStream = clientSocket.getOutputStream()) {
            //2、循环处理用户的请求
            Scanner requestScanner = new Scanner(inputStream);
            while (true){
                //3、如果没有获取到用户输入:就说明客户端已经下线,关闭流并打印日志
                if(!requestScanner.hasNext()){
                    clientInfo = MessageFormat.format("[{0}:{1}] 客户端已经下线.",clientSocket.getInetAddress(),
                            clientSocket.getPort());
                    System.out.println(clientInfo);
                    break;
                }
                //4、获取真实的用户请求数据
                String request = requestScanner.nextLine();
                //4-1根据请求计算响应
                String respond = process(request);
                //5、将响应写回客户端(参考文件操作与IO一节:PrintWriter的使用)
                PrintWriter printWriter = new PrintWriter(outputStream);
                //5-1 写入输出流
                printWriter.println(respond);
                //5-2 强制刷新缓冲区
                printWriter.flush();
                //6、打印日志
                clientInfo = MessageFormat.format("[{0}:{1}],request:{2},response:{3}",
                        clientSocket.getInetAddress(),
                        clientSocket.getPort(),
                        request,
                        respond);
                System.out.println(clientInfo);
            }

        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            clientSocket.close();
        }
    }

    /**
     * 接收客户端请求并返回:回显
     * @param request 客户端用户的请求
     * @return
     */
    private String process(String request) {
        return request;
        //改进为一个简单的消息回复
//        System.out.println("收到新消息");
//        Scanner sc = new Scanner(System.in);
//        String respond = sc.nextLine();
//        return respond;
    }

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

(2)客户端代码 

public class TCPEchoClient {
    //定义一个用于客户端的Socket对象
    private Socket clientSocket;

    /**
     * 初始化客户端Socket
     * @param serverIP 服务器IP地址
     * @param serverPort 服务器端口号
     * @throws IOException
     */
    public TCPEchoClient(String serverIP,int serverPort) throws IOException {
        this.clientSocket = new Socket(serverIP,serverPort);
    }

    /**
     * 启动客户端
     */
    public void start() throws IOException {
        System.out.println("客户端已经启动");
        //inputStream 读取数据,outputStream写入数据
        try(InputStream inputStream = clientSocket.getInputStream();
            OutputStream outputStream = clientSocket.getOutputStream()) {
           //循环处理用户的输入
            while (true){
                System.out.println("->");
                //1、接收用户的输入内容
                Scanner sc = new Scanner(System.in);
                String request = sc.nextLine();
                //2、发送用户请求
                PrintWriter printWriter = new PrintWriter(outputStream);
                printWriter.println(request);
                //3、强制刷新缓冲区
                printWriter.flush();
                //4、接收服务器的响应
                Scanner respondScanner = new Scanner(inputStream);
                //5、获取响应数据
                String respond = respondScanner.nextLine();
                //打印响应数据
                System.out.println("接收到服务器的响应:"+respond);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //关闭
            clientSocket.close();
        }
    }

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

 

问题:当打开多个客户端的时候,发现多个服务器并不能同时处理多个请求。

 分析原因:

 解决办法1:可以手动断开让客户端1。此时客户端2发送数据就有响应了。但是前提还是只能执行一个客户端。

解决办法2:实现多个客户端并行运行——>多线程实现。

        为了实现服务器可以处理多个客户端连接,那么可以为每一个客户端创建一个新的线程,那么请求的处理单独在子线程中取执行。 

改动:

 

实验​​​​结果:

 但是上述创建线程的方式也存在问题:目前来说每接收一个客户端请求就会创建一个新的线程,如果有1w个连接就要创建1w个线程,非常消耗系统资源。C10k现象(非常消耗系统资源导致系统崩溃)。

解决方式3:因此优化频繁创建线程的方式:使用线程池。

 演示:

 

        如果启动多个实例,就会抛出端口已经被占用的异常。因为系统通过某个端口可以确定一个进程。


等待是煎熬的,但又是充满希望的,因为等到结果出来的那一刻,一切都是值得的。

 

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

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

相关文章

java服务端如何接入WebSocket?

日常工作中&#xff0c;我们都是使用http请求&#xff0c;来进行前后交互&#xff0c;那么我们也会有使用websocket来进行前后交互的时候&#xff0c;那么它俩有什么区别呢&#xff1f; http和websocket区别 WebSocket是双向通信协议&#xff0c;模拟Socket协议&#xff0c;可…

商场内如何导航?有没有在商场用的导航?

商场内如何导航&#xff1f;大家可能都有这种感觉&#xff0c;在商场里逛街时&#xff0c;好像经常容易迷路&#xff0c;无论是找出口还是找电梯&#xff0c;总会把自己搞得晕头转向&#xff0c;“每次逛完商场都要给自己预留半小时找车子&#xff0c;也是醉了。”“在商场迷路…

XR云新未来 | 弹性算力赋能可交互、沉浸式商业实践

据XR市场研究报告显示&#xff0c;全球XR市场规模不断扩大&#xff0c;于2020年已超过100亿美元&#xff0c;预计到2025年规模将达到数百亿美元。XR技术广泛应用于娱乐、教育和企业领域&#xff0c;随着技术的进步&#xff0c;高分辨率显示、实时渲染和眼动追踪等创新技术的应用…

Ubantu docker学习笔记(十一)k8s基本操作

文章目录 一、K8s介绍二、容器编排2.1 部署应用2.2 了解应用2.3 公开暴露应用2.4 扩缩应用2.4 滚动更新 三、Kubernetes 对象 相信大家在前面的安装过程中&#xff0c;我们的k8s已经完成了一个超级具体的安装【是不是还是有点难度】&#xff0c;今天我们就在前面已经安装好的基…

消防安全知识答题活动小程序v4.3.0

消防安全知识答题活动小程序v4.3.0 v4.3.0 1&#xff09;实现答题倒计时功能 如果让你给一款答题小程序产品制定一个技术方案&#xff0c;比如实现答题倒计时功能&#xff0c;你会怎么做。 通常&#xff0c;我们对于倒计时的普遍认知是设置一个定时器去实现的&#xff0c;在…

0基础学习VR全景平台篇第32章:场景功能-嵌入文字

大家好&#xff0c;欢迎观看蛙色VR官方系列——后台使用课程&#xff01; 本期为大家带来蛙色VR平台&#xff0c;场景管理模块-嵌入功能文字模块&#xff01; 功能位置示意 一、本功能将用在哪里&#xff1f; 嵌入功能可对VR全景作品嵌入【图片】【视频】【文字】【标尺】四…

Android平台OpenCV入门

一、导入OpenCV 别忘记把libopencv_java3.so添加进来。 二、初始化 OpenCVLoader.initDebug();三、常用方法 1. CvType 数据类型 以CV_64FC2为例&#xff0c;64指64位&#xff0c;F指浮点数&#xff0c;C指通道&#xff0c;2为2通道。 数值具体类型取值范围CV_8U8 位无符…

5.30-cloud support -learning

文章目录 namespaceaccessNSG&#xff08;network security group &#xff09;UDR &#xff08;User-Defined Routing&#xff09;Azure Firewall namespace Namespaces are defined at the Kubernetes cluster level, so each namespace is unique throughout the cluster. I…

C语言笔记 | 数据结构入门指南

文章目录 0x00 前言 0x01 百鸡百钱 0x02 借书方案知多少 0x03 持续更新 0x04 参考文献 0x05 总结 0x00 前言 写这篇《C语言笔记 | 数据结构入门指南》主要是为了帮助更多的编程爱好者打开数据结构的大门&#xff0c;同时也是为了自我编程水平能力的提升。在深奥的数据结构…

正交实验进行方差分析

一、案例介绍 想要从某种草药中提取植物酚&#xff0c;利用专业知识发现可能有三个条件会影响植物酚的提取&#xff0c;每个条件有三个水平&#xff0c;想要通过实验&#xff0c;寻找植物酚的最佳提取条件&#xff0c;其中提取植物酚的参考标准为植物酚的含量&#xff08;案例…

在线原型设计是什么?8款在线原型工具助你高效设计!

原型设计是产品经理、设计师和开发工程师沟通最初的产品设想的重要工具。 在线原型通过云端的方式具象化地呈现产品内容、结构及粗略的布局&#xff0c;说明用户将如何与产品进行交互&#xff0c;搭建了产品经理、设计师和开发工程师沟通的桥梁&#xff0c;帮助产研团队减少信…

什么是深度数据包检测 (DPI)

随着混合工作成为生活的正常部分&#xff0c;新技术每天都在使用&#xff0c;同时总是通过网络传输数据的山体滑坡。通过高正常运行时间、快速解决问题和富有洞察力的情报提供无缝的用户体验至关重要。为此&#xff0c;对网络进行端到端监控非常重要。 深度数据包检测是一种用…

技术招聘漫谈 | 正在招Golang工程师的你,赶快收藏这份识人秘籍!

各位技术面试官&#xff0c;欢迎来到新一期的技术招聘漫谈专栏。 在前两期的专栏中&#xff0c;我们解析了前端工程师&#xff08;点击此处回顾&#xff09;以及 Java 工程师&#xff08;点击此处回顾&#xff09;这两个常见技术岗位的招聘技巧。 今天&#xff0c;我们想把目…

了解和使用 Docker 镜像仓库

前言 在上文 《了解和使用 Docker》 之后&#xff0c;反响不错&#xff0c;也上了热榜。本来是想直接整理一下容器编排工具 Docker Swarm 和 K8s 博文的&#xff0c;但是半路杀出了这个活动&#x1f602;&#xff0c;为表敬意&#xff0c;先参与一波吧。 本文主要介绍一下容…

【MyBatis】MyBatis中#{}与${}的区别是什么?

文章目录 前言一、彻底理解SQL注入二、关于 # { }三、关于$ { }四、Mybatis中#{}与${}的区别五、代码案例使用#{}案例使用$ {}案例 前言 在开发中使用Mybatis经常使用到#{}与${}&#xff0c;二者区别是&#xff1f;来总结一下。 在mybatis中动态 sql 是其主要特性之一&#x…

什么是现代化智慧型档案馆

近日&#xff0c;智慧档案馆建设的新闻热度不断攀升&#xff0c;你知道智慧档案馆是什么吗&#xff1f; ​智慧档案馆是指运用现代信息技术手段&#xff0c;对传统档案馆进行数字化转型&#xff0c;实现档案数字化存储、智能化检索和共享服务。通过数字化转型&#xff0c;不仅可…

< 免费体验ChatGPT:免费且好用,不用使用 “ 魔法 ”,一款功能强大且轻便的插件!webTab! >

免费且好用&#xff0c;不用使用 “ 魔法 ”&#xff0c;一款功能强大且轻便的插件&#xff01;webTab&#xff01; &#x1f449; 前言&#x1f449; 功能演示图&#x1f449; 使用经验分享&#x1f449; 如何安装webTab插件呢&#xff1f;往期内容 &#x1f4a8; tips&#x…

(转载)基于量子遗传算法的函数寻优算法(matlab实现)

8.1 理论基础 8.1.1 量子遗传算法概述 量子遗传算法(quantum genetic algorithm,QGA)是量子计算与遗传算法相结合的产物&#xff0c;是一种新发展起来的概率进化算法。遗传算法是处理复杂优化问题的一种方法&#xff0c;其基本思想是模拟生物进化的优胜劣汰规则与染色体的交…

太优雅了,公司项目终于用上了Spring状态机

1、什么是状态机 1.1 什么是状态 先来解释什么是“状态”&#xff08; State &#xff09;。现实事物是有不同状态的&#xff0c;例如一个自动门&#xff0c;就有 open 和 closed 两种状态。我们通常所说的状态机是有限状态机&#xff0c;也就是被描述的事物的状态的数量是有限…

【HTML 往日冒险 01】标签 元素 属性 注释 文本格式化 颜色 CSS

HTML 往日冒险日志01 说在前面重新开始基础标签 basic元素 elements属性 attributes标题&#xff0c;水平线以及注释 headings段落与折行 paragraphs样式 html_styles文本格式化 formatting注释 comments颜色 colorsCSS 说在前面 HTML 对于现在的我来说&#xff0c;熟悉又陌生…