• TingWebserver服务器代码解读03

TingWebserver服务器代码解读03

2025-05-03 02:57:10 4 阅读

上一篇:TingWebServer服务器代码解读02

TingWebServer服务器代码解读02-CSDN博客

在解读 02中解析完threadpool和http_conn两个文件夹后,我们继续解析后面一行的文件,locker->lst_timer

Locker文件夹

README

线程同步机制包装类

多线程同步,确保任一时刻只能有一个线程能进入关键代码段.

  • 信号量
  • 互斥锁
  • 条件变量

locker.h

一个简单的locker互斥锁的头文件,代码比较简单,所以函数内容也在locker.h中直接给出了,接下来看里面的代码

sem 类(信号量)

class sem
{
public:
    sem() { 
        if (sem_init(&m_sem, 0, 0) != 0) { //信号量初始化为0
            throw std::exception(); 
        }
    }
    sem(int num) { 
        if (sem_init(&m_sem, 0, num) != 0) { //信号量初始化为输入的num
            throw std::exception(); 
        }
    }
    ~sem() { //解析
        sem_destroy(&m_sem); 
    }
    bool wait() {  //等待信号量,为0即为阻塞
        return sem_wait(&m_sem) == 0; 
    }
    bool post() { //增加信号量,为0唤醒一个等待线程
        return sem_post(&m_sem) == 0; 
    }

private:
    sem_t m_sem; // 定义一个信号量
};

locker 类(互斥锁)

class locker
{    
public:
    locker() { //初始化互斥锁,如果失败抛出异常
        if (pthread_mutex_init(&m_mutex, NULL) != 0) { 
            throw std::exception(); 
        } 
    }
    ~locker() { //析构
        pthread_mutex_destroy(&m_mutex); 
    }
    bool lock() { //锁定互斥锁
        return pthread_mutex_lock(&m_mutex) == 0; 
    }
    bool unlock() { //解锁互斥锁
        return pthread_mutex_unlock(&m_mutex) == 0; 
    }
    pthread_mutex_t *get() { 
        return &m_mutex; 
    }

private:
    pthread_mutex_t m_mutex; // 定义互斥锁
};

cond 类(条件变量)

class cond
{
public:
    cond() {                  //初始化条件变量,失败则抛出异常
        if (pthread_cond_init(&m_cond, NULL) != 0) { 
            throw std::exception(); 
        } 
    }
    ~cond() {             //析构
        pthread_cond_destroy(&m_cond); 
    }
    bool wait(pthread_mutex_t *m_mutex) { //等待条件变量的信号
        return pthread_cond_wait(&m_cond, m_mutex) == 0; 
    }
    bool timewait(pthread_mutex_t *m_mutex, struct timespec t) { //等待指定时间(超时)后自动返回
        return pthread_cond_timedwait(&m_cond, m_mutex, &t) == 0; 
    }
    bool signal() { //发送信号给一个等待的线程
        return pthread_cond_signal(&m_cond) == 0; 
    }
    bool broadcast() { //发送信号给所有等待的线程
        return pthread_cond_broadcast(&m_cond) == 0; 
    }

private:
    pthread_cond_t m_cond; // 定义条件变量
};
  • sem 类实现了信号量的基本功能,允许线程间同步访问。
  • locker 类提供了互斥锁的封装,确保多线程环境下对共享资源的独占访问。
  • cond 类则实现了条件变量,允许线程根据某个条件同步或协调执行顺序。

 CGImysql文件夹

README

校验 & 数据库连接池

数据库连接池

  • 单例模式,保证唯一
  • list实现连接池
  • 连接池为静态大小
  • 互斥锁实现线程安全

校验

  • HTTP请求采用POST方式
  • 登录用户名和密码校验
  • 用户注册及多线程注册安全

sql_connection_pool.h

头文件代码

#ifndef _CONNECTION_POOL_
#define _CONNECTION_POOL_

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include "../lock/locker.h"
#include "../log/log.h"

using namespace std;

class connection_pool
{
public:
	MYSQL *GetConnection();				 //获取数据库连接
	bool ReleaseConnection(MYSQL *conn); //释放连接
	int GetFreeConn();					 //获取连接
	void DestroyPool();					 //销毁所有连接

	//单例模式
	static connection_pool *GetInstance();

	void init(string url, string User, string PassWord, string DataBaseName, int Port, int MaxConn, int close_log); 

private:
	connection_pool();
	~connection_pool();

	int m_MaxConn;  //最大连接数
	int m_CurConn;  //当前已使用的连接数
	int m_FreeConn; //当前空闲的连接数
	locker lock;
	list connList; //连接池
	sem reserve;

public:
	string m_url;			 //主机地址
	string m_Port;		 //数据库端口号
	string m_User;		 //登陆数据库用户名
	string m_PassWord;	 //登陆数据库密码
	string m_DatabaseName; //使用数据库名
	int m_close_log;	//日志开关
};

class connectionRAII{

public:
	connectionRAII(MYSQL **con, connection_pool *connPool);
	~connectionRAII();
	
private:
	MYSQL *conRAII;
	connection_pool *poolRAII;
};

#endif

sql_connection_pool.cpp

connection_pool

connection_pool 是数据库连接池的核心类,负责管理数据库连接的创建、获取、释放和销毁

构造函数:connection_pool
connection_pool::connection_pool()
{
    m_CurConn = 0;//当前连接池中正在使用的连接数置0
    m_FreeConn = 0;//当前连接池中空闲的连接数置0
}
静态方法:GetInstance

这是一个单例模式的方法,保证只有一个connection_pool实例存在

connection_pool *connection_pool::GetInstance()
{
    static connection_pool connPool;
    return &connPool;
}
 init 方法:

初始化数据库连接池,具体步骤如下:

  • 设置数据库连接参数(URL、用户名、密码、数据库名、端口号等)。
  • 创建并初始化数据库连接,调用 mysql_real_connect 与数据库建立连接。
  • 将每个连接加入connList,并更新空闲连接数m_FreeConn
void connection_pool::init(string url, string User, string PassWord, string DBName, int Port, int MaxConn, int close_log)
{
    m_url = url;
	m_Port = Port;
	m_User = User;
	m_PassWord = PassWord;
	m_DatabaseName = DBName;
	m_close_log = close_log;

	for (int i = 0; i < MaxConn; i++)
	{
		MYSQL *con = NULL;
		con = mysql_init(con);

		if (con == NULL)
		{
			LOG_ERROR("MySQL Error");
			exit(1);
		}
        //调用 mysql_real_connect 与数据库建立连接
		con = mysql_real_connect(con, url.c_str(), User.c_str(), PassWord.c_str(), DBName.c_str(), Port, NULL, 0);

		if (con == NULL)
		{
			LOG_ERROR("MySQL Error");
			exit(1);
		}
		connList.push_back(con);//将每个连接加入connList
		++m_FreeConn;
	}

	reserve = sem(m_FreeConn);

	m_MaxConn = m_FreeConn;
}
 GetConnection 方法:

该方法用于获取一个数据库连接:

  • 如果连接池中没有空闲连接,返回NULL
  • 否则,使用信号量reserve.wait()确保当前空闲连接数不会超过最大限制。
  • 使用互斥锁lock.lock()来保证线程安全。
  • 从连接池中获取一个连接,将其从connList中移除,更新m_FreeConnm_CurConn
  • 返回可用的数据库连接。
//当有请求时,从数据库连接池中返回一个可用连接,更新使用和空闲连接数
MYSQL *connection_pool::GetConnection()
{
	MYSQL *con = NULL;

	if (0 == connList.size())//没有空闲连接
		return NULL;

	reserve.wait();//使用信号量reserve.wait()确保当前空闲连接数不会超过最大限制
	
	lock.lock();//上锁
    
    //获取连接
	con = connList.front();
	connList.pop_front();        

    //更新
	--m_FreeConn;
	++m_CurConn;
    
	lock.unlock();//解锁
	return con;//返回可用连接
}
ReleaseConnection 方法:

该方法用于释放一个数据库连接:

  • 将连接重新加入connList,更新空闲连接数m_FreeConn和使用中的连接数m_CurConn
  • 使用信号量reserve.post()通知有空闲连接。
//释放当前使用的连接
bool connection_pool::ReleaseConnection(MYSQL *con)
{
	if (NULL == con)
		return false;

	lock.lock();//上锁

    //将连接重新加入connList
	connList.push_back(con);

    //更新空闲连接数m_FreeConn和使用中的连接数m_CurConn
	++m_FreeConn;
	--m_CurConn;

	lock.unlock();//解锁
    
    //使用信号量reserve.post()通知有空闲连接
	reserve.post();
	return true;
}
DestroyPool 方法:

销毁连接池,关闭所有数据库连接,并清空connList。 

//销毁数据库连接池
void connection_pool::DestroyPool()
{

	lock.lock();//上锁
	if (connList.size() > 0)
	{
		list::iterator it;//迭代器循环
		for (it = connList.begin(); it != connList.end(); ++it)
		{
			MYSQL *con = *it;
			mysql_close(con);//关闭连接
		}
        //清零
		m_CurConn = 0;
		m_FreeConn = 0;
		connList.clear();
	}

	lock.unlock();//解锁
}
GetFreeConn 方法:

