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

Linux新的IO模型io_uring

一、Linux下的网络通信模型

在网络开发的过程中,需要处理好几个问题。首先是通信的内核支持问题;其次是通信的模型问题;最后是框架问题。这些问题在闭源的OS如Windows上,基本上不算什么大问题(因为只能用人家的API)。但在开源的OS上,典型的就是Linux上就是一个重要的问题。
在开源的系统上,如果内核原生支持一种IO通信,那么,效率一般来说会比不支持要高很多。网络通信有一个问题,它有点和算法在本质上有些相通,即选用哪种模型会导致它的通信效率差距非常大。举一个不恰当的例子,用一个只有两个槽的插座和十个槽的插座,能同时支持的用电接口个数完全不同。而在前面两个相同,而应用上,由于不同人对整个应用的理解和设计架构不同,导致其对应用支持的效率又有不同。同时,可能一种网络通信模型无法满足一些应用场景,所以框架一般会封装多种网络通信模型,这也是网络通信框架存在的意义。
但是有的时候,模型和框架往往混在一起,所以同一种技术在网络和书籍上的叫法均有不同,大家自己明白怎么回事儿即可,不要在这些细节上纠结。
看过Redis源的都清楚,在Redis中,对类linux的网络通信模型支持的有select,poll,epoll,kqueue。当然,可能不同的版本有所不同,这些细节就不再讨论。
一般来说,网络通信属于IO通信,而在IO通信中常见的几种通信模型有:
1、阻塞IO(Blocking IO)
2、非阻塞IO(NonBlocking IO)
3、IO多路复用(IO Multiplexing)
4、信号驱动(Signal Driven IO)
5、异步IO(Asynchonous IO)
这几种模型在《Unix网络编程》中都有总结。需要说明的是,这是从模型角度来抽象出来的,所以在其实现的过程中,是结合了内核支持和模型特点及应用支持几方面的来说明的。以前一般认为,在Linux中,对异步支持不如Windows好。
但开源技术有一个好处,缺啥补啥。所以在2019年的Linux内核5.1版本中,出现了一种新的IO框架,也就是io_uring。它是由block IO维护者Jens Axboe开发的一个用来支持异步IO通信的框架。

二、IO框架uring

io_uring为什么叫IO框架?因为其一开始主要是给存储搞的。网上的测评那是和SPDK可以一较高低,如果真那样,io_uring可比SPDK要强大,因为intel毕竟是一家国际大公司再加从底层直接支持。
但是,网络也属于IO啊,所以后来也就支持了网络IO。刚刚提到了,在Linux原生支持的IO通信中,大多数都是同步的,即使有一些框架实现了异步非阻塞仍然做的不是很好(AIO),在不少的情况下,仍然会出现非异步的情况。但是io_uring的出现,可以说是一种革命性的突破,它和eBPF是内核的两个重大的技术里程碑。
一个优秀的异步IO框架,一般具有以下几个特点:
1、异步非阻塞
异步和非阻塞往往是和IO通信分不开的,而异步和非阻塞又往往出现在一起。在IO通信中,非阻塞是实现异步的一个前提。而异步非阻塞往往是一种IO通信框架是否高效的基础。
2、减少或删除中间环节
容易理解的是,同步阻塞通信因为等待的原因,对中间环节通信(事件通知、数据拷贝次数、系统调用次数等)敏感性不强。但是异步非阻塞往往意味着海量的IO操作,所以中间环节增加任何一个步骤,付出的代价往往是难以忍受的。这也是AIO被诟病的地方。
3、扩展性要强
一个IO框架,不能只适应一种IO情况。比如磁盘、网络等都可以支持。
4、应用方便快捷
这是一个好的框架必备的特点。再优秀的东西,一旦变得复杂,就会慢慢失去它的优秀性。复杂就意味着高成本和后期维护的困难。
从Linux的异步框架发展来看,在io_uring之前出现过一些异步框架,但是对上述问题的解决都不是太好,所以一直无法融入内核之中,直到io_ring的出现。
而io_uring基本实现了上述几个特点。
首先,io_uring通过使用共享内存技术,大幅减少了内核层和应用层数据的交互拷贝,也减少了内核层和用户层的调用次数。其次数据隔离、交互减少就意味着内核和用户层可以自行其事,安排自己的线程工作状态时不用等待对方状态结果,这也是异步通信的一个重要前提。为了进一步实现异步,io_uring还实现了一个无锁环形队列,通过操作其实现用户态和内核态的IO非阻塞交互。

