linux并发服务器 —— IO多路复用(八)

news2024/11/27 14:33:55

半关闭、端口复用

半关闭只能实现数据单方向的传输;当TCP 接中A向 B 发送 FIN 请求关闭,另一端 B 回应ACK 之后 (A 端进入 FIN_WAIT_2 状态),并没有立即发送 FIN 给 A,A 方处于半连接状态 (半开关),此时 A 可以接收 B 发送的数据,但是 A 已经不能再向 B 发送数据

close不会影响到其他进程,shutdown会影响到其他进程;

网络信息相关的命令

netstat

        -a 所有的Socket

        -p 正在所用socket的程序名称

        -n 直接使用IP地址,不通过域名服务器

端口复用

1. 防止服务器重启时之前绑定的端口还没释放

2. 程序突然退出而系统没有释放端口

IO多路复用简介

I/O多路复用使程序可以同时监听多个文件描述符,提高程序性能;select/poll/epoll

阻塞等待:不占用CPU宝贵时间;但同一时刻只能处理一个操作,效率低。

非阻塞,忙轮询:提高了程序执行效率;但会占用更多的CPU资源。

select/poll:委托内核进行检测,但仍需要进行遍历

epoll:同样委托内核,但无需进行遍历

select

主旨思想:

1. 构造关于文件描述符的列表,将要监听的文件描述符添加到表中

2. 调用系统函数,监听该列表中的文件描述符,知道描述符中的一个/多个进行了I/O操作,函数才返回(该函数是阻塞的,且该函数对于文件描述符的检测是由内核完成的)

3. 返回时,告诉进程有多少描述符要进行I/O操作

返回值: 失败 - -1,成功 - 检测到的描述符个数

#include <iostream>
#include <cstdio>
#include <arpa/inet.h>
#include <cstdlib>
#include <string.h>
#include <sys/select.h>
#include  <unistd.h>

using namespace std;

int main(){

    // 创建socket
    int lfd = socket(PF_INET , SOCK_STREAM , 0);
    struct sockaddr_in saddr;
    saddr.sin_port = htons(9999);
    saddr.sin_family = AF_INET;
    saddr.sin_addr.s_addr = INADDR_ANY;
    // 绑定
    bind(lfd , (struct sockaddr*)&saddr , sizeof(saddr));
    // 监听
    listen(lfd , 8);

    fd_set rdset , tmp;
    FD_ZERO(&rdset);
    FD_SET(lfd , &rdset);
    int maxfd = lfd+1;
    while(1){
        tmp = rdset;
        // 调用select 系统检测
        int ret = select(maxfd+1 , &tmp , NULL , NULL , NULL);
        if(ret == -1){
            perror("select");
            exit(-1);
        }
        else if(ret == 0){
            continue;
        }
        else{
            // 检测到了文件描述符的数据发生了改变
            if(FD_ISSET(lfd , &tmp)){
                // 有客户端连接进来
                struct sockaddr_in caddr;
                socklen_t len = sizeof(caddr);
                int cfd = accept(lfd , (struct sockaddr*)&caddr , &len);
                // 添加进去
                FD_SET(cfd , &rdset);

                maxfd = maxfd>cfd?maxfd:cfd+1;
            }
            for(int i = lfd+1 ; i <= maxfd ; i++){
                if(FD_ISSET(i , &tmp)){
                    // 说明客户端发来了数据
                    char buf[1024];
                    int len = read(i , buf , sizeof(buf));
                    if(len == -1){
                        perror("read");
                        exit(-1);
                    }
                    else if(len == 0){
                        cout<<"client close..."<<endl;
                        close(i);
                        FD_CLR(i,&rdset);
                    }
                    else{
                        cout<<"发来了数据:"<<buf<<endl;
                        write(i , buf , strlen(buf)+1);
                    }
                }
            }
        }
    }
    close(lfd);
    return 0;
}

poll

#include <iostream>
#include <cstdio>
#include <arpa/inet.h>
#include <cstdlib>
#include <string.h>
#include <poll.h>
#include  <unistd.h>

using namespace std;

