【socket】基于poll和epoll通信温度上报

网络socket通信

*
poll函数
epoll函数
poll代码实现
epoll代码实现

poll函数

poll是Linux中的字符设备驱动中的一个函数,poll本质上和select没有区别,它将用户传入的数组拷贝到内核空间,然后查询每个fd对应的设备状态,如果设备就绪则在设备等待队列中加入一项并继续遍历,如果遍历完所有fd后没有发现就绪设备,则挂起当前进程,直到设备就绪或者主动超时,被唤醒后它又要再次遍历fd。这个过程经历了多次无谓的遍历。

int poll(struct pollfd *fds, nfds_t nfds, int timeout);

参数:
pollfd:指向一个struct pollfd类型的数组,每一个pollfd结构体指定了一个被监视的文件描述符,指示poll()监视多个文件描述符。每个结构体的events域是监视该文件描述符的事件掩码,由用户来设置这个域。revents域是文件描述符的操作结果事件掩码,内核在调用返回时设置这个域,events域中请求的任何事件都可能在revents域中返回。

nfds 指定数组中监听的元素个数.

timeout指定等待的毫秒数,无论I/O是否准备好,poll都会返回。timeout指定为负数值表示无限超时,使poll()
一直挂起直到一个指定事件发生;timeout为0指示poll调用立即返回并列出准备好I/O的文件描述符,但并不等待其它的事件。
struct pollfd
{
 int fd;
 short events;
 short revents;
} ;

下表列出指定 events 标志以 及测试 revents 标志的一些常值:

【socket】基于poll和epoll通信温度上报
timeout:该函数成功调用时,poll()返回结构体中revents域不为0的文件描述符个数;如果在超时前没有任何事件发生,poll()返回0; 失败时,poll()返回-1,并设置errno为下列值之一:
EBADF    一个或多个结构体中指定的文件描述符无效。  
EFAULTfds 指针指向的地址超出进程的地址空间。  
EINTR   请求的事件之前产生一个信号,调用可以重新起。  
EINVALnfds 参数超出PLIMIT_NOFILE值。  
ENOMEM    可用内存不足,无法完成请求。

epoll函数

epoll对文件描述符的操作有两种模式: LT(level trigger) 和 ET(edge trigger)。LT模式是默认模式,LT模式与ET模式的区别如下:

水平触发LT模式

  • 水平触发的主要特点是,如果用户在监听epoll事件,当内核有事件的时候,会拷贝
    给用户态事件,但是如果用户只处理了一次,那么剩下没有处理的会在下一次
    epoll_wait再次返回该事件。
    这样如果用户永远不处理这个事件,就导致每次都会有该事件从内核到用户的拷
    贝,耗费性能,但是水平触发相对安全,最起码事件不会丢掉,除非用户处理完

边沿触发ET模式

  • 边缘触发,相对跟水平触发相反,当内核有事件到达, 只会通知用户一次,至于用
    户处理还是不处理,以后将不会再通知。这样减少了拷贝过程,增加了性能,但是
    相对来说,如果用户马虎忘记处理,将会产生事件丢的情况。

边沿触发仅触发一次,水平触发会一直触发。

int epoll_create(int size);
功能:
创建epoll
参数:
    size忽略不用
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *ev);
功能:修改epoll的增删改查
参数:
    epfd是epoll_create返回值
    op是用来指定需要执行的操作
    fd:指明了要修改兴趣列表中的哪一个文件描述符的设定。该参数可以是代表管道、FIFO、套接字、POSIX消息队列、inotify实例、终端、设备,甚至是另一个epoll实例的文件描述符
    ev:的指针结构体

op指定操作的值:添加,删除,修改对fd的监听

EPOLL_CTL_ADD添加fd到epollEPOLL_CTL_MOD修改已经注册fd的事件EPOLL_CTL_DEepfd删除一个fd

结构体epoll_event的指针,结构体的定义如下:

typedef union epoll_data
{
 void *ptr;
 int fd;
 uint32_t u32;
 uint64_t u64;
} epoll_data_t;