返回当前空闲的数据库连接数 

//当前空闲的连接数
int connection_pool::GetFreeConn()
{
	return this->m_FreeConn;
}
析构函数:
connection_pool::~connection_pool()
{
    DestroyPool();//在销毁connection_pool对象时,调用DestroyPool销毁连接池
}

connectionRAII 类 

onnectionRAII 类是一个RAII(Resource Acquisition Is Initialization)管理类,用于自动管理数据库连接的获取与释放。

构造函数:connectionRAII

构造函数通过connPool->GetConnection()获取一个数据库连接,并将其赋给SQL指针。conRAIIpoolRAII成员变量保存当前连接和连接池的引用,确保在connectionRAII对象销毁时,自动释放连接

connectionRAII::connectionRAII(MYSQL **SQL, connection_pool *connPool)
{
    *SQL = connPool->GetConnection();
    conRAII = *SQL;
    poolRAII = connPool;
}
析构函数:

析构函数在对象销毁时,调用connPool->ReleaseConnection(conRAII)释放数据库连接

connectionRAII::~connectionRAII()
{
    poolRAII->ReleaseConnection(conRAII);
}

 log文件夹

README

同步/异步日志系统

同步/异步日志系统主要涉及了两个模块,一个是日志模块,一个是阻塞队列模块,其中加入阻塞队列模块主要是解决异步写入日志做准备.

  • 自定义阻塞队列
  • 单例模式创建日志
  • 同步日志
  • 异步日志
  • 实现按天、超行分类

log.h

//头文件部分
#ifndef LOG_H
#define LOG_H

#include 
#include 
#include 
#include 
#include 
#include "block_queue.h"
//log类定义
class Log
{
public:
    // C++11以后,使用局部变量懒汉不用加锁
    static Log *get_instance()
    {
        static Log instance; // 局部静态变量实例化(懒汉式单例模式)
        return &instance;
    }
    static void *flush_log_thread(void *args)
    {
        Log::get_instance()->async_write_log();  // 调用 Log 类的异步日志写入函数
    }
    //init() 方法
    bool init(const char *file_name, int close_log, int log_buf_size = 8192, int split_lines = 5000000, int max_queue_size = 0);
    //write_log() 方法
    void write_log(int level, const char *format, ...);
    //flush() 方法
    void flush(void);
private:
    Log();
    virtual ~Log();
    void *async_write_log()
    {
        string single_log;
        //从阻塞队列中取出一个日志string,写入文件
        while (m_log_queue->pop(single_log))
        {
            m_mutex.lock();
            fputs(single_log.c_str(), m_fp);
            m_mutex.unlock();
        }
    }
private:
    char dir_name[128]; //路径名
    char log_name[128]; //log文件名
    int m_split_lines;  //日志最大行数
    int m_log_buf_size; //日志缓冲区大小
    long long m_count;  //日志行数记录
    int m_today;        //因为按天分类,记录当前时间是那一天
    FILE *m_fp;         //打开log的文件指针
    char *m_buf;
    block_queue *m_log_queue; //阻塞队列
    bool m_is_async;                  //是否同步标志位
    locker m_mutex;
    int m_close_log; //关闭日志
};
//宏定义部分
#define LOG_DEBUG(format, ...) if(0 == m_close_log) {Log::get_instance()->write_log(0, format, ##__VA_ARGS__); Log::get_instance()->flush();}
#define LOG_INFO(format, ...) if(0 == m_close_log) {Log::get_instance()->write_log(1, format, ##__VA_ARGS__); Log::get_instance()->flush();}
#define LOG_WARN(format, ...) if(0 == m_close_log) {Log::get_instance()->write_log(2, format, ##__VA_ARGS__); Log::get_instance()->flush();}
#define LOG_ERROR(format, ...) if(0 == m_close_log) {Log::get_instance()->write_log(3, format, ##__VA_ARGS__); Log::get_instance()->flush();}

#endif

log.cpp

  • 同步与异步模式:日志系统支持两种模式。异步模式通过创建线程和使用阻塞队列将日志写入文件,减少了阻塞操作,提高了性能。同步模式则是直接在日志函数中写入文件。
  • 按天或按行分割日志:日志会根据时间或者行数进行切割。每天生成新的日志文件,或者当日志行数达到指定上限时创建新的日志文件。
  • 线程安全:通过 m_mutex 互斥锁保护共享资源,确保多线程环境下日志写入不会发生冲突

类成员变量及构造函数:log

Log::Log()
{
    m_count = 0;//初始化日志行数计数器
    m_is_async = false;//默认为同步模式,不启用异步日志记录
}

析构函数 :~Log()

Log::~Log()
{
    if (m_fp != NULL)
    {
        fclose(m_fp);  // 如果文件指针 `m_fp` 不为 NULL,关闭文件
    }
}

初始化函数:init()

init():用于初始化日志系统,包括设置日志文件名、路径、日志缓冲区大小、分割行数等。

  • 如果 max_queue_size >= 1,则启用异步日志记录并创建一个新的线程来执行日志写入。
  • 根据传入的文件名和当前时间,生成日志文件的完整路径和文件名。
  • 打开日志文件并准备写入。
bool Log::init(const char *file_name, int close_log, int log_buf_size, int split_lines, int max_queue_size)
{
    // 如果设置了max_queue_size, 则设置为异步
    if (max_queue_size >= 1)
    {
        m_is_async = true;  // 启用异步日志写入
        m_log_queue = new block_queue(max_queue_size);  // 创建一个阻塞队列用于存放日志
        pthread_t tid;
        // 创建一个线程来执行日志的异步写入
        pthread_create(&tid, NULL, flush_log_thread, NULL);
    }

    m_close_log = close_log;  // 设置日志是否关闭的标志
    m_log_buf_size = log_buf_size;  // 设置日志缓冲区大小
    m_buf = new char[m_log_buf_size];  // 创建日志缓冲区
    memset(m_buf, '', m_log_buf_size);  // 初始化缓冲区为 0
    m_split_lines = split_lines;  // 设置日志文件的最大行数

    // 获取当前系统时间
    time_t t = time(NULL);
    struct tm *sys_tm = localtime(&t);
    struct tm my_tm = *sys_tm;

    const char *p = strrchr(file_name, '/');
    char log_full_name[256] = {0};

    if (p == NULL)
    {
        snprintf(log_full_name, 255, "%d_%02d_%02d_%s", my_tm.tm_year + 1900, my_tm.tm_mon + 1, my_tm.tm_mday, file_name);
    }
    else
    {
        strcpy(log_name, p + 1);  // 复制文件名
        strncpy(dir_name, file_name, p - file_name + 1);  // 复制路径名
        snprintf(log_full_name, 255, "%s%d_%02d_%02d_%s", dir_name, my_tm.tm_year + 1900, my_tm.tm_mon + 1, my_tm.tm_mday, log_name);
    }

    m_today = my_tm.tm_mday;  // 设置当前日期
    m_fp = fopen(log_full_name, "a");  // 打开日志文件(以追加模式)
    if (m_fp == NULL)
    {
        return false;  // 如果文件打开失败,返回 false
    }

    return true;
}

日志写入函数 :write_log()

write_log():这是日志记录的核心方法,它首先格式化日志信息并确定日志级别(如 debug、info、warn、error)。然后判断是否需要切换日志文件(按天或按行分割),并在需要时创建新文件。最后,日志信息会根据是否启用异步模式,写入到文件或者阻塞队列中。

void Log::write_log(int level, const char *format, ...)
{
    struct timeval now = {0, 0};
    gettimeofday(&now, NULL);  // 获取当前时间(秒和微秒)
    time_t t = now.tv_sec;
    struct tm *sys_tm = localtime(&t);  // 转换为本地时间
    struct tm my_tm = *sys_tm;
    char s[16] = {0};

    switch (level)
    {
    case 0: strcpy(s, "[debug]:"); break;
    case 1: strcpy(s, "[info]:"); break;
    case 2: strcpy(s, "[warn]:"); break;
    case 3: strcpy(s, "[erro]:"); break;
    default: strcpy(s, "[info]:"); break;
    }

    m_mutex.lock();  // 锁住日志系统,防止多个线程同时写日志
    m_count++;  // 增加日志行数计数器

    // 如果当前日期与日志文件日期不一致,或者日志行数达到最大行数,切换日志文件
    if (m_today != my_tm.tm_mday || m_count % m_split_lines == 0)
    {
        char new_log[256] = {0};
        fflush(m_fp);  // 刷新文件缓冲区
        fclose(m_fp);  // 关闭当前日志文件
        char tail[16] = {0};
        snprintf(tail, 16, "%d_%02d_%02d_", my_tm.tm_year + 1900, my_tm.tm_mon + 1, my_tm.tm_mday);

        // 如果日期发生变化,切换到新的日志文件
        if (m_today != my_tm.tm_mday)
        {
            snprintf(new_log, 255, "%s%s%s", dir_name, tail, log_name);
            m_today = my_tm.tm_mday;
            m_count = 0;  // 重置日志行数
        }
        else
        {
            snprintf(new_log, 255, "%s%s%s.%lld", dir_name, tail, log_name, m_count / m_split_lines);
        }
        m_fp = fopen(new_log, "a");  // 重新打开日志文件
    }
    m_mutex.unlock();  // 解锁日志系统

    va_list valst;
    va_start(valst, format);  // 开始处理可变参数

    string log_str;
    m_mutex.lock();

    // 写入日志的具体时间
    int n = snprintf(m_buf, 48, "%d-%02d-%02d %02d:%02d:%02d.%06ld %s ",
                     my_tm.tm_year + 1900, my_tm.tm_mon + 1, my_tm.tm_mday,
                     my_tm.tm_hour, my_tm.tm_min, my_tm.tm_sec, now.tv_usec, s);

    // 写入格式化的日志信息
    int m = vsnprintf(m_buf + n, m_log_buf_size - n - 1, format, valst);
    m_buf[n + m] = '
';  // 添加换行符
    m_buf[n + m + 1] = '';
    log_str = m_buf;  // 将日志内容存入字符串

    m_mutex.unlock();  // 解锁

    // 如果是异步模式,日志写入队列;否则直接写入文件
    if (m_is_async && !m_log_queue->full())
    {
        m_log_queue->push(log_str);  // 将日志推入阻塞队列
    }
    else
    {
        m_mutex.lock();
        fputs(log_str.c_str(), m_fp);  // 写入日志文件
        m_mutex.unlock();
    }

    va_end(valst);  // 结束可变参数处理
}