int main(){

    // 创建socket
    int lfd = socket(PF_INET , SOCK_STREAM , 0);
    struct sockaddr_in saddr;
    saddr.sin_port = htons(9999);
    saddr.sin_family = AF_INET;
    saddr.sin_addr.s_addr = INADDR_ANY;
    // 绑定
    bind(lfd , (struct sockaddr*)&saddr , sizeof(saddr));
    // 监听
    listen(lfd , 8);

    // 初始化检测文件描述符数组
    struct pollfd fds[1024];
    for(int i = 0 ; i<1024 ; i++){
        fds[i].fd = -1;
        fds[i].events = POLLIN;
    }
    fds[0].fd = lfd;

    int nfds = 0;

    while(1){
        // poll 系统检测
        int ret = poll(fds , nfds+1 , -1);
        if(ret == -1){
            perror("poll");
            exit(-1);
        }
        else if(ret == 0){
            continue;
        }
        else{
            // 检测到了文件描述符的数据发生了改变
            if(fds[0].revents & POLLIN){
                // 有客户端连接进来
                struct sockaddr_in caddr;
                socklen_t len = sizeof(caddr);
                int cfd = accept(lfd , (struct sockaddr*)&caddr , &len);
                // 添加进去
                for(int i = 1 ; i < 1024 ; i++){
                    if(fds[i].fd == -1){
                        fds[i].fd = cfd;
                        fds[i].events = POLLIN;
                        break;
                    }
                }

                nfds = nfds>cfd?nfds:cfd;
            }
            for(int i = 1 ; i <= nfds ; i++){
                if(fds[i].revents & POLLIN){
                    // 说明客户端发来了数据
                    char buf[1024];
                    int len = read(fds[i].fd , buf , sizeof(buf));
                    if(len == -1){
                        perror("read");
                        exit(-1);
                    }
                    else if(len == 0){
                        cout<<"client close..."<<endl;
                        close(fds[i].fd);
                        fds[i].fd = -1;
                    }
                    else{
                        cout<<"发来了数据:"<<buf<<endl;
                        write(fds[i].fd , buf , strlen(buf)+1);
                    }
                }
            }
        }
    }
    close(lfd);
    return 0;
}

epoll

内核,红黑树记录要检测的文件描述符,避免了用户态到内核态的数据拷贝开销;

内核,双链表存放数据改变的文件描述符

#include <iostream>
#include <cstdio>
#include <arpa/inet.h>
#include <cstdlib>
#include <string.h>
#include <sys/epoll.h>
#include  <unistd.h>

using namespace std;

int main(){
    // 创建socket
    int lfd = socket(PF_INET , SOCK_STREAM , 0);
    struct sockaddr_in saddr;
    saddr.sin_port = htons(9999);
    saddr.sin_family = AF_INET;
    saddr.sin_addr.s_addr = INADDR_ANY;
    // 绑定
    bind(lfd , (struct sockaddr*)&saddr , sizeof(saddr));
    // 监听
    listen(lfd , 8);
    // 创建epoll实例
    int epfd = epoll_create(100);
    // 添加监听文件描述符
    struct epoll_event epev;
    epev.events = EPOLLIN;
    epev.data.fd = lfd;
    epoll_ctl(epfd , EPOLL_CTL_ADD , lfd , &epev);

    struct epoll_event epevs[1024];
    while(1){
        int ret = epoll_wait(epfd , epevs , 1024 , -1);
        if(ret == -1){
            perror("epoll");
            exit(-1);
        }
        cout<<ret<<"个发生了改变"<<endl;
        for(int i = 0 ; i<ret ; i++){
            int curfd = epevs[i].data.fd;
            if(curfd == lfd){
                // 监听的文件描述符有客户端连接
                struct sockaddr_in caddr;
                socklen_t len = sizeof(caddr);
                int cfd = accept(lfd , (struct sockaddr*)&caddr , &len);
                epev.events = EPOLLIN;
                epev.data.fd = cfd;
                epoll_ctl(epfd , EPOLL_CTL_ADD , cfd , &epev);
            }
            else{
                // 有数据到达
                char buf[1024];
                int len = read(curfd , buf , sizeof(buf));
                if(len == -1){
                    perror("read");
                    exit(-1);
                }
                else if(len == 0){
                    cout<<"client close..."<<endl;
                    epoll_ctl(epfd , EPOLL_CTL_DEL , curfd , NULL);
                    close(curfd);
                }
                else{
                    cout<<"发来了数据:"<<buf<<endl;
                    write(curfd , buf , strlen(buf)+1);
                }
            }
        }
    }
    close(lfd);
    close(epfd);

    return 0;
}

epoll的两种工作模式

LT模式 - 水平触发

默认的工作模式,支持block/no-block;,内核告诉你一个文件描述符是否就绪了,然后你可以对这个就绪的 fd 进行IO操作。如果你不作任何操作,内核还是会继续通知你

