C++项目详细分析_WebServer

news2024/9/22 4:08:20

前言

项目地址
项目介绍

源码详细分析

项目路径如下:
在这里插入图片描述

1.webserver.cpp

头文件和构造函数

#include "webserver.h"

WebServer::WebServer()
{
    // http_conn类对象
    users = new http_conn[MAX_FD];

    // root文件夹路径
    char server_path[200];
    getcwd(server_path, 200);  // 获取当前工作目录
    char root[6] = "/root";
    m_root = (char *)malloc(strlen(server_path) + strlen(root) + 1);
    strcpy(m_root, server_path);
    strcat(m_root, root);

    // 定时器
    users_timer = new client_data[MAX_FD];
}
  1. #include "webserver.h":包含WebServer类的声明。
  2. 构造函数WebServer::WebServer():初始化WebServer对象。
    • users = new http_conn[MAX_FD];:创建一个http_conn对象数组,用于处理客户端连接。
    • getcwd(server_path, 200);:获取当前工作目录。
    • 设置root文件夹路径:将当前工作目录和"/root"拼接成新的字符串,存储在m_root中。使用strcpy(m_root, server_path)将当前工作目录的路径复制到m_root中。使用strcat(m_root, root)将/root附加到当前工作目录路径的末尾。
    • users_timer = new client_data[MAX_FD];:创建一个client_data对象数组,用于管理客户端定时器。
      这段代码的主要作用是构建服务器的根目录路径,将当前工作目录与/root路径拼接在一起,最终用于指向服务器资源文件的位置(如HTML、GIF、JPG等文件)。

析构函数

WebServer::~WebServer()
{
    close(m_epollfd);
    close(m_listenfd);
    close(m_pipefd[1]);
    close(m_pipefd[0]);
    delete[] users;
    delete[] users_timer;
    delete m_pool;
}
  • 析构函数WebServer::~WebServer():释放资源。
    • 关闭epoll文件描述符、监听文件描述符、管道文件描述符。
    • 删除动态分配的usersusers_timer数组。
    • 删除线程池对象m_pool

初始化函数

void WebServer::init(int port, string user, string passWord, string databaseName, int log_write, 
                     int opt_linger, int trigmode, int sql_num, int thread_num, int close_log, int actor_model)
{
    m_port = port;
    m_user = user;
    m_passWord = passWord;
    m_databaseName = databaseName;
    m_sql_num = sql_num;
    m_thread_num = thread_num;
    m_log_write = log_write;
    m_OPT_LINGER = opt_linger;
    m_TRIGMode = trigmode;
    m_close_log = close_log;
    m_actormodel = actor_model;
}
  • 初始化函数WebServer::init:初始化服务器的各项参数。
    • 设置端口号、数据库用户名和密码、数据库名、日志写入方式、关闭连接选项、触发模式、数据库连接池大小、线程池大小、日志关闭选项、事件模型等。

触发模式函数

void WebServer::trig_mode()
{
    // LT + LT
    if (0 == m_TRIGMode)
    {
        m_LISTENTrigmode = 0;
        m_CONNTrigmode = 0;
    }
    // LT + ET
    else if (1 == m_TRIGMode)
    {
        m_LISTENTrigmode = 0;
        m_CONNTrigmode = 1;
    }
    // ET + LT
    else if (2 == m_TRIGMode)
    {
        m_LISTENTrigmode = 1;
        m_CONNTrigmode = 0;
    }
    // ET + ET
    else if (3 == m_TRIGMode)
    {
        m_LISTENTrigmode = 1;
        m_CONNTrigmode = 1;
    }
}
  • 触发模式函数WebServer::trig_mode:设置监听和连接的触发模式。
    • LT(水平触发):0
    • ET(边缘触发):1
    • 根据m_TRIGMode的值来设置监听和连接的触发模式。
这段代码的作用:

WebServer::trig_mode函数用于设置服务器的监听(m_LISTENTrigmode)和连接(m_CONNTrigmode)的触发模式。触发模式有两种:LT(水平触发,Level-Triggered)ET(边缘触发,Edge-Triggered),分别用01表示。

