【Linux】多路转接 -- epoll
文章目录
- 1. 认识epoll
- 2. epoll相关系统调用接口
- 3. epoll工作原理
- 4. epoll服务器
- 5. epoll的优点
- 6. epoll的工作方式
- 7. epoll的使用场景
1. 认识epoll
epoll系统调用和select以及poll是一样的,都是可以让我们的程序同时监视多个文件描述符上的事件是否就绪。
epoll在命名上比poll多了一个poll,这个e可以理解为extend,epoll就是为了同时处理大量文件描述符而改进的poll。
epoll在2.5.44内核中被引进,它几乎具备了select和poll的所有优点,被公认为Linux2.6下性能最好的多路IO就绪通知方法。
2. epoll相关系统调用接口
epoll有三个相关系统调用接口,分别是epoll_create,epoll_ctl 和 epoll_wait。
epoll_create
epoll_create函数的作用就是创建一个epoll的文件描述符。
参数说明:
- size:自从Linux2.6.8之后,size参数是被忽略的,但size的值必须设置为大于0的值。
返回值说明:
- epoll模型创建成功返回其对应的文件描述符,否则返回-1,同时错误码会被设置。
注意:当不再使用时,必须调用close函数关闭epoll模型对应的文件描述符,当所有引用epoll实例的文件描述符都已经关闭时,内核将销毁该实例并释放相关资源。
epoll_ctl
epoll_ctl 函数用于向指定的epoll模型中注册事件,它不同于seletct()的一点就是,select在监听事件时告诉内核要监听什么类型的事件,而它是先注册要监听的事件类型。
参数说明:
- epfd:epoll_create的返回值
- op:表示具体的动作,用三个宏来表示
- fd:需要监视的文件描述符
- event:告诉内核需要监听什么事件
第二个参数op的取值有以下三种:
- EPOLL_CTL_ADD:注册新的fd到epfd中。
- EPOLL_CTL_MOD:修改已经注册的fd的监听事件。
- EPOLL_CTL_DEL:从epfd中删除指定的文件描述符。
返回值说明:
- 函数调用成功返回0,调用失败返回-1,同时错误码会被设置。
第四个参数struct epoll_event 结构如下:
struct epoll_event结构当中有两个成员,第一个成员events表示的是需要监听的事件,第二个成员data是一个联合体结构,一般选择使用该结构当中的fd,表示需要监听的文件描述符。
events常用取值如下:
- EPOLLIN:表示对应的文件描述符可以读(包括对端SOCKET正常关闭)
- EPOLLOUT:表示对应的文件描述符可以写
- EPOLLPRI:表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来)
- EPOLLERR:表示对应的文件描述符发送错误
- EPOLLHUP:表示对应的文件描述符被挂断,即对端文件描述符关闭
- EPOLLET:将epoll的工作方式设置为边缘触发模式。
- EPOLLONESHOT:只监听一次事件,当监听完这次事件之后,如果还需要继续监听该文件描述符的话,需要重新将该文件描述符添加到EPOLL队列中。
这些取值也是以宏的方式进行定义的,它们的二进制序列当中有且只有一个比特位是1,且为1的比特位是各不相同的。
epoll_wait
epoll_wait 函数用于收集监视的事件中已经就绪的事件
参数说明:
- epfd:指定的epoll模型,epoll_create的返回值
- events:epoll会把发送的事件赋值到events数组中(events不可以是空指针,内核只负责把数组复制到这个events数组中,不会帮助我们在用户态中分配内存)。
- maxevents:events数组中的元素个数,该值不能大于创建epoll模型使传入的size值。
- timeout:表示epoll_wait函数的超时时间,单位是毫秒(ms)。
参数timeout的取值:
- -1:epoll_wait调用后进行阻塞等待,直到被监视的某个文件描述符的某个事件就绪。
- 0:epoll_wait调用后进行非阻塞等待,无论被监视的文件描述符上的事件是否就绪,epoll_wait检测后都会立刻返回。
- 特定的时间值:epoll_wait 调用后在直到的时间内进行阻塞等待,如果监视的文件描述符上一直没有事件就绪,则在该时间后epoll_wait进行超时返回。
返回值说明:
- 如果函数调用成功,则返回有事件就绪的文件描述符个数。
- 如果timeout时间耗尽,则返回0。
- 如果函数调用失败,则返回-1,同时错误码会被设置。
epoll_wait 调用失败时,错误码可能被设置为:
- EBADF:传入的epoll模型对应的文件描述符无效。
- EFAULT:events指向的数组空间无法通过写入权限访问。
- EINTR:此调用被信号所中断。
- EINVAL:epfd不是一个epoll模型对应的文件描述符,或传入的maxevents小于等于0。
3. epoll工作原理
红黑树和就绪队列
当某一进程调用epoll_create函数时,Linux内核会创建一个eventpoll结构体,也就是我们所说的epoll模型,eventpoll结构体当中的成员rbr和成员rdlist与epoll的使用方式密切相关。
- epoll模型当中的红黑树本质就是告诉内核,需要监视哪些文件描述符上的哪些事件,调用epoll_ctl 函数实际就是在对这颗红黑树进行对应的增删查改操作。
- epoll模型当中的就绪队列本质就是告诉内核,哪些文件描述符上的哪些事件已经就绪了,调用epoll_wait 函数实际就是在从就绪队列当中获取已经就绪的事件。
注意:
- 每一个epoll对象都有一个独立的eventpoll结构体,用于存放通过epoll_ctl方法向epoll对象中添加进来的事件。
- 而这些事件都会挂载在红黑树中,如此,重复添加的事件就可以通过红黑树而高效地识别出来。
- 而所有添加到epoll中的事件都会与设备驱动程序建立回调关系,也就是说,当响应的事件发生时会调用这个回调方法。
- 这个回调方法在内核中叫ep_poll_callback,它会将发生的事件添加到rdlist双链表中。
- 在epoll中,对于每一个事件,都会建立一个epitem结构体。
- 对于epitem结构当中的rbn成员来说,ffd与event的含义是,需要监视的ffd上的event事件是否就绪。
- 对于epitem结构当中的rdllink成员来说,ffd与event的含义是,ffd上的event事件已经就绪了。
- 当调用epoll_wait检查是否有事件发生时,只需要检查eventpoll对象中的rdlist双链表中是否含有epitem元素即可。
- 如果rdlist不为空,则把发送的事件复制到用户态,同时将事件数量返回给用户,这个操作的时间复杂度为O(1)。
说明一下:
- 红黑树是一种二叉搜索树,因此必须有键值key,而这里的文件描述符就可以天然地作为红黑树的key值。
- 调用epoll_ctl向红黑树当中新增节点时,如果设置了EPOLLONESHEOT选项,当监听完这次事件之后,如果还要继续监听该文件描述符则需要重新将其添加到epoll模型中,本质就是当设置了EPOLLONESHOT的事件就绪时,操作系统会自动将其从红黑树当中删除。
- 而如果调用epoll_ctl向红黑树当中新增节点时没有设置EPOLLONSHOT,那么该节点插入红黑树之后就会一直存在,除非用户调用epoll_ctl将该节点从红黑树当中删除。
回调机制
所有添加到红黑树当中的事件,都会与设备(网卡)驱动程序建立回调方法,这个回调方法在内核中叫做ep_poll_callback。
- 对于select和poll来说,操作系统在监视多个文件描述符上的事件是否就绪时,需要让操作系统主动对这多个文件描述符进行轮询检测,这一定会增加操作系统的负担。
- 而对于epoll来说,操作系统不需要主动进行事件的检测,当红黑树中监视的事件就绪时,会自动调用对应的回调方法,将就绪的事件添加到就绪队列当中。
- 当用户调用epoll_wait函数获取就绪事件时,只需要关注底层就绪队列是否为空,如果不为空则将就绪队列当中的就绪事件拷贝给用户即可。
采用回调机制最大的好处,就是不再需要操作系统主动对就绪事件进行检测了,当时间就绪时会自动调用对应的回调函数进行处理。
说明一下:
- 只有添加到红黑树当中的事件才会与底层建立回调方法,因此只有当红黑树当中对应的事件就绪时,才会执行对应的回调方法将其添加到就绪队列当中。
- 当不断有监视的事件就绪时,会不断有回调方法向就绪队列当中插入节点,而上层也会不断调用epoll_wait函数从就绪队列中获取节点,这也是典型的生产者消费者模型。
- 由于就绪队列可能会被多个执行流同时访问,因此必须要使用互斥锁对其进行保护,eventpoll结构当中的lock和mtx就是保护临界资源的,因此epoll本身是线程安全的。
- eventpoll结构当中的wa(wait queue)就是等待队列,当多个执行流想要同时访问同一个epoll模型时,就需要在该等待队列下进行等待。
epoll三部曲
- 调用epoll_create,创建一个epoll模型
- 调用epoll_ctl,将要监视的文件描述符进行注册
- 调用epoll_wait,等待文件描述符就绪
4. epoll服务器
为了简单演示一下epoll的使用方式,这里我们实现一个简单的epoll服务器,该服务器是获取客户端发来的数据并进行打印。
EpollServer类
EpollServer类中除了包含监听套接字和端口号两个成员变量之外,最好将epoll模型对应的文件描述符也作为一个成员变量。
- 在构造EpollServer对象时,需要指明epoll服务器的端口号,当然也可以在初始化epoll服务器的时候指明。
- 在初始化epoll服务器的时候调用Socket类中的函数(该Socket类中封装了进行TCP传输的方法),一次进行套接字的创建、绑定和监听、此外epoll模型的创建可以在服务器初始化的时候进行。
- 在析构函数中调用close函数,将监听套接字和epoll模型对应的文件描述符进行关闭。
#include "Socket.hpp"
#include <sys/epoll.h>#define BACK_LOG 5
#define SIZE 256class EpollServer
{
public:EpollServer(int port): _port(port){}void InitEpollServer(){_listen_sock = Socket::SocketCreate();Socket::SocketBind(_listen_sock, _port);Socket::SocketListen(_listen_sock, BACK_LOG);// 创建epoll模型_epfd = epoll_create(SIZE);if (_epfd < 0){std::cerr << "epoll_create error" << std::endl;exit(5);}}~EpollServer(){if (_listen_sock > 0) close(_listen_sock);if (_epfd) close(_epfd);}private:int _listen_sock; // 监听套接字int _port; // 服务器端口号int _epfd; // epollfd
};
运行服务器
服务器初始化完毕之后就可以开始运行了,而epoll服务器要做的就是不断调用epoll_wait函数,从就绪队列中获取就绪事件进行处理即可。
- 首先,在epoll服务器开始死循环调用epoll_wait之前,需要先调用epoll_ctl将监听套接字添加到epoll模型中,表示服务器开始运行时只需要监视监听套接字的读事件。
- 此后,epoll服务器就不断调用epoll_wait函数监视读事件是否就绪。如果epoll_wait函数的返回值大于0,则说明已经有文件描述符的读事件就绪,并且此事的返回值代表的就是有事件就绪的文件描述符的个数,接下来就应该对就绪事件进行处理。
- 如果epoll_wait的函数返回值等于0,则说明timeout时间耗尽,此事直接准备下一次epoll_wait调用即可。
- 如果epoll_wait函数返回值为-1,此时也让服务器进行下一次epoll_wait调用,但是实际应该进一步判断错误码,根据错误码来判断是否应该继续调用epoll_wait函数。
void HandlerEvent(struct epoll_event revs[], int num){for (int i = 0; i < num; ++i){int fd = revs[i].data.fd; // 就绪的文件描述符if (fd == _listen_sock && revs[i].events & EPOLLIN){// 连接事件就绪struct sockaddr_in peer;memset(&peer, 0, sizeof(peer));socklen_t len = sizeof(peer);int sock = accept(_listen_sock, (struct sockaddr*)&peer, &len);if (sock < 0){std::cerr << "accept error" << std::endl;continue;}std::string peer_ip = inet_ntoa(peer.sin_addr);int peer_port = ntohs(peer.sin_port);std::cout << "get a new link[" << peer_ip << ":" << peer_port << "]" << std::endl;// 将获取到的文件描述符添加到sock中,并关心其读事件AddEvent(sock, EPOLLIN); }else if (revs[i].events & EPOLLIN){char buffer[1024];ssize_t size = recv(fd, buffer, sizeof(buffer) - 1, 0);if (size > 0){buffer[size] = 0;std::cout << "echo# " << buffer << std::endl;}else if (size == 0){std::cout << "client quit" << std::endl;close(fd);DelEvent(fd); // 将fd从epoll中删除}else{std::cerr << "recv error" << std::endl;close(fd);DelEvent(fd);}}}}private:void DelEvent(int sock){epoll_ctl(_epfd, EPOLL_CTL_DEL, sock, nullptr);}
epoll服务器测试
#include "EpollServer.hpp"
#include <string>int main(int argc, char* argv[])
{if (argc != 2){std::cout << "Usage: " << "./EpollServer port" << std::endl;exit(1);}int port = atoi(argv[1]);EpollServer* svr = new EpollServer(port);svr->InitEpollServer();svr->Run();return 0;
}
因为我们在调用epoll_wait函数时,将timeout的值设为了-1,因此服务器运行之后如果没有读事件就绪,那么就会阻塞等待。
5. epoll的优点
- 接口使用方便:虽然拆分成了三个函数,但是反而使用起来更方便高效,不需要每次循环都设置要关注的文件描述符,也做到了输入输出参数分离开。
- 数据拷贝轻量:只在合适的时候使用EPOLL_CTL_ADD将文件描述符结构拷贝到内核中,这个操作并不频繁(而select/poll每次循环都需要进行拷贝)。
- 事件回调机制:避免使用遍历,而是使用回调函数的方式,将就绪的文件描述符加入到就绪队列当中,epoll_wait返回之后,直接访问就绪队列就知道哪些文件描述符就绪,这样即使文件描述符很多,效率也不会受影响。
- 没有数量限制:文件描述符数目无上限。
注意:
- 有人说epoll中使用了内存映射机制,内核直接将底层就绪队列通过mmap的方式映射到用户态,此时用户就可以直接读取到内核中就绪队列当中的数据,避免了内存拷贝的额外性能开销。
- 这种说法是错误的,实际操作系统并没有做任何映射机制,因为操作系统是不相信任何人的,操作系统不会让用户进程直接访问到内核的数据的,用户只能通过系统调用来获取内核的数据。
- 因此用户要获取内核当中的数据,势必还是需要将内核的数据拷贝到用户空间。
6. epoll的工作方式
epoll有两种工作方式,分别是水平触发模式和边缘触发工作模式。
水平触发(LT, Level Triggered)
- 只要底层有事件就绪,epoll就会一直通知用户。
epoll默认状态下就是LT工作模式:
- 由于在LT工作模式下,只要底层有事件就绪就会一直通知用户,因此当epoll检测到底层读事件就绪时,可以不立即进行处理,或者只处理一部分,因为只要底层数据没有处理完,下一次epoll还会通知用户事件就绪。
- select和poll的模式其实就是LT模型
- 支持阻塞读写和非阻塞读写。
边缘触发(ET, Edge Triggered)
- 只有底层就绪事件数量 由无到有 或者 由有到多 的时候,epoll才会通知用户。
如果要将epoll改为ET工作模式,则需要在添加时间时设置EPOLLET选项。
- 由于在ET工作模式下,只有底层就绪事件 由无到有 或者 由有到多 的时候才会通知用户,所以当epoll检测到底层读事件就绪的时候,必须立即进行处理,而且必须全部处理完毕,因为有可能此后底层再也没有事件就绪,那么epoll就再也不好通知用户进行事件处理。
- ET工作模式下epoll通知用户的次数比LT少,因此ET的性能一般比LT性能跟高,Nginx就是默认采用ET模式使用epoll的。
- 只支持非阻塞的读写。
ET工作模式下应该如何进行读写?
因为在ET工作模式下,只有底层就绪事件由无到有或者由有到多时才会通知用户,这就倒逼用户当读事件就绪时必须一次性将数据全部读取完毕,当写事件就绪时就必须一次性将发送缓冲区写满,否则可能再也没有机会进行读写了。
因此读数据时必须循环调用recv函数进行读取,写数据时必须循环调用send函数进行写入。
- 当底层读事件就绪时,循环调用recv函数进行读取,直到某次调用recv函数时,实际读取到的字节数小于期望读取的字节数,则说明本次底层数据已经读取完毕了。
- 但有可能最后一次调用recv读取时,刚好实际读取的字节数和期望读取的字节数相等,但此时底层数据恰好读取完毕,如果我们再调用recv函数进行读取,那么recv就会因为没有数据而被阻塞住。
- 而这里的阻塞是非常严重的,就比如我们这里写的服务器都是单进程的服务器,如果recv被阻塞住,并且此后该数据再也不就绪,那么就相当于我们的服务器挂掉了,因此在ET模式下循环调用recv函数进行读取时,必须将文件描述符设置为非阻塞状态。
- 调用send函数写数据时也是同样的道理,需要循环调用send函数进行数据的写入,并且必须将对应的文件描述符设置为非阻塞状态。
注意:ET工作模式下,recv和send操作的文件描述符必须设置为非阻塞状态,这是必须的!
LT模式与ET模式对比
- 在ET模式下,一个文件描述符就绪之后,用户不会反复收到通知,看起来比LT更高效,但是如果在LT模式下能够做到每次都将就绪的文件描述符进行处理,不让操作系统反复通知用户的话,其实LT和ET性能也是一样的。
- 此外,ET模式的编程难度更高。
7. epoll的使用场景
epoll的高性能,是有特定的场景的,如果场景选择不合适,epoll的性能可能适得其反。
对于多连接,且多连接中只有一部分连接活跃时,比较适合使用epoll。
如果只是系统内部,服务器和服务器之间进行通信,只有少数的几个连接,这种情况下使用epoll就并不合适,具体要根据需求和场景特定来决定使用哪种IO模型。
相关文章:

【Linux】多路转接 -- epoll
文章目录 1. 认识epoll2. epoll相关系统调用接口3. epoll工作原理4. epoll服务器5. epoll的优点6. epoll的工作方式7. epoll的使用场景 1. 认识epoll epoll系统调用和select以及poll是一样的,都是可以让我们的程序同时监视多个文件描述符上的事件是否就绪。 epoll…...

学会RabbitMQ的延迟队列,提高消息处理效率
系列文章目录 手把手教你,本地RabbitMQ服务搭建(windows) 消息队列选型——为什么选择RabbitMQ RabbitMQ灵活运用,怎么理解五种消息模型 RabbitMQ 能保证消息可靠性吗 推或拉? RabbitMQ 消费模式该如何选择 死信是什么…...

ChatGPT会取代搜索引擎吗?BingChat、GoogleBard与ChatGPT区别
目前暂时不会,ChatGPT为代表的聊天机器人很可能会直接集成到搜索中,而不是取代它。微软已经通过Bing Chat和Bing做到了这一点,它将“聊天”选项卡直接放入Bing搜索的菜单中。Google、百度也分别开始尝试通过其AI生成技术将Google Bard、文心一…...

多个QLabel中文字左右对其问题研究
众所周知,关于QLabel 中的文字对其方式,官方提供多种,具体可参考 AlignmentFlag,这里就不详细列举了。 实际开发中有这样一个需求:多个lab中,文字显示不同,长度不一,但想要实现视觉…...

链式二叉树统计结点个数的方法和bug
方法一: 分治:分而治之 int BTreeSize1(BTNode* root) {if (root NULL) return 0;else return BTreeSize(root->left)BTreeSize(root->right)1; } 方法二: 遍历计数:设置一个计数器,对二叉树正常访问&#…...
C语言-报错集锦-03-malloc(): memory corruption: 0x0000000001496d90 ***
一、报错信息 [2023-8]--[ Debug ]--Push Data To StAccessPath OK. [2023-8]--[ Debug ]--Judge Vertex(0) Is Not Accessed. [2023-8]--[ Debug ]--Judge Vertex(2) Is Accessed. [2023-8]--[ Debug ]--Judge Vertex(3) Is Not Accessed. [2023-8]--[ Debug ]--Judge Vertex…...

现代C++中的从头开始深度学习:【5/8】卷积
一、说明 在上一个故事中,我们介绍了机器学习的一些最相关的编码方面,例如 functional 规划、矢量化和线性代数规划。 现在,让我们通过使用 2D 卷积实现实际编码深度学习模型来开始我们的道路。让我们开始吧。 二、关于本系列 我们将学习如何…...

以太网帧格式与吞吐量计算
以太网帧结构 帧大小的定义 以太网单个最大帧 6(目的MAC地址) 6(源MAC地址) 2(帧类型) 1500{IP数据包[IP头(20)DATA(1480)]} 4(CRC校验ÿ…...
vue中install方法
1:语法 vue提供install可供我们开发新的插件及全局注册组件等 install方法第一个参数是vue的构造器,第二个参数是可选的选项对象 export default {install(Vue,option){组件指令混入挂载vue原型} }2:注册组件 一:注册单个组件 1…...

Flutter:文件读取—— video_player、chewie、image_picker、file_picker
前言 简单学习一下几个比较好用的文件读取库 video_player 简介 用于视频播放 官方文档 https://pub-web.flutter-io.cn/packages/video_player 安装 flutter pub add video_player加载网络视频 class _MyHomePageState extends State<MyHomePage> {// 控制器late…...
vim的使用
vim文本编辑器 vim介绍命令模式光标移动选中内容复制内容粘贴内容删除撤销/恢复字符转换 编辑模式末行模式保存/退出查找行号显示文件切换 扩展 vim介绍 vim是Linux自带的文本编辑器,具有命令模式、编辑模式、末行模式三种模式。 模式间的切换: 命令模…...

马氏杆法检查斜视
使用 检查水平向斜视时,使用水平向马氏杆检查;重直向斜视时,使用重直问马氏杆;检查旋转斜视时,使用双马氏杆. 检查水平向斜视 双眼屈光不正全矫 双眼同时打开,右眼前加水平向马氏杆,左眼前不加 双眼同时观察点光源&…...

Mac电脑怎么使用“磁盘工具”修复磁盘
我们可以使用“磁盘工具”的“急救”功能来查找和修复磁盘错误。 “磁盘工具”可以查找和修复与 Mac 磁盘的格式及目录结构有关的错误。使用 Mac 时,错误可能会导致意外行为,而重大错误甚至可能会导致 Mac 彻底无法启动。 继续之前,请确保您…...

c++画出分割图像,水平线和垂直线
1、pca 找到图像某个区域的垂直线,并画出来 // 1、 斑块的框 血管二值化图,pca 找到垂直血管壁的直线, 还是根据斑块找主轴方向吧// Step 1: 提取斑块左右范围内的血管像素点坐标,std::vector<cv::Point> points;for (int y 0; y <…...
Python 程序设计入门(015)—— enumerate() 函数的用法
Python 程序设计入门(015)—— enumerate() 函数的用法 目录 Python 程序设计入门(015)—— enumerate() 函数的用法一、enumerate() 函数的语法二、为可迭代对象创建索引三、将字符串、列表等转换为字典1、将字符串转换为字典2、…...
__dict__属性
__dict__ 是 Python 中的一个特殊属性,通常存在于大多数 Python 对象中,用于存储该对象的可变属性。 以下是关于 __dict__ 的一些关键点和详细信息: 存储属性:对于大多数自定义的 Python 对象,__dict__ 属性包含了这个…...

k8s之Pod控制器
目录 一、Pod控制器及其功用二、pod控制器的多种类型2.1 pod容器中的有状态和无状态的区别 三、Deployment 控制器四、SatefulSet 控制器4.1 StatefulSet由以下几个部分组成4.2 为什么要有headless?4.3 为什么要有volumeClaimTemplate?4.4 滚动更新4.5 扩…...
逆元(求乘法逆元的几种方法)
目录 逆元 加法逆元 乘法逆元 如何求 快速幂 扩展欧几里得 O(n)求1到n的乘法逆元 逆元 数学中,逆元素(英语:Inverse element)推广了加法中的加法逆元和乘法中的倒数。直观地说,它是一个可以取消另一给定元素运…...
没点本事,还真做不好数字化转型
数字化转型逐渐成为企业业务增长的利器 然而,在此过程中 企业最应该注重哪些? 效率?质量? 但还有一个至关重要的点不容忽视 那就是安全 有一家硬核企业通过技术与狠活 硬生生提升了应用安全性 保障了产业与数字化的安全融合…...

windows 10 远程桌面配置
1. 修改远程桌面端口(3389) 打开注册表(winr), 输入regedit 找到配置项【计算机\HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Control\Terminal Server\Wds\rdpwd\Tds\tcp】 , 可以通过搜索“Wds”快速定位。 修改端口配…...

树莓派超全系列教程文档--(61)树莓派摄像头高级使用方法
树莓派摄像头高级使用方法 配置通过调谐文件来调整相机行为 使用多个摄像头安装 libcam 和 rpicam-apps依赖关系开发包 文章来源: http://raspberry.dns8844.cn/documentation 原文网址 配置 大多数用例自动工作,无需更改相机配置。但是,一…...

基于距离变化能量开销动态调整的WSN低功耗拓扑控制开销算法matlab仿真
目录 1.程序功能描述 2.测试软件版本以及运行结果展示 3.核心程序 4.算法仿真参数 5.算法理论概述 6.参考文献 7.完整程序 1.程序功能描述 通过动态调整节点通信的能量开销,平衡网络负载,延长WSN生命周期。具体通过建立基于距离的能量消耗模型&am…...
R语言AI模型部署方案:精准离线运行详解
R语言AI模型部署方案:精准离线运行详解 一、项目概述 本文将构建一个完整的R语言AI部署解决方案,实现鸢尾花分类模型的训练、保存、离线部署和预测功能。核心特点: 100%离线运行能力自包含环境依赖生产级错误处理跨平台兼容性模型版本管理# 文件结构说明 Iris_AI_Deployme…...

以下是对华为 HarmonyOS NETX 5属性动画(ArkTS)文档的结构化整理,通过层级标题、表格和代码块提升可读性:
一、属性动画概述NETX 作用:实现组件通用属性的渐变过渡效果,提升用户体验。支持属性:width、height、backgroundColor、opacity、scale、rotate、translate等。注意事项: 布局类属性(如宽高)变化时&#…...

理解 MCP 工作流:使用 Ollama 和 LangChain 构建本地 MCP 客户端
🌟 什么是 MCP? 模型控制协议 (MCP) 是一种创新的协议,旨在无缝连接 AI 模型与应用程序。 MCP 是一个开源协议,它标准化了我们的 LLM 应用程序连接所需工具和数据源并与之协作的方式。 可以把它想象成你的 AI 模型 和想要使用它…...
C++.OpenGL (10/64)基础光照(Basic Lighting)
基础光照(Basic Lighting) 冯氏光照模型(Phong Lighting Model) #mermaid-svg-GLdskXwWINxNGHso {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-GLdskXwWINxNGHso .error-icon{fill:#552222;}#mermaid-svg-GLd…...

微信小程序云开发平台MySQL的连接方式
注:微信小程序云开发平台指的是腾讯云开发 先给结论:微信小程序云开发平台的MySQL,无法通过获取数据库连接信息的方式进行连接,连接只能通过云开发的SDK连接,具体要参考官方文档: 为什么? 因为…...

RNN避坑指南:从数学推导到LSTM/GRU工业级部署实战流程
本文较长,建议点赞收藏,以免遗失。更多AI大模型应用开发学习视频及资料,尽在聚客AI学院。 本文全面剖析RNN核心原理,深入讲解梯度消失/爆炸问题,并通过LSTM/GRU结构实现解决方案,提供时间序列预测和文本生成…...
AspectJ 在 Android 中的完整使用指南
一、环境配置(Gradle 7.0 适配) 1. 项目级 build.gradle // 注意:沪江插件已停更,推荐官方兼容方案 buildscript {dependencies {classpath org.aspectj:aspectjtools:1.9.9.1 // AspectJ 工具} } 2. 模块级 build.gradle plu…...
Redis的发布订阅模式与专业的 MQ(如 Kafka, RabbitMQ)相比,优缺点是什么?适用于哪些场景?
Redis 的发布订阅(Pub/Sub)模式与专业的 MQ(Message Queue)如 Kafka、RabbitMQ 进行比较,核心的权衡点在于:简单与速度 vs. 可靠与功能。 下面我们详细展开对比。 Redis Pub/Sub 的核心特点 它是一个发后…...