【JavaEE网络】TCP套接字编程详解:从概念到实现

目录

    • TCP流套接字编程
      • ServerSocket API
      • Socket API
      • TCP回显客户端服务器


TCP流套接字编程

TCP用的协议比UDP更多,可靠性

提供的api主要有两个类ServerSocket(给服务器使用的socket),Socket(既会给服务器使用也会给客户端使用)

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

ServerSocket API

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

ServerSocket 构造方法:

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

ServerSocket 方法:

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

Socket API

Socket 是客户端Socket,或服务端中接收到客户端建立连接(accept方法)的请求后,返回的服务端Socket。

不管是客户端还是服务端Socket,都是双方建立连接以后,保存的对端信息,及用来与对方收发数据的。

Socket 构造方法:

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

Socket 方法:

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

TCP回显客户端服务器

TCP版本的回显服务器,进入循环之后要做的事情不是读取客户端的请求,而是先处理客户端的“连接”

服务器代码流程:

  1. 读取请求并分析
  2. 根据请求计算响应
  3. 把响应写回客户端
//处理连接的过程=>此时可能客户端还没来,accept就阻塞等待了
Socket clientSocket = serverSocket.accept();
//把内核中的连接获取到应用程序中了=>这个过程类似于“生产者消费者模型”

accept 是把内核中已经建立好的连接,给拿到应用程序中。

但是这里的返回值并非是一个"Connection"这样的对象,而只是一个 Socket 对象,这个 Socket 对象就像一个耳麦一样,就可以说话,也能听到对方的声音

通过 Socket 对象和对方进行网络通信

一次0,主要是经历两个部分

  1. 等(阻塞)
  2. 拷贝数据

此处是先握手吗?

不是!握手是系统内核负责的.写代码过程感知不到握手的过程

此处主要是处理连接,也就是握手之后得到的东西

一个服务器,要对应很多客户端,服务器内核里有很多客户端的连接。虽然内核中连接很多,但是在应用程序中,还是得一个一个的处理的。

内核中的“连接”就像一个一个“待办事项“。这些待办事项在一个 队列 的数据结构中。应用程序就需要一个一个完成这些任务

而要完成任务,就需要先取任务

TCP中涉及到两种socket

  1. serverSocket
  2. clientSocket
Socket clientSocket = serverSocket.accept();
//TCP通信能实现两台计算机之间的数据交互,通信的两端要严格区分为客户端(Client)与服务端(Server)

通过processConnection这个方法(自己实现)来处理一个连接的逻辑

