TinyWebserver的复现与改进(6):定时器处理非活动连接
如果客户端长时间没有动作,会占用了许多连接资源,严重影响服务器的性能。因此需要通过实现一个服务器定时器,处理这种非活跃连接,释放连接资源。
定时器处理流程
- SIGALARM触发:整个流程开始于一个 SIGALARM 信号,该信号每5秒会触发一次。
- 发送消息:当SIGALARM触发时,会进入
sig_handler
中断服务函数中,在该函数中,会向管道写端发送数据 - Epoll轮询:管道读端接收到消息后,使用 epoll 模型对所有的 socket(特别是管道读端 pipefd[0] )连接进行轮询。由于管道读端有数据,会将 timeout ➡ true
- Tick计时器:当 timeout 为 true 时,会进入tick 函数中检查连接是否超时,由于定时器的数据结构是一个链表,所以这个过程实际上是链表的查询操作
- 超时处理:当链表查询到客户端超时连接时,服务器会调用 cb_func 回调函数来断开这些连接。在这个函数中,服务器可能会执行一些必要的清理工作,比如从 epoll 监视的 socket 列表中删除超时的 sockfd,以及删除定时器。
- 重定时:当查询到各个客户端都不超时或者执行完 cb_func 时,会重新给 SIGALARM 信号 定时5s,然后等待下一次触发
定时器的实现
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;
};
定时器实际上是链表的一个节点,里面存着任务超时时间,这里是使用绝对时间,以及任务的回调函数(cb_func),客户端数据使用的是一个额外的结构体,里面有客户端的地址族、sockfd、以及下一个定时器的指针。(当然也可以用http_conn这个数据类,但比较麻烦)
每个客户端都会产生一个定时器结点,它会存储在一个定时器链表中。
// 定时器链表, 是升序的双向链表, 带有头节点和尾节点
class sort_timer_lst{
public:sort_timer_lst();// 析构函数~sort_timer_lst();// 将目标定时器放在链表中void add_timer(util_timer* timer);// 将定时器 timer 从链表中删除void del_timer(util_timer* timer);// 当某个定时任务发生变化, 调整该定时器在链表中的位置,此函数只考虑定时时间延长的情况,即定时器往链表的后面移动void adjust_timer(util_timer* timer);/* SIGALARM每触发一次就在信号处理函数执行一次tick函数, 以处理链表上的到期任务 */void tick();util_timer* head;util_timer* tail;
private:// 将目标定时器 timer 添加到节点 lst_head 之后的部分链表中void 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;}// timer->expire 是最大的,则插入到末尾if(!tmp){prev->next = timer;timer->prev = prev;timer->next = NULL;tail = timer;}}
};
这个链表类函数的增删改查都与链表的差不多,对此不再赘述
具体函数说明
sig_handler
// 信号的中断处理函数
void timer_sig_handler(int sig)
{int save_errno = errno;int msg = sig;send(pipefd[1], (char*)&msg, 1, 0);errno = save_errno;
}
- pipefd[0] 对应的是管道的读端,pipefd[1] 对应的是管道的写端
这个函数是向管道写端写入信号(alarm)的值
cb_func
// 定时器回调函数, 从epoll上删除sockfd
void cb_func(client_data *user_data)
{printf("close fd : %d\n", user_data->sockfd);epoll_ctl(epollfd, EPOLL_CTL_DEL, user_data->sockfd, 0);assert(&user_data);close(user_data->sockfd);
}
初始化
// 信号的初始化
void timer_sig_init()
{// 创建int ret = socketpair(AF_UNIX, SOCK_STREAM, 0, pipefd);assert(ret != -1);setnonblocking(pipefd[1]);addfd(epollfd, pipefd[0], false);addsig(SIGALRM, timer_sig_handler);addsig(SIGTERM, timer_sig_handler);
}
socketpair
函数用于创建一个全双工的、相互连接的、无名的套接字对。这两个套接字就像是同一个管道的两端,但它们是网络通信的范畴,而不是进程间管道通信的范畴。(简单来说就是模拟成管道通信)- 在默认情况下,当使用
write
或send
函数向管道写入数据时,如果管道的读端缓冲区已满,写操作将被阻塞,直到有空间可用。因此我们需要将pipefd[1]
设置为非堵塞,防止服务器卡在某一个地方
将新客户端添加到链表中
//初始化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;
timer_lst.add_timer(timer);
读端有数据
else if((sockfd == pipefd[0])&&(events[i].events & EPOLLIN))
{int sig;char signals[1024];ret = recv(pipefd[0], signals, sizeof(signals), 0);if (ret == -1){continue;}else if (ret == 0){continue;}else{for (int i = 0; i < ret; ++i){switch (signals[i]){case SIGALRM:{timeout = true;break;}case SIGTERM:{stop_server = true;}}}}
}
当 pipe 读端有数据时,会进入这个if循环中,然后使用 recv
读取”管道“的数据,再依次判断
系列文章
GitHub - yzfzzz/MyWebServer: Linux高并发服务器项目,参考了TinyWebServer,将在此基础上进行性能改进与功能增加。为方便读者学习,附带详细注释和博客!
TinyWebserver的复现与改进(1):服务器环境的搭建与测试-CSDN博客
TinyWebserver的复现与改进(2):项目的整体框架-CSDN博客
TinyWebserver的复现与改进(3):线程同步机制类封装及线程池实现-CSDN博客
TinyWebserver的复现与改进(4):主线程的具体实现-CSDN博客
TinyWebserver的复现与改进(5):HTTP报文的解析与响应-CSDN博客
完整代码
main.cpp
#include <arpa/inet.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
#include <stdlib.h>
#include <sys/epoll.h>
#include "threadpool.hpp"
#include "locker.h"
#include "http_conn.h"
#include <signal.h>
#include <assert.h>
#include "lst_timer.h"
#define MAX_FD 65536 // 最大的文件描述符
#define MAX_EVENT_NUMBER 10000 // 监听的最大事件数
#define TIMESLOT 5
//设置定时器相关参数
static int pipefd[2];
static sort_timer_lst timer_lst;
static int epollfd = 0;
// 信号的中断处理函数
void timer_sig_handler(int sig)
{int save_errno = errno;int msg = sig;send(pipefd[1], (char*)&msg, 1, 0);errno = save_errno;
}
// 定时器回调函数, 从epoll上删除sockfd
void cb_func(client_data *user_data)
{printf("close fd : %d\n", user_data->sockfd);epoll_ctl(epollfd, EPOLL_CTL_DEL, user_data->sockfd, 0);assert(&user_data);close(user_data->sockfd);
}
void timer_handler()
{timer_lst.tick();alarm(TIMESLOT);
}
/* 函数指针的声明: 类型说明符 (*函数名) (参数)void(handler)(int) 声明了一个名为 handler 的函数指针,它指向一个接受一个 int 参数并返回 void 的函数
*/
void addsig(int sig, void(handler)(int), bool restart = false)
{// sigaction的输入参数struct sigaction sa;// 指定sa内存区域的前n个字节都设置为某个特定的值('\0'),用于对新分配的内存进行初始化memset(&sa, '\0', sizeof(sa));// 写入函数指针,指向的函数就是信号捕捉到之后的处理函数sa.sa_handler = handler;if(restart)sa.sa_flags |= SA_RESTART;// 设置临时阻塞信号集sigfillset(&sa.sa_mask);assert(sigaction(sig, &sa, NULL) != -1);
}// 信号的初始化
void timer_sig_init()
{int ret = socketpair(AF_UNIX, SOCK_STREAM, 0, pipefd);assert(ret != -1);setnonblocking(pipefd[1]);addfd(epollfd, pipefd[0], false);addsig(SIGALRM, timer_sig_handler);addsig(SIGTERM, timer_sig_handler);
}int main(int argc, char* argv[])
{if(argc <= 1){// 要求输入格式为 ./a.out 10000 其中10000是端口号 printf("usage: %s port_number\n", basename(argv[0]));return 1;}// 端口号 string -> intint port = atoi(argv[1]);// 如果向一个没有读端的管道写数据,不用终止进程addsig(SIGPIPE, SIG_IGN); // SIG_IGN: 忽略信号,这里指的是忽略信号 · SIGPIPE// 定义一个线程池指针threadpool<http_conn>* pool = NULL;try {// 开辟一个线程池pool = new threadpool<http_conn>;}catch(...){// 若异常则退出return 1;}// 开辟一块连续的http_conn数组,保存所有正在连接的客户端信息http_conn* users = new http_conn[MAX_FD];client_data *users_timer = new client_data[MAX_FD];// 设置监听int listenfd = socket(AF_INET, SOCK_STREAM, 0);int ret = 0;struct sockaddr_in address;address.sin_addr.s_addr = INADDR_ANY;address.sin_family = AF_INET;address.sin_port = htons(port);// 设置端口复用int reuse = 1;setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse));// 绑定ret = bind(listenfd, (struct sockaddr*)&address, sizeof(address));if(ret == -1){perror("bind");exit(-1);}// 开始监听ret = listen(listenfd, 5);if(ret == -1){perror("listen");exit(-1);}// 将listend添加到epoll模型中epoll_event events[MAX_EVENT_NUMBER];epollfd = epoll_create(5);addfd(epollfd, listenfd, false);http_conn::m_epollfd = epollfd;timer_sig_init();bool timeout = false;bool stop_server = false;alarm(TIMESLOT);while(!stop_server){// epoll轮询,等待有数据发送int number = epoll_wait(epollfd, events, MAX_EVENT_NUMBER,-1);if((number < 0) && (errno != EINTR)){printf("epoll failture\n");break;}for(int i = 0; i < number; i++){int sockfd = events[i].data.fd;// 有新的客户端连接if(sockfd == listenfd){struct sockaddr_in client_address;socklen_t client_addresslen = sizeof(client_address);int connfd = accept(listenfd, (struct sockaddr*)&client_address, &client_addresslen);if(connfd < 0){printf("errno is %d\n", errno);continue;}if(http_conn::m_user_count >= MAX_FD){close(connfd);continue;}users[connfd].init(connfd, client_address);//初始化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;timer_lst.add_timer(timer);}else if((sockfd == pipefd[0])&&(events[i].events & EPOLLIN)){int sig;char signals[1024];ret = recv(pipefd[0], signals, sizeof(signals), 0);if (ret == -1){continue;}else if (ret == 0){continue;}else{for (int i = 0; i < ret; ++i){switch (signals[i]){case SIGALRM:{timeout = true;break;}case SIGTERM:{stop_server = true;}}}}}// 若对方异常端开或错误else if(events[i].events & (EPOLLRDHUP | EPOLLHUP | EPOLLERR)){users[sockfd].close_conn();}// 有读事件发生(可读)else if(events[i].events & EPOLLIN){util_timer *timer = users_timer[sockfd].timer;// 有读事件发生if(users[sockfd].read()){// 读的到数据pool->append(users+sockfd);//若有数据传输,则将定时器往后延迟3个单位//并对新的定时器在链表上的位置进行调整if (timer){time_t cur = time(NULL);timer->expire = cur + 3 * TIMESLOT;timer_lst.adjust_timer(timer);}}else{printf("Read Fail!\n");// 读不到数据timer->cb_func(&users_timer[sockfd]);if (timer){timer_lst.del_timer(timer);}// users[sockfd].close_conn();}}// 有写事件发生(可写)else if(events[i].events & EPOLLOUT){util_timer *timer = users_timer[sockfd].timer;if(users[sockfd].write()){if (timer){time_t cur = time(NULL);timer->expire = cur + 3 * TIMESLOT;timer_lst.adjust_timer(timer);}}else{printf("Write Fail!\n");timer->cb_func(&users_timer[sockfd]);if (timer){timer_lst.del_timer(timer);}// users[sockfd].close_conn();}}}if (timeout){timer_handler();timeout = false;}}close(epollfd);close(listenfd);close(pipefd[1]);close(pipefd[0]);delete [] users;delete[] users_timer;delete pool;return 0;
}
lst_timer.h
#pragma once
#include <stdio.h>
#include <time.h>
#include <arpa/inet.h>
#define BUFFER_SIZE 64class 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);// 将定时器 timer 从链表中删除void del_timer(util_timer* timer);// 当某个定时任务发生变化, 调整该定时器在链表中的位置,此函数只考虑定时时间延长的情况,即定时器往链表的后面移动void adjust_timer(util_timer* timer);/* SIGALARM每触发一次就在信号处理函数执行一次tick函数, 以处理链表上的到期任务 */void tick();util_timer* head;util_timer* tail;
private:// 将目标定时器 timer 添加到节点 lst_head 之后的部分链表中void 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;}// timer->expire 是最大的,则插入到末尾if(!tmp){prev->next = timer;timer->prev = prev;timer->next = NULL;tail = timer;}}
};
lst_timer.cpp
#include "lst_timer.h"
#include <signal.h>
#include <errno.h>
#include <cassert>
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;}
}// 将目标定时器放在链表中
void sort_timer_lst::add_timer(util_timer* timer)
{if(!timer){return;}if(!head){head = tail = timer;return;}/* 如果目标定时器的超时时间小于当前链表中所有定时器的超时时间,则把该定时器插入链表头部,作为链表新的头节点,否则就需要调用重载函数 add_timer(),把它插入链表中合适的位置,以保证链表的升序特性 */if(timer->expire < head->expire){timer->next = head;head->prev = timer;head = timer;return;}add_timer(timer, head);
}// 将定时器 timer 从链表中删除
void sort_timer_lst::del_timer(util_timer* timer)
{if(!timer){return;}// 链表中只有一个定时器if((timer == head) && (timer == tail)){delete timer;head = NULL;tail = NULL;return;}// 链表至少有一个定时器, 且头节点恰好是目标定时器if(timer == head){head = head->next;head->prev = NULL;delete timer;return;}// 链表至少有一个定时器, 且尾节点恰好是目标定时器if(timer == tail){tail = tail->prev;tail->next = NULL;delete timer;return;}// 链表至少有一个定时器, 目标定时器处在链表中间timer->prev->next = timer->next;timer->next->prev = timer->prev;delete 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);}
}/* SIGALARM每触发一次就在信号处理函数执行一次tick函数, 以处理链表上的到期任务 */
void sort_timer_lst::tick()
{if(!head){return;}printf( "Timer Tick\n" );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;printf("close client request\n");}
}
相关文章:

TinyWebserver的复现与改进(6):定时器处理非活动连接
如果客户端长时间没有动作,会占用了许多连接资源,严重影响服务器的性能。因此需要通过实现一个服务器定时器,处理这种非活跃连接,释放连接资源。 定时器处理流程 SIGALARM触发:整个流程开始于一个 SIGALARM 信号&…...

ThinkPHP5 5.0.23 远程代码执行漏洞
目录 1、启动环境 2、漏洞利用 3、更改传参方式 4、修改参数 5、发送数据 1、启动环境 docker-compose up -d 2、访问靶机ip端口号8080 2、漏洞利用 使用burpsuite抓包软件抓包 3、更改传参方式 将 GET传参改为POST传参 4、修改参数 url参数 /index.php?scaptcha post参…...

C++鼠标键盘操作自动化
C鼠标键盘操作自动化 #pragma once #include <Windows.h> enum KEYS{A 65,W87,S83,D68,SHIFTVK_LSHIFT,ALT18,Tilde 126,//~TABVK_TAB,B66,SPACEVK_SPACE,ESCVK_ESCAPE,Q81 }; enum MOUSE {ML,MW,MR//左,中,右 }; class simulator//模拟器 { pu…...

多个主流Python GUI库全面解析,助你用Python轻松构建精美界面
Python 作为一门易学易用的编程语言,在各个领域都拥有广泛的应用。而 GUI (Graphical User Interface) 编程更是让 Python 变得更加灵活,可以帮助我们创建各种各样的桌面应用,为用户提供直观的交互体验。本文将介绍几个Python GUI 编程中常用…...