根据m_TRIGMode的值,trig_mode函数将决定监听和连接操作的触发模式:

  • m_TRIGMode == 0: 监听和连接均采用LT(水平触发)。
  • m_TRIGMode == 1: 监听采用LT,连接采用ET(边缘触发)。
  • m_TRIGMode == 2: 监听采用ET,连接采用LT。
  • m_TRIGMode == 3: 监听和连接均采用ET。
水平触发(LT)和边缘触发(ET)的区别:

这两种触发模式是针对I/O事件的不同处理方式,通常用于 epoll 或者 select/poll等 I/O 多路复用机制。

  1. LT(Level-Triggered,水平触发)

    • 工作方式:在水平触发模式下,只要文件描述符上还有数据未处理,epoll会反复通知应用程序。因此,只要某个事件没有被处理,下一次调用epoll_wait时,仍会返回该事件。
    • 特点
      • 容易编程,适合大部分场景。
      • 可能导致重复处理同一事件。
    • 场景:适用于要求及时处理事件的场景,编程简单,但效率相对较低。
  2. ET(Edge-Triggered,边缘触发)

    • 工作方式:在边缘触发模式下,epoll只会在文件描述符状态发生变化时通知应用程序,且只通知一次。如果应用程序没有在第一次通知时处理完所有数据,后续epoll_wait不会再通知该事件,除非状态再次发生变化。
    • 特点
      • 更高效,减少了系统调用次数。
      • 编程复杂,需要确保一次性处理所有数据,否则可能会错过事件。
    • 场景:适用于高性能、高并发服务器,需要精确控制I/O操作。

日志写入函数

void WebServer::log_write()
{
    if (0 == m_close_log)
    {
        // 初始化日志
        if (1 == m_log_write)
            Log::get_instance()->init("./ServerLog", m_close_log, 2000, 800000, 800);
        else
            Log::get_instance()->init("./ServerLog", m_close_log, 2000, 800000, 0);
    }
}
  • 日志写入函数WebServer::log_write:初始化日志系统。
    • 根据m_log_write的值来选择不同的日志初始化方式。

数据库连接池初始化

void WebServer::sql_pool()
{
    // 初始化数据库连接池
    m_connPool = connection_pool::GetInstance();
    m_connPool->init("localhost", m_user, m_passWord, m_databaseName, 3306, m_sql_num, m_close_log);

    // 初始化数据库读取表
    users->initmysql_result(m_connPool);
}
  • 数据库连接池初始化函数WebServer::sql_pool:初始化数据库连接池并读取数据库表。
    • 获取数据库连接池实例,并进行初始化。
    • 调用http_conn对象的initmysql_result方法,初始化数据库读取表。

线程池初始化

void WebServer::thread_pool()
{
    // 线程池
    m_pool = new threadpool<http_conn>(m_actormodel, m_connPool, m_thread_num);
}
  • 线程池初始化函数WebServer::thread_pool:创建并初始化线程池对象m_pool

事件监听函数