刷新日志文件函数:flush()

void Log::flush(void)
{
    m_mutex.lock();
    fflush(m_fp);  // 强制刷新文件流缓冲区
    m_mutex.unlock();
}

block_quene.h

block_queue 类是一个基于循环数组实现的阻塞队列,具有线程安全的特性,支持同步和超时等待的 pop 操作。它在生产者-消费者模式中非常常见,主要用于多线程日志系统等场景

block_queue类

初始化block_queue

初始化一个最大容量为 max_size 的队列,默认值是 1000

  block_queue(int max_size = 1000)
    {
        if (max_size <= 0)//判断大小
        {
            exit(-1);
        }

        m_max_size = max_size;
        m_array = new T[max_size];
        m_size = 0;
        m_front = -1;
        m_back = -1;
    }
 清空队列函数:clear
  void clear()
    {
        m_mutex.lock();
        m_size = 0;
        m_front = -1;
        m_back = -1;
        m_mutex.unlock();
    }
析构函数

释放动态分配的数组 m_array 资源

    ~block_queue()
    {
        m_mutex.lock();
        if (m_array != NULL)
            delete [] m_array;

        m_mutex.unlock();
    }
判断队列是否满:full
 bool full() 
    {
        m_mutex.lock();
        if (m_size >= m_max_size)//判断条件
        {

            m_mutex.unlock();
            return true;
        }
        m_mutex.unlock();
        return false;
    }
判断队列是否为空:empty
  bool empty() 
    {
        m_mutex.lock();
        if (0 == m_size)
        {
            m_mutex.unlock();
            return true;
        }
        m_mutex.unlock();
        return false;
    }
 返回队首元素:front
  bool front(T &value) 
    {
        m_mutex.lock();
        if (0 == m_size)
        {
            m_mutex.unlock();
            return false;
        }
        value = m_array[m_front];
        m_mutex.unlock();
        return true;
    }
 返回队尾元素:back
 bool back(T &value) 
    {
        m_mutex.lock();
        if (0 == m_size)
        {
            m_mutex.unlock();
            return false;
        }
        value = m_array[m_back];
        m_mutex.unlock();
        return true;
    }
求队列长度:size
 int size() 
    {
        int tmp = 0;

        m_mutex.lock();
        tmp = m_size;

        m_mutex.unlock();
        return tmp;
    }
求最大长度:max_size 
int max_size()
    {
        int tmp = 0;

        m_mutex.lock();
        tmp = m_max_size;

        m_mutex.unlock();
        return tmp;
    }

以上都是一些很简单的函数,实现语句就一两句话,主要还是为了模块化运行考虑,把简单功能封装

入队 (push)
  • 先检查是否队列满了,满了就返回 false
  • 计算 m_back 的新位置 (m_back + 1) % m_max_size,实现循环数组结构。
  • 存入数据,增加 m_size
  • 通过 m_cond.broadcast() 唤醒所有可能等待 pop 的线程
 bool push(const T &item)
    {

        m_mutex.lock();
        if (m_size >= m_max_size)//检查队列长度
        {

            m_cond.broadcast();
            m_mutex.unlock();
            return false;
        }

        m_back = (m_back + 1) % m_max_size;//计算新位置
        m_array[m_back] = item;

        m_size++;

        m_cond.broadcast();//唤醒线程
        m_mutex.unlock();
        return true;
    }
