[javaWeb]Socket网络编程

news2024/12/24 8:56:45

网络编程:写一个应用程序,让这个程序可以使用网络通信。这里就需要调用传输层提供的 api。

Socket套接字

传输层提供协议,主要是两个: UDP和TCP
提供了两套不同的 api,这api也叫做socket api。

UDP和 TCP 特点对比:

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

有连接,无连接:客户端和服务器之间,彼此之间使用内存空间保存对端的信息,双方都保存这个信息此时“连接”就出现了。

可靠传输,不可靠传输:不是说,A 给 B 发的消息 100% 能到(这个要求太难了)。A 尽可能的把消息传给 B.并且在传输失败的时候,A 能感知到,**或者在传输成功的时候,也能知道自己传输成了。**可靠传输效率低,不可靠传输效率高。

面向字节流,面向数据报:

  1. TCP 和 文件操作类似,都是“流”式的(由于这里传输的单位是字节,称为字节流)
  2. UDP 是面向数据报,读写的基本单位是一个 UDP 数据报(包含了一系列的数据/属性)

全双工,半双工:

  1. 全双工:一个通道,可以双向通信
  2. 半双工:一个通道,只能单向通信

UDP数据报套接字编程

DatagramSocket API

DatagramSocket API 是一个 Socket 对象。操作系统,使用文件这样的概念,来管理一些软硬件资源,操作系统也是使用文件的方式来管理网卡的。表示网卡的这类文件,称为 Socket 文件。Java 中的 socket 对象,就对应这系统里的 socket 文件。

构造方法:

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

客户端使用哪个端口,系统自动分配。但是服务器使用哪个端口,是手动指定的。对于服务器来说,需要有一个固定的端口号,方便其他客户端找到。一个客户端的主机,上面运行的程序很多,天知道你手动指定的端口是不是被别的程序占用了。让系统自动分配一个端口是更明智的选择。服务器是完全在程序猿手里控制的。程序猿可以把服务器上的多个程序安排好,让他们使用不同的端口。

普通方法:

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

DatagramPacket API

表示了一个 UDP 数据报,代表了系统中设定的 UDP 数据报的二进制结构。

构造方法:

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

普通方法:

方法签名方法说明
SocketAddress getSocketAddress()SocketAddress对象包含了IP地址和端口号
InetAddress getAddress()从接收的数据报中,获取发送端主机IP地址;或从发送的数据报中,获取 接收端主机IP地址
int getPort()从接收的数据报中,获取发送端主机的端口号;或从发送的数据报中,获取接收端主机端口号
byte[] getData()获取数据报中的数据。udp 数据报载荷部分(完整的应用层数据报)

示例一:回显服务器(echo server).

回显服务器(echo server):客户端发啥,服务器返回啥.

// UDP 的 回显服务器.
// 客户端发的请求是啥, 服务器返回的响应就是啥.
public class UdpEchoServer {
    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);
            socket.receive(requestPacket);
            //    这样的转字符串的前提是, 后续客户端发的数据就是一个文本的字符串.
            String request = new String(requestPacket.getData(), 0, requestPacket.getLength());
            // 2. 根据请求, 计算出响应
            String response = process(request);
            // 3. 把响应写回给客户端
            //    此时需要告知网卡, 要发的内容是啥, 要发给谁.
            DatagramPacket responsePacket = new DatagramPacket(response.getBytes(), response.getBytes().length,
                    requestPacket.getSocketAddress());
            socket.send(responsePacket);
            // 记录日志, 方便观察程序执行效果.
            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);
        server.start();
    }
}

客户端代码:

public class UdpEchoClient {
    private DatagramSocket socket = null;
    private String serverIp;
    private int serverPort;

    // 服务器的 ip 和 服务器的端口.
    public UdpEchoClient(String ip, int port) throws SocketException {
        serverIp = ip;
        serverPort = port;
        // 这个 new 操作, 就不再指定端口了. 让系统自动分配一个空闲端口.
        socket = new DatagramSocket();
    }