通过上述的说明基本可以了解io_uring的工程机制,做为一种框架,需要说明一下io_uring操作的流程:
1、应用程序提交IO操作请求到提交队列
2、SQ内核线程读取IO操作
3、SQ内核线程发起IO请求
4、SQ内核线程将结果写回完成队列
5、应用程序在完成队列读到IO结果

io_uring通过共享内存来减少交互,在io_uring中,主要有四种队列,即:
1、提交队列(Submission Queue, SQ):一整块连续的内存空间存储的环形队列,存放将执行 I/O 操作的数据(指向SQE数组的索引)
2、完成队列(Completion Queue, CQ):一整块连续的内存空间存储的环形队列,存放 I/O 操作完成后的结果
3、提交队列项数组(Submission Queue Entry,SQE) :提交队列中的每项是SQ中的数据项
4、CQE - Completion Queue Entry:完成队列项,这是储存在CQ中的数据项

io_uring 在创建时有两个选项,对应着 io_uring 处理任务的不同方式:
1、IORING_SETUP_IOPOLL,io_uring 会使用轮询的方式执行所有的操作
2、IORING_SETUP_SQPOLL,io_uring 会创建一个内核线程专门用来处理用户提交的任务
这两个选项的设置会对之后的交互产生如下影响:
1、都不设置,即以io_uring_enter 提交任务,内核线程任务无需 syscall
2、设置IORING_SETUP_IOPOLL,以io_uring_enter 提交任务和处理任务。
3、设置IORING_SETUP_SQPOLL,直接提交处理任务(不需要syscall)。内核线程自动控制处理并在一定条件下休眠,然后可使用io_uring_enter唤醒。

当用户设置了 IORING_SETUP_SQPOLL选项(SQPOLL模式)创建 io_uring 时,内核将会创建一个名为 io_uring-sq 的内核线程(即SQ线程),SQ线程从提交队列读取IO操作,并且发起IO请求。
当IO请求完成以后,SQ线程将会把IO操作的结果写回到完成队列,这样,应用程序就可以从中得到IO操作的结果。

三、应用例程

直接使用io_uring有些复杂,开发者在io_uring的基础上又封装了一个库即liburing,这样普通开发者可以直接使用它。看一下其UDP通信的例程:

#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <sys/mman.h>
#include <stdlib.h>
#include <string.h>
#include <netinet/udp.h>
#include <arpa/inet.h>#include "liburing.h"#define QD 64
#define BUF_SHIFT 12 /* 4k */
#define CQES (QD * 16)
#define BUFFERS CQES
#define CONTROLLEN 0struct sendmsg_ctx {struct msghdr msg;struct iovec iov;
};struct ctx {struct io_uring ring;struct io_uring_buf_ring *buf_ring;unsigned char *buffer_base;struct msghdr msg;int buf_shift;int af;bool verbose;struct sendmsg_ctx send[BUFFERS];size_t buf_ring_size;
};static size_t buffer_size(struct ctx *ctx)
{return 1U << ctx->buf_shift;
}static unsigned char *get_buffer(struct ctx *ctx, int idx)
{return ctx->buffer_base + (idx << ctx->buf_shift);
}static int setup_buffer_pool(struct ctx *ctx)
{int ret, i;void *mapped;struct io_uring_buf_reg reg = { .ring_addr = 0,.ring_entries = BUFFERS,.bgid = 0 };ctx->buf_ring_size = (sizeof(struct io_uring_buf) + buffer_size(ctx)) * BUFFERS;mapped = mmap(NULL, ctx->buf_ring_size, PROT_READ | PROT_WRITE,MAP_ANONYMOUS | MAP_PRIVATE, 0, 0);if (mapped == MAP_FAILED) {fprintf(stderr, "buf_ring mmap: %s\n", strerror(errno));return -1;}ctx->buf_ring = (struct io_uring_buf_ring *)mapped;io_uring_buf_ring_init(ctx->buf_ring);reg = (struct io_uring_buf_reg) {.ring_addr = (unsigned long)ctx->buf_ring,.ring_entries = BUFFERS,.bgid = 0};ctx->buffer_base = (unsigned char *)ctx->buf_ring +sizeof(struct io_uring_buf) * BUFFERS;ret = io_uring_register_buf_ring(&ctx->ring, &reg, 0);if (ret) {fprintf(stderr, "buf_ring init failed: %s\n""NB This requires a kernel version >= 6.0\n",strerror(-ret));return ret;}for (i = 0; i < BUFFERS; i++) {io_uring_buf_ring_add(ctx->buf_ring, get_buffer(ctx, i), buffer_size(ctx), i,io_uring_buf_ring_mask(BUFFERS), i);}io_uring_buf_ring_advance(ctx->buf_ring, BUFFERS);return 0;
}static int setup_context(struct ctx *ctx)
{struct io_uring_params params;int ret;memset(&params, 0, sizeof(params));params.cq_entries = QD * 8;params.flags = IORING_SETUP_SUBMIT_ALL | IORING_SETUP_COOP_TASKRUN |IORING_SETUP_CQSIZE;ret = io_uring_queue_init_params(QD, &ctx->ring, &params);if (ret < 0) {fprintf(stderr, "queue_init failed: %s\n""NB: This requires a kernel version >= 6.0\n",strerror(-ret));return ret;}ret = setup_buffer_pool(ctx);if (ret)io_uring_queue_exit(&ctx->ring);memset(&ctx->msg, 0, sizeof(ctx->msg));ctx->msg.msg_namelen = sizeof(struct sockaddr_storage);ctx->msg.msg_controllen = CONTROLLEN;return ret;
}static int setup_sock(int af, int port)
{int ret;int fd;uint16_t nport = port <= 0 ? 0 : htons(port);fd = socket(af, SOCK_DGRAM, 0);if (fd < 0) {fprintf(stderr, "sock_init: %s\n", strerror(errno));return -1;}if (af == AF_INET6) {struct sockaddr_in6 addr6 = {.sin6_family = af,.sin6_port = nport,.sin6_addr = IN6ADDR_ANY_INIT};ret = bind(fd, (struct sockaddr *) &addr6, sizeof(addr6));} else {struct sockaddr_in addr = {.sin_family = af,.sin_port = nport,.sin_addr = { INADDR_ANY }};ret = bind(fd, (struct sockaddr *) &addr, sizeof(addr));}if (ret) {fprintf(stderr, "sock_bind: %s\n", strerror(errno));close(fd);return -1;}if (port <= 0) {int port;struct sockaddr_storage s;socklen_t sz = sizeof(s);if (getsockname(fd, (struct sockaddr *)&s, &sz)) {fprintf(stderr, "getsockname failed\n");close(fd);return -1;}port = ntohs(((struct sockaddr_in *)&s)->sin_port);fprintf(stderr, "port bound to %d\n", port);}return fd;
}static void cleanup_context(struct ctx *ctx)
{munmap(ctx->buf_ring, ctx->buf_ring_size);io_uring_queue_exit(&ctx->ring);
}static bool get_sqe(struct ctx *ctx, struct io_uring_sqe **sqe)
{*sqe = io_uring_get_sqe(&ctx->ring);if (!*sqe) {io_uring_submit(&ctx->ring);*sqe = io_uring_get_sqe(&ctx->ring);}if (!*sqe) {fprintf(stderr, "cannot get sqe\n");return true;}return false;
}static int add_recv(struct ctx *ctx, int idx)
{struct io_uring_sqe *sqe;if (get_sqe(ctx, &sqe))return -1;io_uring_prep_recvmsg_multishot(sqe, idx, &ctx->msg, MSG_TRUNC);sqe->flags |= IOSQE_FIXED_FILE;sqe->flags |= IOSQE_BUFFER_SELECT;sqe->buf_group = 0;io_uring_sqe_set_data64(sqe, BUFFERS + 1);return 0;
}static void recycle_buffer(struct ctx *ctx, int idx)
{io_uring_buf_ring_add(ctx->buf_ring, get_buffer(ctx, idx), buffer_size(ctx), idx,io_uring_buf_ring_mask(BUFFERS), 0);io_uring_buf_ring_advance(ctx->buf_ring, 1);
}static int process_cqe_send(struct ctx *ctx, struct io_uring_cqe *cqe)
{int idx = cqe->user_data;if (cqe->res < 0)fprintf(stderr, "bad send %s\n", strerror(-cqe->res));recycle_buffer(ctx, idx);return 0;
}static int process_cqe_recv(struct ctx *ctx, struct io_uring_cqe *cqe,int fdidx)
{int ret, idx;struct io_uring_recvmsg_out *o;struct io_uring_sqe *sqe;if (!(cqe->flags & IORING_CQE_F_MORE)) {ret = add_recv(ctx, fdidx);if (ret)return ret;}if (cqe->res == -ENOBUFS)return 0;if (!(cqe->flags & IORING_CQE_F_BUFFER) || cqe->res < 0) {fprintf(stderr, "recv cqe bad res %d\n", cqe->res);if (cqe->res == -EFAULT || cqe->res == -EINVAL)fprintf(stderr,"NB: This requires a kernel version >= 6.0\n");return -1;}idx = cqe->flags >> 16;o = io_uring_recvmsg_validate(get_buffer(ctx, cqe->flags >> 16),cqe->res, &ctx->msg);if (!o) {fprintf(stderr, "bad recvmsg\n");return -1;}if (o->namelen > ctx->msg.msg_namelen) {fprintf(stderr, "truncated name\n");recycle_buffer(ctx, idx);return 0;}if (o->flags & MSG_TRUNC) {unsigned int r;r = io_uring_recvmsg_payload_length(o, cqe->res, &ctx->msg);fprintf(stderr, "truncated msg need %u received %u\n",o->payloadlen, r);recycle_buffer(ctx, idx);return 0;}if (ctx->verbose) {struct sockaddr_in *addr = io_uring_recvmsg_name(o);struct sockaddr_in6 *addr6 = (void *)addr;char buff[INET6_ADDRSTRLEN + 1];const char *name;void *paddr;if (ctx->af == AF_INET6)paddr = &addr6->sin6_addr;elsepaddr = &addr->sin_addr;name = inet_ntop(ctx->af, paddr, buff, sizeof(buff));if (!name)name = "<INVALID>";fprintf(stderr, "received %u bytes %d from [%s]:%d\n",io_uring_recvmsg_payload_length(o, cqe->res, &ctx->msg),o->namelen, name, (int)ntohs(addr->sin_port));}if (get_sqe(ctx, &sqe))return -1;ctx->send[idx].iov = (struct iovec) {.iov_base = io_uring_recvmsg_payload(o, &ctx->msg),.iov_len =io_uring_recvmsg_payload_length(o, cqe->res, &ctx->msg)};ctx->send[idx].msg = (struct msghdr) {.msg_namelen = o->namelen,.msg_name = io_uring_recvmsg_name(o),.msg_control = NULL,.msg_controllen = 0,.msg_iov = &ctx->send[idx].iov,.msg_iovlen = 1};io_uring_prep_sendmsg(sqe, fdidx, &ctx->send[idx].msg, 0);io_uring_sqe_set_data64(sqe, idx);sqe->flags |= IOSQE_FIXED_FILE;return 0;
}
static int process_cqe(struct ctx *ctx, struct io_uring_cqe *cqe, int fdidx)
{if (cqe->user_data < BUFFERS)return process_cqe_send(ctx, cqe);elsereturn process_cqe_recv(ctx, cqe, fdidx);
}int main(int argc, char *argv[])
{struct ctx ctx;int ret;int port = -1;int sockfd;int opt;struct io_uring_cqe *cqes[CQES];unsigned int count, i;memset(&ctx, 0, sizeof(ctx));ctx.verbose = false;ctx.af = AF_INET;ctx.buf_shift = BUF_SHIFT;while ((opt = getopt(argc, argv, "6vp:b:")) != -1) {switch (opt) {case '6':ctx.af = AF_INET6;break;case 'p':port = atoi(optarg);break;case 'b':ctx.buf_shift = atoi(optarg);break;case 'v':ctx.verbose = true;break;default:fprintf(stderr, "Usage: %s [-p port] ""[-b log2(BufferSize)] [-6] [-v]\n",argv[0]);exit(-1);}}sockfd = setup_sock(ctx.af, port);if (sockfd < 0)return 1;if (setup_context(&ctx)) {close(sockfd);return 1;}ret = io_uring_register_files(&ctx.ring, &sockfd, 1);if (ret) {fprintf(stderr, "register files: %s\n", strerror(-ret));return -1;}ret = add_recv(&ctx, 0);if (ret)return 1;while (true) {ret = io_uring_submit_and_wait(&ctx.ring, 1);if (ret == -EINTR)continue;if (ret < 0) {fprintf(stderr, "submit and wait failed %d\n", ret);break;}count = io_uring_peek_batch_cqe(&ctx.ring, &cqes[0], CQES);for (i = 0; i < count; i++) {ret = process_cqe(&ctx, cqes[i], 0);if (ret)goto cleanup;}io_uring_cq_advance(&ctx.ring, count);}cleanup:cleanup_context(&ctx);close(sockfd);return ret;
}