出队 (pop
  • 如果队列为空,则调用 m_cond.wait(m_mutex.get()),阻塞等待生产者放入新元素。
  • 取出 m_front + 1 位置的元素,并更新 m_front,实现循环数组结构
  • m_size--,释放队列空间。
bool pop(T &item)
    {

        m_mutex.lock();
        while (m_size <= 0)
        {
            
            if (!m_cond.wait(m_mutex.get()))
            {
                m_mutex.unlock();
                return false;
            }
        }

        m_front = (m_front + 1) % m_max_size;
        item = m_array[m_front];
        m_size--;
        m_mutex.unlock();
        return true;
    }
超时出队 (pop with timeout)
  • 如果队列为空,先计算超时时间 t(基于 gettimeofday())。
  • 调用 m_cond.timewait(m_mutex.get(), t),如果超时仍然为空,返回 false
  • 否则执行正常的 pop 逻辑。
bool pop(T &item, int ms_timeout)
    {
        struct timespec t = {0, 0};
        struct timeval now = {0, 0};
        gettimeofday(&now, NULL);
        m_mutex.lock();
        if (m_size <= 0)
        {
            t.tv_sec = now.tv_sec + ms_timeout / 1000;
            t.tv_nsec = (ms_timeout % 1000) * 1000;
            if (!m_cond.timewait(m_mutex.get(), t))
            {
                m_mutex.unlock();
                return false;
            }
        }

        if (m_size <= 0)
        {
            m_mutex.unlock();
            return false;
        }

        m_front = (m_front + 1) % m_max_size;
        item = m_array[m_front];
        m_size--;
        m_mutex.unlock();
        return true;
    }

 Timer文件夹

README

定时器处理非活动连接

由于非活跃连接占用了连接资源,严重影响服务器的性能,通过实现一个服务器定时器,处理这种非活跃连接,释放连接资源。利用alarm函数周期性地触发SIGALRM信号,该信号的信号处理函数利用管道通知主循环执行定时器链表上的定时任务.

  • 统一事件源
  • 基于升序链表的定时器
  • 处理非活动连接

lst_timer.h

头文件定义,没有多少内容,详情看接下来的lst_timer.cpp文件

#ifndef LST_TIMER
#define LST_TIMER

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

#include 
#include "../log/log.h"

class util_timer;

struct client_data
{
    sockaddr_in address;
    int sockfd;
    util_timer *timer;
};

class util_timer
{
public:
    util_timer() : prev(NULL), next(NULL) {}

public:
    time_t expire;
    
    void (* cb_func)(client_data *);
    client_data *user_data;
    util_timer *prev;
    util_timer *next;
};

class sort_timer_lst
{
public:
    sort_timer_lst();
    ~sort_timer_lst();

    void add_timer(util_timer *timer);
    void adjust_timer(util_timer *timer);
    void del_timer(util_timer *timer);
    void tick();

private:
    void add_timer(util_timer *timer, util_timer *lst_head);

    util_timer *head;
    util_timer *tail;
};

class Utils
{
public:
    Utils() {}
    ~Utils() {}

    void init(int timeslot);

    //对文件描述符设置非阻塞
    int setnonblocking(int fd);

    //将内核事件表注册读事件,ET模式,选择开启EPOLLONESHOT
    void addfd(int epollfd, int fd, bool one_shot, int TRIGMode);

    //信号处理函数
    static void sig_handler(int sig);

    //设置信号函数
    void addsig(int sig, void(handler)(int), bool restart = true);

    //定时处理任务,重新定时以不断触发SIGALRM信号
    void timer_handler();

    void show_error(int connfd, const char *info);

public:
    static int *u_pipefd;
    sort_timer_lst m_timer_lst;
    static int u_epollfd;
    int m_TIMESLOT;
};

void cb_func(client_data *user_data);

#endif

lst_timer.cpp

 构造函数

sort_timer_lst::sort_timer_lst()//把队列头尾清零
{
    head = NULL;
    tail = NULL;
}

析构函数

循环释放队列

sort_timer_lst::~sort_timer_lst()
{
    util_timer *tmp = head;
    while (tmp)
    {
        head = tmp->next;
        delete tmp;
        tmp = head;
    }
}

添加计时器:add_timer 

/ 向定时器链表添加定时器  
void sort_timer_lst::add_timer(util_timer *timer)  
{  
    if (!timer) // 如果定时器为空,直接返回  
    {  
        return;  
    }  
    if (!head) // 如果链表为空,将定时器设为头尾  
    {  
        head = tail = timer;  
        return;  
    }  
    // 如果新定时器的过期时间小于当前头节点的过期时间  
    if (timer->expire < head->expire)   
    {  
        timer->next = head; // 新定时器成为新的头节点  
        head->prev = timer; // 旧头节点的前指针指向新定时器  
        head = timer; // 更新头指针  
        return;  
    }  
    add_timer(timer, head); // 否则,将定时器插入到合适的位置  
}  

调整定时器的位置:adjust_timer

// 调整定时器的位置  
void sort_timer_lst::adjust_timer(util_timer *timer)  
{  
    if (!timer) // 如果定时器为空,返回  
    {  
        return;  
    }  
    util_timer *tmp = timer->next; // 获取下一个定时器  
    // 如果定时器本身已经在正确的位置,返回  
    if (!tmp || (timer->expire < tmp->expire))   
    {  
        return;   
    }  
    // 如果是头定时器,移动头指针并重新插入  
    if (timer == head)   
    {  
        head = head->next; // 移动头指针  
        head->prev = NULL; // 更新头指针的前指针  
        timer->next = NULL; // 将定时器的下一个指针置为空  
        add_timer(timer, head); // 重新插入  
    }  
    else // 否则,链表中间或尾部的定时器  
    {  
        timer->prev->next = timer->next; // 断开当前定时器  
        timer->next->prev = timer->prev; // 从链表中移除  
        add_timer(timer, timer->next); // 重新插入  
    }  
}  

定时器检测函数:tick

遍历列表检测计时器是否过期,调用回调函数,删除已过期计时器

// 定时器检测,处理到期的定时器  
void sort_timer_lst::tick()  
{  
    if (!head) // 如果链表为空,返回  
    {  
        return;  
    }  
    
    time_t cur = time(NULL); // 获取当前时间  
    util_timer *tmp = head; // 从头节点开始检查  
    while (tmp) // 遍历链表  
    {  
        if (cur < tmp->expire) // 如果当前时间还未到期  
        {  
            break; // 不再检查后面的定时器  
        }  
        tmp->cb_func(tmp->user_data); // 执行回调函数  
        head = tmp->next; // 更新头指针  
        if (head) // 如果头指针不为空  
        {  
            head->prev = NULL; // 更新头节点的前指针  
        }  
        delete tmp; // 删除已到期的定时器  
        tmp = head; // 继续遍历  
    }  
}  

 向已排序的链表中插入定时器函数:add_timer

遍历列表寻找合适位置,插入位置,更新列表

// 向已排序的链表中插入定时器  
void sort_timer_lst::add_timer(util_timer *timer, util_timer *lst_head)  
{  
    util_timer *prev = lst_head; // 记录前一个节点  
    util_timer *tmp = prev->next; // 记录当前节点  
    // 遍历寻找合适的插入位置  
    while (tmp)  
    {  
        if (timer->expire < tmp->expire) // 如果新定时器过期时间更早  
        {  
            prev->next = timer; // 插入新定时器  
            timer->next = tmp; // 更新新定时器的下一个指针  
            tmp->prev = timer; // 更新当前节点的前指针  
            timer->prev = prev; // 更新前一个节点的后指针  
            break; // 退出循环  
        }  
        prev = tmp; // 更新前一个节点  
        tmp = tmp->next; // 更新当前节点  
    }  
    // 如果没有找到合适的位置,插入到尾部  
    if (!tmp)   
    {  
        prev->next = timer; // 更新前一个节点的下一个指针  
        timer->prev = prev; // 维持双向链接  
        timer->next = NULL; // 更新新定时器的下一个指针  
        tail = timer; // 更新尾指针  
    }  
}  

 Utils 类的定义与实现  

定义函数:init
void Utils::init(int timeslot)  
{  
    m_TIMESLOT = timeslot; // 初始化时间槽  
}  
 设置文件描述符为非阻塞函数: setnonblocking
// 设置文件描述符为非阻塞  
int Utils::setnonblocking(int fd)  
{  
    int old_option = fcntl(fd, F_GETFL); // 获取当前文件描述符的选项  
    int new_option = old_option | O_NONBLOCK; // 设置非阻塞标志  
    fcntl(fd, F_SETFL, new_option); // 应用新的标志  
    return old_option; // 返回原来的选项  
}  
 向内核事件表注册读事件函数:addfd
// 向内核事件表注册读事件,ET模式,选择开启EPOLLONESHOT  
void Utils::addfd(int epollfd, int fd, bool one_shot, int TRIGMode)  
{  
    epoll_event event;  
    event.data.fd = fd; // 设置事件数据为文件描述符  

    // 根据触发模式选择事件类型  
    if (1 == TRIGMode)  
        event.events = EPOLLIN | EPOLLET | EPOLLRDHUP; // 边缘触发  
    else  
        event.events = EPOLLIN | EPOLLRDHUP; // 水平触发  

    if (one_shot) // 如果启用单次触发  
        event.events |= EPOLLONESHOT; // 设置单次触发标志  
    
    epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &event); // 注册事件  
    setnonblocking(fd); // 设置文件描述符为非阻塞  
}  
 信号处理函数:sig_handler
// 信号处理函数  
void Utils::sig_handler(int sig)  
{  
    // 为保证函数可重入,保留原来的 errno  
    int save_errno = errno;   
    int msg = sig;  
    send(u_pipefd[1], (char *)&msg, 1, 0); // 发送信号信息到管道  
    errno = save_errno; // 还原 errno  
}  
设置信号函数:addsig 
// 设置信号函数  
void Utils::addsig(int sig, void(handler)(int), bool restart)  
{  
    struct sigaction sa;   
    memset(&sa, '', sizeof(sa)); // 清零信号集  
    sa.sa_handler = handler; // 设置处理函数  
    if (restart)  
        sa.sa_flags |= SA_RESTART; // 设置重启标志  
    sigfillset(&sa.sa_mask); // 填充信号集  
    assert(sigaction(sig, &sa, NULL) != -1); // 注册信号处理函数  
}  
定时处理任务函数:timer_handler 
// 定时处理任务,触发 SIGALRM 信号  
void Utils::timer_handler()  
{  
    m_timer_lst.tick(); // 处理定时器  
    alarm(m_TIMESLOT); // 重新设定定时器  
}  
 发送错误信息并关闭连接函数:show_error
// 发送错误信息并关闭连接  
void Utils::show_error(int connfd, const char *info)  
{  
    send(connfd, info, strlen(info), 0); // 发送错误信息  
    close(connfd); // 关闭连接  
}  
 客户端数据的回调函数 :cb_func
// 静态成员变量的初始化  
int *Utils::u_pipefd = 0; // 用于处理信号的管道  
int Utils::u_epollfd = 0; // epoll 文件描述符  

// 客户端数据的回调函数  
void cb_func(client_data *user_data)  
{  
    epoll_ctl(Utils::u_epollfd, EPOLL_CTL_DEL, user_data->sockfd, 0); // 从事件表中删除文件描述符  
    assert(user_data); // 确保用户数据存在  
    close(user_data->sockfd); // 关闭套接字  
    http_conn::m_user_count--; // 减少活动用户计数  
}  

总结:

至此,TingWebServer的所有代码都留出了注释和函数解释,下一篇将会从服务器开始运行到发送请求到最后的服务器关闭,带着所有函数名过一遍具体的服务器运行流程

本文地址:https://www.vps345.com/7426.html

搜索文章

Tags