void WebServer::eventListen()
{
    // 网络编程基础步骤
    m_listenfd = socket(PF_INET, SOCK_STREAM, 0);
    assert(m_listenfd >= 0);

    // 优雅关闭连接
    if (0 == m_OPT_LINGER)
    {
        struct linger tmp = {0, 1};
        setsockopt(m_listenfd, SOL_SOCKET, SO_LINGER, &tmp, sizeof(tmp));
    }
    else if (1 == m_OPT_LINGER)
    {
        struct linger tmp = {1, 1};
        setsockopt(m_listenfd, SOL_SOCKET, SO_LINGER, &tmp, sizeof(tmp));
    }

    int ret = 0;
    struct sockaddr_in address;
    bzero(&address, sizeof(address));
    address.sin_family = AF_INET;
    address.sin_addr.s_addr = htonl(INADDR_ANY);
    address.sin_port = htons(m_port);

    int flag = 1;
    setsockopt(m_listenfd, SOL_SOCKET, SO_REUSEADDR, &flag, sizeof(flag));
    ret = bind(m_listenfd, (struct sockaddr *)&address, sizeof(address));
    assert(ret >= 0);
    ret = listen(m_listenfd, 5);
    assert(ret >= 0);

    utils.init(TIMESLOT);

    // epoll创建内核事件表
    epoll_event events[MAX_EVENT_NUMBER];
    m_epollfd = epoll_create(5);
    assert(m_epollfd != -1);

    utils.addfd(m_epollfd, m_listenfd, false, m_LISTENTrigmode);
    http_conn::m_epollfd = m_epollfd;

    ret = socketpair(PF_UNIX, SOCK_STREAM, 0, m_pipefd);
    assert(ret != -1);
    utils.setnonblocking(m_pipefd[1]);
    utils.addfd(m_epollfd, m_pipefd[0], false, 0);

    utils.addsig(SIGPIPE, SIG_IGN);
    utils.addsig(SIGALRM, utils.sig_handler, false);
    utils.addsig(SIGTERM, utils.sig_handler, false);

    alarm(TIMESLOT);

    // 工具类,信号和描述符基础操作
    Utils::u_pipefd = m_pipefd;
    Utils::u_epollfd = m_epollfd;
}
  • 事件监听函数WebServer::eventListen:设置监听socket和epoll事件。
    • 创建监听socket m_listenfd,并设置相关选项(如SO_LINGER)。
    • 将socket绑定到指定的地址和端口,并开始监听。
    • 初始化工具类utils
    • 创建epoll实例m_epollfd,并将监听socket m_listenfd加入epoll监听列表。
    • 创建UNIX域套接字对 m_pipefd,用于进程间通信,并将其加入epoll监听列表。
    • 设置信号处理函数,忽略SIGPIPE信号,处理SIGALRMSIGTERM信号。
    • 启动定时器 alarm

定时器相关函数

void WebServer::timer(int connfd, struct sockaddr_in client_address)
{
    users[connfd].init(connfd, client_address, m_root, m_CONNTrigmode, m_close_log, m_user, m_passWord, m_databaseName);

    // 初始化client_data数据
    users_timer[connfd].address = client_address;
    users_timer[connfd].sockfd = connfd;
    util_timer *timer = new util_timer;
    timer->user_data = &users_timer[connfd];
    timer->cb_func = cb_func;
    time_t cur = time(NULL);
    timer->expire = cur + 3 * TIMESLOT;
    users_timer[connfd].timer = timer;
    utils.m_timer_lst.add_timer(timer);
}
  • 定时器相关函数WebServer::timer:为新连接初始化定时器。
    • 初始化http_conn对象。
    • 初始化client_data对象,并创建新的util_timer定时器。
    • 将定时器加入定时器链表m_timer_lst

调整定时器

void WebServer::adjust_timer(util_timer *timer)
{
    time_t cur = time(NULL);
    timer->expire = cur + 3 * TIMESLOT;
    utils.m_timer_lst.adjust_timer(timer);

    LOG_INFO("%s", "adjust timer once");
}
  • 调整定时器函数WebServer::adjust_timer:调整定时器的过期时间并重新加入定时器链表。

定时器回调函数

void WebServer::deal_timer(util_timer *timer, int sockfd)
{
    timer->cb_func(&users_timer[sockfd]);
    if (timer)
    {
        utils.m_timer_lst.del_timer(timer);
    }

    LOG_INFO("close fd %d", users_timer[sockfd].sockfd);
}
  • 定时器回调函数WebServer::deal_timer:处理定时器过期事件,关闭连接并删除定时器。

处理客户端数据