struct epoll_event
{
 uint32_t events;
 epoll_data_t data;
};
int epoll_wait(int epfd, struct epoll_event *evlist, int maxevents, int timeout);
功能:
等待就绪事件
参数:
    epfd是epoll_create()的返回值
    evlist所指向的结构体数组中返回的是有关就绪态文件描述符的信息,数组evlist的空间由调用者负责申请;
    maxevents指定所evlist数组里包含的元素个数;
    timeout用来确定epoll_wait()的阻塞行为,有如下几种:
    如果timeout等于-1,调用将一直阻塞,直到兴趣列表中的文件描述符上有事件产生或者直到捕获到一个信号为止。
    如果timeout等于0,执行一次非阻塞式地检查,看兴趣列表中的描述符上产生了哪个事件。
    如果timeout大于0,调用将阻塞至多timeout毫秒,直到文件描述符上有事件发生,或者直到捕获到一个信号为止

events中的值如下所示:

常量说明作为 epoll_ctl()的输入作为epoll_wait()的返回EPOLLIN可读取非高优先级数据能能EPOLLPRI可读取高优先级数据能能EPOLLRDHUPsocket对端关闭能能EPOLLOUT普通数据可写能能EPOLLET采用边沿触发事件通知能EPOLLONESHOT在完成事件通知之后禁用检查能EPOLLERR有错误发生能POLLHUP出现挂断能

epoll的优点

  • 支持一个进程打开大数目的socket描述符(fd)
  • IO效率不随fd数目增加而线性下降
  • 使用mmap加速内核与用户空间的消息传递
  • 内核微调

poll代码实现

#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include

#define ARRAY_SIZE(x)   (sizeof(x)/sizeof(x[0]))

int socket_Server_init(char *listen_ip,int listen_port);
void sqlite_tem(char *buf);

void print_usage(char *progname)
{
     printf("%s usage: \n", progname);
     printf("-p(--port): sepcify server listen port.\n");
     printf("-h(--Help): print this help information.\n");
     printf("-d(--daemon):set program running on background\n");
     return ;
}

int main (int argc, char **argv)
{

    int                 listenfd = -1;
    int                 clifd;
    struct sockaddr_in  servaddr;
    struct sockaddr_in  cliaddr;
    socklen_t           len;
    int                serv_port = 0;
    int                 ch;
    int                 rv ;
    int                 on = 1;

    char                buf[1024];
    int                 i,j;
    int                 found;
    int                 max;
    int                 daemon_run = 0;
    char                *progname = NULL;
    struct pollfd       fds_arry[1024];

struct option opts[] =
{
     {"daemon",no_argument,NULL,'b'},
     {"port", required_argument, NULL, 'p'},
     {"help", no_argument, NULL, 'h'},
     {NULL, 0, NULL, 0}
};
progname = basename(argv[0]);

 while( (ch=getopt_long(argc, argv, "bp:h", opts, NULL)) != -1 )
      {
           switch(ch)
                {
                    case 'p':
                        serv_port=atoi(optarg);
                        break;
                    case 'b':
                        daemon_run=1;
                        break;
                    case 'h':
                        print_usage(argv[0]);
                        return 0;
                }
    }
  if( !serv_port )
  {
      print_usage(argv[0]);
      return 0;
  }
    if( (listenfd = socket_Server_init(NULL, serv_port)) <0)
    {
        printf("ERROR %s server listen on port %d failure\n",argv[0],serv_port);
        return -1;
    }

    printf("%s server start to listen on port %d\n",argv[0],serv_port);

    if(daemon_run)
    {
        daemon(0,0);
    }

    for(i=0; i<ARRAY_SIZE(fds_arry); i++)
    {
        fds_arry[i].fd=-1;
    }
    fds_arry[0].fd = listenfd;

    fds_arry[0].events = POLLIN;
    max = 0;

    for( ; ; )
    {

        rv = poll(fds_arry, max+1, -1);
    if(rv < 0)
    {
        printf("POLL failure:%s\n",strerror(errno));
        break;
    }
    else if( rv ==0 )
    {
        printf("poll get timeout\n");
        continue;
    }
    if( fds_arry[0].revents & POLLIN )
    {
        if( (clifd=accept(listenfd,(struct sockaddr *)NULL,NULL)) < 0)
        {
            printf("accept new client failure:%s\n",strerror(errno));
            continue;
        }

        found = 0;
        for(i=0; i<ARRAY_SIZE(fds_arry);i++)
        {
            if( fds_arry[i].fd< 0 )
            {
                printf("accept new client [%d] and add it into array\n",clifd);
                fds_arry[i].fd = clifd;
                fds_arry[i].events = POLLIN;
                found = 1;
                break;
            }
        }
        if(!found)
        {
            printf("accept new client [%d] but full, so refuse it\n",clifd);
            close(clifd);
            continue;
        }
        max = i>max ? i:max;
        if( rv 0 )
            continue;
    }
    else
    {
        for ( i=1; i<ARRAY_SIZE(fds_arry);i++)
        {
            if(fds_arry[i].fd < 0)
                continue;
            if( (rv=read(fds_arry[i].fd,buf,sizeof(buf))) 0)
            {
                printf("socket [%d] read failure or get disconnected\n",fds_arry[i].fd);
                close(fds_arry[i].fd);
                fds_arry[i].fd=-1;
            }
            else
            {
                printf("%s\n",buf);
                sqlite_tem(buf);
                printf("Database inserted successfully!\n");
            }
        }
    }
}
cleanup:
    close(listenfd);
    return 0;
}