    // 让这个客户端反复的从控制台读取用户输入的内容. 把这个内容构造成 UDP 请求, 发给服务器. 再读取服务器返回的 UDP 响应
    // 最终再显示在客户端的屏幕上.
    public void start() throws IOException {
        Scanner scanner = new Scanner(System.in);
        System.out.println("客户端启动!");
        while (true) {
            // 1. 从控制台读取用户输入的内容
            System.out.print("-> "); // 命令提示符, 提示用户要输入字符串.
            String request = scanner.next();
            // 2. 构造请求对象, 并发给服务器.
            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);
            String response = new String(responsePacket.getData(), 0, responsePacket.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();
    }
}

可以启动多个客户端,服务器也是可以应对的。IDEA 上启动多个程序,需要稍微设置一下:

image-20230828095652845

其他人的电脑不能访问我这个程序。原因是:网络环境的现状,NAT 机制是主流。NAT 机制下,就把 IP 地址分成了外网 IP 和内网 IP.(内网IP 不能直接访问)。

示例二:翻译服务器

翻译服务器:请求是一些英文单词,响应则是对应的中文翻译。代码很多和回显服务器相同,所以继承UdpEchoServer 来实现代码的复用。

public class UdpDictServer extends UdpEchoServer {
    private Map<String, String> dict = new HashMap<>();

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

        dict.put("cat", "小猫");
        dict.put("dog", "小狗");
        dict.put("hello", "你好");
        // 可以在这里继续添加千千万万个单词. 使每个单词都有一个对应的翻译.
    }

    // 是要复用之前的代码, 但是又要做出调整.
    @Override
    public String process(String request) {
        // 把请求对应单词的翻译, 给返回回去.
        return dict.getOrDefault(request, "该词没有查询到!");
    }

    public static void main(String[] args) throws IOException {
        UdpDictServer server = new UdpDictServer(9090);
        // start 不需要重新再写一遍了. 直接就复用了之前的 start !
        server.start();
    }
}

TCP流套接字编程

TCP 分量要比 UDP 更重用的,用的更多的协议。

TCP 提供的 api 也是主要有两个类

  • ServerSoeket:给服务器使用的 socket
  • Socket: 既会给服务器使用,也会给客户端使用

字节流,一个字节一个字节进行传输的。一个 tcp 数据报,就是一个 字节数组 byte[]。

ServerSocket API

ServerSocket 构造方法:

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

ServerSocket 方法:

方法签 名方法说明
Socket accept()开始监听指定端口(创建时绑定的端口),有客户端连接后,返回一个服务端Socket 对象,并基于该Socket建立与客户端的连接,否则阻塞等待
void close()关闭此套接字

Socket API

Socket 构造方法:

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

Socket 方法:

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

ServerSocket的accept()方法负责产生连接,产生连接的类型是Socket。Socket负责后续的连接后的通信。 ServerSocket的accept()是在服务器中使用,当服务器执行到 accept 的时候, 此时客户端可能还没来呢。accept 就会阻塞到有客户端连接成功为止。

accept()把内核中的连接获取到应用程序中了,这个过程类似于,"生产者消费者模型”。

image-20230829100509766

TCP版本的回显服务器

客户端代码,要做的事情:

  1. 读取请求并解析.
  2. 根据请求计算响应
  3. 把响应写回给客户端.
public class TcpEchoServer {

    private ServerSocket serverSocket = null;