bool WebServer::dealclinetdata()
{
    struct sockaddr_in client_address;
    socklen_t client_addrlength = sizeof(client_address);

    if (0 == m_LISTENTrigmode)
    {
        int connfd = accept(m_listenfd, (struct sockaddr *)&client_address, &client_addrlength);
        if (connfd < 0)
        {
            LOG_ERROR("%s:errno is:%d", "accept error", errno);
            return false;
        }
        if (http_conn::m_user_count >= MAX_FD)
        {
            utils.show_error(connfd, "Internal server busy");
            LOG_ERROR("%s", "Internal server busy");
            return false;
        }
        timer(connfd, client_address);
    }
    else
    {
        while (1)
        {
            int connfd = accept(m_listenfd, (struct sockaddr *)&client_address, &client_addrlength);
            if (connfd < 0)
            {
                LOG_ERROR("%s:errno is:%d", "accept error", errno);
                break;
            }
            if (http_conn::m_user_count >= MAX_FD)
            {
                utils.show_error(connfd, "Internal server busy");
                LOG_ERROR("%s", "Internal server busy");
                break;
            }
            timer(connfd, client_address);
        }
        return false;
    }
    return true;
}
  • 处理客户端数据函数WebServer::dealclinetdata:处理新客户端连接。
    • 接受新的客户端连接。
    • 如果触发模式为LT,则一次接受一个连接;如果为ET,则循环接受所有连接。
    • 如果连接数达到最大值,则显示错误信息。

信号处理函数

bool WebServer::dealwithsignal(bool &timeout, bool &stop_server)
{
    int ret = 0;
    int sig;
    char signals[1024];
    ret = recv(m_pipefd[0], signals, sizeof(signals), 0);
    if (ret == -1)
    {
        return false;
    }
    else if (ret == 0)
    {
        return false;
    }
    else
    {
        for (int i = 0; i < ret; ++i)
        {
            switch (signals[i])
            {
            case SIGALRM:
            {
                timeout = true;
                break;
            }
            case SIGTERM:
            {
                stop_server = true;
                break;
            }
            }
        }
    }
    return true;
}
  • 信号处理函数WebServer::dealwithsignal:处理信号。
    • 接收信号并根据信号类型设置标志位。
    • 处理SIGALRM信号,设置timeout标志位。
    • 处理SIGTERM信号,设置stop_server标志位。

处理读事件

void WebServer::dealwithread(int sockfd)
{
    util_timer *timer = users_timer[sockfd].timer;

    // reactor
    if (1 == m_actormodel)
    {
        if (timer)
        {
            adjust_timer(timer);
        }

        m_pool->append(users + sockfd, 0);

        while (true)
        {
            if (1 == users[sockfd].improv)
            {
                if (1 == users[sockfd].timer_flag)
                {
                    deal_timer(timer, sockfd);
                    users[sockfd].timer_flag = 0;
                }
                users[sockfd].improv = 0;
                break;
            }
        }
    }
    else
    {
        if (users[sockfd].read_once())
        {
            LOG_INFO("deal with the client(%s)", inet_ntoa(users[sockfd].get_address()->sin_addr));

            // 若监测到读事件,将该事件放入请求队列
            m_pool->append_p(users + sockfd);

            if (timer)
            {
                adjust_timer(timer);
            }
        }
        else
        {
            deal_timer(timer, sockfd);
        }
    }
}
  • 处理读事件函数WebServer::dealwithread:处理客户端的读事件。
    • 如果是Reactor模型,则调整定时器并将事件加入线程池处理。
    • 如果是Proactor模型,则直接读取数据,并将事件加入线程池处理。

处理写事件

void WebServer::dealwithwrite(int sockfd)
{
    util_timer *timer = users_timer[sockfd].timer;
    // reactor
    if (1 == m_actormodel)
    {
        if (timer)
        {
            adjust_timer(timer);
        }

        m_pool->append(users + sockfd, 1);

        while (true)
        {
            if (1 == users[sockfd].improv)
            {
                if (1 == users[sockfd].timer_flag)
                {
                    deal_timer(timer, sockfd);
                    users[sockfd].timer_flag = 0;
                }
                users[sockfd].improv = 0;
                break;
            }
        }
    }
    else
    {
        if (users[sockfd].write())
        {
            LOG_INFO("send data to the client(%s)", inet_ntoa(users[sockfd].get_address()->sin_addr));

            if (timer)
            {
                adjust_timer(timer);
            }
        }
        else
        {
            deal_timer(timer, sockfd);
        }
    }
}
  • 处理写事件函数WebServer::dealwithwrite:处理客户端的写事件。
    • 如果是Reactor模型,则调整定时器并将事件加入线程池处理。
    • 如果是Proactor模型,则直接写入数据,并将事件加入线程池处理。