int socket_Server_init(char *listen_ip, int listen_port)
{

    struct sockaddr_in  servaddr;
    int     rv = 0;
    int     on = 1;
    int     listenfd;

    if( (listenfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
    {
        printf("use socket()to create a TCP socket failure:%s\n",strerror(errno));
                return -1;
    }
    setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));

    memset(&servaddr,0,sizeof(servaddr));
    servaddr.sin_family=AF_INET;
    servaddr.sin_port = htons(listen_port);

    if( !listen_ip )
    {
        servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    }
    else
    {
        if(inet_pton(AF_INET, listen_ip, &servaddr.sin_addr) 0)
        {
            printf("inet_pton set listen IP address failure\n");
            rv = -2;
            goto cleanup;
        }
    }

    if( bind(listenfd,(struct sockaddr *)&servaddr,sizeof(servaddr)) < 0)
    {
        printf("socket[%d] bind to port failure:%s\n",listenfd,strerror(errno));
        rv = -3;
        goto cleanup;
    }

    if( listen(listenfd,13) < 0)
    {
         printf("use bind to bind tcp socket failure:%s\n",strerror(errno));
         rv = -4;
         goto cleanup;
    }

cleanup:
    if(rv < 0)
        close(listenfd);
    else
        rv = listenfd;

    return rv;
}

void sqlite_tem(char *buf)
{
    int             nrow=0;
    int             ncolumn = 0;
    char          **azResult=NULL;
    int             rv;
    sqlite3        *db=NULL;
    char           *zErrMsg = 0;
    char            sql1[100];
    char           *ipaddr=NULL;
    char           *datetime=NULL;
    char           *temper=NULL;
    char           *sql = "create table if not exists temperature(ipaddr char(30), datetime char(50), temper  char(30))";

    ipaddr = strtok(buf,"/");
    datetime = strtok(NULL, "/");
    temper = strtok(NULL, "/");

    rv = sqlite3_open("tempreture.db", &db);
    if(rv)
    {
        printf("Can't open database:%s\n", sqlite3_errmsg(db));
        sqlite3_close(db);
        return;
    }
    printf("opened a sqlite3 database named tempreture.db successfully!\n");

    int ret = sqlite3_exec(db,sql, NULL, NULL, &zErrMsg);
    if(ret != SQLITE_OK)
    {
        printf("create table fail: %s\n",zErrMsg);
    }

    if(snprintf(sql1,sizeof(sql1), "insert into temper values('%s','%s','%s')", ipaddr, datetime, temper) < 0)
    {
        printf("Failed to write data\n");
    }

    sqlite3_exec(db, sql1, 0, 0, &zErrMsg);
    sqlite3_free(zErrMsg);
    sqlite3_close(db);
    return;
}

epoll代码实现

#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include

#include "sqlite3.h"

#define BACKLOG 13
#define MAX_EVENTS  512