更多的代码可以参看GITHUB中的相关代码。

四、技术说明

通过上面的分析,大家是否有所明白。包括DPDK等一些高效的框架,一些主要的特点,基本都是通过共享内存来减少内核与应用层的交互(特别是数据的拷贝),通过无锁队列来实现高效的非阻塞的通信。这意味着内核以后的工作可能会尽量减少和上层应用的直接通信。换句话说,内核要做一个高效的管理者和协调者而不是一个大而全的工作者。
这其实给开发者的一个借鉴在于:设计和开发时,要适当抽象(抽象越高,意味着引入的中间层越多),在可能的前提下尽量减少中间环节;引入新技术,如无锁编程等;异步工作是方向。
以Linux的IO技术发展为例,可以为两大部分,即内核原生支持和非原生支持(DPDK)。而原生支持里,从同步阻塞IO开始慢慢发展到异步IO的过程,又到io_uring融入内核的过程,就是给予开发者借鉴的一个经典的例子。可能大多数开发者意识不到这一点,也有可能在国内这个过程非常迅速甚至忽略某些中间过程。但实际上,整个的方向和发展的过程的经历基本是类似的。
本文对io_uring只是一个基础的介绍说明,大家有兴趣可以深入学习并引入自己的工程中去。实践出真知。

五、总结

