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

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

基于多反应堆的高并发服务器【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模块的封装与事件判断是服务器端编程中的重要概念,通过合理使用事件检测和回调函数可以提高服务器的性能和可靠性。

(?)Dispatcher模块的实现思路

未完待续~

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

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

相关文章

SuperMap Hi-Fi 3D SDK for Unity基础开发教程

作者&#xff1a;kele 一、背景 众所周知&#xff0c;游戏引擎&#xff08;Unity&#xff09;功能强大&#xff0c;可以做出很多炫酷的游戏和动画效果&#xff0c;这部分功能的实现往往不仅仅是靠可视化界面就能够实现的&#xff0c;还需要代码开发。SuperMap Hi-Fi SDKS for …

清风数学建模笔记-插值算法

内容&#xff1a;插值算法 概念&#xff1a; 二.种类 1.牛顿插值法&#xff0c;拉格朗日插值法&#xff0c;两者容易出现龙格现象 2.分段线性插值&#xff1a;与上面两种相比要更好一些,原理是两线之间构成一条直线&#xff0c;在这条直线上插值&#xff0c;除此之外还有分段…

读算法霸权笔记01_数学杀伤性武器

1. 数学应用助推数据经济&#xff0c;但这些应用的建立是基于不可靠的人类所做的选择 1.1. 房地产危机&#xff0c;大型金融机构倒闭&#xff0c;失业率上升&#xff0c;在幕后运用着神奇公式的数学家们成为这些灾难的帮凶 1.2. 数学逐渐不再关注全球金融市场动态&#xff0c…

用Python处理PDF:拆分与合并PDF文档

PDF文档在信息共享和数据保存方面被广泛使用&#xff0c;处理PDF文档也成为常见需求。其中&#xff0c;合并和拆分PDF文档能够帮助我们更有效地管理PDF文档&#xff0c;使文档内容分布更合理。通过合并&#xff0c;可以将相关文档整合成一个文件&#xff0c;以便更好地组织和提…

LeetCode394.字符串解码

这道题有点像我之前写过的一道题有效的括号&#xff08;不只是栈&#xff09;-CSDN博客 但是比那道题要难&#xff0c;但用的方法是一样的&#xff0c;就是用栈的先进后出进行括号匹配&#xff0c;所以有写过之前那道题&#xff0c;这道题按照这个思路走我就写出了如下屎山代码…

使用宝塔面板部署前端项目到服务器

目录 文章目录 前言 一、第一步&#xff1a;创建文件夹 二、第二步&#xff1a;部署前端项目 三、第三步&#xff1a;打开防火墙 文章目录 前言第一步&#xff1a;创建文件夹第二步&#xff1a;部署前端项目第三步&#xff1a;打开防火墙总结 前言 在此之前&#xff0c;我…

智能优化算法应用:基于鹰栖息算法3D无线传感器网络(WSN)覆盖优化 - 附代码

智能优化算法应用&#xff1a;基于鹰栖息算法3D无线传感器网络(WSN)覆盖优化 - 附代码 文章目录 智能优化算法应用&#xff1a;基于鹰栖息算法3D无线传感器网络(WSN)覆盖优化 - 附代码1.无线传感网络节点模型2.覆盖数学模型及分析3.鹰栖息算法4.实验参数设定5.算法结果6.参考文…

DHorse v1.5.0 发布,基于 k8s 的发布平台

版本说明 新增特性 支持同一机器部署多个DHorse服务&#xff1b;支持Next、.NET应用部署&#xff1b;优化Node、Nuxt应用构建和部署的性能&#xff1b;默认使用fabric8客户端与k8s集群交互&#xff0c;可以通过指定参数-Dkubernetes-clientofficial切回到k8s官方客户端&#…

亚马逊推出 Graviton4:具有 536.7 GBps 内存带宽的 96 核 ARM CPU

如今&#xff0c;许多云服务提供商都设计自己的芯片&#xff0c;但亚马逊网络服务 (AWS) 开始领先于竞争对手&#xff0c;目前其子公司 Annapurna Labs 开发的处理器可以与 AMD 和英特尔的处理器竞争。本周&#xff0c;AWS 推出了 Graviton4 SoC&#xff0c;这是一款基于 ARM 的…

【Filament】绘制圆形

1 前言 Filament环境搭建中介绍了 Filament 的 Windows 和 Android 环境搭&#xff0c;绘制三角形中介绍了绘制纯色和彩色三角形&#xff0c;绘制矩形中介绍了绘制纯色和彩色矩形&#xff0c;本文将使用 Filament 绘制圆形。 2 绘制圆形 本文项目结构如下&#xff0c;完整代码…