void print_usage(char* progname);
void sig_stop(int signum);
void sqlite_tem(char *buf);
int socket_listen(char *listen_ip, int port);

static int g_stop = 0;

int main(int argc, char* argv[])
{
    int                     rv;
    int                     ret;
    int                     opt;
    int                     idx;
    int                     port;
    int                     log_fd;
    int                     ch = 1;
    int                     daemon_run = 0;

    int                     ser_fd = -1;
    int                     cli_fd = -1;
    struct sockaddr_in      cli_addr;
    socklen_t               cliaddr_len = 20;
    int                     epollfd;
    struct epoll_event      event;
    struct epoll_event      event_array[MAX_EVENTS];
    int                     events;
    int                     found;
    int                     a;
    int                     i;

    char                    *zErrMsg;
    sqlite3                 *db;
    char                    buf[1024];

    struct option            opts[] = {
            {"daemon", no_argument, NULL, 'd'},
            {"port", required_argument, NULL, 'p'},
            {"help", no_argument, NULL, 'h'},
            {NULL, 0, NULL, 0}
    };
    while ((opt = getopt_long(argc, argv, "dp:h", opts, &idx)) != -1)
    {
        switch (opt)
        {
        case 'd':
            daemon_run = 1;
            break;
        case 'p':
            port = atoi(optarg);
            break;
        case 'h':
            print_usage(argv[0]);
            return 0;
        }
    }

    if (!port)
    {
        print_usage(argv[0]);
        return 0;
    }

    if (daemon_run)
    {
        printf("Program %s is running at the background now\n", argv[0]);

        log_fd = open("receive_temper.log", O_CREAT | O_RDWR, 0666);
        if (log_fd < 0)
        {
            printf("Open the logfile failure : %s\n", strerror(errno));
            return 0;
        }

        dup2(log_fd, STDOUT_FILENO);
        dup2(log_fd, STDERR_FILENO);

        if ((daemon(1, 1)) < 0)
        {
            printf("Deamon failure : %s\n", strerror(errno));
            return 0;
        }
    }

    signal(SIGUSR1, sig_stop);

    if( (ser_fd = socket_listen(NULL, port)) < 0 )
    {
        printf("ERROR: %s server listen on serv_port %d failure\n", argv[0], port);
        return -2;
    }
    printf("server start to listen on serv_port %d\n",  port);

    if ((epollfd = epoll_create(MAX_EVENTS)) < 0)
    {
        printf("epoll_create failure:%s\n", strerror(errno));
        return 0;
    }

    event.events = EPOLLIN;
    event.data.fd = ser_fd;

    if (epoll_ctl(epollfd, EPOLL_CTL_ADD, ser_fd, &event) < 0)
    {
        printf("epoll add ser_fd failure:%s\n", strerror(errno));
        return 0;
    }

    while (!g_stop)
    {
        events = epoll_wait(epollfd, event_array, MAX_EVENTS, -1);
        if (events < 0)
        {
            printf("epoll failure:%s\n", strerror(errno));
            break;
        }
        else if (events == 0)
        {
            printf("epoll get timeout\n");
            continue;
        }

        for (i = 0;i < events;i++)
        {
            if ((event_array[i].events & EPOLLERR) || (event_array[i].events & EPOLLHUP))
            {
                printf("epoll_wait get error on fd[%d]:%s\n", event_array[i].data.fd, strerror(errno));
                epoll_ctl(epollfd, EPOLL_CTL_DEL, event_array[i].data.fd, NULL);
                close(event_array[i].data.fd);
            }

            if (event_array[i].data.fd == ser_fd)
            {
                cli_fd = accept(ser_fd, (struct sockaddr*) & cli_addr, &cliaddr_len);
                if (cli_fd < 0)
                {
                    printf("Accept the request from client failure:%s\n", strerror(errno));
                    continue;
                }
                event.data.fd = cli_fd;
                event.events = EPOLLIN;
                if (epoll_ctl(epollfd, EPOLL_CTL_ADD, cli_fd, &event) < 0)
                {
                    printf("epoll add client socket failure:%s\n", strerror(errno));
                    close(cli_fd);
                    continue;
                }
            }
            else
            {
                memset(buf, 0, sizeof(buf));

                a = read(cli_fd, buf, sizeof(buf));
                if (a < 0)
                {
                    printf("Read information from client failure:%s\n", strerror(errno));
                    close(cli_fd);
                    exit(0);
                }
                else if (a == 0)
                {
                    printf("The connection with client has broken!\n");
                    close(cli_fd);
                    exit(0);
                }
                else
                {
                    printf("%s\n",buf);
                    sqlite_tem(buf);
                    printf("Database inserted successfully!\n");
                }

            }
        }
    }
    close(ser_fd);

    return 0;
}