这里没有更细节展开谈IO模型和同步异步以及阻塞非阻塞的问题,这些基础的问题,不明白的需要去查阅相关资料。这篇文章只是一个开始,以后有机会的话,会将整个网络开发进行整体的分析说明和具体应用。
技术的进步一定是向前发展的,开发者们一定睁开眼睛,瞭望世界,不要固守一隅,夜郎自大。

相关文章:

Linux新的IO模型io_uring

一、Linux下的网络通信模型 在网络开发的过程中&#xff0c;需要处理好几个问题。首先是通信的内核支持问题&#xff1b;其次是通信的模型问题&#xff1b;最后是框架问题。这些问题在闭源的OS如Windows上&#xff0c;基本上不算什么大问题&#xff08;因为只能用人家的API&am…...

FFmpeg 命令:从入门到精通 | FFmpeg 基本介绍

FFmpeg 命令&#xff1a;从入门到精通 | FFmpeg 基本介绍 FFmpeg 命令&#xff1a;从入门到精通 | FFmpeg 基本介绍FFmpeg 简介FFmpeg 基础知识复用与解复用编解码器码率和帧率 资料 FFmpeg 命令&#xff1a;从入门到精通 | FFmpeg 基本介绍 本系列文章要解决的问题&#xff1…...

数组篇 第一题:删除排序数组中的重复项

更多精彩内容请关注微信公众号&#xff1a;听潮庭。 第一题&#xff1a;删除排序数组中的重复项 给你一个 非严格递增排列 的数组 nums &#xff0c;请你 原地 删除重复出现的元素&#xff0c;使每个元素 只出现一次 &#xff0c;返回删除后数组的新长度。元素的 相对顺序 应…...

堆的初步认识

在学习本节文章前要先了解&#xff1a;大顶堆与小顶堆&#xff1a; &#xff08;优先级队列_加瓦不加班的博客-CSDN博客&#xff09; 堆实现 计算机科学中&#xff0c;堆是一种基于树的数据结构&#xff0c;通常用完全二叉树实现。 什么叫完全二叉树&#xff1f; 答&#x…...