try (InputStream inputStream = clientSocket.getInputStream();//相当于耳麦(clientSocket)的耳机(inputStream)
     OutputStream outputStream = clientSocket.getOutputStream()) {//相当于耳麦(clientSocket)的麦克风(outputStream)
public class TcpEchoServer {
    private ServerSocket serverSocket = null;
    //此处不应该创建一个固定线程数目的线程池,不然就有上限了
    private ExecutorService service= Executors.newCachedThreadPool();

    //这个操作就会绑定端口号
    public TcpEchoServer(int port) throws IOException {
        serverSocket = new ServerSocket(port);
    }

    //启动服务器
    public void start() throws IOException {
        System.out.println("服务器启动");
        while (true) {
            //处理连接的过程=>此时可能客户端还没来,accept就阻塞等待了
            Socket clientSocket = serverSocket.accept();
            //把内核中的连接获取到应用程序中了

            processConnection(clientSocket);

        }
    }

    //通过这个方法来处理一个连接的逻辑
    private void processConnection(Socket clientSocket) {
        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();//“上完厕所冲一下”。
                //这里加上 flush 更稳妥,不加也不一定就出错!!缓冲区内置了一定的刷新策略
                //比如缓冲区满了,就会触发刷新; 再比如,程序退出,也会触发刷新......推荐大家把 flush 刷新给加上

                /*
                IO操作是比较有开销的,相比于访问内存。进入IO操作次数越多,程序的速度越慢。
                方法:使用一块内存作为缓冲区,写数据的时候,先写到缓冲区,攒一波数据,统一进入IO。
                PrintWriter内置了缓冲区,手动刷新,确保这里的数据是真的通过网卡发出去了,而不是残留在内存缓冲区中
                 */

                //日志,打印当前的请求详情
                System.out.printf("[%s:%d] req: %s, resp: %s\n", clientSocket.getInetAddress().toString(), clientSocket.getPort(),
                        request, response);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

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

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

以上代码有两个问题:

但没有线程安全问题,因为没有多线程

1、关闭

在这个程序中,设计到两类Socket:

  1. ServerSocket(只有一个,生命周期跟随程序,不关闭也没事)
while (true) {
    //处理连接的过程=>此时可能客户端还没来,accept就阻塞等待了
    Socket clientSocket = serverSocket.accept();
    //把内核中的连接获取到应用程序中了

    processConnection(clientSocket);

}
  1. 而Socket在1w个客户端就有1w个Socket,此处的Socket是被反复创建的。因此要确保在连接断开后,socket能被关闭
catch (IOException e) {
    e.printStackTrace();
} finally {
    //在finally中加入close,确保当前socket被及时关闭
    try {
        clientSocket.close();
    } catch (IOException e) {
        e.printStackTrace();
    }
}
//在trycatch后关闭socket

写到这以上代码中对于Scanner和PrintWriter没有close是否会有文件资源泄露呢?不会

因为流对象持有的资源有两个部分:

  1. 内存(对象销毁,内存就回收了)while循环一圈内存自然销毁
  2. 文件描述符(scanner和printWriter没有持有文件描述符;持有的是inputStream和outputStream的引用,这两个进行关闭了,或者更准确的说是socket对象持有的,socket对象关闭了就ok)

不是每个流对象都持有文件描述符,持有文件描述符是要调用操作系统提供的open方法(系统调用,是要在内核完成的,相当重量/严肃的事情)

2、第二个问题是在写完客户端后再说

客户端代码流程:

  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 scConsole=new Scanner(System.in);
        try(InputStream inputStream=socket.getInputStream();
            OutputStream outputStream=socket.getOutputStream()){
            while(true){
                //1、从控制台输入一个字符串
                System.out.print("-> ");
                String request = scConsole.next();
                //2、把请求发送给服务器
                PrintWriter printWriter=new PrintWriter(outputStream);
                //使用println带上换行,后续服务器读取请求,就可以使用Scanner.next来读取了
                printWriter.println(request);
                //别忘记flush,确保数据真的发出去了
                printWriter.flush();
                //3、从服务器读取响应
                Scanner scNetwork=new Scanner(inputStream);
                String response = scNetwork.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();
    }
}
//以目前以上的代码执行结果
//服务器
服务器启动
[/127.0.0.1:63945] 客户端上线
[/127.0.0.1:63945] req: 你好, resp: 你好
[/127.0.0.1:63945] req: hello, resp: hello
[/127.0.0.1:63945] 客户端下线
//客户端
客户端启动
-> 你好
你好
-> hello
hello
-> //这里客户端退出了

回到上面遗留的第二个问题

现象:当第一个客户端连接好了之后,第二个客户端不能正确被处理,服务器看不到客户端上线,同时客户端发来的请求也无法被处理,当第一个客户端退出之后,之前第二个客户端发的请求,就能正确响应了。

问题出在:在服务器这边,当一个客户端来了,accept就可以正确返回,进入processConnection,然后进入循环处理该客户端的请求,一直等到这个客户端结束,才能回到start方法中

问题关键在于,处理一个客户端的请求过程中,无法第二次调用accept(即使第二个客户端来了也无法处理)

我们期望能够同时让多个客户端进入调用accept,因此用到多线程

主线程负责找到客户端,在有客户端到来时,创建新的线程,让新的线程负责处理客户端的各种请求

以下是改进方式:

//启动服务器
public void start() throws IOException {
    System.out.println("服务器启动");
    while (true) {
        //处理连接的过程=>此时可能客户端还没来,accept就阻塞等待了
        Socket clientSocket = serverSocket.accept();
        //把内核中的连接获取到应用程序中了

        //单个线程不太方便同时完成多个任务,因此要多线程,主线程主要负责寻找客户端,每有一个客户端就创建一个新的线程
    /*
        Thread t=new Thread(()->{
            processConnection(clientSocket);
        });
        t.start();
    */
        //使用线程池
        service.submit(new Runnable() {
            @Override
            public void run() {
                processConnection(clientSocket);
            }
        });
    }
}

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

  1. 一个连接中只传输一次请求和响应(短连接)
  2. 一个连接中可以传输多次请求和响应(长连接)

而由于我们只是通过普通的方式创建线程,有一个连接就创建一个线程,如果有多个客户端,频繁连接/断开,服务器就频繁创建/释放线程了,因此我们直接采用线程池的方式

注意:我们上述的不能写成这种方式

try(Socket clientSocket = serverSocket.accept()){
    service.submit(new Runnable() {
        @Override
        public void run() {
            processConnection(clientSocket);
        }
    });
};
/*
processConnection 和主线程就是不同线程了
执行 processConnection 过程中,主线程 try 就执行完毕了。
这就会导致 clientSocket 还没用完呢,就关闭了
因此,还是要把clientSocket交给processConnection里来关闭
*/

虽然使用了线程池,避免了频繁创建销毁线程,但如果仍然很多客户端,创建大量线程仍有很大开销,可以称为高并发

方法:解决高并发引入了很多技术手段,IO多路复用/IO多路转接(但不是说用了一下子就解决了)

解决高并发(四个字):

  1. 开源:引入更多的硬件资源(本质上是减少线程的数量)
  2. 节流:提高单位硬件资源能够处理的请求数

(同样的请求数,消耗的硬件资源更少)

完整代码

服务器

public class TcpEchoServer {
    private ServerSocket serverSocket = null;
    //此处不应该创建一个固定线程数目的线程池,不然就有上限了
    private ExecutorService service= Executors.newCachedThreadPool();

    //这个操作就会绑定端口号
    public TcpEchoServer(int port) throws IOException {
        serverSocket = new ServerSocket(port);
    }

    //启动服务器
    public void start() throws IOException {
        System.out.println("服务器启动");
        while (true) {
            //处理连接的过程=>此时可能客户端还没来,accept就阻塞等待了
            Socket clientSocket = serverSocket.accept();
            //把内核中的连接获取到应用程序中了

            //单个线程不太方便同时完成多个任务,因此要多线程,主线程主要负责寻找客户端,每有一个客户端就创建一个新的线程
        /*
            Thread t=new Thread(()->{
                processConnection(clientSocket);
            });
            t.start();
        */
            //使用线程池
            service.submit(new Runnable() {
                @Override
                public void run() {
                    processConnection(clientSocket);
                }
            });
        }
    }

    //通过这个方法来处理一个连接的逻辑
    //服务器一启动,就会执行accept,并阻塞等待,当客户端连接上之后,就会立即执行这个方法
    private void processConnection(Socket clientSocket) {
        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);
                //hasNext这里在客户端没有发请求的时候也会阻塞等待,等到客户端真正发数据或者客户端退出,hasNext就返回了
                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();//“上完厕所冲一下”。
                //这里加上 flush 更稳妥,不加也不一定就出错!!缓冲区内置了一定的刷新策略
                //比如缓冲区满了,就会触发刷新; 再比如,程序退出,也会触发刷新......推荐大家把 flush 刷新给加上

                /*
                IO操作是比较有开销的,相比于访问内存。进入IO操作次数越多,程序的速度越慢。
                方法:使用一块内存作为缓冲区,写数据的时候,先写到缓冲区,攒一波数据,统一进入IO。
                PrintWriter内置了缓冲区,手动刷新,确保这里的数据是真的通过网卡发出去了,而不是残留在内存缓冲区中
                 */

                //日志,打印当前的请求详情
                System.out.printf("[%s:%d] req: %s, req: %s\n", clientSocket.getInetAddress().toString(), clientSocket.getPort(),
                        request, response);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            //在finally中加入close,确保当前socket被及时关闭
            try {
                clientSocket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

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

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

客户端

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 scConsole=new Scanner(System.in);
        try(InputStream inputStream=socket.getInputStream();
            OutputStream outputStream=socket.getOutputStream()){
            while(true){
                //1、从控制台输入一个字符串
                System.out.print("-> ");
                String request = scConsole.next();
                //2、把请求发送给服务器
                PrintWriter printWriter=new PrintWriter(outputStream);
                //使用println带上换行,后续服务器读取请求,就可以使用Scanner.next来读取了
                printWriter.println(request);
                //别忘记flush,确保数据真的发出去了
                printWriter.flush();
                //3、从服务器读取响应
                Scanner scNetwork=new Scanner(inputStream);
                String response = scNetwork.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();
    }
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

基于uni-app的动态表单

一、应用场景和意义 可以通过配置字段和校验规则,快速完成页面开发、提升开发效率 二、应用前提 形成ui/业务规范,最好是应用在问卷调查之类的业务 三、动态表单的功能 字段报错、快速滚动定位报错信息、支持字段值和字段规则拆分,便于实…

Linux安装Matlab运行时

一般而言,安装Matlab的linux系统是带桌面版的,如果没带,不在本教程范围内。 一、下载Matlab 下载地址:MATLAB Runtime - MATLAB Compiler - MATLAB 本教程使用R2020b(9.9) 二、linux系统中进行解压 将zip传入linux系统&#xf…

微电子领域常见概念(八)靶材

微电子领域常见概念(八)靶材 靶材是用于物理气相沉积(PVD)技术中的一种关键材料,它在制备薄膜的过程中起到至关重要的作用。PVD技术包括多种不同的工艺,如磁控溅射、离子束溅射、分子束外延(MBE…

Vue:vue的工程化

Vue前端工程化 前后端分离开发 即前端人员开发前端工程,将开发好的前端工程打包部署在前端服务器上 后端开发人员开发后端工程,再将后端工程打包部署在后端服务器上,这种模式称为前后端分离开发 而前后端要顺利对接的关键就是要遵循一定的开发规范 开发规范 这种开发规范定…

CCF区块链会议--Middleware 2024 截止5.24 附录用率

会议名称:Middleware CCF等级:CCF B类会议 类别:软件工程/系统软件/程序设计语言 录用率:2022年录用率38%(8/21) Topics of Interest The Middleware conference seeks original submissions of resear…

LAMP(Linux+Apache+MySQL+PHP)环境介绍、配置、搭建

LAMP(LinuxApacheMySQLPHP)环境介绍、配置、搭建 LAMP介绍 LAMP是由Linux, Apache, MySQL, PHP组成的,即把Apache、MySQL以及PHP安装在Linux系统上,组成一个环境来运行PHP的脚本语言。Apache是最常用的Web服务软件&a…

科技赋能无人零售

科技赋能无人零售,使其具备以下独特优势: 1. 全天候无缝服务 :无人零售店依托科技,实现24小时不间断运营,不受人力限制,满足消费者随时购物需求,尤其惠及夜间工作者、夜猫子及急需购物者&…

微前端是如何实现作用域隔离的?

微前端是如何实现作用域隔离的? 一、前言 沙箱(Sandbox)是一种安全机制,目的是让程序运行在一个相对独立的隔离环境,使其不对外界的程序造成影响,保障系统的安全。作为开发人员,我们经常会同沙…

03-JAVA设计模式-访问者模式

访问者模式 什么是访问者模式 访问者模式(Visitor Pattern)是软件设计模式中的一种行为模式,它用于将数据结构中的元素与操作这些元素的操作解耦。这种模式使得可以在不修改数据结构的情况下添加新的操作。 在访问者模式中,我们…

PHP+MYSQL多条件选一通用搜索系统功能单文件7KB

通用功能: 快速填写参数用于自己的mysql数据表搜索,ajax载入数据 <?php header("content-Type: text/html; charsetUTF-8"); //error_reporting(0);$dbhost "localhost"; //数据库地址本地localhost $dbuser "chalidecom"; //数据库账号 …

Tkinter是什么?

Tkinter是Python标准库中的一个模块&#xff0c;用于创建图形用户界面&#xff08;GUI&#xff09;应用程序。它提供了一组工具和组件&#xff0c;使开发者能够在Python中创建窗口、按钮、标签、文本框、菜单等各种界面元素&#xff0c;并通过这些元素构建交互式的用户界面。 T…

稀碎从零算法笔记Day59-LeetCode: 感染二叉树需要的总时间

题型&#xff1a;树、图、BFS、DFS 链接&#xff1a;2385. 感染二叉树需要的总时间 - 力扣&#xff08;LeetCode&#xff09; 来源&#xff1a;LeetCode 题目描述 给你一棵二叉树的根节点 root &#xff0c;二叉树中节点的值 互不相同 。另给你一个整数 start 。在第 0 分钟…

Three.js入门学习笔记

学习资料&#xff1a; 【Three.js】Three.js快速上手教程_three.module.js-CSDN博客 2024年了&#xff0c;是该学学Three.js了_three.js 2024-CSDN博客 一、three.js简介 three.js是JavaScript编写的WebGL第三方库。 three.js&#xff0c;webGL&#xff0c;openGL三者的关…

微信小程序4~6章总结

目录 第四章 页面组件总结 4.1 组件的定义及属性 4.2 容器视图组件 4.2.1 view 4.2.2 scroll-view 4.2.3 swiper 4.3 基础内容组件 4.3.1 icon ​编辑 4.3.2 text 4.3.3 progress ​编辑 4.4 表单组件 4.4.1 button 4.4.2 radio 4.4.3 checkbox 4.4.4 switch …

第27天:安全开发-PHP应用TP框架路由访问对象操作内置过滤绕过核心漏洞

第二十七天 一、TP框架-开发-路由访问&数据库&文件上传&MVC模型 1.TP框架-开发-配置架构&路由&MVC模型 参考&#xff1a;https://www.kancloud.cn/manual/thinkphp5_1 配置架构-导入使用路由访问-URL访问数据库操作-应用对象文件上传操作-应用对象前端页…

51.HarmonyOS鸿蒙系统 App(ArkUI)通知

普通文本通知测试 长文本通知测试 多行文本通知测试 图片通知测试 进度条通知测试 通知简介 应用可以通过通知接口发送通知消息&#xff0c;终端用户可以通过通知栏查看通知内容&#xff0c;也可以点击通知来打开应用。 通知常见的使用场景&#xff1a; 显示接收到的短消息、…

L1-099 帮助色盲 - java

L1-099 帮助色盲 代码长度限制 16 KB 时间限制 400 ms 内存限制 64 MB 栈限制 8192 KB 题目描述&#xff1a; 在古老的红绿灯面前&#xff0c;红绿色盲患者无法分辨当前亮起的灯是红色还是绿色&#xff0c;有些聪明人通过路口的策略是这样的&#xff1a;当红灯或绿灯亮起时&am…

VMware-Linux切换桥接模式上网教程(超详细)

这里写目录标题 1. 虚拟机关机2. VMware 虚拟网络配置2.1 检查是否存在 VMnet02.2 修改桥接模式2.3 修改Linux虚拟机网络适配器 3. Linux 系统配置3.1 修改系统网卡配置3.1.1 配置项含义解释3.1.2 查看物理机网络信息3.3.3 修改配置 3.2 重启服务 4. 测试网络连接情况5. 注意事…

数据可视化-ECharts Html项目实战(14)

在之前的文章中&#xff0c;我们深入学习ECharts鼠标左键触发。想了解的朋友可以查看这篇文章。同时&#xff0c;希望我的文章能帮助到你&#xff0c;如果觉得我的文章写的不错&#xff0c;请留下你宝贵的点赞&#xff0c;谢谢。 数据可视化-ECharts Html项目实战&#xff08;…

目标检测——YOLOv7算法解读

论文&#xff1a;YOLOv7: Trainable bag-of-freebies sets new state-of-the-art for real-time object detectors (2022.7.6) 作者&#xff1a;Chien-Yao Wang, Alexey Bochkovskiy, Hong-Yuan Mark Liao 链接&#xff1a;https://arxiv.org/abs/2207.02696 代码&#xff1a;h…