主函数运行

void WebServer::eventLoop()
{
    bool timeout = false;
    bool stop_server = false;

    while (!stop_server)
    {
        int number = epoll_wait(m_epollfd, events, MAX_EVENT_NUMBER, -1);
        if (number < 0 && errno != EINTR)
        {
            LOG_ERROR("%s", "epoll failure");
            break;
        }

        for (int i = 0; i < number; i++)
        {
            int sockfd = events[i].data.fd;

            // 处理新到的客户连接
            if (sockfd == m_listenfd)
            {
                bool flag = dealclinetdata();
                if (false == flag)
                    continue;
            }
            // 处理信号
            else if ((sockfd == m_pipefd[0]) && (events[i].events & EPOLLIN))
            {
                bool flag = dealwithsignal(timeout, stop_server);
                if (false == flag)
                    continue;
            }
            // 处理客户连接上接收到的数据
            else if (events[i].events & EPOLLIN)
            {
                dealwithread(sockfd);
            }
            else if (events[i].events & EPOLLOUT)
            {
                dealwithwrite(sockfd);
            }
        }
        if (timeout)
        {
            utils.timer_handler();
            LOG_INFO("%s", "timer tick");
            timeout = false;
        }
    }
}
  • 主循环函数WebServer::eventLoop:服务器主事件循环。
    • 进入循环,通过epoll_wait等待事件发生。
    • 处理各种事件:
      • 新的客户端连接。
      • 信号事件。
      • 客户端读事件。
      • 客户端写事件。
    • 如果定时器超时,则处理定时器事件。

总结

这个webserver.cpp文件的主要作用是实现一个Web服务器的核心逻辑。具体来说,它负责:

  1. 事件管理和处理:通过epoll管理所有的网络事件,包括客户端连接、读写操作、以及异常和超时事件。

  2. 连接处理:实现新客户端连接的接收和管理,包括创建连接对象,并将其注册到epoll中进行监听。

  3. 读写操作:根据服务器的模式(ReactorProactor),处理客户端的读写请求。在Reactor模式下,主要通过线程池异步处理请求;在Proactor模式下,则在读写完成后直接处理业务逻辑。

  4. 定时器管理:使用定时器对客户端连接进行超时控制,通过调整和删除定时器来管理连接的生命周期,确保资源及时释放。

  5. 信号处理:处理系统信号(如终止信号、定时信号),用于控制服务器的停止和定时任务的执行。

总体来说,这个cpp文件实现了Web服务器运行的主循环和核心功能,确保服务器在高并发情况下高效、稳定地处理网络请求。

1.1 为什么网络编程需要套接字(Socket)

套接字(Socket)是计算机网络编程中的基础概念和工具,它的作用和必要性可以从以下几个方面理解:

1.1.1 通信抽象
  • 统一的接口:套接字提供了一个统一的接口,使程序员能够通过相同的方式进行网络通信,无论底层使用的是哪种协议(例如TCP、UDP)。这就像是一种抽象层,屏蔽了底层实现的复杂性。
  • 跨平台:套接字在不同操作系统上表现一致,提供了跨平台的通信能力,使开发者能够编写具有良好可移植性的网络应用程序。
1.1.2. 网络通信的基础
  • 网络通信的端点:在网络通信中,套接字扮演的是“通信端点”的角色。任何网络通信都是在两个端点(一个客户端和一个服务器端)之间进行的。套接字就是这个端点,它代表了一个IP地址和端口的组合。
  • 支持多种协议:套接字不仅仅支持TCP(面向连接的通信),还支持UDP(无连接的通信)等协议,能够满足不同类型的网络通信需求。
1.1.3. 数据传输的机制
  • 数据收发:套接字提供了发送(send)和接收(recv)数据的机制,通过这些函数,程序可以在网络中传输数据。这是实现网络功能的核心部分。
  • 流控制和连接管理:对于TCP套接字,套接字还提供了连接的管理(例如监听、接受连接)以及流控制等功能,使得数据能够可靠地传输。