    // 这个操作就会绑定端口号
    public TcpEchoServer(int port) throws IOException {
        serverSocket = new ServerSocket(port);
    }
    // 启动服务器
    public void start() throws IOException {
        System.out.println("服务器启动");
        while (true){
            Socket clientSocket = serverSocket.accept();
            processConnection(clientSocket);
        }
    }
    // 通过这个方法来处理一个连接的逻辑.
    private void processConnection(Socket clientSocket) throws IOException {
        System.out.printf("[%s:%d]客户端上线\n",clientSocket.getInetAddress().toString(),clientSocket.getPort());
        // 接下来就可以读取请求, 根据请求计算响应, 返回响应三步走了.
        // Socket 对象内部包含了两个字节流对象, 可以把这俩字节流对象获取到, 完成后续的读写工作
        try (InputStream inputStream = clientSocket.getInputStream();
             OutputStream outputStream = clientSocket.getOutputStream()){
            // 一次连接中, 可能会涉及到多次请求/响应
            while (true){
                //1. 读取请求并解析. 为了读取方便, 直接使用 Scanner.
                Scanner sc = new Scanner(inputStream);
                if(!sc.hasNext()){
                    //读取完毕,客户端下线
                    System.out.printf("[%s:%d]客户端下线\n",clientSocket.getInetAddress().toString(),clientSocket.getPort());
                    break;
                }
                // 这个代码暗含一个约定, 客户端发过来的请求, 得是文本数据, 同时, 还得带有空白符作为分割. (比如换行这种)
                String request = sc.next();
                // 2. 根据请求计算响应
                String response = process(request);
                // 3. 把响应写回给客户端. 把 OutputStream 使用 PrinterWriter 包裹一下, 方便进行发数据.
                PrintWriter writer = new PrintWriter(outputStream);
                //    使用 PrintWriter 的 println 方法, 把响应返回给客户端.
                //    此处用 println, 而不是 print 就是为了在结尾加上 \n . 方便客户端读取响应, 使用 scanner.next 读取.
                writer.println(response);
                //    这里还需要加一个 "刷新缓冲区" 操作.
                writer.flush();
                // 日志, 打印当前的请求详情.
                System.out.printf("[%s:%d] req: %s,resp:%s\n",clientSocket.getInetAddress().toString(),clientSocket.getPort(),
                        request,response);

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

    }

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

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

客户端代码,要做的事情:
1.从控制台读取用户的输入
2.把输入的内容构造成请求并发送给服务器
3.从服务器读取响应
4.把响应显示到控制台上

public class TcpEchoClient {
    private Socket socket = null;

    // 要和服务器通信, 就需要先知道, 服务器所在的位置.
    public TcpEchoClient(String serverIp, int serverPort) throws IOException {
        // 这个 new 操作完成之后, 就完成了 tcp 连接的建立.
        socket = new Socket(serverIp, serverPort);
    }

    public void start() {
        System.out.println("客户端启动");

        Scanner scannerConsole = new Scanner(System.in);

        try (InputStream inputStream = socket.getInputStream();
             OutputStream outputStream = socket.getOutputStream()) {
            while (true) {
                // 1. 从控制台输入字符串.
                System.out.print("-> ");
                String request = scannerConsole.next();
                // 2. 把请求发送给服务器
                PrintWriter printWriter = new PrintWriter(outputStream);
                //    使用 println 带上换行. 后续服务器读取请求, 就可以使用 scanner.next 来获取了
                printWriter.println(request);
                //    不要忘记 flush, 确保数据是真的发送出去了!!
                printWriter.flush();
                // 3. 从服务器读取响应.
                Scanner scannerNetwork = new Scanner(inputStream);
                String response = scannerNetwork.next();
                // 4. 把响应打印出来
                System.out.println(response);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

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

多线程版本的会先服务器

当前代码还存在一个很大的问题。多个客户端访问会出现bug。当第一个客户端连接好了之后,第二个客户端,不能正确被处理服务器。看不到客户端上线,同时客户端发来的请求也无法被处理。当第一个客户端退出之后,之前第二个客户端发的请求,就能正确响应了。

上面的单线程代码中,serverSocket.accept(),建立一个连接,但是在进入processConnection()后,连接不断开,下一个连接就没办法连接上。主线程一个线程,又要建立连接,又要进行通信,只有通信结束连接断开之后,才会有下一个连接。要想解决这个问题我们就需要用多线程来解决,主线程负责建立连接,新建立的线程负责连接后的通信,各司其职就能解决这个问题。

public TcpEchoServer(int port) throws IOException {
        serverSocket = new ServerSocket(port);
    }
    // 启动服务器
    public void start() throws IOException {
        System.out.println("服务器启动");
        while (true){
            Socket clientSocket = serverSocket.accept();
            Thread t = new Thread(()->{
                processConnection(clientSocket);
            });
        }
    }

经过上述改进,只要服务器系统资源足够,有几个客户端都是可以的了。

TCP 程序的时候,涉及到两种写法

  1. 一个连接中只传输一次请求和响应.短连接

  2. 一个连接中,可以传输多次请求和响应.长连接

上面代码属于长连接。其实如果我们刚才的代码,不写成这个样子。比如要求每个客户端只能发一次请求,发完就断开。上述情况能得到一定的缓解,但是还是有类似的问题的。处理多个消息,自然就会延长 processConnection 的执行时间,后续的连接也得等到上个连接断开后才能继续连接。

现在是有一个连接,就有一个新的线程。如果很多客户端,频繁的来连接/断开,服务器就涉及到频繁创建/释放线程了。我们使用线程池会比较好的解决这个问题。

    // 此处不应该创建固定线程数目的线程池.
    private ExecutorService service = Executors.newCachedThreadPool();
        // 启动服务器
    public void start() throws IOException {
        System.out.println("服务器启动");
        while (true){
            Socket clientSocket = serverSocket.accept();
            // 使用线程池, 来解决上述问题
            service.submit(new Runnable() {
                @Override
                public void run() {
                    processConnection(clientSocket);
                }
            });
        }
    }

虽然使用线程池,避免了频繁创建销毁线程。但是毕竟是每个客户端对应一个线程。如果服务器对应的客户端很多,服务器就需要创建出大量的线程.对于服务器的开销是很大的。当客户端进一步增加,线程数目进一步增加系统的负担就越来越重,响应速度也会大打折扣。

< C10K问题:同一时刻,有 10k 个客户端(1w 个客户端),通过前面的一些技术手段,硬件设备,是可以处理好.(线程池之类的)
随着互联网的发展,客户端越来越多,请求也越来越多,进而引发了C10M问题。
C10M: 同一时刻,有 1kw 的客户端并发请求。引入了很多技术手段.其中一个非常 有效/必要 的手段,IO 多路复用/IO 多路转接.
IO 多路复用:一个线程完成多个IO操作。 不是说使用了这一个方法就能解决C10M问题,但是解决 高并发(C10M) 重要手之一。

解决高并发,就是四个字:

  1. 开源: 引入更多的硬件资源 (多核CPU)
  2. 节流: 提高单位硬件资源能够处理的请求数。同样的请求数,消耗的硬件资源更少。

IO 多路复用,就是节流的方式。同样的请求,消耗的硬件资源更少了。本质上就是减少线程的数量。操作系统提供的另一组网络编程的 API 可以和 tcp udp api 配合使用的。可以去搜索 Java NIO 一些关键词来学习IO多路复用。

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

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

相关文章

数据结构day05(单链表)

今日任务&#xff1a; 思维导图&#xff1a; 实现 代码&#xff1a;&#xff08;多文件&#xff09; head.h #ifndef __HEAD_H__ #define __HEAD_H__#include <stdio.h> #include <string.h> #include <stdlib.h> typedef int datatype;typedef struct Lin…

Nuxt3 与 Vue3 的 Server api 全栈开发之路

Nuxt Server Api 阅读时长&#xff1a;15分钟 本文内容&#xff1a; 国内关于Nuxt3的资料太少了&#xff0c;而Nuxt3又发布了没有多久&#xff0c;导致资料太少。本文浓缩讲解了&#xff0c;对于一个前端开发&#xff0c;上手使用 Nuxt3&#xff0c;并一个人承担前后端开发的所…

Hive3第六章:更换引擎

系列文章目录 Hive3第一章&#xff1a;环境安装 Hive3第二章&#xff1a;简单交互 Hive3第三章&#xff1a;DML数据操作 Hive3第三章&#xff1a;DML数据操作(二) Hive3第四章&#xff1a;分区表和分桶表 Hive3第五章&#xff1a;函数 Hive3第六章&#xff1a;更换引擎 文章目…

网络安全—0基础学习笔记(黑客)

一、前言 1.这是一条坚持的道路,三分钟的热情可以放弃往下看了. 2.多练多想,不要离开了教程什么都不会了.最好看完教程自己独立完成技术方面的开发. 3.有时多 google,baidu,我们往往都遇不到好心的大神,谁会无聊天天给你做解答. 4.遇到实在搞不懂的,可以先放放,以后再来解决. …

2023年智慧政务一网通办云平台顶层设计与建设方案PPT

导读&#xff1a;原文《2023年智慧政务一网通办云平台顶层设计与建设方案PPT》&#xff08;获取来源见文尾&#xff09;&#xff0c;本文精选其中精华及架构部分&#xff0c;逻辑清晰、内容完整&#xff0c;为快速形成售前方案提供参考。 部分内容&#xff1a; 喜欢文章&#…

Spring——RESTful Web服务

文章目录 RESTful Web 服务介绍内容概览下载 Lombok 优化代码利器RESTful Web 服务开发运行项目并测试效果 RESTful Web 服务介绍 本节我们将开发一个简单的 RESTful Web 服务。 RESTful Web 服务与传统的 MVC 开发一个关键区别是返回给客户端的内容的创建方式&#xff1a;传…

Go操作各大消息队列教程(RabbitMQ、Kafka)

Go操作各大消息队列教程 1 RabbitMQ 1.1 概念 ①基本名词 当前市面上mq的产品很多&#xff0c;比如RabbitMQ、Kafka、ActiveMQ、ZeroMQ和阿里巴巴捐献给Apache的RocketMQ。甚至连redis这种NoSQL都支持MQ的功能。 Broker&#xff1a;表示消息队列服务实体Virtual Host&#x…

感觉车载测试的这一波敏捷风快过去了

敏捷&#xff0c;算不得汽车行业的原生产物&#xff0c;几年前&#xff0c;耳边很少听到这个字眼&#xff0c;基本算是在近几年传统汽车行业开始衰落的大背景下&#xff0c;而后伴随着软件从互联网等行业传进来的。 这两年&#xff0c;大家开始把敏捷谈得风生水起&#xff0c;…

用idea查看sqlite数据库idea sqlite

1、安装Database Navigator插件 2、导入数据库并查看 3、删除数据库连接 在此做个笔记

VUE环境下 CSS3+JS 实现发牌 翻牌

创建牌容器&#xff08;关键点&#xff1a;overflow&#xff1a;hidden&#xff09;&#xff1a; <div class"popup-box"></div> .popup-box {position: absolute;width: 100vw;height: 100vh;top: 0px;left: 0;overflow: hidden; } 创建每一张牌《固…

python+TensorFlow实现人脸识别智能小程序的项目(包含TensorFlow版本与Pytorch版本)(一)

pythonTensorFlow实现人脸识别智能小程序的项目&#xff08;包含TensorFlow版本与Pytorch版本&#xff09;&#xff08;一&#xff09; 一&#xff1a;TensorFlow基础知识内容部分&#xff08;简明扼要&#xff0c;快速适应&#xff09;1、下载Cifar10数据集&#xff0c;并进行…

react17:生命周期函数

挂载时更新时 setState触发更新、父组件重新渲染时触发更新forceUpdate触发更新卸载时 react&#xff08;v17.0.2&#xff09;的生命周期图谱如下。 相较于16版本&#xff0c;17版本生命周期函数有如下变化&#xff1a; componentWillMount() componentWillUpdate() compone…

mac电脑屏幕录制Berrycast Mac屏幕录制软件

Berrycast是一款为Mac设计的优秀屏幕录制软件&#xff0c;它让屏幕录制变得简单而高效。以下是Berrycast的一些主要特点&#xff1a; 简单的用户界面&#xff1a;Berrycast拥有直观和简洁的用户界面&#xff0c;使得用户可以轻松上手。高质量的视频输出&#xff1a;Berrycast能…

电商系统架构设计系列(十):怎么能避免写出慢SQL?

上篇文章中&#xff0c;我给你留了一个思考题&#xff1a;怎么能避免写出慢SQL&#xff1f; 我们知道&#xff0c;一个慢 SQL 就可以直接让 MySQL 瘫痪。以我个人经验总结来看&#xff0c;一般情况下系统出问题&#xff0c;大多数都是因为SQL语句的问题。掌握和用好了SQL&…

软件面试题:文件上传下载测试点

目前关于云文档的业务还是挺多的&#xff0c;相信出去面试的同学&#xff0c;大多会遇到这道高频软件测试面试题&#xff1a;文件上传下载测试点。今天向大家分享下&#xff0c;希望对大家有所启发。 一、文件上传测试点 1、文件大小 一般情况下&#xff0c;系统会设定上传文…

基于OV2640/ OV5640 的图像采集显示系统

基于OV2640/ OV5640 的图像采集显示系统系列文章目录&#xff1a; &#xff08;1&#xff09;基于 OV5640 摄像头理论知识讲解-成像和采样原理 &#xff08;2&#xff09;基于 OV5640 摄像头理论知识讲解-数字接口和控制接口 &#xff08;3&#xff09;基于 OV5640 摄像头理论知…

Mac软件删除方法?如何删除不会有残留

Mac电脑如果有太多无用的应用程序&#xff0c;很有可能会拖垮Mac系统的运行速度。因此&#xff0c;卸载电脑中无用的软件是优化Mac系统运行速度的最佳方式之一。Mac卸载应用程序的方式是和Windows有很大的区别&#xff0c;特别对于Mac新用户来说&#xff0c;如何无残留的卸载删…

Python Qt(七)Listview

源代码&#xff1a; # -*- coding: utf-8 -*-# Form implementation generated from reading ui file qt_listview.ui # # Created by: PyQt5 UI code generator 5.15.9 # # WARNING: Any manual changes made to this file will be lost when pyuic5 is # run again. Do not…

2024王道408数据结构P144 T18

2024王道408数据结构P144 T18 思考过程 首先还是先看题目的意思&#xff0c;让我们在中序线索二叉树里查找指定结点在后序的前驱结点&#xff0c;这题有一点难至少对我来说…我讲的不清楚理解一下我做的也有点糊涂。在创建结构体时多两个变量ltag和rtag&#xff0c;当ltag0时…

基于Axios完成前后端分离项目数据交互

一、安装Axios npm i axios -S 封装一个请求工具&#xff1a;request.js import axios from axios// 创建可一个新的axios对象 const request axios.create({baseURL: http://localhost:9090, // 后端的接口地址 ip:porttimeout: 30000 })// request 拦截器 // 可以自请求…