RetinaNet:Focal Loss for Dense Object Detection(CVPR2018)

文章目录 Abstract北京发现问题并给出方法成果 IntroductionRelated WorkRobust 评估 Focal LossBalanced Cross EntropyFocal Loss DefinitionClass Imbalance and Model InitializationClass Imbalance and Two-stage Detectors RetinaNet DetectorExperimentsConclusion hh …

基于SpringBoot的桃花峪滑雪场租赁系统 JAVA简易版

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 游客服务2.2 雪场管理 三、数据库设计3.1 教练表3.2 教练聘请表3.3 押金规则表3.4 器材表3.5 滑雪场表3.7 售票表3.8 器材损坏表 四、系统展示五、核心代码5.1 查询教练5.2 教练聘请5.3 查询滑雪场5.4 滑雪场预定5.5 新…

WebAssembly 的魅力:高效、安全、跨平台(上)

&#x1f90d; 前端开发工程师&#xff08;主业&#xff09;、技术博主&#xff08;副业&#xff09;、已过CET6 &#x1f368; 阿珊和她的猫_CSDN个人主页 &#x1f560; 牛客高级专题作者、在牛客打造高质量专栏《前端面试必备》 &#x1f35a; 蓝桥云课签约作者、已在蓝桥云…

JUC并发编程 08——原子操作类

目录 一.原子更新基本类型类 实现原理 二.原子更新数组 三.原子更新引用类型 四.原子更新字段类 Java从JDK1.5开始提供了J.U.C下的atomic包&#xff0c;atomic包提供了一系列的操作简单&#xff0c;性能高效&#xff0c;并能保证线程安全的类去更新基本类型变量&#xff0…

WebAssembly 的魅力:高效、安全、跨平台(下)

&#x1f90d; 前端开发工程师&#xff08;主业&#xff09;、技术博主&#xff08;副业&#xff09;、已过CET6 &#x1f368; 阿珊和她的猫_CSDN个人主页 &#x1f560; 牛客高级专题作者、在牛客打造高质量专栏《前端面试必备》 &#x1f35a; 蓝桥云课签约作者、已在蓝桥云…

Unity中的导航系统

NavMesh(导航网格)是3D游戏世界中用于实现动态物体自动寻路的一种技术&#xff0c;将游戏中复杂的结构组织关系简化为带有一定信息的网格&#xff0c;在这些网格的基础上通过一系列的计算来实现自动寻路。导航时&#xff0c;只需要给导航物体挂载导航组建&#xff0c;导航物体便…

【Grafana】Grafana匿名访问以及与LDAP连接

上一篇文章利用Docker快速部署了Grafana用来展示Zabbix得监控数据&#xff0c;但还需要给用户去创建账号允许他们登录后才能看展示得数据&#xff0c;那有什么办法让非管理员更方便得去访问Grafana呢&#xff1f;下面介绍两个比较方便实现的&#xff1a; 在开始设置前&#xff…

显示器屏幕oled的性能、使用场景、维护

OLED显示器屏幕具有许多独特的性能和使用场景&#xff0c;以下是关于OLED显示器屏幕的性能、使用场景和维护的详细介绍&#xff1a; 一、性能 色彩鲜艳&#xff1a;OLED显示器屏幕能够呈现出更加鲜艳的色彩&#xff0c;色彩饱和度高&#xff0c;色彩还原性好&#xff0c;可以给…

SpringBoot+JaywayJsonPath实现Json数据的DSL(按照指定节点表达式解析json获取指定数据)

场景 若依前后端分离版手把手教你本地搭建环境并运行项目&#xff1a; 若依前后端分离版手把手教你本地搭建环境并运行项目_前后端分离项目本地运行-CSDN博客 在上面搭建SpringBoot项目的基础上&#xff0c;并且在项目中引入fastjson、hutool等所需依赖后。 Jayway JsonPat…

【【C++11特性篇】【强制/禁止 】生成默认函数的关键字default&delete(代码演示)

前言 大家好吖&#xff0c;欢迎来到 YY 滴C系列 &#xff0c;热烈欢迎&#xff01; 本章主要内容面向接触过C的老铁 主要内容含&#xff1a; 欢迎订阅 YY滴C专栏&#xff01;更多干货持续更新&#xff01;以下是传送门&#xff01; YY的《C》专栏YY的《C11》专栏YY的《Linux》…