1.1.4. 操作系统的支持
  • 操作系统接口:在操作系统中,套接字是与操作系统网络栈交互的接口。通过套接字,应用程序可以与操作系统内核进行通信,进而通过网络适配器与外部世界通信。
  • 资源管理:套接字作为一种系统资源,由操作系统管理,能够确保资源的合理分配和回收。这避免了网络资源的浪费和冲突。
1.1.5 总结

套接字在网络编程中是不可或缺的,因为它提供了网络通信的基础设施和统一的接口,使得复杂的网络操作变得可管理和可操作。通过套接字,开发者能够构建出跨平台、可扩展的网络应用程序。没有套接字,程序将无法直接与网络进行通信,网络编程也就无从谈起。

1.2 epoll是什么

epoll 是 Linux 内核提供的一种高效的 I/O 多路复用机制,用于监控多个文件描述符,以便在这些文件描述符上发生事件时通知应用程序进行相应处理。相比于传统的 selectpollepoll 在处理大量文件描述符时表现更为高效,特别是在高并发场景下。

epoll 的主要特点:

  1. 高效性

    • epoll 使用的是基于事件通知的机制,只有发生事件的文件描述符才会被返回,因此在大量文件描述符中只有少数有事件发生时,epoll 的性能优势显著。
    • epoll 在内核空间维护了一个事件表,避免了每次调用都要传递整个文件描述符集合,减少了内核与用户态之间的数据拷贝。
  2. 水平触发和边缘触发

    • 水平触发(Level-triggered, LT):默认模式,只要某个文件描述符上有事件发生,epoll_wait 就会返回该文件描述符,直到事件被处理。
    • 边缘触发(Edge-triggered, ET):更为高效,但要求更细致的处理。当文件描述符状态从无事件变为有事件时才会通知,适用于减少系统调用频率,提高程序效率。
  3. 对文件描述符数量的支持

    • epoll 能够支持大规模的文件描述符集合,理论上上限是系统的最大文件描述符数,而 selectpoll 通常有较小的文件描述符限制。

epoll 的工作流程:

  1. 创建 epoll 实例

    • 使用 epoll_createepoll_create1 函数创建一个 epoll 实例,返回一个 epoll 文件描述符。
  2. 注册事件

    • 使用 epoll_ctl 函数将需要监控的文件描述符添加到 epoll 实例中,并指定要监听的事件类型(如可读、可写、异常等)。
  3. 等待事件发生

    • 使用 epoll_wait 函数等待事件的发生,当某个或多个文件描述符上的事件满足条件时,epoll_wait 会返回这些文件描述符。
  4. 处理事件

    • 处理返回的事件,执行相应的读写操作,或根据应用程序逻辑进行其他处理。

使用场景:

epoll 特别适合用于高并发的网络服务器中,比如 Web 服务器、聊天服务器等。这些应用通常需要处理大量并发连接,并且每个连接可能频繁进行 I/O 操作。epoll 能够有效地提升这些应用的性能。

总之,epoll 是在 Linux 环境下构建高性能网络服务器的重要工具,它通过高效的事件通知机制帮助开发者更好地管理大量并发 I/O 操作。

复现过程中遇到的问题

1. 解决“E: 无法定位软件包 mysql-workbench-community”问题

用这个指令:
sudo apt install mysql-workbench-community
会报错“E: 无法定位软件包 mysql-workbench-community”问题
解决方法为改用这个指令:
apt-get install mysql-workbench
成功:
在这里插入图片描述
分析下可能的原因:使用 mysql-workbench 是因为它在 Ubuntu 默认的软件源中,而 mysql-workbench-community 需要从 MySQL 官方仓库中获取。如果没有配置 MySQL 官方仓库,系统会找不到 mysql-workbench-community 包,导致错误信息的出现。

2. 解决"正在设定ttf-mscorefonts-installer"

在这里插入图片描述
这里如果直接关了会导致后续包安装时会出现非法占用
解决方案:
按tab将光标移动到确定键上 然后回车就完事了

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

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

相关文章