void print_usage(char* progname)
{
    printf("-d(--daemon):let program run in the background.\n");
    printf("-p(--port):enter server port.\n");
    printf("-h(--help):print this help information.\n");

    return;
}

void sig_stop(int signum)
{
    if (SIGUSR1 == signum)
    {
        g_stop = 1;
    }

    return;
}

int socket_listen(char *listen_ip, int port)
{
    int                     rv = 0;
    int                     on = 1;
    int                     ser_fd;
    struct sockaddr_in      servaddr;

    if ( (ser_fd = socket(AF_INET, SOCK_STREAM, 0)) < 0 )
    {
        printf("Use socket() to create a TCP socket failure: %s\n", strerror(errno));
        return -1;
    }

    setsockopt(ser_fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));

    memset(&servaddr, 0, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_port = htons(port);

    if( !listen_ip )
    {
        servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    }
    else
    {
        if( inet_pton(AF_INET, listen_ip, &servaddr.sin_addr)  0 )
        {
            printf("Inet_pton() set listen IP address failure\n");
            rv = -2;
            goto cleanup;
        }
    }

    if( bind(ser_fd, (struct sockaddr *) &servaddr, sizeof(servaddr)) < 0 )
    {
        printf("Use bind() to bind the TCP socket failure: %s\n", strerror(errno));
        rv = -3;
        goto cleanup;
    }

    if( listen(ser_fd, 64) < 0 )
    {
        printf("Use bind() to bind the TCP socket failure: %s\n", strerror(errno));
        rv = -4;
        goto cleanup;
    }

cleanup:
    if( rv < 0 )
        close(ser_fd);
    else
        rv = ser_fd;
    return rv;
}

void sqlite_tem(char *buf)
{
    int             nrow=0;
    int             ncolumn = 0;
    char          **azResult=NULL;
    int             rv;
    sqlite3        *db=NULL;
    char           *zErrMsg = 0;
    char            sql1[100];
    char           *ipaddr=NULL;
    char           *datetime=NULL;
    char           *temper=NULL;
    char           *sql = "create table if not exists temperature(ipaddr char(30), datetime char(50), temper  char(30))";

    ipaddr = strtok(buf,"/");
    datetime = strtok(NULL, "/");
    temper = strtok(NULL, "/");

    rv = sqlite3_open("tempreture.db", &db);
    if(rv)
    {
        printf("Can't open database:%s\n", sqlite3_errmsg(db));
        sqlite3_close(db);
        return;
    }
    printf("opened a sqlite3 database named tempreture.db successfully!\n");

    int ret = sqlite3_exec(db,sql, NULL, NULL, &zErrMsg);
    if(ret != SQLITE_OK)
    {
        printf("create table fail: %s\n",zErrMsg);
    }

    if(snprintf(sql1,sizeof(sql1), "insert into temper values('%s','%s','%s')", ipaddr, datetime, temper) < 0)
    {
        printf("Failed to write data\n");
    }

    sqlite3_exec(db, sql1, 0, 0, &zErrMsg);
    sqlite3_free(zErrMsg);
    sqlite3_close(db);
    return;
}