CycleGAN模型之Pytorch实战

一、CycleGAN基本介绍 1. CycleGAN论文:《Unpaired Image-to-Image Translation using Cycle-Consistent Adversarial Networks》 2. 原文代码:https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix 3. 网传精简代码:https://github.com/aitorzip/PyTorch-CycleGAN …...

C++(STL容器适配器)

前言&#xff1a; 适配器也称配接器&#xff08;adapters&#xff09;在STL组件的灵活组合运用功能上&#xff0c;扮演着轴承、转换器的角色。 《Design Patterns》对adapter的定义如下&#xff1a;将一个class的接口转换为另一个class的接口&#xff0c;使原本因接口不兼容而…...

软考 系统架构设计师系列知识点之软件架构风格(7)

接前一篇文章&#xff1a;软考 系统架构设计师系列知识点之软件架构风格&#xff08;6&#xff09; 这个十一注定是一个不能放松、保持“紧”的十一。由于报名了全国计算机技术与软件专业技术资格&#xff08;水平&#xff09;考试&#xff0c;11月4号就要考试&#xff0c;因此…...

【Vue3】自定义指令

除了 Vue 内置的一系列指令 (比如 v-model 或 v-show) 之外&#xff0c;Vue 还允许你注册自定义的指令 (Custom Directives)。 1. 生命周期钩子函数 一个自定义指令由一个包含类似组件生命周期钩子的对象来定义。钩子函数会接收到指令所绑定元素作为其参数。 在 <script …...

UG\NX CAM二次开发 加工模块获取 UF _ask_application_module

