当前位置: 首页 > news >正文

深入理解Reactor模型的原理与应用

1、什么是Reactor模型

        Reactor意思是“反应堆”,是一种事件驱动机制。

        和普通函数调用的不同之处在于:应用程序不是主动的调用某个 API 完成处理,而是恰恰相反,Reactor逆置了事件处理流程,应用程序需要提供相应的接口并注册到 Reactor 上,如果相应的时间发生,Reactor将主动调用应用程序注册的接口,这些接口又称为“回调函数”。

        对于刚开始接触这个机制,个人感觉翻译成“感应器”可能会更好理解一点,因为注册在Reactor上的函数就像感应器一样,只要有事件到达,就会触发它开始工作。

        Reactor 模式是编写高性能网络服务器的必备技术之一。


2、Reactor模型的优点

  • 响应快,不必为单个同步时间所阻塞,虽然 Reactor 本身依然是同步的;
  • 编程相对简单,可以最大程度的避免复杂的多线程及同步问题,并且避免了多线程/进程的切换开销;
  • 可扩展性强,可以方便的通过增加 Reactor 实例个数来充分利用 CPU 资源;
  • 可复用性高,reactor 框架本身与具体事件处理逻辑无关,具有很高的复用性;
           Reactor 模型开发效率上比起直接使用 IO 复用要高,它通常是单线程的,设计目标是希望单线程使用一颗 CPU 的全部资源。
            优点即每个事件处理中很多时候可以不考虑共享资源的互斥访问。可是缺点也是明显的,现在的硬件发展,已经不再遵循摩尔定律,CPU 的频率受制于材料的限制不再有大的提升,而改为是从核数的增加上提升能力,当程序需要使用多核资源时,Reactor 模型就会悲剧 , 为什么呢?
            如果程序业务很简单,例如只是简单的访问一些提供了并发访问的服务,就可以直接开启多个反应堆,每个反应堆对应一颗 CPU 核心,这些反应堆上跑的请求互不相关,这是完全可以利用多核的。例如 Nginx 这样的 http 静态服务器。

3、通过对网络编程(epoll)代码的优化,深入理解Reactor模型

1、epoll的普通版本,根据fd类型(listen_fd和client_fd)分为两大类处理。

        如果是listen_fd,调用accept处理连接请求;

        如果是client_fd,调用recv或者send处理数据。

         代码实现:


#include <stdio.h>
#include <string.h>
#include <stdlib.h>#include <unistd.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>
#include <sys/epoll.h>#include <errno.h>int main(int argc, char* argv[])
{if (argc < 2)return -1;int port = atoi(argv[1]);   //字符串转换为整型int sockfd = socket(AF_INET, SOCK_STREAM, 0);if (sockfd < 0)return -1;struct sockaddr_in addr;memset(&addr, 0, sizeof(struct sockaddr_in));   //新申请的空间一定要置零addr.sin_family = AF_INET;addr.sin_port = htons(port);    //转换成网络字节序addr.sin_addr.s_addr = INADDR_ANY;if (bind(sockfd, (struct sockaddr*)&addr, sizeof(struct sockaddr_in)) < 0)return -2;if (listen(sockfd, 5) < 0)return -3;//epollint epfd = epoll_create(1); //创建epoll,相当于红黑树的根节点struct epoll_event ev, events[1024] = {0};  //events相当于就绪队列,一次性可以处理的集合ev.events = EPOLLIN;ev.data.fd = sockfd;epoll_ctl(epfd, EPOLL_CTL_ADD, sockfd, &ev);    //将ev节点加入到epoll,此处的sockfd参数随便添加没有意义,需要操作系统索引和它有对应的句柄while (1){int nready = epoll_wait(epfd, events, 1024, -1);    //第四个参数-1表示一直等待,有事件才返回if (nready < 1) //没有事件触发,nready代表触发事件的个数break;int i = 0;for (i = 0; i < nready; i++)    //epoll_wait带出的就绪fd包括两大类:1、处理连接的listen_fd,2、处理数据的send和recv{if (events[i].data.fd == sockfd) //如果是listenfd,就将它加入到epoll{struct sockaddr_in client_addr;memset(&client_addr, 0, sizeof(struct sockaddr_in));socklen_t client_len = sizeof(client_addr);int client_fd = accept(sockfd, (struct sockaddr *)&client_addr, &client_len);if (client_fd <= 0)continue;char str[INET_ADDRSTRLEN] = {0};printf("recv from IP = %s ,at Port= %d\n", inet_ntop(AF_INET, &client_addr.sin_addr, str, sizeof(str)), ntohs(client_addr.sin_port));ev.events = EPOLLIN | EPOLLET;  //epoll默认是LT模式ev.data.fd = client_fd;epoll_ctl(epfd, EPOLL_CTL_ADD, client_fd, &ev);}else    //fd进行读写操作{//对fd的读写操作没有分开int client_fd = events[i].data.fd;char buf[1024] = {0};int ret = recv(client_fd, buf, 1024, 0);if (ret < 0){if (errno == EAGAIN || errno == EWOULDBLOCK){//}else{//}printf("ret < 0,断开连接:%d\n", client_fd);close(client_fd);ev.events = EPOLLIN;ev.data.fd = client_fd;epoll_ctl(epfd, EPOLL_CTL_DEL, client_fd, &ev);}else if (ret == 0)  //接收到了客户端发来的断开连接请求FIN后,没有及时调用close函数,进入了CLOSE _WAIT状态{printf("ret = 0,断开连接:%d\n", client_fd);close(client_fd);ev.events = EPOLLIN;ev.data.fd = client_fd;epoll_ctl(epfd, EPOLL_CTL_DEL, client_fd, &ev); //close关闭连接后要将它既是从epoll中删除}else{printf("Recv: %s, %d Bytes\n", buf, ret);}//区分fd的读写操作,即recv和sendif (events[i].events & EPOLLIN){int client_fd = events[i].data.fd;char buf[1024] = {0};int ret = recv(client_fd, buf, 1024, 0);if (ret < 0){if (errno == EAGAIN || errno == EWOULDBLOCK){//...}else{//...}printf("ret < 0,断开连接:%d\n", client_fd);close(client_fd);ev.events = EPOLLIN;ev.data.fd = client_fd;epoll_ctl(epfd, EPOLL_CTL_DEL, client_fd, &ev);}else if (ret == 0)  //接收到了客户端发来的断开连接请求FIN后,没有及时调用close函数,进入了CLOSE _WAIT状态{printf("ret = 0,断开连接:%d\n", client_fd);close(client_fd);ev.events = EPOLLIN;ev.data.fd = client_fd;epoll_ctl(epfd, EPOLL_CTL_DEL, client_fd, &ev); //close关闭连接后要将它既是从epoll中删除}else{printf("Recv: %s, %d Bytes\n", buf, ret);}}if (events[i].events & EPOLLOUT)    //为什么需要判断EPOLLOUT,而不是直接else?因为一个fd有可能同时存在可读和可写事件的{int client_fd = events[i].data.fd;char buf[1024] = {0};send(client_fd, buf, sizeof(buf), 0);}}}}return 0;
}

 