Original: https://www.cnblogs.com/Ye-Wei/p/16728599.html
Author: 西故黄鹤楼
Title: 【socket】基于poll和epoll通信温度上报

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/606344/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • rsync

    rsync简介 rsync是linux系统下的数据镜像备份工具。使用快速增量备份工具Remote Sync可以远程同步,支持本地复制,或者与其他SSH、rsync主机同步。 rsy…

    Linux 2023年6月6日
    089
  • FastDFS安装和简介详细总结

    1、fastDFS简介 1 FastDFS是用c语言编写的一款开源的分布式文件系统。 2 FastDFS为互联网量身定制,充分考虑了冗余备份、负载均衡、线性扩容等机制,并注重高可用…

    Linux 2023年6月7日
    0124
  • Linux FastDFS安装

    1.0、 fastDFS fastDFS介绍 FastDFS是用c语言编写的一款开源的分布式文件系统,它是由淘宝资深架构师余庆编写并开源。FastDFS专为互联网量身定制,充分考虑…

    Linux 2023年6月7日
    087
  • DNS

    DNS是域名系统(Domain Name System),简单来说就是平时上网输入的URL,如 www.baidu.com 就是域名,而DNS就是将这个域名解析成IP地址,如 ww…

    Linux 2023年6月7日
    085
  • Java动态脚本Groovy,高级啊!

    前言:请各大网友尊重本人原创知识分享,谨记本人博客: 南国以南i 简介: Groovy是用于Java虚拟机的一种敏捷的动态语言,它是一种成熟的面向对象编程语言,既可以用于面向对象编…

    Linux 2023年6月14日
    0145
  • redis的事务不是原子性

    Reference: https://blog.csdn.net/u011692780/article/details/81213010 一、事务的四大特性 关系型数据库的事务具有…

    Linux 2023年5月28日
    093
  • firewalld dbus接口使用指南

    404. 抱歉,您访问的资源不存在。 可能是URL不正确,或者对应的内容已经被删除,或者处于隐私状态。 [En] It may be that the URL is incorre…

    Linux 2023年5月27日
    0127
  • 苹果手机使用altstore免越狱安装第三方应用

    转自52pojie &#x5F00;&#x53D1;&#x4EBA;&#x5458;Riley Testut&#x63A8;&#x5…

    Linux 2023年6月7日
    0274
  • Linux巡检脚本

    #!/bin/bash sys:centos6.x/7.x [ $(id -u) -ne 0 ] && echo "&#x8BF7;&#x…

    Linux 2023年6月6日
    098
  • Conda虚拟环境中的pip,python 等路径是base环境而非本虚拟环境

    现象 一次运行项目发现,原本可以正常运行的项目,突然提示有个包不存在,但是经过 pip list 发现在我的虚拟环境中是存在这个包的,并且此时我是正常的位于我的虚拟环境中。 报错:…

    Linux 2023年6月7日
    098
  • win11下配置vue3版本

    安装node.js PS:全局需要使用管理员权限打开CMD** 下载nodejs的地址 选择左边就好 下载安装后,选择自己需要安装的盘符,即可,不再叙述。 打开CMD查看node是…

    Linux 2023年6月14日
    0107
  • PHP 获取数组长度

    count()函数,默认是获取一维数组,参数为:COUNT_NORMAL,添加第二个参数:COUNT_RECURSIVE,则可以获取多维关联数组的长度(意思为递归获取),例如:co…

    Linux 2023年6月7日
    0123
  • logstash写入文件慢的问题排查记录

    终于找到根本原因了!!!!! logstash部署到k8s集群内部的,当所在节点的CPU资源被其他应用抢占时,logstash的处理速度就会降低 问题现象 logstash从kaf…

    Linux 2023年6月14日
    0175
  • WIN10下启动VMware虚拟机蓝屏的解决办法

    问题: 每次启动虚拟机就会蓝屏,提示错误代码: PAGE_FAULT_IN_NONPAGED_AREA 解决办法: 禁用 Hyper-V 功能 打开”控制面板&#821…

    Linux 2023年6月7日
    086
  • 【C++基础】数据类型

    C++规定在创建一个变量或者产量时,必须要指定相应的数据类型,否则无法给变量分配内存空间 数据类型的存在意义:给变量分配合适的内存空间 整型 作用:整型变量表示的是整数类型的数据 …

    Linux 2023年6月13日
    099
  • FusionCompute制作Linux虚拟机模板

    创建虚拟机 创建虚拟机下一步这里实验就创建红帽7.4选择存储磁盘精简创建先在存储上传红帽镜像挂载镜像VNC登录安装安装完成配置yum源安装bzip*(因为最小化安装没有bzip程序…

    Linux 2023年6月8日
    0142
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球