Kotlin学习-01创建kotlin学习
安装idea https://www.jetbrains.com/zh-cn/ 创建项目 选择kotlin 修改Main.kt fun main() {print("Hello World!") }运行...

Java、python、php版的企业单位考勤打卡管理系统的设计与实现(源码、调试、LW、开题、PPT)
💕💕作者:计算机源码社 💕💕个人简介:本人 八年开发经验,擅长Java、Python、PHP、.NET、Node.js、Android、微信小程序、爬虫、大数据、机器学习等,大家有这一块的问题可以一起交流&…...

在IntelliJ IDEA中使用Git推送项目
去gitee网站注册用户 gitee网站地址:https://gitee.com/ github网站地址:https://github.com/ 一、创建仓库 以下以gitee为例进行介绍,github操作雷同。 1、创建仓库 点击页面右上方的"“并选择"创建仓库” 2、设置仓库相关信息 首先输入仓库名&…...

CNN代码实战
CNN的原理 从 DNN 到 CNN (1)卷积层与汇聚 ⚫ 深度神经网络 DNN 中,相邻层的所有神经元之间都有连接,这叫全连接;卷积神经网络 CNN 中,新增了卷积层(Convolution)与汇聚(…...

迁移学习代码复现
一、前言 说来可能令人难以置信,迁移学习技术在实践中是非常简单的,我们仅需要保留训练好的神经网络整体或者部分网络,再在使用迁移学习的情况下把保留的模型重新加载到内存中,就完成了迁移的过程。之后,我们就可以像训练普通神经网络那样训练迁移过来的神经网络了。 我们…...

Elasticsearch(ES)常用命令
常用运维命令 一、基本命令1.1、查看集群的健康状态1.2、查看节点信息1.3、查看索引列表1.4、创建索引1.5、删除索引1.6、关闭索引1.7、打开索引1.8、查看集群资源使用情况(各个节点的状态,包括磁盘,heap,ram的使用情况࿰…...

C/C++ 不定参函数
C语言不定参函数 函数用法总结 Va_list 作用:类型定义,生命一个变量,该变量被用来访问传递给不定参函数的可变参数列表用法:供后续函数进调用,通过该变量访问参数列表 typedefchar* va_list; va_start 作用ÿ…...

C语言——函数专题
1.概念 在C语言中引入函数的概念,有些翻译为子程序。C语言中的函数就是一个完成某项特定任务的一小段代码,这个代码是有特殊的写法和调用方法的。一般我们可以分为两种函数:库函数和自定义函数。 2.库函数 C语言国际标准ANSIC规定了一些常…...

springboot打可执行jar包
1. pom文件如下 <project xmlns"http://maven.apache.org/POM/4.0.0"xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><m…...

【SQL】科目种类
目录 题目 分析 代码 题目 表: Teacher ------------------- | Column Name | Type | ------------------- | teacher_id | int | | subject_id | int | | dept_id | int | ------------------- 在 SQL 中,(subject_id, dept_id) 是该表的主键。 该表…...

【深度学习】【语音】TTS,最新TTS模型概览,扩散模型TTS,MeloTTS、StyleTTS2、Matcha-TTS
文章目录 基础介绍对比基础介绍 MeloTTS: MeloTTS 是 MyShell.ai 开发的一个多语言语音合成模型,支持包括英语、西班牙语、法语、中文、日语和韩语等多种语言。它以高质量的语音合成为特色,尤其擅长处理中英混合内容。该模型优化了在 CPU 上的实时推理能力,使其在多种应用场…...

【论文笔记】LION: Linear Group RNN for 3D Object Detection in Point Clouds
原文链接:https://arxiv.org/abs/2407.18232 简介:Transformer在3D点云感知任务中有二次复杂度,难以进行长距离关系建模。线性RNN则计算复杂度较低,适合进行长距离关系建模。本文提出基于窗口的网络线性组RNN(即对分组…...

打造高可用集群的基石:深度解析Keepalived实践与优化
高可用集群 集群类型 集群类型主要分为负载均衡集群(LB)、高可用集群(HA)和高性能计算集群(HPC)三大类。每种集群类型都有其特定的应用场景和优势。 1. 负载均衡集群(LB) 负载均衡集…...

Web大学生网页作业成品——环保主题介绍网页网站设计与实现(HTML+CSS)(5个页面)
🎉🎉🎉 常见网页设计作业题材有**汽车、环保、明星、文化、国家、抗疫、景点、人物、体育、植物、公益、图书、节日、游戏、商城、旅游、家乡、学校、电影、动漫、非遗、动物、个人、企业、美食、婚纱、其他**等网页设计题目, 可满足大学生网…...

Qt登录窗口设计
widget.h #ifndef WIDGET_H #define WIDGET_H#include <QWidget> #include <QIcon> //图标类 #include <QPushButton> #include <QLineEdit> //行编辑 #include <QLabel> #include <QTextEdit> #include <QMovie>class Widge…...

探索数据矿藏:我的AI大模型与数据挖掘实战经验分享
🚀 探索数据矿藏:我的AI大模型与数据挖掘实战经验分享 💖 前言:数据的金矿,AI的翅膀 在人工智能的浪潮中,我有幸作为项目负责人,带领团队深入挖掘数据的潜力,利用AI大模型的力量&am…...

linux C语言strcat函数及相关函数
讯飞星火 strcat函数是C语言中的一个字符串处理函数,用于将一个字符串追加到另一个字符串的末尾。它的原型如下: char *strcat(char *dest, const char *src);参数说明: dest:目标字符串,即要将src字符串追加到这个字符串的末尾。src:源字符串,即要追加到dest字符串末…...

使用 sort 进行文本文件处理
sort 使用经验笔记 一、简介 sort 是一个命令行工具,用于对文本文件中的行进行排序。它广泛应用于 Unix/Linux 系统中,可以用来对文件的内容进行简单的排序操作,也可以与其他命令结合使用来完成更复杂的任务。 二、基本用法 排序文件: sor…...

HarmonyOS笔记4:从云数据库获取数据
移动应用获取数据的方式主要有: 1.从网络中获取数据接口API。 2.从华为云数据库获取云数据库的资源。 3.从移动终端直接获取本地的数据 在HarmonyOS笔记3中已经完成了方式一从网络中获取数据接口API的方式。在本篇笔记中,将讨论从云数据库中获取数据。 因…...

QT5生成独立运行的exe文件
目录 1 生成独立运行的exe文件1.1 设置工程Release版本可执行文件存储路径1.2 将工程编译成Release版本 2 使用QT5自带的windeployqt拷贝软件运行依赖项3 将程序打包成一个独立的可执行软件exe4 解决QT5 This application failed to start because no Qt platform plugin could…...

LabVIEW光纤水听器闭环系统
开发了一种利用LabVIEW软件开发的干涉型光纤水听器闭环工作点控制系统。该系统通过调节光源频率和非平衡干涉仪的光程差,实现了工作点的精确控制,从而提高系统的稳定性和检测精度,避免了使用压电陶瓷,使操作更加简便。 项目背景 …...

Shell——流程控制语句(if、case、for、while等)
在 Shell 编程中,流程控制语句用于控制脚本的执行顺序和逻辑。这些语句包括 if、case、for、while 等,它们的使用可以使脚本实现更复杂的逻辑。以下是它们的详细说明和语法结构: 1. if 语句 if 语句用于条件判断,执行符合条件的…...

【redis的大key问题】
在使用 Redis 的过程中,如果未能及时发现并处理 Big keys(下文称为“大Key”),可能会导致服务性能下降、用户体验变差,甚至引发大面积故障。 本文将介绍大Key产生的原因、其可能引发的问题及如何快速找出大Key并将其优…...

HighPoint SSD7749M2:128TB NVMe 存储卡实现28 GB/s高速传输
HighPoint Technologies推出了一款全新的SSD7749M2 RAID卡,能够在标准的桌面工作站中安装多达16个M.2 SSD,实现高达128TB的闪存存储。该卡通过PCIe Gen4 x16接口提供高达28 GB/s的顺序读写性能。这些令人瞩目的性能规格伴随着高昂的价格标签。 #### 技术…...

ARM 裸机与 Linux 驱动对比及 Linux 内核入门
目录 ARM裸机代码和驱动的区别 Linux系统组成 内核五大功能 设备驱动分类 内核类型 驱动模块 驱动模块示例 Makefile配置 命令 编码辅助工具 内核中的打印函数 printk 函数 修改打印级别 编辑 打印级别含义 驱动多文件编译 示例 模块传递参数 命令行传递参数…...

0101DNS TCP fallback on UDP query timeout disabled-redission-中间件
文章目录 1.问题描述2.临时解决方案 结语 1.问题描述 Springcloud 项目,微服务模块使用redission,启动报错 DNS TCP fallback on UDP query timeout disabled. Upgrade Netty to 4.1.105 or higher.相关软件版本如下 软件版本描述springboot2.7.18spr…...