【C++】N卡无法录制,如何下载C++

N卡无法录制&#xff0c;如何下载C C 官方下载路径&#xff1a; https://www.microsoft.com/zh-cn/download/details.aspx?id48145&134b2bb0-86c1-fe9f-d523-281faef416951&751be11f-ede8-5a0c-058c-2ee190a24fa6True 第一步 检查N卡驱动是不是最新版本 第二步 下载…

拓扑结构_替代SN6505推挽式低噪声隔离变压驱动器输出功率1-3W

PC6505 是一款专门为小体积、低待机功耗微功率隔离电源而设计的推挽式变压器驱动器&#xff0c;其外围只需匹配简单的输入输出滤波电容、隔离变压器和整流电路&#xff0c;即可实现 3.3V 或 5V 输入、3.3V~24V 输出、输出功率 1~3W 的隔离电源。 PC6505 芯片内部集成振荡器&am…

简单的棒棒图绘制教程

原文教程链接&#xff1a;R 语言绘图 | GO、KEGG等富集棒棒图 往期部分文章 1. 最全WGCNA教程&#xff08;替换数据即可出全部结果与图形&#xff09; WGCNA分析 | 全流程分析代码 | 代码一 WGCNA分析 | 全流程分析代码 | 代码二 WGCNA分析 | 全流程代码分享 | 代码三 WGC…

stun和trun

在 WebRTC 中&#xff0c;STUN&#xff08;Session Traversal Utilities for NAT&#xff09;和 TURN&#xff08;Traversal Using Relays around NAT&#xff09;是用于NAT穿透的两种不同的技术&#xff0c;它们解决的问题不同&#xff0c;因此在某些情况下需要同时使用。 ST…

VM虚拟机:虚拟机能ping通主机,主机ping不通虚拟机,永久解决办法。

最近在安装VM虚拟机的时候,出现了虚拟机能ping通主机,主机ping不通虚拟机。着实令人恶心,尤其是虚拟机在设置网络的时候,网上五花八门,修改什么配置的都有,最多的就是修改宿主机的ipv4,这种我个人感觉不可取。宿主机不要乱改配置,需要修改尽量在虚拟机中修改即可。 还需…

el-time-select 动态增加时间

<template><div><div v-for"(item, index) in timeSlots" :key"index"><el-time-select placeholder"起始时间" v-model"item.startTime" :picker-options"{start: 00:00,step: 00:15,end: 23:59,}"&g…

VMware安装windows虚拟机详细过程

目录 准备工作配置虚拟机为虚拟机设置操作系统镜像安装windows10 准备工作 安装好VMware软件并激活&#xff0c;激活码自行查找 准备好系统镜像文件&#xff0c;可以在MSDN中下载&#xff0c;地址&#xff1a;https://next.itellyou.cn/ 配置虚拟机 选择自定义 默认 选择稍后…

C语言操作符详解1(含进制转换,原反补码)

文章目录 一、操作符的分类二、二进制和进制转换1.二进制与十进制的相互转换2,二进制与八进制的相互转换3.二进制与十六进制的相互转换 三、原码、反码和补码四、移位操作符1.左移操作符&#xff08;1&#xff09;左移操作符移位方法&#xff08;2&#xff09;左移操作符规律总…

编程要由 “手动挡” 变 “自动挡” 了?Cursor+Claude-3.5-Sonnet,Karpathy 点赞的 AI 代码神器。如何使用详细教程

Cursor 情况简介 AI 大神 Andrej Karpathy 都被震惊了&#xff01;他最近在试用 VS Code Cursor Claude Sonnet 3.5&#xff0c;结果发现这玩意儿比 GitHub Copilot 还好用&#xff01; Cursor 在短短时间内迅速成为程序员群体的顶流神器&#xff0c;其背后的原因在于其默认使…

代码随想录 刷题记录-24 图论 (1)理论基础 、深搜与广搜

一、理论基础 参考&#xff1a; 图论理论基础 深度优先搜索理论基础 广度优先搜索理论基础 dfs dfs搜索可一个方向&#xff0c;并需要回溯&#xff0c;所以用递归的方式来实现是最方便的。 有递归的地方就有回溯&#xff0c;例如如下代码&#xff1a; void dfs(参数) {…

