WebServer实现:muduo库的主丛Reactor架构
前言
作为服务器,核心自然是高效的处理来自client的多个连接啦,那问题在于,如何高效的处理client的连接呢?这里就介绍两种架构:单Reactor架构和主丛Reactor架构。
单Reactor架构
单Reactor架构的核心为,由一个主线程监听包括ServerFd在内的所有fd,当某个fd 可读/可写的时候,就把它交给线程池中的某个线程去处理,核心流程大概是:
while(true) {epoll_wait();/*遍历所有监听到的event*/for(所有活跃的events的fd) {if(fd == listenfd) dealListen(); /*如果是listen fd可读 ,则accept建立新的连接*/if(fd == readfd) dealRead(); /*如果clientfd可读,就交给线程池处理可读*/if(fd == writefd) dealWrite(); /*如果可写 交给线程池处理可写*/}
}
/*伪代码*/
dealListen()
{do{int retFd = accept();if(retFd < 0) /*没有待处理的fd了*/break;/*其它和retFd相关的处理*/}while(true);
}
这看起来是个很合理的实现,但是在某些场景下是会遇到性能瓶颈的,试想这样一个场景:如果同时突然间出现了大量的client请求链接,那我们的epoll_wait监听到的应该是listenFd可读,调用dealListen()来处理listen事件,dealListen可能是调用多次accept函数直到返回值小于0,才表明没有新的连接了,这个过程很费时间,也就意味着:哪怕有些client已经建立连接了,并且触发可读/可写了,主线程都无法及时将事件交给线程池处理,而这就是它的性能瓶颈
这就引入了我们的主丛Reactor架构,它的处理逻辑是:主线程的epoll只负责监听,监听到了就把这个fd交给其他的epoll去处理(监听它的可读可写),这样,在面对突发IO连接的时候,也能及时响应。
其中主Reactor的核心是Acceptor中处理listenfd的可读/写操作,然后把监听到的clientfd分发给其他的sub_reactor,当client_fd可读可写的时候,由对应的sub_reactor的epoll监听到进行读写。
有了思路,剩下的事情就是怎么做,我们来看看muduo库的实现。
Channel类—>对fd的封装
对于每个fd,不管是clientfd还是listenfd也好,监听到可读/可写后,都应该调用自身对应的可读/写回调函数进行处理;同时对于每个fd,他也有自己对应的epoll(在这里用EventLoop对epoll和thread进一步的封装)
class Channel : noncopyable
{
public:/*只是起了个别名的作用*/using EventCallback = std::function<void()>; // muduo仍使用typedefusing ReadEventCallback = std::function<void(Timestamp)>;// read和时间挂钩了Channel(EventLoop *loop, int fd);~Channel();// fd得到Poller通知以后 处理事件 handleEvent在EventLoop::loop()中调用void handleEvent(Timestamp receiveTime);// 设置回调函数对象// 用move比直接赋值好// 直接赋值有两次拷贝操作(实参到形参 形参到类内对象) 但是move简化了第二次// 使用move的前提:移动的资源在堆上,且支持这样的操作void setReadCallback(ReadEventCallback cb) { readCallback_ = std::move(cb); }void setWriteCallback(EventCallback cb) { writeCallback_ = std::move(cb); }void setCloseCallback(EventCallback cb) { closeCallback_ = std::move(cb); }void setErrorCallback(EventCallback cb) { errorCallback_ = std::move(cb); }// 防止当channel被手动remove掉 channel还在执行回调操作void tie(const std::shared_ptr<void> &);int fd() const { return fd_; }int events() const { return events_; }void set_revents(int revt) { revents_ = revt; }// 设置fd相应的事件状态 相当于epoll_ctl add deletevoid enableReading() { events_ |= kReadEvent; update(); }void disableReading() { events_ &= ~kReadEvent; update(); }void enableWriting() { events_ |= kWriteEvent; update(); }void disableWriting() { events_ &= ~kWriteEvent; update(); }void disableAll() { events_ = kNoneEvent; update(); }/*只是可读*/void setReading() { events_ = kReadEvent; update(); }/*只是可写*/void setWriting() { events_ = kWriteEvent; update(); }// 返回fd当前的事件状态bool isNoneEvent() const { return events_ == kNoneEvent; }bool isWriting() const { return events_ & kWriteEvent; }bool isReading() const { return events_ & kReadEvent; }int index() { return index_; }void set_index(int idx) { index_ = idx; }// one loop per threadEventLoop *ownerLoop() { return loop_; }void remove();private:void update();void handleEventWithGuard(Timestamp receiveTime);static const int kNoneEvent;static const int kReadEvent;static const int kWriteEvent;EventLoop *loop_; // 事件循环const int fd_; // fd,Poller监听的对象int events_; // 注册fd感兴趣的事件(只是一个表示,通过update调用到epoller的ctl重新设置flag)int revents_; // Poller返回的具体发生的事件(poller在wait到之后通过set_revents返回值)int index_;std::weak_ptr<void> tie_; /*观察对象是否存在*/bool tied_;// 因为channel通道里可获知fd最终发生的具体的事件events,所以它负责调用具体事件的回调操作ReadEventCallback readCallback_; /*具体可读函数得看具体实现是咋样的*/EventCallback writeCallback_;EventCallback closeCallback_;EventCallback errorCallback_;
};
/*核心处理函数,当可读/写/ERROR发生的时候 调用对应的回调*/
void Channel::handleEventWithGuard(Timestamp receiveTime)
{//LOG_INFO<<"channel handleEvent revents:"<<revents_;// LOG_INFO("channel handleEvent revents:[%d]", revents_);// 关闭if ((revents_ & EPOLLHUP) && !(revents_ & EPOLLIN)) // 当TcpConnection对应Channel 通过shutdown 关闭写端 epoll触发EPOLLHUP{if (closeCallback_){closeCallback_();}}// 错误if (revents_ & EPOLLERR){if (errorCallback_){errorCallback_();}}// 读if (revents_ & (EPOLLIN | EPOLLPRI)){if (readCallback_){readCallback_(receiveTime);}}// 写if (revents_ & EPOLLOUT){if (writeCallback_){writeCallback_();}}
}/*update只是对epoll_ctl的进一步封装*//*只是可读*/void setReading() { events_ = kReadEvent; update(); }/*只是可写*/void setWriting() { events_ = kWriteEvent; update(); }
Epoller—>对Epoll的封装
对于常用API进行了抽象和封装,epoll本身也归属于某个EventLoop。
EventLoop----“one loop per thread的体现”
对于主Reactor也好,子Reactor也好,它们每个都是对应着某个线程,这也就是我们为什么抽象EventLoop这样一个类,那么思考EventLoop得有什么呢?
- 得有个线程处理函数func一直在执行
- fuc要做两件事:(1) 处理epoll_wait;(2) 处理和其它线程/资源交互的信息(比如主线程怎么把clientfd分发给其他线程)
清楚了这两件事,那如何做呢?我们来看看muduo库的做法
线程间交互: eventfd + 回调函数机制
如果想要和其它线程/资源交互,能想到的一点就是利用信号量/互斥量等操作,但是这样就有了另一个问题:我们的线程处理函数是要同时执行epoll_wait的,等不到的时候可是阻塞当前线程的,所以对于线程间的交互,处理的会不及时。
while(true)
{epoll_wait(超时时间);for(){处理所有的事件};lock(); /*最坏情况得等到超时时间到了才能处理*/for(){处理线程间的交互事件}unlock();
}
这里就引入了一个eventfd。我们给每一个EventLoop的Epoll,除了要监视clientfd之外,还需要额外监视一个fd----eventfd,关于它如果想多了解可以自行搜索,粗略的讲,eventfd里面有个计数器,每次write()写就会增加(此时会触发epoll可读),而每次读read()就会清零计数器。
那以此来看其它线程可以怎么通过eventfd来和当前线程交互呢?—就是queueInLoop函数
// 把cb放入队列中 唤醒loop所在的线程执行cb(由其他线程或者本线程调用)
void EventLoop::queueInLoop(Functor cb)
{{std::unique_lock<std::mutex> lock(mutex_);pendingFunctors_.emplace_back(cb);/*存储了所有需要交互的函数*/}/*** || callingPendingFunctors的意思是 当前loop正在执行回调中 但是loop的pendingFunctors_中又加入了新的回调 需要通过wakeup写事件* 唤醒相应的需要执行上面回调操作的loop的线程 让loop()下一次poller_->poll()不再阻塞(阻塞的话会延迟前一次新加入的回调的执行),然后* 继续执行pendingFunctors_中的回调函数**/if (!isInLoopThread() || callingPendingFunctors_){wakeup(); // 唤醒loop所在线程,本质就是对eventfd进行写的操作}
}
// 当epoll触发的时候,eventfd对应的Channel绑定的回调函数
void EventLoop::handleRead()
{uint64_t one = 1;ssize_t n = read(wakeupFd_, &one, sizeof(one)); /*不清零就会一直epoll可读*/if (n != sizeof(one)){//LOG_ERROR<<"EventLoop::handleRead() reads"<<n<<"bytes instead of 8";LOG_ERROR("EventLoop::handleRead() reads[%d]bytes instead of 8",n);}
}
依次就能写出这个EventLoop的loop函数的逻辑啦,eventfd确实帮了大忙简化了很多逻辑
EventLoop的核心----loop()函数
void EventLoop::loop()
{looping_ = true;quit_ = false;//LOG_INFO<<"EventLoop start looping";LOG_INFO("EventLoop start looping");while (!quit_){activeChannels_.clear();/*清空容器所有变量*/pollRetureTime_ = poller_->poll(kPollTimeMs, &activeChannels_);for (Channel *channel : activeChannels_){//LOG_INFO("active channel fd:[%d]",channel->fd());// Poller监听哪些channel发生了事件 然后上报给EventLoop 通知channel处理相应的事件// 包括cilentfd和eventfd两类(子Reactor)// 包括listenfd和eventfd两类(主Reactor)// 当然还有一个timefd,可以epoll + timefd处理一些超时事件// 感兴趣可以了解一下channel->handleEvent(pollRetureTime_);}/*** 执行当前EventLoop事件循环需要处理的回调操作 对于线程数 >=2 的情况 IO线程 mainloop(mainReactor) 主要工作:* accept接收连接 => 将accept返回的connfd打包为Channel => TcpServer::newConnection通过轮询将TcpConnection对象分配给subloop处理** mainloop调用queueInLoop将回调加入subloop(该回调需要subloop执行 但subloop还在poller_->poll处阻塞) queueInLoop通过wakeup将subloop唤醒**/doPendingFunctors(); /*调用vector保存的所有回调函数*/}//LOG_INFO<<"EventLoopstop looping";LOG_INFO("EventLoopstop looping")looping_ = false;
}
Acceptor:ListenFd的封装
Acceptor里面的成员函数,就是主Reactor对应的线程的操作函数。我们来想Acceptor需要做什么:
- 得能开启listen
- 监听到可读事件后,需要调用对应的可读回调函数处理
- 在回调函数中,应该把clientfd分配给某个EventLoop
Acceptor::Acceptor(EventLoop *loop, const InetAddress &listenAddr, bool reuseport): loop_(loop), acceptSocket_(createNonblocking()), acceptChannel_(loop, acceptSocket_.fd()), listenning_(false)
{//LOG_INFO("server id:[%d]",acceptSocket_.fd());//LOG_INFO("Acceptor make success");acceptSocket_.setReuseAddr(true);acceptSocket_.setReusePort(true);acceptSocket_.bindAddress(listenAddr);// TcpServer::start() => Acceptor.listen() 如果有新用户连接 要执行一个回调(accept => connfd => 打包成Channel => 唤醒subloop)// baseloop监听到有事件发生 => acceptChannel_(listenfd) => 执行该回调函数acceptChannel_.setReadCallback(std::bind(&Acceptor::handleRead, this));
}Acceptor::~Acceptor()
{acceptChannel_.disableAll(); // 把从Poller中感兴趣的事件删除掉acceptChannel_.remove(); // 调用EventLoop->removeChannel => Poller->removeChannel 把Poller的ChannelMap对应的部分删除
}void Acceptor::listen()
{listenning_ = true;acceptSocket_.listen(); // 开启listenacceptChannel_.enableReading(); // acceptChannel_注册至Poller,要不怎么监听呢
}// listenfd有事件发生了,就是有新用户连接了
void Acceptor::handleRead()
{InetAddress peerAddr;do{int connfd = acceptSocket_.accept(&peerAddr);if(connfd < 0)break;//LOG_INFO("listen fd:[%d] success",connfd);//fcntl(connfd, F_SETFL, fcntl(connfd, F_GETFD, 0) | O_NONBLOCK);(不用 已经设计过了)if (connfd >= 0){if (NewConnectionCallback_) /*Tcp Server中调用的*/{/*这里实现看需求了,所以封装成回调函数的形式更灵活*/NewConnectionCallback_(connfd, peerAddr); // 轮询找到subLoop 唤醒并分发当前的新客户端的Channel}else{::close(connfd);}}else{LOG_ERROR("accept Err");if (errno == EMFILE){LOG_ERROR("sockfd reached limit");}}} while (true);}
EventLoopThreadPool----对应subReactor的操作
#include "../MultiReactor/EventLoopThread.h"
#include "../MultiReactor/EventLoop.h"EventLoopThread::EventLoopThread(const ThreadInitCallback &cb,const std::string &name): loop_(nullptr), exiting_(false), thread_(std::bind(&EventLoopThread::threadFunc, this), name), mutex_(), cond_(), callback_(cb)
{
}
// 由谁调用呢
EventLoopThread::~EventLoopThread()
{exiting_ = true;if (loop_ != nullptr){loop_->quit();thread_.join();//调用EventLoopThread析构的线程会阻塞 直到thread_对应的线程运行完毕}
}/*是让主Reactor调用的*/
EventLoop *EventLoopThread::startLoop()
{thread_.start(); // 启用底层线程Thread类对象thread_中通过start()创建的线程EventLoop *loop = nullptr;{std::unique_lock<std::mutex> lock(mutex_);cond_.wait(lock, [this](){return loop_ != nullptr;});loop = loop_;}return loop;
}// 下面这个方法 是在单独的新线程里运行的
void EventLoopThread::threadFunc()
{// 给每个线程创建一个EventLoopEventLoop loop;if(callback_){callback_(&loop);//如果设置了回调函数}// 新线程调用回调就能获得自己的EventLoop了// 所有的Loop是由谁创建的呢?{std::unique_lock<std::mutex> lock(mutex_);loop_ = &loop;cond_.notify_one();}loop.loop(); // 执行EventLoop的loop() 开启了底层的Poller的poll()std::unique_lock<std::mutex> lock(mutex_);loop_ = nullptr;
}#include <memory>
#include "../MultiReactor/EventLoopThreadPool.h"
#include "../MultiReactor/EventLoopThread.h"
#include "../Log/log.h"EventLoopThreadPool::EventLoopThreadPool(EventLoop *baseLoop, const std::string &nameArg): baseLoop_(baseLoop), name_(nameArg), started_(false), numThreads_(12), next_(0)
{
}
/*析构就是啥也不做?不过好像确实这个是整个程序的生命周期*/
EventLoopThreadPool::~EventLoopThreadPool()
{// Don't delete loop, it's stack variable
}void EventLoopThreadPool::start(const ThreadInitCallback &cb)
{started_ = true;for (int i = 0; i < numThreads_; ++i){char buf[name_.size() + 32];snprintf(buf, sizeof buf, "%s%d", name_.c_str(), i);/*池子确定了回调函数是什么*//*这是一个创建成功的回调函数 新线程创建成功了通过callback就能获得自己的专属loop_*/EventLoopThread *t = new EventLoopThread(cb, buf);threads_.push_back(std::unique_ptr<EventLoopThread>(t));loops_.push_back(t->startLoop()); // 底层创建线程 绑定一个新的EventLoop 并返回该loop的地址}if (numThreads_ == 0 && cb) // 整个服务端只有一个线程运行baseLoop{cb(baseLoop_);}
}// 如果工作在多线程中,baseLoop_(mainLoop)会默认以轮询的方式分配Channel给subLoop
EventLoop *EventLoopThreadPool::getNextLoop()
{// 如果只设置一个线程 也就是只有一个mainReactor 无subReactor // 那么轮询只有一个线程 getNextLoop()每次都返回当前的baseLoop_EventLoop *loop = baseLoop_; // 通过轮询获取下一个处理事件的loop// 如果没设置多线程数量,则不会进去,相当于直接返回baseLoopif(!loops_.empty()) {loop = loops_[next_];++next_;// 轮询if(next_ >= loops_.size()){next_ = 0;}}return loop;
}std::vector<EventLoop *> EventLoopThreadPool::getAllLoops()
{if (loops_.empty()){return std::vector<EventLoop *>(1, baseLoop_);}else{return loops_;}
}
相关文章:
WebServer实现:muduo库的主丛Reactor架构
前言 作为服务器,核心自然是高效的处理来自client的多个连接啦,那问题在于,如何高效的处理client的连接呢?这里就介绍两种架构:单Reactor架构和主丛Reactor架构。 单Reactor架构 单Reactor架构的核心为,由一个主线程监…...
每天一个前端小知识 Day 7 - 现代前端工程化与构建工具体系
现代前端工程化与构建工具体系 1. 为什么要工程化?(面试高频问题) 问题痛点: 模块太多、无法组织;代码冗长、性能差;浏览器兼容性差;团队协作混乱,缺少规范与自动化。 工程化目标…...
nginx的下载与安装 mac
1. 下载 方法一:本地下载 链接:https://nginx.org/en/download.html(可直接搜官网) 下载到本地后,上传到linux的某个文件夹中 方法二:直接linux上下载(推荐) wget -c http://ngi…...
[持续集成]
学习目标 能够使用 Git 代码托管平台管理代码能够实现 jenkinspostman 的持续集成能够实现 jenkins代码 的持续集成 持续集成 概念 : 将自己工作成果持续不断地把代码聚集在一起,成员可以每天集成一次或多次相关工具 : git : 代码管理工具,自带本地仓库gitee : 远程代码管理…...
Spring Aop @AfterThrowing (异常通知): 使用场景
核心定义 AfterThrowing 是 Spring AOP 中专门用于处理异常场景的**通知(Advice)**类型。它的核心作用是: 仅在目标方法(连接点)的执行过程中抛出异常时,执行一段特定的逻辑。如果目标方法成功执行并正常…...
【赵渝强老师】Kubernetes的安全框架
Kubernetes集群的安全框架主要由以下认证、鉴权和准入控制三个阶段组成。这三个阶段的关系如下图所示。 视频讲解如下 【赵渝强老师】Kubernetes的安全框架 认证(Authentication) 当客户端与Kubernetes集群建立HTTP通信时,首先HTTP请求会进…...
【Python小练习】3D散点图
资产风险收益三维分析 背景 王老师是一名金融工程研究员,需要对多个资产的预期收益、风险(波动率)和与市场的相关性进行综合分析,以便为投资组合优化提供决策依据。 代码实现 import matplotlib.pyplot as plt from mpl_toolk…...
腾讯混元3D制作简单模型教程-2
以下是腾讯混元3D制作简单模型的详细教程,整合最新版本特性(截至2025年6月),操作门槛低且无需专业基础: 🖥 一、在线生成(最快30秒完成) 访问平台 打开 腾讯混元3D创作引擎官网…...
NVIDIA开源Fast-dLLM!解析分块KV缓存与置信度感知并行解码技术
Talk主页:http://qingkeai.online/ 文章原文:https://mp.weixin.qq.com/s/P0PIAMo1GVYH4mdWdIde_Q Fast-dLLM 是NVIDIA联合香港大学、MIT等机构推出的扩散大语言模型推理加速方案。 论文:Fast-dLLM: Training-free Acceleration of Diffusion…...
大白话说目标检测中的IOU(Intersection over Union)
很多同学在学习目标检测时都会遇到IoU这个概念,但总觉得理解不透彻。这其实很正常,因为IoU就像个"多面手",在目标检测的各个阶段都要"打工",而且每个阶段的"工作内容"还不太一样。 今天我就让IoU自…...
CentOS 8解决ssh连接github时sign_and_send_pubkey失败问题
我在一台centos8机器上安装git环境以连接到github,首先第一步需配置好ssh环境,因为我已经有一台Ubuntu机器已经配置好ssh环境,所以我ftp Ubuntu机器取得id_rsa id_rsa.pub known_hosts三个文件,然后执行命令: $ git …...
回答 如何通过inode client的SSLVPN登录之后,访问需要通过域名才能打开的服务
需要dns代理 1 配置需求或说明 1.1 适用的产品系列 本案例适用于软件平台为Comware V7系列防火墙:本案例适用于如F5080、F5060、F5030、F5000-M等F5000、F5000-X系列的防火墙。 注:本案例是在F100-C-G2的Version 7.1.064, Release 9510P08版本上进行…...
OpenCV实现二值图细化(骨架提取)
对二值图进行细化(骨架提取),也就是把每根线条细化到一个像素的宽度。有两个比较成熟的算法实现此功能,分别是Zhang-Suen算法和Guo-Hall算法。 我们下面使用OpenCVSharp,使用C#实现上述两个算法: private…...
Excel常用公式大全
资源宝整理分享:https://www.httple.net Excel常用公式大全可以帮助用户提高工作效率,掌握常用的Excel公式,让数据处理和计算工作更加便捷高效。了解公式学习方法、用途,不再死记硬背,拒绝漫无目的。 命令用途注释说…...
在 Windows 上使用 Docker Desktop 快速搭建本地 Kubernetes 环境(附详细部署教程)
言简意赅的讲解Docker Desktop for Windows搭建Kubernetes解决的痛点 目标读者: 对 Docker Desktop 有一定了解,能在 Windows 上成功安装和使用 Docker Desktop。想要在本地快速搭建一套 Kubernetes 环境进行测试或学习的开发者。 一、准备工作 安装 Doc…...
Python设计模式终极指南:18种模式详解+正反案例对比+框架源码剖析
下面我将全面解析18种Python设计模式,每种模式都包含实际应用场景、优缺点分析、框架引用案例、可运行代码示例以及正反案例对比,帮助您深入理解设计模式的价值。 一、创建型模式(5种) 1. 单例模式(Singleton&#x…...
第1章: 伯努利模型的极大似然估计与贝叶斯估计
伯努利模型的极大似然估计与贝叶斯估计 import numpy as np import matplotlib.pyplot as plt from scipy.stats import beta, bernoulli from scipy.optimize import minimize_scalar# 设置中文字体 plt.rcParams[font.sans-serif] [SimHei] # 使用黑体 plt.rcParams[axes.…...
IPv4编址及IPv4路由基础
一、实验目的 掌握接口 IPv4 地址的配置方法理解 LoopBack 接口的作用与含义理解直连路由的产生原则掌握静态路由的配置方法并理解其生效的条件掌握通过 PING 工具测试网络层连通性掌握并理解特殊静态路由的配置方法与应用场景 二、实验环境 安装有eNSP模拟器的PC一台&#…...
基于Python的机动车辆推荐及预测分析系统
博主介绍:java高级开发,从事互联网行业六年,熟悉各种主流语言,精通java、python、php、爬虫、web开发,已经做了六年的毕业设计程序开发,开发过上千套毕业设计程序,没有什么华丽的语言࿰…...
SpringBoot扩展——发送邮件!
发送邮件 在日常工作和生活中经常会用到电子邮件。例如,当注册一个新账户时,系统会自动给注册邮箱发送一封激活邮件,通过邮件找回密码,自动批量发送活动信息等。邮箱的使用基本包括这几步:先打开浏览器并登录邮箱&…...
啊啊啊啊啊啊啊啊code
前序遍历和中序遍历构建二叉树 /*** Definition for a binary tree node.* public class TreeNode {* int val;* TreeNode left;* TreeNode right;* TreeNode() {}* TreeNode(int val) { this.val val; }* TreeNode(int val, TreeNode left, TreeNod…...
不同程度多径效应影响下的无线通信网络电磁信号仿真数据生成程序
生成.mat数据: %创建时间:2025年6月19日 %zhouzhichao %遍历生成不同程度多径效应影响的无线通信网络拓扑推理数据用于测试close all clearsnr 40; n 30;dataset_n 100;for bias 0.1:0.1:0.9nodes_P ones(n,1);Sampling_M 3000;%获取一帧信号及对…...
C语言学习day17-----位运算
目录 1.位运算 1.1基础知识 1.1.1定义 1.1.2用途 1.1.3软件控制硬件 1.2运算符 1.2.1与 & 1.2.2或 | 1.2.3非 ~ 1.2.4异或 ^ 1.2.5左移 << 1.2.6右移 >> 1.2.7代码实现 1.2.8置0 1.2.9置1 1.2.10不借助第三方变量,实现两个数的交换…...
Spring MVC参数绑定终极手册:单多参对象集合JSON文件上传精讲
我们通过浏览器访问不同的路径,就是在发送不同的请求,在发送请求时,可能会带一些参数,本文将介绍了Spring MVC中处理不同请求参数的多种方式 一、传递单个参数 接收单个参数,在Spring MVC中直接用方法中的参数就可以…...
宽度优先遍历(bfs)(2)——fllodfill算法
欢迎来到博主的专栏:算法解析 博主ID:代码小豪 文章目录 floodfiil算法leetcode733——图像渲染题目解析算法原理题解代码 leetcode130——被围绕的区域题目解析算法原理题解代码 floodfiil算法 floodfill算法,在博主这里看来则是一个区域填…...
嵌入式编译工具链熟悉与游戏移植
一、Linux 系统编译工具链使用与 mininim 源码编译 在 Ubuntu 系统上编译 mininim 开源游戏需要正确配置编译工具链和依赖库。以下是详细的操作步骤和故障解决方法: 1. 环境准备与源码获取 首先需要安装必要的编译工具和依赖库: # 更新系统软件包索引…...
STUN (Session Traversal Utilities for NAT) 服务器是一种网络协议
STUN (Session Traversal Utilities for NAT) 服务器是一种网络协议,主要用于帮助位于网络地址转换 (NAT) 设备(如路由器)后面的客户端发现自己的公共 IP 地址和端口号。这对于建立点对点 (P2P) 通信至关重要,尤其是在 VoIP&#…...
Transformer结构介绍
[编码器 Encoder] ←→ [解码器 Decoder] 编码器: 输入:源语言序列输出:每个词的上下文表示(embedding) 解码器:输入:目标语言序列编码器输出输出:下一个词的概率分布(目标句子生成)…...
SpringBoot扩展——应用Web Service!
应用Web Service Web Service是一个SOA(面向服务的编程)架构,这种架构不依赖于语言,不依赖于平台,可以在不同的语言之间相互调用,通过Internet实现基于HTTP的网络应用间的交互调用。Web Service是一个可以…...
5G核心网周期性注册更新机制:信令流程与字段解析
一、周期性注册更新的技术背景与流程概述 1.1 注册更新的核心目的 在5G网络中,UE通过周期性注册更新维持与核心网的连接状态,主要作用包括: 状态保活:避免AMF因超时而释放UE上下文(T3512定时器超时前需完成更新);位置更新:通知网络UE的当前位置,确保寻呼可达;能力同…...
【LLM学习笔记3】搭建基于chatgpt的问答系统(下)
目录 一、检查结果检查有害内容检查是否符合产品信息 二、搭建一个简单的问答系统三、评估输出1.当存在一个简单的正确答案2.当不存在一个简单的正确答案 一、检查结果 本章将引领你了解如何评估系统生成的输出。在任何场景中,无论是自动化流程还是其他环境&#x…...
算法导论第十九章 并行算法:解锁计算新维度
第十九章 并行算法:解锁计算新维度 “并行计算不是未来,而是现在。” —— David Patterson 在单核性能增长放缓的时代,并行算法成为突破计算极限的关键。本章将带你探索多核处理器、分布式系统和GPU加速的奇妙世界,揭示如何通过协…...
Python 数据分析与可视化 Day 1 - Pandas 数据分析基础入门
🎯 今日目标 理解 Pandas 的作用和核心概念学会创建 Series 和 DataFrame掌握基本数据读取(CSV)与常用查看方法 🧰 1. 什么是 Pandas? Pandas 是基于 NumPy 的强大数据分析库,提供了灵活的表格数据结构 Da…...
【数字人开发】Unity+百度智能云平台实现长短文本个性化语音生成功能
一、创建自己的应用 百度智能云控制台网址:https://console.bce.baidu.com/ 1、创建应用 2、获取APIKey和SecretKey 3、Api调试 调试网址:https://console.bce.baidu.com/support/?timestamp1750317430400#/api?productAI&project%E8%AF%AD%E9%…...
(哈希)128. 最长连续序列
题目 给定一个未排序的整数数组 nums ,找出数字连续的最长序列(不要求序列元素在原数组中连续)的长度。 请你设计并实现时间复杂度为 O(n) 的算法解决此问题。 示例 1: 输入:nums [100,4,200,1,3,2] 输出ÿ…...
MFC中使用CRichEditCtrl控件让文本框中的内容部分加粗
MFC中文本框控件的内容,设置好字体格式后,只能单一的显示,如果相对文本框的内容部分加粗,或者部分加颜色、链接等都无法实现,但MFC中提供了CRichEditCtrl控件,就很方便的实现文本框中部分内容需要特殊处理的…...
Redis 的优势有哪些,它是CP 还是 AP?CAP 理论又是什么?
Redis的核心优势 Redis作为当今最流行的内存数据库之一,具有以下显著优势: 1. 卓越的性能表现 内存存储:数据主要存储在内存中,读写速度极快(10万 QPS)单线程架构:避免多线程竞争,…...
flink的多种部署模式
## 部署模式和运行模式 ### 部署模式 - 本地local - 单机无需分布式资源管理 - 集群 - 独立集群standalone - 需要flink自身的任务管理工具 - jobmanager接收和调度任务 - taskmanager执行 - on其他资源管理工具yarn/k8s …...
SQL分片工具类
SQL分片工具类(SqlShardingUtil)提供数据库查询的智能分片功能,支持数字和字符串两种字段类型的分片策略。对于数字字段,可以指定分片数量均匀划分数值区间;对于字符串字段,则按照ASCII字符范围自动划分。工具类确保分片后的SQL语…...
死锁相关知识
死锁是什么 死锁(Deadlock)是指两个或多个进程(或线程)在执行过程中,因为互相等待对方释放资源,导致永远无法继续执行的状态。 ✅ 死锁的形成条件(必须同时满足以下四个)࿱…...
oscp靶机练习PG Reconstruction
枚举阶段 nmap -A -T4 -p- -Pn -n 192.168.217.103 发现ftp,进行连接枚举 都下载到本地 这里提示我们两点,可以看看pcap文件,还有就是可能有密码遗留还没有删除。 使用下面命令进行过滤筛选流量包,查看与密码相关 http.reque…...
写题。贪心题组
一、 解题思路:主要还是写出val / m,按这个排序,就行了 #include<bits/stdc.h> #define endl "\n" #define ll long long #define pii pair<int,int> using namespace std;struct doro {int m, val;double cmp; } arr…...
UE官方文档学习 TAarry 查询
这个很简单经常用。 二.GetData() . GetData(),像C里拿到数组首地址一样。它不具有越界保护机制,StrArr拥有越界保护机制。这个地址在数组不做改变,如扩容等有用。 void AWXArrayActor::WXFindArray() {TArray<FString> StrArr { &q…...
使用Haproxy搭建Web群集
LVS负载均衡群集 Haproxy介绍http请求负载均衡常用调度算法常见的web群集调度器 示例操作安装httpd(两台网站服务器操作一致)编译安装haproxyhaproxy服务器配置(1)建立haproxy的配置文件(2)修改haproxy.cfg配置文件 测试haproxy的日志(1)修改 haproxy 配…...
Linux 基础命令:`ls`、`cd`、`du` 快速入门
在 Linux 系统中,ls、cd 和 du 是日常操作中最常用的三个命令。掌握它们能大幅提升文件管理效率。 1. ls:查看目录内容 用途:列出当前或指定目录下的文件和子目录。 常用命令: ls -l # 详细列表(权限、大…...
[论文阅读] 人工智能 + 软件工程 | USEagent:迈向统一的AI软件工程师
论文信息 article{applis2025unified,title{Unified Software Engineering agent as AI Software Engineer},author{Applis, Leonhard and Jiang, Nan and Zhang, Yuntong and Tan, Lin and Liang, Shanchao and Roychoudhury, Abhik},journal{arXiv preprint arXiv:2506.1468…...
微信小程序传参过来了,但是数据没有获取到
使用本方法前,已经采用encodeURIComponent把拼接的参数编码之后,拼接在链接上,在接受的页面的onLoad生命周期,接收到参数之后,采用decodeURIComponent进行解码的操作,如果这个也不行,不是说不行…...
微信小程序form表单手机号正则检验pattern失效
好奇怪啊,h5页面校验没问题,在微信小程序模拟器以及真机运行都失效,排查半天,记录一下 PS:身份证号校验也没问题,就手机号校验有问题,奇奇怪怪的 之前的写法(在小程序上不生效&…...
repo 工具
repo 是 Google 为管理多个 Git 仓库而开发的工具,主要用于 Android 开源项目(AOSP)等大型项目。它通过清单文件(manifest.xml)统一管理多个 Git 仓库的依赖关系。以下是核心用法和常见命令: 一、安装 repo…...
Python实例题:基于 TensorFlow 的图像识别与分类系统
目录 Python实例题 题目 问题描述 解题思路 关键代码框架 难点分析 扩展方向 Python实例题 题目 基于 TensorFlow 的图像识别与分类系统 问题描述 开发一个基于 TensorFlow 的图像识别与分类系统,包含以下功能: 图像分类模型:基于…...