#include <iostream>
#include <cstdio>
#include <arpa/inet.h>
#include <cstdlib>
#include <string.h>
#include <sys/epoll.h>
#include  <unistd.h>

using namespace std;

int main(){
    // 创建socket
    int lfd = socket(PF_INET , SOCK_STREAM , 0);
    struct sockaddr_in saddr;
    saddr.sin_port = htons(9999);
    saddr.sin_family = AF_INET;
    saddr.sin_addr.s_addr = INADDR_ANY;
    // 绑定
    bind(lfd , (struct sockaddr*)&saddr , sizeof(saddr));
    // 监听
    listen(lfd , 8);
    // 创建epoll实例
    int epfd = epoll_create(100);
    // 添加监听文件描述符
    struct epoll_event epev;
    epev.events = EPOLLIN;
    epev.data.fd = lfd;
    epoll_ctl(epfd , EPOLL_CTL_ADD , lfd , &epev);

    struct epoll_event epevs[1024];
    while(1){
        int ret = epoll_wait(epfd , epevs , 1024 , -1);
        if(ret == -1){
            perror("epoll");
            exit(-1);
        }
        cout<<ret<<"个发生了改变"<<endl;
        for(int i = 0 ; i<ret ; i++){
            int curfd = epevs[i].data.fd;
            if(curfd == lfd){
                // 监听的文件描述符有客户端连接
                struct sockaddr_in caddr;
                socklen_t len = sizeof(caddr);
                int cfd = accept(lfd , (struct sockaddr*)&caddr , &len);
                epev.events = EPOLLIN;
                epev.data.fd = cfd;
                epoll_ctl(epfd , EPOLL_CTL_ADD , cfd , &epev);
            }
            else{
                // 有数据到达
                char buf[5];
                int len = read(curfd , buf , sizeof(buf));
                if(len == -1){
                    perror("read");
                    exit(-1);
                }
                else if(len == 0){
                    cout<<"client close..."<<endl;
                    epoll_ctl(epfd , EPOLL_CTL_DEL , curfd , NULL);
                    close(curfd);
                }
                else{
                    cout<<"发来了数据:"<<buf<<endl;
                    write(curfd , buf , strlen(buf)+1);
                }
            }
        }
    }
    close(lfd);
    close(epfd);

    return 0;
}

ET模式 - 边沿触发

告诉工作方式,只支持no-block,在这种模式下,当描述符从未就绪变为就绪时,内核通过epol告诉你。然后它会假设你知道文件描述符已经就绪,并且不会再为那个文件描述符发送更多的就绪通知,直到你做了某些操作导致那个文件描述符不再为就绪状态了。但是请注意如果一直不对这个 fd 作IO操作,内核不会发送更多的通知 (only once) 。但是缓冲区中的数据不会丢失

ET模式效率比LT模式高,ET模式下必须使用非阻塞套接口,避免由于一个描述符的阻塞读/阻塞写操作把处理多个文件描述符的任务饿死;

要设置边沿触发

1. 需要在epoll_event中设置EPOLLET

2. 

UDP通信实现 - 无需多进程/多线程的并发实现

// server
#include <iostream>
#include <stdio.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
using namespace std;

int main(){
    // 创建socket
    int fd = socket(PF_INET , SOCK_DGRAM , 0);
    if(fd == -1){
        perror("socket");
        exit(-1);
    }
    // 绑定
    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port = htons(9999);
    addr.sin_addr.s_addr = INADDR_ANY;
    
    int ret = bind(fd ,(struct sockaddr*) &addr , sizeof(addr));
    if(ret == -1){
        perror("bind");
        exit(-1);
    }

    // 通信
    while(1){
        // 接收数据
        char buf[128];
        char ip[16];
        struct sockaddr_in caddr;
        socklen_t len = sizeof(caddr);
        int num = recvfrom(fd , buf , sizeof(buf) , 0 , (struct sockaddr*)&caddr , &len);
        string s1 = "IP: ";
        s1 += inet_ntop(AF_INET , &caddr.sin_addr.s_addr , ip , sizeof(ip));
        string s2 = "Port: ";
        s2 += ntohs(caddr.sin_port);
        cout<<s1<<" "<<s2<<endl;
        cout<<"rcv data: "<<buf<<endl;

        sendto(fd , buf , strlen(buf)+1 , 0 , (struct sockaddr*)&caddr , len);
    }

    close(fd);
    return 0;
}
// client
#include <iostream>
#include <stdio.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
using namespace std;