文章作者:代工 来源网站:NX CAM二次开发专栏 简介: UG\NX CAM二次开发 加工模块获取 UF _ask_application_module 代码: void MyClass::do_it() { // TODO: add your code here // 获取NX当前所在的模块 int module_id = 0; // UF_ask_application_module(&…...

借助GPU算力编译Android

借助GPU算力编译Android 借助GPU编译Android代码的意义在于提高编译的效率和速度。传统的CPU编译方式在处理大量代码时可能会遇到性能瓶颈,而GPU编译利用了显卡的并行计算能力,可以同时处理多个任务,加快编译过程。通过利用GPU的并行计算能力,可以将编译过程中的多个任务分…...

docker-compose一键部署mysql

1.创建安装目录 mnt为硬盘挂载目录&#xff0c;根据实际情况修改 mkdir -p /mnt/mysql cd /mnt/mysql vim docker-compose.yml2.编写docker-compose.yml version: 3.1 services:db:image: mysql:5.7 #mysql版本volumes:- ./data/db:/var/lib/mysql #数据文件- ./etc/my.cnf:/…...

MATLAB 函数签名器

文章目录 MATLAB 函数签名器注释规范模板参数类型 kind数据格式 type选项的支持 使用可执行程序封装为m函数程序输出 编译待办事项推荐阅读附录 MATLAB 函数签名器 MATLAB 函数签名器 (FUNCSIGN) &#xff0c;在规范注释格式的基础上为函数文件或类文件自动生成函数签名&#…...

2019强网杯随便注bugktu sql注入

一.2019强网杯随便注入 过滤了一些函数&#xff0c;联合查询&#xff0c;报错&#xff0c;布尔&#xff0c;时间等都不能用了&#xff0c;尝试堆叠注入 1.通过判断是单引号闭合 ?inject1-- 2.尝试堆叠查询数据库 ?inject1;show databases;-- 3.查询数据表 ?inject1;show …...

Html+Css+Js计算时间差,返回相差的天/时/分/秒(从未来的一个日期时间到当前日期时间的差)。

Html部分 <!DOCTYPE html> <html><head><meta charset"utf-8" /><title></title><link rel"stylesheet" type"text/css" href"css/index.css" /><script src"js/index.js" t…...

mybatis项目启动报错:reader entry: ���� = v

问题再现 解决方案一 由于指定的VFS没有找&#xff0c;mybatis启用了默认的DefaultVFS&#xff0c;然后由于DefaultVFS的内部逻辑&#xff0c;从而导致了reader entry乱码。 去掉mybatis配置文件中关于别名的配置&#xff0c;然后在mapper.xml文件中使用完整的类名。 待删除的…...

【GIT版本控制】--什么是版本控制

一、为什么需要版本控制&#xff1f; 版本控制是在软件开发和许多其他领域中非常重要的工具&#xff0c;因为它解决了许多与协作、追踪更改和管理项目相关的问题。以下是一些主要原因&#xff0c;解释了为什么需要版本控制&#xff1a; 追踪更改历史: 版本控制系统允许您准确…...

ChatGPT付费创作系统V2.3.4独立版 +WEB端+ H5端 + 小程序最新前端

人类小徐提供的GPT付费体验系统最新版系统是一款基于ThinkPHP框架开发的AI问答小程序&#xff0c;是基于国外很火的ChatGPT进行开发的Ai智能问答小程序。当前全民热议ChatGPT&#xff0c;流量超级大&#xff0c;引流不要太简单&#xff01;一键下单即可拥有自己的GPT&#xff0…...

GEE16: 区域日均降水量计算

Precipitation 1. 区域日均降水量计算2. 降水时间序列3. 降水数据年度时间序列对比分析 1. 区域日均降水量计算 今天分析一个计算区域日均降水量的方法&#xff1a; 数据信息&#xff1a;   Climate Hazards Group InfraRed Precipitation with Station data (CHIRPS) is a…...

打开MySQL数据库

在命令行里输入mysql --version就可以查看&#xff1a; mysql -uroot -p之前设置的密码&#xff08;不用输入&#xff09;就可登录成功&#xff1a;...

玩转ChatGPT:DALL·E 3生成图像

一、写在前面 好久不更新咯&#xff0c;因为没有什么有意思的东西分享的。 今天更新&#xff0c;是因为GPT整合了自家的图像生成工具&#xff0c;名字叫作DALLE 3。 DALLE 3是OpenAI推出的一种生成图像的模型&#xff0c;它基于GPT-3架构进行训练&#xff0c;但是它的主要目…...

小程序入门笔记(一) 黑马程序员前端微信小程序开发教程

微信小程序基本介绍 小程序和普通网页有以下几点区别&#xff1a; 运行环境&#xff1a;小程序可以在手机的操作系统上直接运行&#xff0c;如微信、支付宝等&#xff1b;而普通网页需要在浏览器中打开才能运行。 开发技术&#xff1a;小程序采用前端技术进行开发&#xff0c;…...

【进程管理】初识进程

一.何为进程 教材一般会给出这样的答案: 运行起来的程序 或者 内存中的程序 这样说太抽象了&#xff0c;那我问程序和进程有什么区别呢&#xff1f;诶&#xff1f;这我知道&#xff0c;书上说&#xff0c;动态的叫进程&#xff0c;静态的叫程序。那么静态和动态又是什么意思…...

ArcGIS Maps SDK for JS:监听按钮点击事件控制图层的visible属性

文章目录 1 需求描述2 解决方案 1 需求描述 现在有这么一个需求&#xff1a;在地图中添加一些图层&#xff0c;添加图层列表按钮。打开图层列表后用户会打开某些图层使其可见&#xff0c;要求关闭图层列表时&#xff0c;隐藏某些图层&#xff08;若visibletrue&#xff09; 2…...

微信小程序-1

微信开发文档 https://developers.weixin.qq.com/miniprogram/dev/framework/ 报错在调试器的console里找 一、结构 Ctrl 放大字体 Ctrl - 缩小 设置 - - - 外观设置 - - - 可以修改喜欢的主题颜色 index.js index.json index.wxml 》 html <view class"box&qu…...

不容易解的题10.5

31.下一个排列 31. 下一个排列 - 力扣&#xff08;LeetCode&#xff09;https://leetcode.cn/problems/next-permutation/?envTypelist&envIdZCa7r67M会做就不算难题&#xff0c;如果没做过不知道思路&#xff0c;这道题将会变得很难。 这道题相当于模拟cpp的next_permu…...

后端面经学习自测(二)

文章目录 1、Http1.1和2.0的区别大概是什么&#xff1f;HTTP & HTTPS 2、HTTP&#xff0c;用户后续的操作&#xff0c;服务端如何知道属于同一个用户cookie & session & token手机验证码登录流程SSO单点登录 3、如果服务端是一个集群机器&#xff1f;4、hashmap是线…...

使用Jest测试Cesium源码

使用Jest测试Cesium源码 介绍环境Cesium安装Jest安装Jest模块包安装babel安装Jest的VSC插件 测试例子小结 介绍 在使用Cesium时&#xff0c;我们常常需要编写自己的业务代码&#xff0c;其中需要引用Cesium的源码&#xff0c;这样方便调试。此外&#xff0c;目前代码中直接使用…...

buuctf-[GXYCTF2019]禁止套娃 git泄露,无参数rce

用dirsearch扫一下&#xff0c;看到flag.php 访问一下没啥东西&#xff0c;使用githack python2 GitHack.py http://8996e81f-a75c-4180-b0ad-226d97ba61b2.node4.buuoj.cn/.git/查看index.php <?php include "flag.php"; echo "flag在哪里呢&#xff1f;…...

【逐步剖C】-第十一章-动态内存管理

一、为什么要有动态内存管理 从我们平常的学习经历来看&#xff0c;所开辟的数组一般都为固定长度大小的数组&#xff1b;但从很多现实需求来看需要我们开辟一个长度“可变”的数组&#xff0c;即这个数组的大小不能在建立数组时就指定&#xff0c;需要根据某个变量作为标准。…...

【树】树的直径和重心

目录 一.树的直径 &#xff08;1&#xff09;定义 &#xff08;2&#xff09;思路 &#xff08;3&#xff09;例题 &#xff08;4&#xff09;std(第一小问) 二.树的重心 &#xff08;1&#xff09;介绍 &#xff08;2&#xff09;求重心 &#xff08;3&#xff09;例…...

王牌网站做代理/淘词神器

近日&#xff0c;Netcraft 发布了 2021 年 10 月份的全球 Web 服务器调查报告。 据悉&#xff0c;Netcraft 是一家总部位于英国巴斯始于 1995 年的互联网服务公司&#xff0c;该公司发布的全球 Web 服务器调查报告每月更新一次&#xff0c;该报告已经成为人们了解全球网站数量…...

非诚勿扰吴铮真帮做网站的男人/武汉seo百度

注意&#xff1a;第三个测试点&#xff01;&#xff01;&#xff01; 有可能会有多余的结点&#xff0c;所以不能直接用给出的结点数量 给定一个单链表 L ​1→L​2→⋯→Ln−1→Ln&#xff0c;请编写程序将链表重新排列为 Ln→L1→L​n−1→L2→⋯。例如&#xff1a;给定L为1…...

做视频网站源码/一份完整的电商运营方案

( 注&#xff1a; HR911105A是网络插座变压器&#xff0c;而RJ45是不带网络变压器的 ) 在以太网设备中&#xff0c;通过PHY接RJ45时&#xff0c;中间都会加一个网络变压器。有的变压器中心抽头接到地。而且接电源时&#xff0c;电源值又可以不一样&#xff0c;3.3V&#xff0c;…...

做医院网站公司/百度招聘官网首页

单播&#xff1a;单播MAC地址是从源到目的的唯一地址。 广播&#xff1a;就是一个主机向所有主机发送一个数据包。 组播&#xff1a;就是把数据发送给一组主机或者发送给感兴趣的主机。&#xff08;组播的MAC地址是以&#xff1a;01-00-5E开头的&#xff0c;组播的IP地址224.0.…...

免费网站加速软件/免费隐私网站推广

各有关单位&#xff1a; 为推广我国高校数据分析实践教学&#xff0c;培养学生数据分析的应用和创新能力&#xff0c;增加校企交流合作和信息共享&#xff0c;提升我国高校的教学质量和企业的竞争能力&#xff0c;由泰迪杯数据分析技能赛组织委员会主办&#xff0c;广东泰迪智…...

如何制作网站连接数据库/重庆森林为什么叫这个名字

1.算法引入1.1定义在《数学之美》中&#xff0c;吴军教授称EM算法是上帝的算法&#xff0c;为了了解EM算法的奥妙之处。首先说说什么是EM算法&#xff0c;EM算法即最大期望算法&#xff08;Expectation-maximization algorithm&#xff09;&#xff0c;在统计中被用于寻找&…...