一份高质量的测试用例如何养成?

测试一个新功能时&#xff0c;最重要的一个步骤就是编写测试用例&#xff0c;测试用例写好了&#xff0c;那么后面的测试工作基本就非常顺利了&#xff0c;那么怎样提高测试用例的质量呢&#xff1f; 充分理解需求 拿到测试需求后&#xff0c;不应该拿到什么就是什么&#xf…

【esp32】VScode添加库

以添加PubSubClient库为例 如图操作&#xff0c;在搜索框输入PubSubClient&#xff0c;点击下载 给你的某一个工程添加该库 编译成功

建模杂谈系列254 GMM的拟合

说明 首先假设数据由多个正态分布叠加而成&#xff0c;这个场景应该也是比较有普遍意义的。 内容 数据还是之前产生的三波 import numpy as np import matplotlib.pyplot as plt from sklearn.mixture import GaussianMixture# 生成示例数据 np.random.seed(0) data1 np.r…

51单片机的pwm控制的智能台灯设计【proteus仿真+程序+报告+原理图+演示视频】

1、主要功能 该系统由AT89C51/STC89C52单片机LCD1602显示模块DS1302时间模块光敏传感器模块人体红外模块按键等模块构成。适用于智能台灯、PWM调节灯光亮度等相似项目。 可实现基本功能: 1、LCD1602实时显示北京时间、环境光照强度、手动/自动模式、台灯亮度等信息&#xff1…

029、架构_高可用_水位和分组

GoldenDB分组技术 GoldenDB灵活智能的数据可用性策略名称是gTank。包含了分组技术和高低水位两个技术点。在分布式一主多备架构下,全节点的数据同步,耗时长、用户体验差。因此GoldenDB采用分组技术,将数据节点和事务节点GTM实现分组管理,实现业务的灵活配置。 数据节点集群…

【实战教程】用 Next.js 和 shadcn-ui 打造现代博客平台

你是否梦想过拥有一个独特、现代化的个人博客平台&#xff1f;今天&#xff0c;我们将一起动手&#xff0c;使用 Next.js 和 shadcn-ui 来创建一个功能丰富、外观精美的博客系统。无论你是刚接触 Web 开发&#xff0c;还是经验丰富的程序员&#xff0c;这个教程都将带你step by…

【Next.js 入门指南】5分钟创建你的第一个 Next.js 应用

你是否曾经梦想过构建一个快速、高效且 SEO 友好的 React 应用&#xff1f;今天&#xff0c;我们将一起探索 Next.js —— 一个革命性的 React 框架&#xff0c;它将帮助你轻松实现这个梦想。在接下来的 5 分钟里&#xff0c;你将创建并运行你的第一个 Next.js 应用&#xff0c…

开发团队如何应对突发的技术故障与危机:策略与实践

&#x1f493; 博客主页&#xff1a;倔强的石头的CSDN主页 &#x1f4dd;Gitee主页&#xff1a;倔强的石头的gitee主页 ⏩ 文章专栏&#xff1a;《热点时事》 期待您的关注 目录 引言 一、快速响应与问题定位策略 1. 建立紧急响应团队 2. 利用自动化监控工具 3. 快速定位问…

图片转PDF:2024四大转换工具推荐!

在数字化时代&#xff0c;我们经常需要将图片转换成PDF格式&#xff0c;无论是为了打印、存档还是分享。“图片转PDF”已经成为一个常见的需求&#xff0c;而市场上有多种工具可以帮助我们轻松实现这一转换。本文将介绍几款备受好评的图片转PDF工具&#xff01; 福昕PDF转换大…

代码随想录算法训练营第三十四天| 62.不同路径 63. 不同路径 II

62.不同路径 题目&#xff1a; 一个机器人位于一个 m x n 网格的左上角 &#xff08;起始点在下图中标记为 “Start” &#xff09;。 机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角&#xff08;在下图中标记为 “Finish” &#xff09;。 问总共有多少…