int main(){
    // 创建socket
    int fd = socket(PF_INET , SOCK_DGRAM , 0);
    if(fd == -1){
        perror("socket");
        exit(-1);
    }

    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port = htons(9999);
    inet_pton(AF_INET , "127.0.0.1" , &addr.sin_addr.s_addr);

    // 通信
    int num = 0;
    socklen_t len = sizeof(addr);
    while(1){
        char buf[128];
        sprintf(buf , "hello 647 %d" , num++);
        sendto(fd , buf , strlen(buf)+1 , 0 , (struct sockaddr*)&addr , len);
        // 接收数据
        int num = recvfrom(fd , buf , sizeof(buf) , 0 , NULL , NULL);
        cout<<"rcv data: "<<buf<<endl;

        sleep(1);

    }

    close(fd);
    return 0;
}

广播和组播 - 只能使用UDP

广播 - 向子网中多台计算机发送消息,每个广播消息都包含一个特殊的IP地址,这个IP中子网内主机标志部分的二进制全部为1;

1. 只能在局域网中使用

2. 客户端需要绑定服务器广播使用的端口,才能接收到广播消息

组播 (多播) - 标识一组IP接口,是在单播和广播之间的一种折中方案,多播数据包只由对其感兴趣的接口接收;

1. 组播可以用于局域网和广域网

2. 客户端需要加入多播组才能接收到

本地套接字通信

作用:用于进程间的通信;实现流程和网络套接字类似,一般采用TCP的通信流程

服务器端

1. 创建监听的套接字

      int lfd = socket(AF_UNIX/AF_LOCAL , SOCK_STREAM , 0);

2. 监听套接字绑定本地的套接字文件

        struct sockaddr_un addr;

        bind(lfd, addr, len); // 绑定成功后sun_path中的套接字文件会自动生成

3. 监听

4. 等待并接受客户端请求

5. 通信

6. 关闭连接
客户端

1. 创建通信的套接字

2. 绑定本地IP端口

3. 连接服务器

4. 通信

5. 关闭连接
// 服务端
#include <iostream>
#include <stdio.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <sys/un.h>
using namespace std;

int main(){
    unlink("server.sock");
    // 创建监听套接字
    int lfd = socket(AF_LOCAL , SOCK_STREAM , 0);
    if(lfd == -1){
        perror("socket");
        exit(-1);
    }
    // 绑定本地套接字文件
    struct sockaddr_un addr;
    addr.sun_family = AF_LOCAL;
    strcpy(addr.sun_path , "server.sock");
    int ret = bind(lfd , (struct sockaddr*)&addr , sizeof(addr));
    if(ret == -1){
        perror("bind");
        exit(-1);
    }
    // 监听
    ret = listen(lfd , 100);
    if(ret == -1){
        perror("listen");
        exit(-1);
    }
    // 等待客户端连接
    struct sockaddr_un caddr;
    socklen_t len = sizeof(caddr);
    int cfd = accept(lfd , (struct sockaddr*)&caddr , &len);
    if(cfd == -1){
        perror("accept");
        exit(-1);
    }

    cout<<"客户端文件:"<<caddr.sun_path<<endl;
    // 通信
    while(1){
        char buf[128];
        int len = recv(cfd , buf , sizeof(buf) , 0);
        if(len ==  -1){
            perror("recv");
            exit(-1);
        }
        else if(len == 0){
            cout<<"client close..."<<endl;
            break;
        }
        else{
            cout<<"recv data: "<<buf<<endl;
            send(cfd , buf , len , 0);
        }
    }
    close(cfd);
    close(lfd);
    return 0;
}
// 客户端
#include <iostream>
#include <stdio.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <sys/un.h>
using namespace std;

int main(){
    unlink("client.sock");
    // 创建监听套接字
    int cfd = socket(AF_LOCAL , SOCK_STREAM , 0);
    if(cfd == -1){
        perror("socket");
        exit(-1);
    }
    // 绑定本地套接字文件
    struct sockaddr_un addr;
    addr.sun_family = AF_LOCAL;
    strcpy(addr.sun_path , "client.sock");
    int ret = bind(cfd , (struct sockaddr*)&addr , sizeof(addr));
    if(ret == -1){
        perror("bind");
        exit(-1);
    }
    // 连接服务器
    struct sockaddr_un saddr;
    saddr.sun_family = AF_LOCAL;
    strcpy(saddr.sun_path , "server.sock");
    ret = connect(cfd , (struct sockaddr*)&saddr , sizeof(saddr));
    if(ret == -1){
        perror("connect");
        exit(-1);
    }

    // 通信
    int num = 0;
    while(1){
        char buf[128];
        sprintf(buf , "hello 647 %d\n" , num++);
        send(cfd , buf , strlen(buf)+1 , 0);

        int len = recv(cfd , buf , sizeof(buf) , 0);
        if(len ==  -1){
            perror("recv");
            exit(-1);
        }
        else if(len == 0){
            cout<<"Server close..."<<endl;
            break;
        }
        else{
            cout<<"recv data: "<<buf<<endl;
        }
        sleep(1);
    }
    close(cfd);
    return 0;
}

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

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