PV计算 带宽计算 流量带宽 服务器带宽 上行带宽 上行速率 什么是上行带宽? CC攻击 攻击怎么办 流量攻击 DDOS攻击 服务器被攻击怎么办 源IP 服务器 linux 运维 游戏 云计算 服务器安全 网络安全策略 防御服务器攻击 安全威胁和解决方案 程序员博客保护 数据保护 安全最佳实践 docker 容器 java-rabbitmq java 网络工程师 网络管理 软考 2024 2024年上半年 下午真题 答案 RTSP xop RTP RTSPServer 推流 视频 Ollama Qwen2.5-coder 离线部署 阿里云 网络 网络安全 网络协议 deepseek DeepSeek-R1 API接口 llama 算法 opencv 自然语言处理 神经网络 语言模型 ubuntu ssh php 开发语言 macos windows conda javascript 前端 chrome edge 模型联网 API CherryStudio Deepseek Deepseek-R1 大模型 私有化部署 推理模型 物联网 python MCP 进程 操作系统 进程控制 Ubuntu harmonyos 华为 typescript 计算机网络 数据库 centos oracle 关系型 安全 分布式 vscode jellyfin nas fastapi mcp mcp-proxy mcp-inspector fastapi-mcp agent sse word图片自动上传 word一键转存 复制word图片 复制word图文 复制word公式 粘贴word图文 粘贴word公式 vue.js spring boot nginx jvm 虚拟机 英语 Dify DNS gnu 人工智能 深度学习 计算机视觉 卷积神经网络 后端 机器学习 eureka tcp/ip mcu c语言 单片机 tomcat fstab 经验分享 科技 ai 个人开发 ip协议 zotero WebDAV 同步失败 代理模式 开发环境 ubuntu 18.04 安装教程 json 云原生 ip redis YOLO 网络结构图 笔记 android adb kafka debian PVE 负载均衡 数据库系统 USB转串口 CH340 mysql node.js gateway c++ Clion Nova ResharperC++引擎 Centos7 远程开发 react.js 前端面试题 持续部署 智能路由器 外网访问 内网穿透 端口映射 gitee 面试 性能优化 jdk intellij-idea 架构 嵌入式 linux驱动开发 arm开发 嵌入式硬件 驱动开发 硬件工程 嵌入式实习 pip rust腐蚀 开源 Cline 学习 高级IO epoll apache vnc Docker Hub docker pull 镜像源 daemon.json Linux asm 策略模式 numpy SRS 流媒体 直播 自动化 spring c# efficientVIT YOLOv8替换主干网络 TOLOv8 计算机外设 bug Agent DeepSeek LLM CrewAI onlyoffice opengl sublime text 编辑器 nlp Hyper-V WinRM TrustedHosts AI编程 多线程 电脑 mac 软件需求 gcc centos 7 ui jenkins qt stm32项目 stm32 QT 5.12.12 QT开发环境 Ubuntu18.04 微信 微信分享 鸿蒙 Image wxopensdk 运维开发 GPU环境配置 Ubuntu22 CUDA PyTorch Anaconda安装 数据分析 ide matplotlib fonts-noto-cjk 集成学习 集成测试 C# MQTTS 双向认证 emqx HarmonyOS Next 大数据 spark hive ollama llm kylin unix 部署 openvpn server openvpn配置教程 centos安装openvpn 媒体 websocket django C 环境变量 进程地址空间 ssh漏洞 ssh9.9p2 CVE-2025-23419 并查集 leetcode rtsp服务器 rtsp server android rtsp服务 安卓rtsp服务器 移动端rtsp服务 大牛直播SDK oracle fusion oracle中间件 udp 本地环回 bind 宝塔面板访问不了 宝塔面板网站访问不了 宝塔面板怎么配置网站能访问 宝塔面板配置ip访问 宝塔面板配置域名访问教程 宝塔面板配置教程 DevEco Studio audio vue音乐播放器 vue播放音频文件 Audio音频播放器自定义样式 播放暂停进度条音量调节快进快退 自定义audio覆盖默认样式 cron crontab日志 思科 https pycharm remote-ssh 学习方法 oceanbase 传统数据库升级 银行 windows 服务器安装 多线程服务器 Linux网络编程 FTP 服务器 .netcore dify 区块链 ue5 vr MacOS录屏软件 指令 pytorch yolov5 rpc sql ip命令 新增网卡 新增IP 启动网卡 bash Linux的权限 Doris搭建 docker搭建Doris Doris搭建过程 linux搭建Doris Doris搭建详细步骤 Doris部署 jupyter kubernetes 程序人生 golang 腾讯云 像素流送api 像素流送UE4 像素流送卡顿 像素流送并发支持 mount挂载磁盘 wrong fs type LVM挂载磁盘 Centos7.9 pygame 华为云 华为od 互信 机器人 WebUI DeepSeek V3 AI大模型 gpu算力 数据结构 链表 github k8s log4j AI IM即时通讯 QQ 企业微信 剪切板对通 HTML FORMAT svn 鸿蒙系统 前端框架 vim live555 rtsp rtp 缓存 灵办AI C语言 web安全 系统安全 网络药理学 生信 生物信息学 gromacs 分子动力学模拟 MD 动力学模拟 自动化编程 WSL2 Ubuntu22.04 虚拟化 开发人员主页 ddos sqlserver Invalid Host allowedHosts vue uni-app burp suite 抓包 axure 富文本编辑器 WLAN 多层架构 解耦 rancher WSL2 上安装 Ubuntu vue3 HTML audio 控件组件 vue3 audio音乐播放器 Audio标签自定义样式默认 vue3播放音频文件音效音乐 自定义audio播放器样式 播放暂停调整声音大小下载文件 dubbo rabbitmq GaN HEMT 氮化镓 单粒子烧毁 辐射损伤 辐照效应 进程优先级 调度队列 进程切换 创意 社区 VMware创建虚拟机 http cpu 内存 实时 使用 Windsurf yum docker-compose docker compose go Flask FastAPI Waitress Gunicorn uWSGI Uvicorn protobuf 序列化和反序列化 安装 虚拟局域网 maxkb ARG prometheus 监控k8s集群 集群内prometheus flutter git elasticsearch 串口服务器 游戏程序 ios 大模型技术 本地部署大模型 transformer 聚类 jar gradle 程序员创富 软件测试 远程连接 rdp 实验 统信UOS 麒麟 bonding 链路聚合 KylinV10 麒麟操作系统 Vmware spring cloud compose devops 命名管道 客户端与服务端通信 ESP32 camera Arduino 电子信息 EtherCAT转Modbus ECT转Modbus协议 EtherCAT转485网关 ECT转Modbus串口网关 EtherCAT转485协议 ECT转Modbus网关 课程设计 cursor VMware安装mocOS VMware macOS系统安装 powerpoint Dell R750XS AIGC 人工智能生成内容 低代码 爬虫 数据挖掘 网络用户购物行为分析可视化平台 大数据毕业设计 1024程序员节 iot 信息与通信 ros 话题通信 服务通信 openstack Xen KVM 温湿度数据上传到服务器 Arduino HTTP unity dell服务器 Cursor 产品经理 agi microsoft perf linux内核 虚拟现实 Linux PID 系统 黑苹果 私有化 本地部署 KingBase 程序 编程 性能分析 vmware 卡死 java-ee milvus kind rc.local 开机自启 systemd IPMI alias unalias 别名 Alexnet lvm 磁盘挂载 磁盘分区 僵尸进程 目标检测 centos-root /dev/mapper yum clean all df -h / du -sh tcpdump VMware安装Ubuntu Ubuntu安装k8s etcd 数据安全 RBAC Hive环境搭建 hive3环境 Hive远程模式 seatunnel 迁移指南 ffmpeg 音视频 3d 数学建模 ue4 着色器 虚幻 postman 测试工具 JAVA Java 向日葵 outlook 错误代码2603 无网络连接 2603 windwos防火墙 defender防火墙 win防火墙白名单 防火墙白名单效果 防火墙只允许指定应用上网 防火墙允许指定上网其它禁止 rust ssl grafana 进程信号 实时音视频 实时互动 银河麒麟 kylin v10 麒麟 v10 远程工作 html 大模型应用 小程序 微信小程序域名配置 微信小程序服务器域名 微信小程序合法域名 小程序配置业务域名 微信小程序需要域名吗 微信小程序添加域名 express p2p vSphere vCenter 软件定义数据中心 sddc LDAP ESXi 浪潮信息 AI服务器 CPU 主板 电源 网卡 aws googlecloud iftop 网络流量监控 xcode webrtc HCIE 数通 virtualenv 权限 filezilla 无法连接服务器 连接被服务器拒绝 vsftpd 331/530 postgresql 微服务 springcloud 监控 自动化运维 DevOps 软件交付 数据驱动 应用场景 EtherNet/IP串口网关 EIP转RS485 EIP转Modbus EtherNet/IP网关协议 EIP转RS485网关 EIP串口服务器 zabbix mariadb 蓝耘科技 元生代平台工作流 ComfyUI 文件系统 路径解析 飞牛nas fnos code-server 知识库 本地化部署 flask threejs 3D okhttp SenseVoice qps 高并发 html5 firefox kamailio sip VoIP 大数据平台 网易邮箱大师 echarts 信息可视化 网页设计 系统架构 硬件架构 计算机 mamba Vmamba AI-native Docker Desktop tcp eclipse 图像处理 Carla 智能驾驶 Java Applet URL操作 服务器建立 Socket编程 网络文件读取 大模型入门 大模型教程 串口驱动 CH341 uart 485 mq rocketmq TrueLicense 微信小程序 miniapp 真机调试 调试 debug 断点 网络API请求调试方法 npm Trae IDE AI 原生集成开发环境 Trae AI 远程桌面 防火墙 端口号 开放端口 访问列表 C++ web3.py Chatbox flash-attention 报错 pdf 办公自动化 自动化生成 pdf教程 高德地图 鸿蒙接入高德地图 HarmonyOS5.0 pyqt ollama下载加速 redhat bcompare Beyond Compare mongodb Kylin-Server 国产操作系统 服务器安装 其他 rustdesk 设置代理 实用教程 rag ragflow 大模型部署 OD机试真题 华为OD机试真题 服务器能耗统计 离线部署dify IO 服务器主板 AI芯片 NFS 无人机 ux notepad springboot hadoop adobe 大语言模型 LLMs Python 网络编程 聊天服务器 套接字 TCP 客户端 Socket zip unzip 智能音箱 智能家居 linux安装配置 opcua opcda KEPServer安装 tar chatgpt oneapi kali 共享文件夹 键盘 GameFramework HybridCLR Unity编辑器扩展 自动化工具 wsl 数据集 隐藏文件 隐藏目录 管理器 通配符 pyautogui 安卓 dash 正则表达式 中间件 iis 中兴光猫 换光猫 网络桥接 自己换光猫 云服务 安防软件 minicom 串口调试工具 fd 文件描述符 Linux 维护模式 Linux无人智慧超市 LInux多线程服务器 QT项目 LInux项目 单片机项目 gpt 视频编解码 嵌入式系统开发 edge浏览器 单元测试 功能测试 selenium fpga开发 毕设 kvm 远程控制 远程看看 远程协助 visualstudio Xinference RAGFlow 银河麒麟操作系统 国产化 c 磁盘清理 list 浏览器自动化 三级等保 服务器审计日志备份 云桌面 微软 AD域控 证书服务器 换源 国内源 Debian trae RAGFLOW database 图形化界面 搜索引擎 api frp DigitalOcean GPU服务器购买 GPU服务器哪里有 GPU服务器 wsl2 Kali Linux 黑客 渗透测试 信息收集 arm 大文件分片上传断点续传及进度条 如何批量上传超大文件并显示进度 axios大文件切片上传详细教 node服务器合并切片 vue3大文件上传报错提示错误 大文件秒传跨域报错cors Google pay Apple pay 压测 ECS maven ssrf 失效的访问控制 UEFI Legacy MBR GPT U盘安装操作系统 监控k8s 监控kubernetes 代码调试 ipdb 通信工程 毕业 Mermaid 可视化图表 vu大文件秒传跨域报错cors wireshark 显示过滤器 ICMP Wireshark安装 linux环境变量 xrdp ping++ rime WebVM odoo 服务器动作 Server action 命令 流水线 脚本式流水线 MQTT协议 消息服务器 代码 ci/cd 视觉检测 ufw 源码剖析 rtsp实现步骤 流媒体开发 thingsboard android studio webstorm 智能手机 小艺 Pura X excel .net nftables 直播推流 MacMini Mac 迷你主机 mini Apple burpsuite 安全工具 mac安全工具 burp安装教程 渗透工具 sqlite3 ipython TRAE 强制清理 强制删除 mac废纸篓 华为证书 HarmonyOS认证 华为证书考试 5G 3GPP 卫星通信 微信公众平台 GPU xml 王者荣耀 YOLOv8 NPU Atlas800 A300I pro asi_bench docker run 数据卷挂载 交互模式 Ark-TS语言 css openwrt Wi-Fi AI写作 远程服务 上传视频文件到服务器 uniApp本地上传视频并预览 uniapp移动端h5网页 uniapp微信小程序上传视频 uniapp app端视频上传 uniapp uview组件库 政务 分布式系统 监控运维 Prometheus Grafana ros2 moveit 机器人运动 工具 mybatis nvm 虚拟显示器 ai小智 语音助手 ai小智配网 ai小智教程 智能硬件 esp32语音助手 diy语音助手 selete keepalived n8n dity make HP Anyware 远程 执行 sshpass 操作 llama3 Chatglm 开源大模型 重启 排查 系统重启 日志 原因 iBMC UltraISO windows日志 python2 ubuntu24.04 CPU 使用率 系统监控工具 linux 命令 gpt-3 文心一言 GCC aarch64 编译安装 HPC 环境配置 export import save load 迁移镜像 域名服务 DHCP 符号链接 配置 Ubuntu共享文件夹 共享目录 Linux共享文件夹 交互 基础环境 树莓派 VNC deepseek-r1 大模型本地部署 实战案例 序列化反序列化 主从复制 Docker引擎已经停止 Docker无法使用 WSL进度一直是0 镜像加速地址 jmeter 压力测试 测试用例 shell 磁盘监控 内网渗透 靶机渗透 服务器配置 系统开发 binder 车载系统 framework 源码环境 docker搭建pg docker搭建pgsql pg授权 postgresql使用 postgresql搭建 FunASR ASR docker搭建nacos详解 docker部署nacos docker安装nacos 腾讯云搭建nacos centos7搭建nacos ansible playbook 剧本 file server http server web server gitlab springsecurity6 oauth2 授权服务器 前后端分离 P2P HDLC 一切皆文件 linux上传下载 安卓模拟器 YOLOv12 镜像下载 freebsd iperf3 带宽测试 博客 uv wps CentOS Stream CentOS UOS 统信操作系统 visual studio code 软件工程 多进程 WebServer 混合开发 环境安装 JDK ecmascript 深度优先 图论 并集查找 换根法 树上倍增 深度求索 私域 NAS Termux Samba SSH Xterminal 设计模式 mock mock server 模拟服务器 mock服务器 Postman内置变量 Postman随机数据 玩游戏 腾讯云大模型知识引擎 京东云 命令行 基础入门 程序员 DeepSeek行业应用 Heroku 网站部署 ragflow 源码启动 Redis Desktop 产测工具框架 IMX6ULL 管理框架 ruoyi chrome 浏览器下载 chrome 下载安装 谷歌浏览器下载 快捷键 旋转屏幕 自动操作 云原生开发 K8S k8s管理系统 IIS服务器 IIS性能 日志监控 react next.js 部署next.js Portainer搭建 Portainer使用 Portainer使用详解 Portainer详解 Portainer portainer ebpf uprobe 国产数据库 瀚高数据库 数据迁移 下载安装 大模型微调 目标跟踪 OpenVINO 推理应用 ROS2 glibc cuda cudnn anaconda 高效I/O rclone AList webdav fnOS Reactor 华为认证 交换机 Ubuntu DeepSeek DeepSeek Ubuntu DeepSeek 本地部署 DeepSeek 知识库 DeepSeek 私有化知识库 本地部署 DeepSeek DeepSeek 私有化部署 银河麒麟服务器操作系统 系统激活 矩阵 开发 工业4.0 OpenManus vite ubuntu20.04 开机黑屏 飞牛NAS 飞牛OS MacBook Pro hibernate harmonyOS面试题 pillow 大屏端 shell脚本免交互 expect linux免交互 lio-sam SLAM Ubuntu Server Ubuntu 22.04.5 Linux24.04 deepin 软件构建 宝塔面板 同步 备份 建站 安全威胁分析 safari WSL win11 无法解析服务器的名称或地址 回显服务器 UDP的API使用 生活 游戏引擎 微信开放平台 微信公众号配置 HiCar CarLife+ CarPlay QT RK3588 CLion ros1 Noetic 20.04 apt 安装 Node-Red 编程工具 流编程 firewalld 读写锁 openEuler ftp 豆瓣 追剧助手 迅雷 c/c++ 串口 linux 命令 sed 命令 计算生物学 生物信息 基因组 dns 恒源云 模拟实现 intellij idea open webui 自动化任务管理 开机自启动 yum源切换 更换国内yum源 top Linux top top命令详解 top命令重点 top常用参数 bot Docker sqlite pgpool 线程 openssl 密码学 VSCode 业界资讯 鲲鹏 端口测试 模拟退火算法 田俊楠 代理 Docker快速入门 MQTT mosquitto 消息队列 cocoapods ROS 自动驾驶 HistoryServer Spark YARN jobhistory web 版本 HarmonyOS Tabs组件 TabContent TabBar TabsController 导航页签栏 滚动导航栏 AISphereButler yolov8 Attention NLP 宝塔面板无法访问 Kali 渗透 .net core wsgiref Web 服务器网关接口 DeepSeek r1 Open WebUI 联网 easyconnect 代码托管服务 RAG 检索增强生成 文档解析 大模型垂直应用 minio 免费域名 域名解析 kernel DIFY 热榜 qt5 客户端开发 java-rocketmq 做raid 装系统 BMC Jellyfin 单例模式 v10 软件 安全架构 x64 SIGSEGV SSE xmm0 ukui 麒麟kylinos openeuler Netty W5500 OLED u8g2 TCP服务器 统信 虚拟机安装 跨域 trea idea 医疗APP开发 app开发 边缘计算 自动化测试 云服务器 VPS Cookie VM搭建win2012 win2012应急响应靶机搭建 攻击者获取服务器权限 上传wakaung病毒 应急响应并溯源 挖矿病毒处置 应急响应综合性靶场 bat 模拟器 教程 服务器繁忙 open Euler dde elk RTMP 应用层 arcgis 华为OD 可以组成网络的服务器 ShapeFile GeoJSON Nginx big data opensearch helm 飞书 MI300x 孤岛惊魂4 WebRTC 匿名管道 samba 昇腾 npu 职场和发展 单一职责原则 软链接 硬链接 IPMITOOL 硬件管理 ollama api ollama外网访问 SSH 服务 SSH Server OpenSSH Server 相机 游戏服务器 TrinityCore 魔兽世界 sysctl.conf vm.nr_hugepages XCC Lenovo qemu libvirt asp.net大文件上传 asp.net大文件上传下载 asp.net大文件上传源码 ASP.NET断点续传 asp.net上传文件夹 asp.net上传大文件 .net core断点续传 群晖 文件分享 代理服务器 Docker Compose 繁忙 解决办法 替代网站 汇总推荐 AI推理 prompt 移动云 CDN 网络爬虫 可信计算技术 token sas dba stable diffusion 推荐算法 ocr python3.11 uniapp SSL 域名 skynet rsyslog 远程过程调用 Windows环境 Anolis nginx安装 linux插件下载 跨平台 电视剧收视率分析与可视化平台 cd 目录切换 Linux awk awk函数 awk结构 awk内置变量 awk参数 awk脚本 awk详解 代码规范 saltstack 服务器数据恢复 数据恢复 存储数据恢复 raid5数据恢复 磁盘阵列数据恢复 embedding cnn GoogLeNet 智慧农业 开源鸿蒙 团队开发 nfs 服务器部署ai模型 IIS Hosting Bundle .NET Framework vs2022 大大通 第三代半导体 碳化硅 conda配置 conda镜像源 apt docker命令大全 虚幻引擎 virtualbox tensorflow pyicu DocFlow LORA PX4 FTP服务器 信号处理 ubuntu24 vivado24 r语言 Linux权限 权限命令 特殊权限 h.264 nextjs reactjs visual studio MAC SecureCRT env 变量 黑客技术 crosstool-ng 显示器 论文阅读 流式接口 URL 怎么卸载MySQL MySQL怎么卸载干净 MySQL卸载重新安装教程 MySQL5.7卸载 Linux卸载MySQL8.0 如何卸载MySQL教程 MySQL卸载与安装 稳定性 看门狗 Typore 输入法 matlab xpath定位元素 EasyConnect 内网环境 k8s集群资源管理 bootstrap 技能大赛 小番茄C盘清理 便捷易用C盘清理工具 小番茄C盘清理的优势尽显何处? 教你深度体验小番茄C盘清理 C盘变红?!不知所措? C盘瘦身后电脑会发生什么变化? 网工 软件卸载 系统清理 vpn esp32 蓝牙 Helm k8s集群 less lsb_release /etc/issue /proc/version uname -r 查看ubuntu版本 RoboVLM 通用机器人策略 VLA设计哲学 vlm fot robot 视觉语言动作模型 具身智能 大版本升 升级Ubuntu系统 云电竞 云电脑 todesk Isaac Sim 虚拟仿真 大模型面经 大模型学习 iphone 分析解读 交叉编译 ArkTs ArkUI copilot Office vue-i18n 国际化多语言 vue2中英文切换详细教程 如何动态加载i18n语言包 把语言json放到服务器调用 前端调用api获取语言配置文件 MCP server C/S 全文检索 图搜索算法 实习 string模拟实现 深拷贝 浅拷贝 经典的string类问题 三个swap Unity Dedicated Server Host Client 无头主机 SSL证书 sublime text3 ELF加载 H3C NPS 雨云服务器 雨云 能力提升 面试宝典 技术 IT信息化 环境迁移 崖山数据库 YashanDB db 信号 searxng Ubuntu 24.04.1 轻量级服务器 PPI String Cytoscape CytoHubba Mac内存不够用怎么办 宠物 毕业设计 免费学习 宠物领养 宠物平台 cmake 机柜 1U 2U 高效日志打印 串口通信日志 服务器日志 系统状态监控日志 异常记录日志 firewall ftp服务 文件上传 Zoertier 内网组网 相差8小时 UTC 时间 金仓数据库 2025 征文 数据库平替用金仓 netty Qualcomm WoS QNN AppBuilder 抗锯齿 信创 信创终端 中科方德 scapy es 数据可视化 进程程序替换 execl函数 execv函数 execvp函数 execvpe函数 putenv函数 状态管理的 UDP 服务器 Arduino RTOS 个人博客 无桌面 gitea 智能电视 risc-v 蓝桥杯 CosyVoice 冯诺依曼体系 fork wait waitpid exit su sudo VR手套 数据手套 动捕手套 动捕数据手套 make命令 makefile文件 React Next.js 开源框架 联想开天P90Z装win10 MAVROS 四旋翼无人机 Windows 浏览器开发 AI浏览器 云耀服务器 scikit-learn 执法记录仪 智能安全帽 smarteye 强化学习 硬件 设备 PCI-Express SysBench 基准测试 服务器时间 ecm bpm 磁盘 空间 查错 ceph 宕机切换 服务器宕机 cmos 网络攻击模型 av1 电视盒子 机顶盒ROM 魔百盒刷机 数码 联机 僵尸毁灭工程 游戏联机 开服 7z Claude vmamba sonoma 自动更新 idm eNSP 网络规划 VLAN 企业网络 显卡驱动 AnythingLLM AnythingLLM安装 xshell termius iterm2 es6 qt6.3 g726 neo4j 数据仓库 数据库开发 数据库架构 Apache Beam 批流统一 案例展示 数据分区 容错机制 网页服务器 web服务器 telnet 远程登录 实时内核 Minecraft 双系统 DOIT 四博智联 kotlin 数据管理 数据治理 数据编织 数据虚拟化 iDRAC R720xd ArcTS 登录 ArcUI GridItem ajax arkUI netlink libnl3 powerbi grep 内核 HarmonyOS NEXT 原生鸿蒙 curl wget 容器技术 k8s资源监控 annotations自动化 自动化监控 监控service 监控jvm Linux Vim sudo原理 su切换 chrome devtools chromedriver 笔灵AI AI工具 csrutil mac恢复模式进入方法 SIP 恢复模式 VMware Tools vmware tools安装 vmwaretools安装步骤 vmwaretools安装失败 vmware tool安装步骤 vm tools安装步骤 vm tools安装后不能拖 vmware tools安装步骤 安装MySQL vasp安装 ruby 日志分析 系统取证 cfssl 沙盒 iventoy VmWare OpenEuler 拓扑图 langchain deep learning 开源软件 服务器无法访问 ip地址无法访问 无法访问宝塔面板 宝塔面板打不开 can 线程池 XFS xfs文件系统损坏 I_O error 多产物 LInux OpenHarmony VPN wireguard muduo springboot远程调试 java项目远程debug docker远程debug java项目远程调试 springboot远程 X11 Xming Reactor反应堆 Playwright 工具分享 iNode Macos ssh远程登录 uni-file-picker 拍摄从相册选择 uni.uploadFile H5上传图片 微信小程序上传图片 nac 802.1 portal 健康医疗 互联网医院 本地部署AI大模型 GRUB引导 Linux技巧 技术共享 小游戏 五子棋 AP配网 AK配网 小程序AP配网和AK配网教程 WIFI设备配网小程序UDP开 sdkman sequoiaDB EMQX 通信协议 RAID RAID技术 存储 网络文件系统 图形渲染 硅基流动 ChatBox lb 协议 nvidia prometheus数据采集 prometheus数据模型 prometheus特点 jetty undertow yaml Ultralytics 可视化 grub 版本升级 扩容 xfce perl AI代码编辑器 etl 源码 chromium dpi MNN Qwen rnn OpenSSH 语音识别 Ubuntu 22.04 MySql 算家云 算力租赁 磁盘镜像 服务器镜像 服务器实时复制 实时文件备份 桌面环境 mysql离线安装 ubuntu22.04 mysql8.0 Erlang OTP gen_server 热代码交换 事务语义 Dell HPE 联想 浪潮 ArkTS 移动端开发 webgl 金融 游戏机 hugo 即时通信 NIO 考研 DBeaver kerberos 在线office SWAT 配置文件 服务管理 网络共享 HTTP 服务器控制 ESP32 DeepSeek gaussdb llama.cpp diskgenius jina .net mvc断点续传 MDK 嵌入式开发工具 论文笔记 AI作画 聊天室 k8s二次开发 集群管理 Linux的基础指令 宝塔 玩机技巧 软件分享 软件图标 micropython mqtt 思科模拟器 Cisco 服务器扩容没有扩容成功 nuxt3 composer 服务器管理 配置教程 网站管理 qt项目 qt项目实战 qt教程 MS Materials Mac软件 飞牛 国标28181 视频监控 监控接入 语音广播 流程 SDP 视频平台 录像 视频转发 性能测试 视频流 beautifulsoup 算力 授时服务 北斗授时 社交电子 华为机试 高效远程协作 TrustViewer体验 跨设备操作便利 智能远程控制 umeditor粘贴word ueditor粘贴word ueditor复制word ueditor上传word图片 ueditor导入word ueditor导入pdf ueditor导入ppt 邮件APP 免费软件 自定义客户端 SAS 银河麒麟高级服务器 外接硬盘 Kylin UOS1070e clickhouse Xshell 框架搭建 vscode 1.86 网站搭建 serv00 ubuntu24.04.1 服务器部署 本地拉取打包 历史版本 下载 NLP模型 直流充电桩 充电桩 自学笔记 小米 澎湃OS Android 软负载 SEO chfs ubuntu 16.04 漏洞 显示管理器 lightdm gdm CNNs 图像分类 unity3d RustDesk自建服务器 rustdesk服务器 docker rustdesk 网络穿透 火绒安全 Nuxt.js fast lighttpd安装 Ubuntu配置 Windows安装 服务器优化 升级 CVE-2024-7347 端口 查看 ss 工作流 workflow nacos 弹性计算 裸金属服务器 弹性裸金属服务器 banner 图片增强 增强数据 毕昇JDK 小智 致远OA OA服务器 服务器磁盘扩容 IPv4 子网掩码 公网IP 私有IP Web服务器 多线程下载工具 PYTHON 上传视频至服务器代码 vue3批量上传多个视频并预览 如何实现将本地视频上传到网页 element plu视频上传 ant design vue vue3本地上传视频及预览移除 SSH 密钥生成 SSH 公钥 私钥 生成 CORS 备选 网站 调用 示例 easyui AutoDL 半虚拟化 硬件虚拟化 Hypervisor 用户缓冲区 Web应用服务器 镜像 支付 微信支付 开放平台 大模型训练/推理 推理问题 mindie k8s部署 MySQL8.0 高可用集群(1主2从) ShenTong 音乐服务器 Navidrome 音流 智能合约 哈希算法 wpf Linux find grep 钉钉 打不开xxx软件 无法检查其是否包含恶意软件 IPv6 IPv6测试 IPv6测速 IPv6检测 IPv6查询 GeneCards OMIM TTD 分子对接 autodock mgltools PDB PubChem navicat arkTs 电路仿真 multisim 硬件工程师 硬件工程师学习 电路图 电路分析 仪器仪表 pyside6 界面 抓包工具 autoware g++ g++13 语法 flink WINCC AzureDataStudio 我的世界服务器搭建 minecraft 存储维护 NetApp存储 EMC存储 服务器ssl异常解决 Pyppeteer iTerm2 项目部署到linux服务器 项目部署过程 超融合 安全漏洞 信息安全 李心怡 管道 servlet ranger MySQL8.0 网卡的名称修改 eth0 ens33 影刀 #影刀RPA# 反向代理 web3 docker部署Python NAT转发 NAT Server 北亚数据恢复 oracle数据恢复 运维监控 IMM 嵌入式Linux IPC 企业网络规划 华为eNSP EMUI 回退 降级 figma 程序化交易 量化交易 高频交易 rpa LVM lvresize 磁盘扩容 pvcreate SystemV 多端开发 智慧分发 应用生态 鸿蒙OS mcp服务器 client close DenseNet 常用命令 文本命令 目录命令 Headless Linux 僵尸世界大战 游戏服务器搭建 mysql安装报错 windows拒绝安装 增强现实 沉浸式体验 技术实现 案例分析 AR 考试 css3 zookeeper 分布式账本 信任链 共识算法 MobaXterm 文件传输 ai工具 阿里云ECS ldap yashandb 元服务 应用上架 GIS 遥感 WebGIS retry 重试机制 Sealos 架构与原理 多个客户端访问 IO多路复用 TCP相关API 脚本 进程间通信 查看显卡进程 fuser gunicorn ArtTS IO模型 cpp-httplib 充电桩平台 充电桩开源平台 Ubuntu 24 常用命令 Ubuntu 24 Ubuntu vi 异常处理 烟花代码 烟花 元旦 Ardupilot vllm deekseek fiddler 环境搭建 Maven 子系统 网络建设与运维 网络搭建 神州数码 神州数码云平台 云平台 MacOS zerotier 免密 公钥 私钥 Linux环境 锁屏不生效 NFC 近场通讯 智能门锁 initramfs Linux内核 Grub tidb GLIBC midjourney sentinel 搭建个人相关服务器 知识图谱 佛山戴尔服务器维修 佛山三水服务器维修 swoole wordpress 无法访问wordpess后台 打开网站页面错乱 linux宝塔面板 wordpress更换服务器 laravel mm-wiki搭建 linux搭建mm-wiki mm-wiki搭建与使用 mm-wiki使用 mm-wiki详解 问题解决 我的世界 我的世界联机 Ubuntu20.04 2.35 glm4 hosts 加解密 Yakit yaklang MVS 海康威视相机 干货分享 黑客工具 密码爆破 resolv.conf tailscale derp derper 中转 triton 模型分析 Python基础 Python教程 Python技巧 线性代数 电商平台 阻塞队列 生产者消费者模型 服务器崩坏原因 C++软件实战问题排查经验分享 0xfeeefeee 0xcdcdcdcd 动态库加载失败 程序启动失败 程序运行权限 标准用户权限与管理员权限 proxy模式 性能调优 安全代理 本地知识库部署 DeepSeek R1 模型 IDEA 录音麦克风权限判断检测 录音功能 录音文件mp3播放 小程序实现录音及播放功能 RecorderManager 解决录音报错播放没声音问题 Echarts图表 折线图 柱状图 异步动态数据 鸿蒙开发 可视化效果 欧标 OCPP 软件开发 lua nohup后台启动 ECT转485串口服务器 ECT转Modbus485协议 ECT转Modbus串口服务器 服务网格 istio js 音乐库 pppoe radius docker desktop 输入系统 终端 合成模型 扩散模型 图像生成 EVE-NG word 极限编程 多路转接 蓝桥杯C++组 Radius USB网络共享 移动开发 termux openjdk Spring Security react native AimRT 捆绑 链接 谷歌浏览器 youtube google gmail Qwen2.5-VL 物联网开发 项目部署 新盘添加 partedUtil dock 加速 GRE massa sui aptos sei ISO镜像作为本地源 游戏开发 负载测试 大模型推理 备份SQL Server数据库 数据库备份 傲梅企业备份网络版 regedit 开机启动 whistle linuxdeployqt 打包部署程序 appimagetool deployment daemonset statefulset cronjob autodl AD域 win服务器架设 windows server webview AI员工 dns是什么 如何设置电脑dns dns应该如何设置 银河麒麟桌面操作系统 Kylin OS xss 在线预览 xlsx xls文件 在浏览器直接打开解析xls表格 前端实现vue3打开excel 文件地址url或接口文档流二进 分布式训练 seleium AI agent IMX317 MIPI H265 VCU TCP协议 计算机科学与技术 底层实现 达梦 DM8 macbook Logstash 日志采集 IP配置 netplan 对比 meld DiffMerge 状态模式 端口聚合 windows11 终端工具 远程工具 移动魔百盒 System V共享内存 进程通信 计算虚拟化 弹性裸金属 VS Code 根服务器 小智AI服务端 xiaozhi TTS image AD 域管理 nosql llamafactory 微调 armbian u-boot junit hexo 电脑桌面出现linux图标 电脑桌面linux图标删除不了 电脑桌面Liunx图标删不掉 linux图标删不掉 deepseak 豆包 KIMI 腾讯元宝 AI Agent 字节智能运维 内存管理 内网服务器 内网代理 内网通信 Claude Desktop Claude MCP Windows Cli MCP deepseek r1 7-zip 需求分析 规格说明书 显卡驱动持久化 GPU持久化 vscode1.86 1.86版本 ssh远程连接 docker部署翻译组件 docker部署deepl docker搭建deepl java对接deepl 翻译组件使用 网络原理 LLM Web APP Streamlit kubeless WireGuard 异地组网 查询数据库服务IP地址 SQL Server 行情服务器 股票交易 速度慢 切换 股票量化接口 股票API接口 nohup 异步执行 弹性服务器 搜狗输入法 中文输入法 NVM Node Yarn PM2 NVML nvidia-smi 解决方案 ABAP 配置原理 ardunio BLE 机械臂 GPU训练 rtc 动态规划 西门子PLC 通讯 Python 视频爬取教程 Python 视频爬取 Python 视频教程 桌面快捷方式 智能体开发 IPv4/IPv6双栈 双栈技术 网路规划设计 ensp综合实验 IPv4过渡IPv6 IPv4与IPv6 UDP yum换源 VGG网络 卷积层 池化层 零售 pthread 风扇控制软件 免费 postgres Dify重启后重新初始化 动静态库 框架 跨域请求 设计规范 流量运营 java-zookeeper Unity插件 数字证书 签署证书 智能体 autogen openai coze c/s SVN Server tortoise svn ubuntu安装 linux入门小白 带外管理 服务器正确解析请求体 设备树 localhost 券商 股票交易接口api 类型 特点 macOS 金仓数据库概述 金仓数据库的产品优化提案 archlinux kde plasma solidworks安装 HAProxy Bug解决 Qt platform OpenCV 区块链项目 接口优化 PyQt PySide6 CUPS 打印机 Qt5 粘包问题 mac设置host 用户管理 飞腾处理器 brew node 三次握手 海康 Qt QModbus 物理地址 页表 虚拟地址 星河版 支持向量机 文件存储服务器组件 logstash 源代码 商用密码产品体系 client-go hdc mvc 源代码管理 mybase Alist mount 挂载 网盘 鸿蒙NEXT RDP element-ui 上传视频并预览视频 vue上传本地视频及进度条功能 vue2选择视频上传到服务器 upload上传视频组件插件 批量上传视频 限制单个上传视频 Webserver 异步 post.io 企业邮箱 搭建邮箱 软考设计师 中级设计师 SQL 软件设计师 华为昇腾910b3 可用性测试 CAD瓦片化 栅格瓦片 矢量瓦片 Web可视化 DWG解析 金字塔模型 comfyui comfyui教程 UFW MinIO Qwen3 qwen3 32b chatbox 静态IP linux子系统 忘记密码 命令模式 solr #STC8 #STM32 计算机学习路线 编程语言选择 向量数据库 安装部署 milvus安装