2、epoll的优化版本,根据事件类型(读和写)分为两大类处理。

         代码实现:

        for (i = 0; i < nready; i++)    //epoll_wait带出的就绪fd包括两大类:1、处理连接的listen_fd,2、处理数据的send和recv{//区分fd的读写操作if (events[i].events & EPOLLIN){if (events[i].data.fd == sockfd) //如果是listenfd,就将它加入到epoll{struct sockaddr_in client_addr;memset(&client_addr, 0, sizeof(struct sockaddr_in));socklen_t client_len = sizeof(client_addr);int client_fd = accept(sockfd, (struct sockaddr *)&client_addr, &client_len);if (client_fd <= 0)continue;char str[INET_ADDRSTRLEN] = {0};printf("recv from IP = %s ,at Port= %d\n", inet_ntop(AF_INET, &client_addr.sin_addr, str, sizeof(str)), ntohs(client_addr.sin_port));ev.events = EPOLLIN | EPOLLET;  //epoll默认是LT模式ev.data.fd = client_fd;epoll_ctl(epfd, EPOLL_CTL_ADD, client_fd, &ev);}else {int client_fd = events[i].data.fd;char buf[1024] = {0};int ret = recv(client_fd, buf, 1024, 0);if (ret < 0){if (errno == EAGAIN || errno == EWOULDBLOCK){//...}else{//...}printf("ret < 0,断开连接:%d\n", client_fd);close(client_fd);ev.events = EPOLLIN;ev.data.fd = client_fd;epoll_ctl(epfd, EPOLL_CTL_DEL, client_fd, &ev);}else if (ret == 0)  //接收到了客户端发来的断开连接请求FIN后,没有及时调用close函数,进入了CLOSE _WAIT状态{printf("ret = 0,断开连接:%d\n", client_fd);close(client_fd);ev.events = EPOLLIN;ev.data.fd = client_fd;epoll_ctl(epfd, EPOLL_CTL_DEL, client_fd, &ev); //close关闭连接后要将它既是从epoll中删除}else{printf("Recv: %s, %d Bytes\n", buf, ret);}}}//为什么需要判断EPOLLOUT,而不是直接else?因为一个fd有可能同时存在可读和可写事件的if (events[i].events & EPOLLOUT)    {int client_fd = events[i].data.fd;char buf[1024] = {0};send(client_fd, buf, sizeof(buf), 0);}}

 

3、epoll的Reactor模式, epoll由以前的对网络io(fd)进行管理,转变成对events事件进行管理。

         代码实现:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>#include <unistd.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>