相关文章

vscode使用delve调试golang程序

环境配置 delve仓库&#xff0c;含有教程&#xff1a;https://github.com/go-delve/delve golang的debugging教程&#xff1a;https://github.com/golang/vscode-go/wiki/debugging > go version go version go1.20 windows/amd64> go install github.com/go-delve/de…

使用 Nacos 在 Spring Boot 项目中实现服务注册与配置管理

&#x1f337;&#x1f341; 博主猫头虎&#xff08;&#x1f405;&#x1f43e;&#xff09;带您 Go to New World✨&#x1f341; &#x1f984; 博客首页——&#x1f405;&#x1f43e;猫头虎的博客&#x1f390; &#x1f433; 《面试题大全专栏》 &#x1f995; 文章图文…

转录因子分析预测 cistrome db

Toolkit for CistromeDBhttp://dbtoolkit.cistrome.org/基因表达调控系列问题汇总(持续更新) - 知乎 (zhihu.com)

Java环境的安装

最近博主也是在学校开始学习了Java&#xff0c;也通过老师知道了可以通过大学生学生证申(bai)请(piao) IDEA的企业版&#xff08;社区版也是够学习用的&#xff09;有很多同学还是没有搞懂便做一下分享。 &#x1f331;博客主页&#xff1a;青竹雾色间. &#x1f618;博客制作…

初步了解android如何锁键

百年三万六千日&#xff0c;光阴只有瞬息间。 手机下面的三个图形&#xff0c;正方形&#xff0c;园形&#xff0c;三角形分别的什么建&#xff1f;都起到什么功能&#xff1f; 三角形的那个叫返回键&#xff0c;就是可以返回你的上一个操作; 圆形是HOME键&#xff0c;按一下可…

线上 udp 客户端请求服务端客户端句柄泄漏问题

本题分别从如下三个方面来分享&#xff1a; 问题描述 自定义连接池的编写 common_pool 的使用 问题描述 线上有一个业务&#xff0c;某个通服务通知 udp 客户端通过向 udp 服务端&#xff08;某个硬件设备&#xff09;发送 udp 包来进行用户上线操作 当同时有大量的请求打到…

Docker 实现 MySQL 一主一从配置

1、新建主服务器容器实例&#xff0c;端口&#xff1a; 3307 docker run \ -p 3307:3306 \ --name mysql-master \ -v /var/docker/mysql-master/log:/var/log/mysql \ -v /var/docker/mysql-master/data:/var/lib/mysql \ -v /var/docker/mysql-master/conf:/etc/mysql \ --p…

MSST-NET:用于高光谱和多光谱图像融合的多尺度空间-光谱Transfomer网络

1.网络结构 主要贡献&#xff1a; 提出了一种多尺度空间光谱Transformer网络光谱多头注意力旨在提取光谱特征引入多尺度波段/补丁嵌入来提取多尺度特征自监督训练 痛点&#xff1a;卷积核的感受野有限&#xff0c;基于卷积神经网络CNN的融合方法无法利用特征图中的全局关系…

pip install mysqlclient报错

安装mysqlclient时报错 先查看安装的python版本 python -V 根据版本下载下载对应的 mysqlclient 文件 https://www.lfd.uci.edu/~gohlke/pythonlibs/#mysqlclienthttps://www.lfd.uci.edu/~gohlke/pythonlibs/#mysqlclient 我的是3.7版本的 就下载3.7版本&#xff0c;64位系…

aarch64 arm64 部署 stable diffusion webui 笔记 【1】准备 venv 安装pytorch 验证cuda

