基于多反应堆的高并发服务器【C/C++/Reactor】(中)Channel 模块的实现

在这篇文章中虽然实现了能够和多客户端建立连接,并且同时和多个客户端进行通信。

基于多反应堆的高并发服务器【C/C++/Reactor】(上)-CSDN博客icon-default.png?t=N7T8https://blog.csdn.net/weixin_41987016/article/details/135141316?spm=1001.2014.3001.5501但是有一个问题(O_O)?这个程序它是单线程的。如果我们想要程序的效率更高一些,就需要使用多线程。

研究一下:若使用多线程,需要在什么地方把子线程创建出来。在服务器端有两类文件描述符:一类是用于通信的,一类是用于监听的。关于监听的文件描述符,在服务器端有且仅有一个。所以我们把它在主线程里边创建出来之后,就不需要做其他的监听的文件描述符的创建了。通过这一个唯一的监听文件描述符,服务器就能接收到客户端的连接请求,并且和多个客户端建立连接。

int epollRun(int lfd) {...while(1) {int num = epoll_wait(epfd,evs,size,-1);if(num == -1) {perror("epoll_wait");return -1;}for(int i=0;i<num;++i) {int fd = evs[i].data.fd;if(fd == lfd) {// 建立新连接 acceptacceptClient(lfd,epfd);}else{// 主要是接收对端的数据recvHttpRequest(fd,epfd);}}}return 0;
}

epollRun函数中,在while循环里边,需要判断文件描述符的类型,如果是监听的文件描述符,它的读事件被触发了。那么我们就和客户端建立新连接。还有一种情况是通信的文件描述符,我们就需要和客户端进行通信。因此不管是建立连接还是和客户端通信,都可以把它放到一个子线程里面去做。也就是说我们需要在这两个函数调用的位置分别创建子线程。把acceptClient函数或者是recvHttpRequest函数传递给子线程,让子线程去执行这个处理动作。

int epollRun(int lfd) {// 1.创建epoll实例int epfd = epoll_create(1);if(epfd == -1) {perror("epoll_create");return -1;}// 2.添加监听fd lfd上树 对于监听的描述符来说只需要看一下有没有新的客户端连接struct epoll_event ev;ev.data.fd = lfd;ev.events = EPOLLIN;// 委托epoll(内核)帮我们检测lfd的读事件int ret = epoll_ctl(epfd,EPOLL_CTL_ADD,lfd,&ev);if(ret == -1) {perror("epoll_ctl");return -1;}// 3.检测struct epoll_event evs[1024];// int size = sizeof(evs)/sizeof(epoll_event);int size = sizeof(evs)/sizeof(evs[0]);while(1) {int num = epoll_wait(epfd,evs,size,-1);if(num == -1) {perror("epoll_wait");return -1;}for(int i=0;i<num;++i) {struct FdInfo* info = (struct FdInfo*)malloc(sizeof(struct FdInfo));int fd = evs[i].data.fd;info->epfd = epfd;info->fd = fd;if(fd == lfd) {// 建立新连接 accept// acceptClient(lfd,epfd);// 两个参数// 两个参数->只能够对数据进行封装pthread_create(&info->tid,NULL,acceptClient,info);}else{// 主要是接收对端的数据// recvHttpRequest(fd,epfd);// 子线程被创建出来后,它对应的处理动作是 recvHttpRequest,给这个函数传递的实参是// info这个结构体里边的数据pthread_create(&info->tid,NULL,recvHttpRequest,info);}}}return 0;
}

接下来要做的事情就是把acceptClient函数和recvHttpRequest函数原型修改一下。因为对于一个子线程来说,它的回调函数对应的是一个函数指针,函数指针的返回值是void*类型。它的参数也是void*类型。先切换到头文件,修改头文件。

Server.h

修改前:
// 和客户端建立连接
int acceptClient(int lfd,int epfd);
// 主要是接收对端的数据
int recvHttpRequest(int cfd,int epfd);修改后:
// 和客户端建立连接
void* acceptClientThread(void* arg);
// 主要是接收对端的数据
void* recvHttpRequest(void* arg);

 (一)第二阶段模块功能概述

Listerner 有监听端口和用于监听的文件描述符。把用于监听的文件描述符或者通信的文件描述符进行了封装,封装好了之后对应一个通道。我如果想要接收客户端的连接,需要一个文件描述符。所有的客户端向我发起了连接请求,都需要通过这个文件描述符来完成对应的动作。如果要通信,每个客户端都对应一个通信的文件描述符。所以就可以把每个通信的文件描述符理解为专属的路径。在封装Channel的时候,都有什么样的关键性要素呢?

  1. 文件描述符可以是通信的,也可以是监听
  2. 监听/通信文件描述符最终都要把它放入I/O多路复用模型里边进行检测
  3. Dispatcher有三种I/O多路复用技术供我们选择,分别是pollselectepoll(三选一,并非同时使用)

通过Dispatcher检测一系列的文件描述符所对应的事件,因此在Channel中我们除了封装这些文件描述符之外,还需要指定这些文件描述符需要检测的事件。有三类,包括读、写、读写。另外,反应堆模型的基本要素是回调,有了回调之后,才能实现一个反应堆模型。因此需要注册事件对应的处理动作(回调函数)。我们需要告诉Channel,如果这个读事件触发了,它所对应的处理动作是什么?把这些注册好了之后,什么时候调用呢?Dispatcher在检测fd事件的时候,如果检测到了读事件,它会帮助程序员去调用对应的处理动作

文件描述符如何封装为Channel?文件描述符被封装为通道式通过将通信的文件描述符或用于监听的文件描述符进行封装实现的。封装后的通道对应一个专属的文件路径,用于接收客户端连接和通信。在封装过程中,关键性要素包括文件描述符,它可用于通信或监听。这些文件描述符最终被放到一个IO多路复用模型中进行检测,该模型是反应堆模型的核心组件之一。通过封装的ChannelMap模块,根据文件描述符找到对应的channel实例,从而根据事件触发对应的回调函数或处理动作。

反应堆模型中的Channel是一个封装了文件描述符的通道,用于通信和监听,这个Chanel 对应一个文件描述符,可以是通信的,也可以是监听的。在Channel中,除了封装这些文件描述符之外,还需要指定这些文件描述符需要检测的事件,包括读、写、读写。这些事件对应的处理动作就是回调函数,由Dispatcher模型来调用。

ChannelMap:反应堆模型中的ChannelMap是一个关键的模块,用于存储每个文件描述符与对应的Channel实例之间的对应关系。通过ChannelMap,程序能够根据文件描述符找到对应的Channel,进而确定文件描述符对应的事件触发后所对应的回调函数(处理动作)。因此,ChannelMap在反应堆模型中起着重要的桥梁作用,帮助程序实现时间驱动的处理机制。

EventLoop:事件循环,因为服务器启动起来之后,一直有事件不停地触发,这个事件包括客户端的新连接,以及已经建立连接的客户端和服务器之间的数据通信。例如数据的发送:服务器给客户端发送数据,有两种方式:

  • ①主动发送,调用write函数;通过文件描述符对应的写事件去发送数据,默认情况下可以不监测文件描述符的写事件,因此这个写事件对应的回调函数肯定是不会被调用的,当我们要发送数据的时候,给这个文件描述符添加写事件的检测,这个文件描述符对应的写缓冲区如果可写,写事件就会被触发。因此如果把写事件的检测追加给这个文件描述符,它所对应的事件马上就会被触发了。被触发之后,就会调用这个回调函数;
  • ②被动发送,让文件描述符触发写事件,它所对应的回调函数就可以帮助我们去发送对应的数据块。所以EventLoop主要用于程序中所有事件的处理,比如说要往Dispatcher上追加事件,所谓的追加就是modify(修改),还有往Dispatcher上添加事件,就是事件对应的文件描述符原来没有在Dispatcher检测模型上边,我们文件描述符添加到了Dispatcher上边,待检测的节点多了一个。还有一种情况Dispatcher检测这些节点已经和客户端断开了连接,那么我们就不需要再对它进行检测,因此就需要把这个节点进行检测了,因此就需要把这个节点从Dispatcher上移除。

IO多路复用模型如何检测文件描述符?IO多路复用模型通过检测一系列的文件描述符来检测文件描述符。这些文件描述符可以是通信的文件描述符或监听的文件描述符,它们被放入IO多路复用中进行检测。在检测时,会指定这些文件描述符需要检测的事件,包括读、写和读写事件。当这些事件被触发时,相应的处理动作(回调函数)会被调用。这个处理动作是由IO多路复用模型来调用的,而不是程序的开发者直接调用。

HTTP服务器底层是什么?HTTP服务器底层是一个TCP服务器。这个TCP服务器底层是一个反应堆模型,其中包含一个listener监听器,用于监听客户端连接。当客户端发起一个TCP连接时,listener监听器就会创建一个channel通道,用于接收客户端连接。当客户端连接建立之后,listener监听器就会创建一个HTTP请求响应模块,用于解析客户端发过来的HTTP请求,并基于请求数据组织HTTP响应。

反应堆模型的主要功能是什么?反应堆模型的主要功能是处理事件和调用对应的处理动作。它通过分发器模型检测文件描述符事件,并在事件触发时调用相应的回调函数。此外,反应堆模型还负责处理客户端的新连接和数据通信,包括数据的接收和发送。

如何实现多反应堆加多线程服务器?要实现一个多反应堆加多线程,也就是线程池的高并发的服务器,需要开发出以下模块:

  1. 反应堆模型:能够监测事件,并处理对应的事件
  2. 多线程:使用线程池,需要先编辑单个线程的模型,然后基于单个模型编写线程池
  3. IO模型:涉及数据的读和写操作,包括接收和发送数据
  4. TCP connection:在套接字通信过程中,除了监听的文件描述符,还需要一个用于通信的文件描述符
  5. HTTP协议:主要分成HTTP请求和HTTP响应两部分。在HTTP请求里边,主要适用于解析客户端发过来的HTTP请求,通过这个模块,把客户端发过来的请求数据的请求行和请求解析出来之后,保存起来。基于这些数据去组织HTTP响应,HTTP响应就是组织回复的数据,如何去组织回复的数据块以及如何去发送数据都是通过HTTP响应这个模块来实现的

如何组织数据块并发送给客户端?组织数据块并发送给客户端,首先先需要一块内存来构建数据块,例如HTTP响应消息,然后将数据写入这块内存中。最后通过write函数或send函数将数据发送客户端。在项目中,这个读写数据的内存被封装成了一个buffer

线程池在多线程中起到什么作用?线程池在多线程中起到管理线程的作用。线程池可以控制线程的数量,避免频繁地创建和销毁线程,提高系统的性能和稳定性。同时,线程池还可以对人物进行调度和分配,使得多个线程可以协同工作,提高任务的执行效率。

(二)Channel 模块的实现

重构一下服务器端的代码,在重构的时候,基于一个由简单到复杂,由部分到整体的思想来把程序写一下。先来看第一部分Channel,主要是封装了一个文件描述符,这个文件描述符。可能适用于监听的,也可能适用于通信的, 在服务器端用于监听的文件描述符有且只有一个,用于通信的文件描述符,就有若干个了。程序中如果有多个Channel的话,大部分肯定是用于通信的,除了这个文件描述符之外,我们还需要检测这个文件描述符对应的事件,肯定是基于IO多路模型的。通过IO多路模型检测到文件描述符对应的事件被触发了,那么在多路IO模型里边就需要调用事件对应的处理函数。因此,需要Channel这个结构里边指定文件描述符它的读事件和写事件对应的回调函数。除此之外,还有data,如果说我调用了一个读回调或者调用了一个写回调,这个读写回调函数是不是有可能有参数?关于这个参数应该很好理解,在执行一段代码的过程中,很可能需要动态数据。这个动态数据怎么来的呢?就是通过参数传递进来的。

写事件检测是如何设置的?写事件检测是通过设置channel的写属性来实现的。具体来说,可以通过调用一个函数来设置写事件被检测或者是设置它不被检测。这个函数的第一个参数是一个channel类型的结构体实例,通过这个实例可以指定要设置写事件的channel。第二个参数是一个布尔值,如果flag等于true,则给channel设置检测写事件;如果flag等于false,则将channel的写事件设置为不检测。在C语言中,可以通过位运算实现对标志位的设置和判断

如何判断文件描述符的读写属性是否存在?要判断文件描述符的读写属性是否存在,可以通过读取标志位来判断。具体的处理思路可以学习C语言里边的通用做法。由于一个整形数有32位,其中可以定义读写事件的值。例如,读事件可以定义为100,写事件可以定义为10。通过读取这些标志位,可以判断读写属性是否存在。如果某个标志位为1,则表示对应的属性存在;如果标志位为0,则表示对应的属性不存在。因此,通过判断标志位,可以确定文件描述符的读写属性是否存在。

channel->events |= WriteEvent;的作用是将channel对应的文件描述符的写事件设置为被检测。通过将WriteEvent与channel->events进行按位或操作,可以设置写事件的检测标志位为1,表示需要检测写事件。

channel->events = channel->events & (~WriteEvent);这行代码的作用是将channel的写事件检测标志位清零。通过与操作符&和按位取反操作符~的组合使用,将channel->events中的写事件位清零,从而实现不检测写事件的效果。

channel->events & WriteEvent;的作用是判断channel的写事件是否被检测。这个操作会检查channelevents整型数中写事件对应的标志位是否为1,如果是1则表示写事件被检测,否则表示写事件未被检测。

typedef int(*handleFunc)(void* arg);这是一个C语言的代码片段,用于定义一个函数指针类型。我们可以逐步解释这段代码:

1. typedef: 这是一个关键字,用于为已有的数据类型定义一个新的名称或别名。

2. int(*handleFunc)(void* arg): 这是一个函数指针类型的定义。 

  • handleFunc 是新定义的类型名称。    
  • (*handleFunc) 表示这是一个函数指针。   
  • void* arg 表示这个函数的参数是一个void*类型的指针。   
  • int 表示这个函数的返回值是int类型。

所以,这个代码定义了一个名为handleFunc的函数指针类型,该函数接受一个 void* 类型的参数并返回一个 int。在实际使用中,你可以使用这个类型来声明一个变量,该变量可以存储指向这种函数的指针:

handleFunc func = someFunction; // 假设someFunction是符合该定义的函数

然后,你可以通过这个指针来调用相应的函数:

int result = func(somePointer); // somePointer 是 void* 类型

在C语言中,函数指针是一种指针,它指向函数的地址。函数指针类型就是指这种指针的类型,它描述了指针所指向的函数具有什么参数和返回值。使用函数指针类型,可以让我们将函数作为参数传递给其他函数,或者将函数存储在数组中,实现更加灵活和动态的编程。以下是一个简单的示例,演示如何使用函数指针类型:

#include <stdio.h>
// 定义一个函数指针类型
typedef int (*func_ptr_type)(int);
// 定义一个函数,接受一个整数参数并返回其平方
int square(int x) {return x * x;
}
// 定义一个函数,接受一个函数指针作为参数,并调用该函数
int apply_func(func_ptr_type func, int x) {return func(x);
}
int main() {// 声明一个函数指针变量,并指向 square 函数func_ptr_type ptr = square;// 通过函数指针调用 square 函数printf("%d\n", ptr(5)); // 输出:25// 将 square 函数作为参数传递给 apply_func 函数printf("%d\n", apply_func(square, 6)); // 输出:36return 0;
}

在上面的示例中,我们首先定义了一个名为func_ptr_type的函数指针类型,它接受一个整数参数并返回一个整数。然后,我们定义了两个函数:squareapply_funcsquare函数接受一个整数参数并返回其平方,而apply_func函数接受一个函数指针作为参数,并调用该函数。在main函数中,我们声明了一个func_ptr_type类型的变量ptr,并将其指向square函数。然后,我们通过ptr调用square函数,并将结果打印出来。最后,我们将square函数作为参数传递给apply_func函数,并打印出结果。 

(1)IO多路模型与事件检测

  1. IO多路模型:允许同时对多个文件描述符进行操作,当某个文件描述符对应的事件被触发时,系统会调用相应的事件处理函数。
  2. 事件检测:基于IO多路模型,当文件描述符对应的事件被触发时,系统会检测到该事件。

(2)Channel结构体与回调函数

  1. 回调函数定义:在channel结构体中,回调函数的返回值可以指定为其他类型,如int、void等。
  2. 回调函数参数:回调函数的参数、返回值类型和名称都可以根据实际需求进行定义。

(3)操作函数定义

  1. 初始化函数:用于初始化channel的实例。(channelInit)
  2. 修改写事件检测函数:通过此函数可以设置写事件被检测不被检测。(writeEventEnable)
  3. 事件判断函数:判断是否正在检测文件描述符对应的写事件。(isWriteEventsEnable)

(4)注意事项

  1. 在定义操作函数时需要指定返回值为struct channel类型,以满足返回channel实例的需求。
  2. 在实现修改文件描述符写事件函数时,需要判断第二个参数是否等于truefalse,以决定是否追加写属性取消检测

(5)相关头文件

  1. 为了使用布尔类型和channel结构体,需要包含相关的头文件。
  • Channel.h
#pragma once
#include <stdbool.h>
// 定义函数指针
typedef int(*handleFunc)(void* arg);// 定义文件描述符的读写事件
enum FDEvent {TimeOut = 0x01;ReadEvent = 0x02;WriteEvent = 0x04;
};struct Channel {// 文件描述符int fd;// 事件int events;// 回调函数handleFunc readCallback;// 读回调handleFunc writeCallback;// 写回调// 回调函数的参数void* arg;
};// 初始化一个Channel 
struct Channel* channelInit(int fd, int events, handleFunc readFunc, handleFunc writeFunc, void* arg);// 修改fd的写事件(检测 or 不检测)
void writeEventEnable(struct Channel* channel, bool flag);// 判断是否需要检测文件描述符的写事件
bool isWriteEventsEnable(struct Channel* channel);
  • Channel.c
#include "Channel.h"struct Channel* channelInit(int fd, int events, handleFunc readFunc, handleFunc writeFunc, void* arg) {struct Channel* channel = (struct Channel*)malloc(sizeof(struct Channel));channel->fd = fd;channel->events = events;channel->readFunc = readFunc;channel->writeFunc = writeFunc;channel->arg = arg;return channel;
}void writeEventEnable(struct Channel* channel, bool flag) {if(flag) {channel->events |= WriteEvent;}else{channel->events = channel->events & (~WriteEvent);}
}bool isWriteEventsEnable(struct Channel* channel) {return channel->events & WriteEvent;
}

总结:Channel模块的封装主要包括文件描述符、事件检测和回调函数。在服务器端,Channel主要用于封装文件描述符,用于监听和通信。事件检测是基于IO多路模型的,当文件描述符对应的事件被触发时,会调用相应的事件处理函数。在Channel结构中,需要指定读事件和写事件对应的回调函数。此外,还有一个data参数用于传递动态数据。在判断事件方面,对于通信的文件描述符,不能去掉写事件的检测,否则无法接收客户端数据。可以通过添加或删除文件描述符对应的写事件来实现对客户端新链接的检测。判断写事件检测的函数isWriteEventsEnable用于判断参数Channel里边儿对应的事件里是否有写事件的检测。在实现Channel模块的封装时,需要定义三个API函数:初始化Channel的函数、修改文件描述符的写事件函数和判断写事件检测的函数。这些函数的实现相对简单,主要是对结构体中的各个数据成员进行初始化或修改。实现ChannelWriteEventsEnable,首先需要判断第二个参数flag是否等于true。如果等于true则追加写属性;如果等于FALSE则去掉写属性。具体的实现方式可以学习C语言里边的通用做法,通过判断标志位来确定是否有写事件。例如,可以定义读事件为100,写事件为10,通过读取标志位来判断是否有写事件。如果标志位为1,则表示有写事件;如果标志位为0,则表示没有写事件。

综上所述,Channel模块的封装与事件判断是服务器端编程中的重要概念,通过合理使用事件检测和回调函数可以提高服务器的性能和可靠性。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/223334.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

3D小球跑酷

目录 一、前言 二、开发环境 三、场景搭建 1. 创建项目 2. 创建场景内物体 2.1 创建跑道 2.2 创建玩家 2.3 创建障碍物 2.4 改变跑道和障碍物的颜色 2.4.1 创建材质 2.4.2 给跑道和障碍物更换材质 四、功能脚本实现 1. 创建玩家脚本 2. 相机跟随 3. 胜负的判定 3…

服务器IBM x3650 m2 管理口访问故障处理

服务器的内存告警后&#xff0c;连接管理口查看信息&#xff0c;管理口状态灯显示正常&#xff0c;但是无法ping通和访问。 处理过程如下&#xff1a; 1、在centos 6.6中安装ipmitool&#xff0c;替换为阿里云的yum源&#xff0c;然后安装。 # wget -O /etc/yum.repos.d/Cen…

SpringMVC:执行原理详解、配置文件和注解开发实现 SpringMVC

文章目录 SpringMVC - 01一、概述二、SpringMVC 执行原理三、使用配置文件实现 SpringMVC四、使用注解开发实现 SpringMVC1. 步骤2. 实现 五、总结注意&#xff1a; SpringMVC - 01 一、概述 SpringMVC 官方文档&#xff1a;点此进入 有关 MVC 架构模式的内容见之前的笔记&a…

抖店定好品类赛道之后,怎么选品和快速出单?相关教程分享如下!

我是王路飞。 之前分享抖店流程相关内容时&#xff0c;我说过&#xff0c;类目选择大于一切&#xff0c;且要优于、重于选品。 至于定类目的标准&#xff0c;我之前也给你们说过&#xff0c;不要以自己个人喜好为标准去判断市场&#xff0c;也不要凭借自己以往认知和经验确定…

JBoss 5.x/6.x 反序列化 CVE-2017-12149 已亲自复现

JBoss 5.x/6.x 反序列化 CVE-2017-12149 已亲自复现 漏洞名称漏洞描述影响版本 漏洞复现环境搭建漏洞利用 修复建议总结 漏洞名称 漏洞描述 2017年8月30日&#xff0c;厂商Redhat发布了一个JBOSSAS 5.x 的反序列化远程代码执行漏洞通告。该漏洞位于JBoss的HttpInvoker组件中的…

设计模式-解释器模式

设计模式专栏 模式介绍模式特点应用场景解释器模式和模板模式有什么区别和联系代码示例Java实现解释器模式Python实现解释器模式 解释器模式在spring中的应用 模式介绍 解释器模式是一种行为型设计模式&#xff0c;它定义了一门语言的文法表示&#xff0c;并建立了一个解释器来…

如何快速实现地源热泵远程监控

地源热泵远程监控解决方案 一、项目背景 山东省潍坊市盛世花园小区地源热泵项目是一个先进的供暖与制冷系统&#xff0c;旨在为整个小区提供高效且节能的温控服务。该系统主要由地下管道网络、地源热泵单元以及室内分配系统组成。 针对现有的地源热泵系统的管理和监控问题&a…

计算机服务器中了halo勒索病毒如何解密,halo勒索病毒解密数据恢复

计算机技术的不断发展&#xff0c;为企业的生产运营提供了极大便利&#xff0c;但也为网络安全埋下隐患&#xff0c;网络上的勒索病毒种类也在不断增加&#xff0c;给企业的数据安全带来了严重威胁。近日&#xff0c;云天数据恢复中心接到许多企业的求助&#xff0c;企业的计算…

Deployment Controller详解(上)

上一篇在《Kubectl 部署无状态应用》中介绍了如何使用 Deployment 部署五个 hello world 实例时&#xff0c;我们并没有详细探讨 Deployment Controller 的各项功能。因此&#xff0c;本文将深入介绍 Deployment Controller 的作用以及它能够完成的任务。 本文来自官方文档梳理…

UE4移动端最小包优化实践

移动端对于包大小有着严苛的要求,然而UE哪怕是一个空工程打出来也有90+M,本文以一个复杂的工程为例,探索怎么把包大小降低到最小,我这里从200M+减少到了87M 一、工程简介 工程包含代码、插件、资源、iOS原生库工程。 二、按官方文档进行基础优化 官方文档 1、勾选U…

铭飞CMS cms/content/list接口存在SQL注入 附POC

@[toc] 铭飞CMS cms/content/list接口存在SQL注入 附POC 免责声明:请勿利用文章内的相关技术从事非法测试,由于传播、利用此文所提供的信息或者工具而造成的任何直接或者间接的后果及损失,均由使用者本人负责,所产生的一切不良后果与文章作者无关。该文章仅供学习用途使用…

Mac OS 13+,Apple Silicon,删除OBS虚拟摄像头(virtual camera),

原文链接: https://www.reddit.com/r/MacOS/comments/142cv OBS为了捕获摄像头视频,将虚拟摄像头插件内置为系统插件了.如下 直接删除没有权限的,要删除他,在mac os 13以后,需要关闭先关闭苹果系统的完整性保护(SIP) Apple 芯片(M1,....)的恢复模式分为两种,回退恢复模式,和…

研究论文 20231123-Genome Biology:零样本学习预测细基因表达顺式调控模式

Li, Yongge, et al. "CREaTor: zero-shot cis-regulatory pattern modeling with attention mechanisms." Genome Biology 24.1 (2023): 266. 2023年11月23日见刊 微信分享&#xff1a;Genome Biology | CREaTor: 零样本学习预测细胞类型特异的基因表达顺式调控模式…

STM32的以太网外设+PHY(LAN8720)使用详解(7):以太网数据接收及发送测试

0 工具准备 1.野火 stm32f407霸天虎开发板 2.LAN8720数据手册 3.STM32F4xx中文参考手册 4.Wireshark1 以太网数据接收测试 1.1 以太网数据接收测试&#xff08;轮询&#xff09; 我们在主循环内轮询RX DMA描述符标志位查看是否接收到了数据&#xff0c;如果接收到了则将数据…

【Flutter】黑白图片

一、将图片处理成黑白图片 //第一种方法CachedNetworkImage(imageUrl: imageUrl,width: 80,height: 80,fit: BoxFit.cover,color: Colors.black,//目标颜色colorBlendMode: BlendMode.color,//颜色混合模式)//第二种方法ShaderMask(shaderCallback: (Rect bounds) {return Lin…

【Amazon 实验②】Amazon WAF功能增强之使用Cloudfront、Lambda@Edge阻挡攻击

文章目录 一、方案介绍二、架构图三、部署方案1. 进入Cloud9 编辑器&#xff0c;新打开一个teminal2. 克隆代码3. 解绑上一个实验中Cloudfront 分配绑定的防火墙4. 使用CDK部署方案5. CDK部署完成6. 关联LambdaEdge函数 四、方案效果 一、方案介绍 采用 LambdaEdge DynamoDB 架…

强化学习(五)-Deterministic Policy Gradient (DPG) 算法及公式推导

针对连续动作空间&#xff0c;策略函数没法预测出每个动作选择的概率。因此使用确定性策略梯度方法。 0 概览 1 actor输出确定动作2 模型目标&#xff1a; actor目标&#xff1a;使critic值最大 critic目标&#xff1a; 使TD error最大3 改进&#xff1a; 使用两个target 网络…

【IntelliJ IDEA】打开项目Git突然无法识别解决方案

这个问题也是我今天突然偶尔遇到的&#xff0c;当时没在意&#xff0c;项目打开之后又关闭&#xff0c;后来很久才又打开&#xff0c;发现项目明明有git版本控制的&#xff0c;咋突然开发工具右下角没有标识了&#xff0c;然后检查了一下git配置还报错了。 其实从图上我们可以看…

三相异步电机动态数学模型推导及矢量控制仿真

文章目录 **原文链接&#xff0c;点击跳转**三相异步电机动态数学模型及矢量控制仿真1、异步电机三相方程2、坐标变换3、磁链3/2变换推导4、两相静止坐标系下的方程5、两相旋转坐标系下的方程6、以 ω-is-Ψr 为状态变量的状态方程7、矢量控制及 matlab 仿真 原文链接&#xff…

SpringBoot+SSM项目实战 苍穹外卖(7)(Spring Cache)

继续上一节的内容&#xff0c;本节实现缓存菜品、缓存套餐、添加购物车、查看购物车和清空购物车功能。 目录 缓存菜品缓存套餐(基于Spring Cache)EnableCaching、Cacheable、CachePut和CacheEvictSpring Cache实现缓存套餐 添加购物车查看购物车清空购物车 缓存菜品 用户端小…