#include <sys/epoll.h>#include <errno.h>//每个fd所对应的信息
struct sockitem
{int sockfd;int (*callback)(int fd, int events, void*arg);char sendbuf[1024];char recvbuf[1024];
};//每个epoll所对应的信息
struct epollitem
{int epfd;struct epoll_event events[1024];    //events相当于就绪队列,一次性可以处理的集合
};struct epollitem *eventloop = NULL;int recv_cb(int fd, int events, void*arg);
int send_cb(int fd, int events, void*arg);int accept_cb(int fd, int events, void*arg)
{printf("---accept_cb(int fd, int events, void*arg)---\n");struct sockaddr_in client_addr;memset(&client_addr, 0, sizeof(struct sockaddr_in));socklen_t client_len = sizeof(client_addr);int client_fd = accept(fd, (struct sockaddr *)&client_addr, &client_len);if (client_fd <= 0)return -1;char str[INET_ADDRSTRLEN] = {0};printf("recv from IP = %s ,at Port= %d\n", inet_ntop(AF_INET, &client_addr.sin_addr, str, sizeof(str)), ntohs(client_addr.sin_port));struct epoll_event ev;ev.events = EPOLLIN | EPOLLET;  //epoll默认是LT模式struct sockitem *si = (struct sockitem*)malloc(sizeof(struct sockitem));si->sockfd = client_fd;si->callback = recv_cb;ev.data.ptr = si;epoll_ctl(eventloop->epfd, EPOLL_CTL_ADD, client_fd, &ev);return client_fd;
}int recv_cb(int fd, int events, void*arg)
{printf("---recv_cb(int fd, int events, void*arg)---\n");struct epoll_event ev;struct sockitem *sit = (struct sockitem*)arg;int ret = recv(fd, sit->recvbuf, 1024, 0);if (ret < 0){if (errno == EAGAIN || errno == EWOULDBLOCK){//...}else{//...}printf("ret < 0,断开连接:%d\n", fd);ev.events = EPOLLIN;epoll_ctl(eventloop->epfd, EPOLL_CTL_DEL, fd, &ev);    //close关闭连接后要将它既是从epoll中删除close(fd);free(sit);  //连接关闭后释放内存}else if (ret == 0)  //接收到了客户端发来的断开连接请求FIN后,没有及时调用close函数,进入了CLOSE _WAIT状态{printf("ret = 0,断开连接:%d\n", fd);ev.events = EPOLLIN;epoll_ctl(eventloop->epfd, EPOLL_CTL_DEL, fd, &ev); close(fd);free(sit);}else{printf("Recv from recvbuf:  %s, %d Bytes\n", sit->recvbuf, ret);ev.events = EPOLLIN | EPOLLOUT;  //sit->sockfd = fd;sit->callback = send_cb;ev.data.ptr = sit;epoll_ctl(eventloop->epfd, EPOLL_CTL_MOD, fd, &ev);}return ret;
}int send_cb(int fd, int events, void*arg)
{struct epoll_event ev;struct sockitem *sit = (struct sockitem*)arg;strncpy(sit->sendbuf, sit->recvbuf, sizeof(sit->recvbuf) + 1);send(fd, sit->sendbuf, sizeof(sit->recvbuf) + 1, 0);ev.events = EPOLLIN | EPOLLET;  //sit->sockfd = fd;sit->callback = recv_cb;ev.data.ptr = sit;epoll_ctl(eventloop->epfd, EPOLL_CTL_MOD, fd, &ev);return fd;
}int main(int argc, char* argv[])
{if (argc < 2)return -1;int port = atoi(argv[1]);   //字符串转换为整型int sockfd = socket(AF_INET, SOCK_STREAM, 0);if (sockfd < 0)return -1;struct sockaddr_in addr;memset(&addr, 0, sizeof(struct sockaddr_in));   //新申请的空间一定要置零addr.sin_family = AF_INET;addr.sin_port = htons(port);    //转换成网络字节序addr.sin_addr.s_addr = INADDR_ANY;if (bind(sockfd, (struct sockaddr*)&addr, sizeof(struct sockaddr_in)) < 0)return -2;if (listen(sockfd, 5) < 0)return -3;//epolleventloop = (struct epollitem *)malloc(sizeof(struct epollitem));eventloop->epfd = epoll_create(1); //创建epoll,相当于红黑树的根节点struct epoll_event ev;ev.events = EPOLLIN | EPOLLET;struct sockitem *si = (struct sockitem*)malloc(sizeof(struct sockitem));si->sockfd = sockfd;si->callback = accept_cb;ev.data.ptr = si;   //将fd和对应的回调函数绑定一起带进epollepoll_ctl(eventloop->epfd, EPOLL_CTL_ADD, sockfd, &ev);    //将ev节点加入到epoll,此处的sockfd参数随便添加没有意义,需要操作系统索引和它有对应的句柄while (1){int nready = epoll_wait(eventloop->epfd, eventloop->events, 1024, -1);    //第四个参数-1表示一直等待,有事件才返回if (nready < 1) //没有事件触发,nready代表触发事件的个数break;int i = 0;for (i = 0; i < nready; i++){//区分fd的读写操作if (eventloop->events[i].events & EPOLLIN){struct sockitem *sit = (struct sockitem*)eventloop->events[i].data.ptr;sit->callback(sit->sockfd, eventloop->events[i].events, sit);    //不用区分listen_fd和recv_fd,相应的fd都会调用他们所对应的callback}//为什么需要判断EPOLLOUT,而不是直接else?因为一个fd有可能同时存在可读和可写事件的if (eventloop->events[i].events & EPOLLOUT)    {struct sockitem *sit = (struct sockitem*)eventloop->events[i].data.ptr;sit->callback(sit->sockfd, eventloop->events[i].events, sit);}}}return 0;
}

4、Reactor模型的应用 

        1、单线程模式的Reactor,参考libevent、redis;

        2、多线程模式的Reactor,参考memcached;

        3、多进程模式的Reactor,参考nginx。

相关文章:

深入理解Reactor模型的原理与应用

1、什么是Reactor模型 Reactor意思是“反应堆”&#xff0c;是一种事件驱动机制。 和普通函数调用的不同之处在于&#xff1a;应用程序不是主动的调用某个 API 完成处理&#xff0c;而是恰恰相反&#xff0c;Reactor逆置了事件处理流程&#xff0c;应用程序需要提供相应的接口并…...

微信小程序开发的投票评选系统设计与实现

摘要 越来越多信息化融入到我们生活当中的同时&#xff0c;也在改变着我们的生活和学习方式&#xff0c;当然&#xff0c;变化最明显的除了我们普通民众之外&#xff0c;要数高校学生的生活方式以及校园信息化的变革。智慧是改变生活和生产的一种来源&#xff0c;那么智慧的体…...

【校招VIP】算法考点之堆排

考点介绍&#xff1a; 排序算法属于数据结构和算法的基础内容&#xff0c;并且也是大厂笔试中的高频考点。 堆排序是使用一棵树存储序列这个课树只保证跟节点是这棵树中的最小值&#xff0c;但并不保证其他节点是按顺序的。因此他的排序是每次从堆中取得堆顶&#xff0c;取得 n…...

关于yarn安装时报“node“ is incompatible with this module的解决办法

前提&#xff1a; 在用vue写一个h5页面时&#xff0c;当在用yarn安装时&#xff0c;提示如下错误&#xff1a; The engine “node” is incompatible with this module. Expected version "^14.18.0 || ^16.14.0 || >18. 解决办法 我是使用命令忽略错误&#xff1a…...

开源利器推荐:美团动态线程池框架的接入分享及效果展示

前言 蛮早前有些过关于线程池的使用及参数的一些参考配置&#xff0c;有兴趣的可以翻看以前的博文&#xff0c;但终究无法解决线程池的动态监控和实时修改。 以前读过美团早期发布的动态线程池框架的思路相关文章&#xff0c;但想要独自实现不是一件容易的事。 去年&#xff0c…...

Linux目录结构与文件管理 (02)(四)

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 目录 前言 一、查看文件内容 二、创建文件 三、删除文件 四、 移动文件 五、复制文件 六、编辑文件内容 总结 前言 今天是在昨天的基础上继续学习&#xff0c;主要…...

对1GHz脉冲多普勒雷达进行快速和慢速处理生成5个移动目标的距离多普勒图研究(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…...

uni.uploadFile上传 PHP接收不到

开始这样&#xff0c;后端$file $request->file(file);接收不到 数据跑到param中去了 去掉Content-Type&#xff0c;就能接收到了 param只剩下...

2023年高教社杯 国赛数学建模思路 - 复盘:光照强度计算的优化模型

文章目录 0 赛题思路1 问题要求2 假设约定3 符号约定4 建立模型5 模型求解6 实现代码 建模资料 0 赛题思路 &#xff08;赛题出来以后第一时间在CSDN分享&#xff09; https://blog.csdn.net/dc_sinor?typeblog 1 问题要求 现在已知一个教室长为15米&#xff0c;宽为12米&…...

Netty简易聊天室

文章目录 本文目的参考说明环境说明maven依赖日志配置单元测试 功能介绍开发步骤 本文目的 通过一个简易的聊天室案例&#xff0c;讲述Netty的基本使用。同时分享案例代码。项目中用到了log4j2&#xff0c;junit5&#xff0c;同时分享这些基础组件的使用。项目中用到了awt&…...

Flutter Cannot run with sound null safety, because the following dependencies

flutter sdk 版本升级到2.0或者更高的版本后&#xff0c;运行之前的代码会报错 Error: Cannot run with sound null safety, because the following dependencies dont support null safety:- package:flutter_swiper- package:flutter_page_indicator- package:transformer_p…...

利用改进的遗传算法(种群隔离与个体迁移)mpi并行解决tsp问题

序 关于tsp问题的概述以及如何使用遗传算法进行求解已经在上一篇文章中说明了&#xff1a;遗传算法解决TSP问题. 但是&#xff0c;作为一种演化算法&#xff0c;遗传算法还存在着许多问题&#xff0c;比如早熟的情况&#xff0c;很容易在算法前期就已经收敛了&#xff0c;大量…...

【C++】—— C++11之线程库

前言&#xff1a; 在本期&#xff0c;我将给大家介绍的是 C11 中新引进的知识&#xff0c;即关于线程库的相关知识。 目录 &#xff08;一&#xff09;线程库的介绍 1、线程库的由来 2、线程库的简单介绍 &#xff08;二&#xff09;线程函数参数 &#xff08;三&#xf…...

前端面试:【性能优化】前端缓存、CDN、懒加载和预加载

亲爱的前端开发者&#xff0c;Web性能对用户体验至关重要。如果你想让你的网站更快、更具吸引力&#xff0c;就需要关注前端性能优化。在这篇文章中&#xff0c;我们将深入探讨四个关键的性能优化策略&#xff1a;前端缓存、CDN&#xff08;内容分发网络&#xff09;、懒加载和…...

民族传统文化分享系统uniapp 微信小程序

管理员、用户可通过Android系统手机打开系统&#xff0c;注册登录后可进行管理员后端&#xff1b;首页、个人中心、用户管理、知识分类管理、知识资源管理、用户分享管理、意见反馈、系统管理&#xff0c;用户前端&#xff1b;首页、知识资源、用户分享、我的等。 本系统的使用…...

netty(二):NIO——处理可写事件

处理可写事件 什么情况下需要注册可写事件&#xff1f; 在服务端一次性无法把数据发送完的情况下&#xff0c;需要注册可写事件 服务端一次性是否能够把数据全部发送完成取决于服务端的缓冲区大小&#xff0c;该缓冲区不受程序控制 注册可写事件的步骤 判断ByteBuffer是否仍…...

PHP基本语法解析与应用指南

PHP&#xff08;Hypertext Preprocessor&#xff09;是一种广泛应用的开源脚本语言&#xff0c;特别适用于Web开发。本文将深入探讨PHP的基本语法&#xff0c;包括变量、数据类型、运算符、控制流等方面的内容。我们将详细介绍每个主题的基本概念、语法规则和常见应用&#xff…...

ICS PA1

ICS PA1 init.shmake 编译加速ISA计算机是个状态机程序是个状态机准备第一个客户程序parse_argsinit_randinit_loginit_meminit_isa load_img剩余的初始化工作运行第一个客户程序调试&#xff1a;零断点TUI 基础设施单步执行打印寄存器状态扫描内存 表达式求值词法分析递归求值…...

Java学数据结构(4)——散列表Hash table 散列函数 哈希冲突

目录 引出散列表Hash table关键字Key和散列函数(hash function)散列函数解决collision哈希冲突&#xff08;碰撞&#xff09;分离链接法(separate chaining)探测散列表(probing hash table)双散列(double hashing) Java标准库中的散列表总结 引出 1.散列表&#xff0c;key&…...

OVRL-V2: A simple state-of-art baseline for IMAGENAV and OBJECTNAV 论文阅读

论文信息 题目&#xff1a;OVRL-V2: A simple state-of-art baseline for IMAGENAV and OBJECTNAV 作者:Karmesh Yadav&#xff0c; Arjun Majumdar&#xff0c; Ram Ramrakhya 来源&#xff1a;arxiv 时间&#xff1a;2023 代码地址&#xff1a; https://github.com/ykarmesh…...

Excel办公必备4个技巧:格式转换、隔列插入、限制编辑、文本数字分离

在日常办公中&#xff0c;Excel是我们使用频率最高的软件之一&#xff0c;但很多人只掌握了最基础的录入和简单计算功能&#xff0c;遇到一些“卡脖子”的小问题就束手无策&#xff0c;不得不手动折腾半天。其实&#xff0c;Excel中隐藏着不少实用的小技巧&#xff0c;能帮你轻…...

从.bib到.bbl:手把手教你搞定LaTeX参考文献的完整流程

从.bib到.bbl&#xff1a;手把手教你搞定LaTeX参考文献的完整流程 如果你曾被LaTeX的参考文献格式折磨得焦头烂额&#xff0c;这篇文章就是为你准备的。我们将从零开始&#xff0c;完整走一遍从文献管理到最终PDF生成的每个步骤&#xff0c;特别关注那些让新手困惑的.bib、.bbl…...

B站视频下载工具终极指南:3分钟快速上手,轻松保存你喜欢的每一帧画面

B站视频下载工具终极指南&#xff1a;3分钟快速上手&#xff0c;轻松保存你喜欢的每一帧画面 【免费下载链接】BiliTools A cross-platform bilibili toolbox. 跨平台哔哩哔哩工具箱&#xff0c;支持视频、音乐、番剧、课程下载……持续更新 项目地址: https://gitcode.com/G…...

Pixel Fashion Atelier基础教程:硬核8-Bit界面操作逻辑与非对称布局解析

Pixel Fashion Atelier基础教程&#xff1a;硬核8-Bit界面操作逻辑与非对称布局解析 1. 像素时装锻造坊简介 Pixel Fashion Atelier是一款基于Stable Diffusion与Anything-v5的图像生成工具&#xff0c;它彻底改变了传统AI工具的界面设计理念。这款工具将复古日系RPG的"…...

Opencascade避坑指南:Select()函数7个常见使用误区与调试技巧

Opencascade避坑指南&#xff1a;Select()函数7个常见使用误区与调试技巧 在三维建模和CAD开发领域&#xff0c;Opencascade作为一款强大的开源几何内核&#xff0c;其交互功能一直是开发者关注的焦点。而AIS_InteractiveContext中的Select()函数&#xff0c;作为对象选取的核心…...

TTL串口设计及其注意事项

一、TTL串口设计概述我们常见的处理器&#xff08;单片机&#xff09;引出来的串口是UART、USART,其中有没有S取决于有没有时钟信号&#xff08;SLK&#xff09;&#xff0c;出来的电平是TTL电平&#xff0c;常见的UART串口设计有3线串口设计&#xff0c;单线串口设计&#xff…...

从ImageNet到CV落地:深度解读AlexNet的6个工程优化技巧

从AlexNet到现代CV工程&#xff1a;6个历久弥新的优化策略解析 当AlexNet在2012年ImageNet竞赛中以压倒性优势夺冠时&#xff0c;它带来的不仅是准确率的飞跃&#xff0c;更是一套影响深远的工程实践方法论。十年过去&#xff0c;尽管网络架构已迭代数十代&#xff0c;但AlexNe…...

【数电】组合逻辑电路模块:从原理到系统级应用

1. 组合逻辑电路基础入门 第一次接触数字电路时&#xff0c;我被那些密密麻麻的逻辑门符号搞得头晕眼花。直到真正理解了组合逻辑电路的精髓&#xff0c;才发现它就像搭积木一样有趣。组合逻辑电路的特点是输出只取决于当前的输入状态&#xff0c;不像时序电路那样需要考虑历史…...

新手入门:跟快马学做项目,从零实现简易版z-library书库网站

作为一个刚入门编程的新手&#xff0c;最近想尝试做一个简单的在线书库网站。这个想法源于我经常使用的z-library&#xff0c;虽然它功能很强大&#xff0c;但作为学习项目&#xff0c;我决定先从最基础的功能开始模仿。下面记录下我的学习过程&#xff0c;希望能帮到同样想入门…...

Pixel Fashion Atelier保姆级教程:修复WebUI中文乱码与像素字体缺失问题

Pixel Fashion Atelier保姆级教程&#xff1a;修复WebUI中文乱码与像素字体缺失问题 1. 问题背景与现象 Pixel Fashion Atelier作为一款融合复古像素风格的AI图像生成工具&#xff0c;其独特的界面设计是其核心亮点之一。然而&#xff0c;部分用户在部署和使用过程中可能会遇…...