aarch64 pytorch(没有aarch64对应版本&#xff0c;自行编译) pytorch-v2.0.1 cuda arm64 aarch64 torch 2.0.1cu118 源码编译笔记【2】验证cuda安装 成功_hkNaruto的博客-CSDN博客 创建venv [rootceph3 stable-diffusion-webui]# /usr/local/Python-3.10.12/bin/python3 -m v…

一种结合白平衡统计信息和曝光信息的软光敏算法专利学习(专利四)

图像分块&#xff1a; 参见下图&#xff0c;一幅图像大小为5*6(像素)&#xff0c;每个像素包含R、G、B三个分量&#xff0c;该图像划分为4个分块&#xff0c;第一分块的大小为3*3像素&#xff0c;第二分块的大小为3*3(像素)&#xff0c;第三分块的大小为2*3像素&#xff0c;第四…

OpenCV基础(一):图片加载,图片腐蚀,图片模糊,图片边缘检测,图片保存

前言 在Android音视频开发中&#xff0c;网上知识点过于零碎&#xff0c;自学起来难度非常大&#xff0c;不过音视频大牛Jhuster提出了《Android 音视频从入门到提高 - 任务列表》&#xff0c;结合我自己的工作学习经历&#xff0c;我准备写一个音视频系列blog。本文是音视频系…

Android逆向学习(四)app修改smali函数跳过弹窗广告,等待广告,更新提醒

Android逆向学习&#xff08;四&#xff09;app修改smali函数跳过弹窗广告&#xff0c;等待广告&#xff0c;更新提醒 一、写在前面 这是吾爱破解课程的第三个练习&#xff0c;我在写这篇博客时遇到了vscode插件bug&#xff0c;已经想办法联系原作者了&#xff0c;希望能够尽…

【已解决】pyqt5界面拖拽文件进入时,显示禁止图标,槽函数也没有进入。怎么办?

我今天遇到了一个很无语的问题&#xff0c;不知道为啥&#xff0c;用 QMainwindow 创建了一个简单的界面&#xff0c;里面只有一个 Qlabel。 想要尝试拖拽文件进来&#xff0c;然后打印文件路径。但是怎么改都没反应。一直显示禁止的一个图标&#xff0c;类似于&#xff1a; 一…

LeetCode——顺时针打印矩形

题目地址 力扣&#xff08;LeetCode&#xff09;官网 - 全球极客挚爱的技术成长平台 题目解析 按照顺时针一次遍历&#xff0c;遍历外外层遍历里层。 代码如下 class Solution { public:vector<int> spiralOrder(vector<vector<int>>& matrix) {if(…

anaconda3最新版安装|使用详情|Error: Please select a valid Python interpreter

Win11查看安装的Python路径及安装的库 anaconda3最新版安装|使用详情|Error: Please select a valid Python interpreter 介绍开源包管理系统和环境管理系统 &#xff0c;包括多种语言的包安装&#xff0c;运行&#xff0c;更新&#xff0c;删除&#xff0c;最重要的是可以解…

YOLOV8模型改进-添加CBAM注意力机制

1.CBAM介绍 CBAM注意力机制是yolov8自带的注意力机制&#xff0c;它是通道注意模块和空间注意模块的结合。 2.查看CBAM在yolov8中的位置 路径&#xff1a;ultralytics/nn/modules/conv.py 3.添加CBAM 1.首先打开tasks.py文件&#xff1a;ultralytics/nn/modules/tasks.py 2.…

centos编译升级cmake,痛苦的Linux小白

环境 root 用户 下载 cmake官网下载地址&#xff1a;https://cmake.org/download/ 获取下载地址&#xff0c;右击cmake-3.27.4.tar.gz 命令行输入链接地址&#xff0c;下载 wget https://github.com/Kitware/CMake/releases/download/v3.27.4/cmake-3.27.4.tar.gz解压 tar -zx…

​Vue + Element UI前端篇(二):Vue + Element 案例 ​

Vue Element UI 实现权限管理系统 前端篇&#xff08;二&#xff09;&#xff1a;Vue Element 案例 导入项目 打开 Visual Studio Code&#xff0c;File --> add Folder to Workspace&#xff0c;导入我们的项目。 安装 Element 安装依赖 Element 是国内饿了么公司提…

【算法训练-链表 三】删除链表的倒数第N个节点

最近工作有点忙&#xff0c;刷道中等题缓解下压力&#xff01;删除链表的倒数第N个节点&#xff0c;使用【链表】这个基本的数据结构来实现&#xff0c;这个高频题的站点是&#xff1a;CodeTop&#xff0c;筛选条件为&#xff1a;目标公司最近一年出现频率排序&#xff0c;由高…