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, ®, 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(¶ms, 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, ¶ms);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下的网络通信模型 在网络开发的过程中,需要处理好几个问题。首先是通信的内核支持问题;其次是通信的模型问题;最后是框架问题。这些问题在闭源的OS如Windows上,基本上不算什么大问题(因为只能用人家的API&am…...
FFmpeg 命令:从入门到精通 | FFmpeg 基本介绍
FFmpeg 命令:从入门到精通 | FFmpeg 基本介绍 FFmpeg 命令:从入门到精通 | FFmpeg 基本介绍FFmpeg 简介FFmpeg 基础知识复用与解复用编解码器码率和帧率 资料 FFmpeg 命令:从入门到精通 | FFmpeg 基本介绍 本系列文章要解决的问题࿱…...
数组篇 第一题:删除排序数组中的重复项
更多精彩内容请关注微信公众号:听潮庭。 第一题:删除排序数组中的重复项 给你一个 非严格递增排列 的数组 nums ,请你 原地 删除重复出现的元素,使每个元素 只出现一次 ,返回删除后数组的新长度。元素的 相对顺序 应…...
堆的初步认识
在学习本节文章前要先了解:大顶堆与小顶堆: (优先级队列_加瓦不加班的博客-CSDN博客) 堆实现 计算机科学中,堆是一种基于树的数据结构,通常用完全二叉树实现。 什么叫完全二叉树? 答&#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容器适配器)
前言: 适配器也称配接器(adapters)在STL组件的灵活组合运用功能上,扮演着轴承、转换器的角色。 《Design Patterns》对adapter的定义如下:将一个class的接口转换为另一个class的接口,使原本因接口不兼容而…...
软考 系统架构设计师系列知识点之软件架构风格(7)
接前一篇文章:软考 系统架构设计师系列知识点之软件架构风格(6) 这个十一注定是一个不能放松、保持“紧”的十一。由于报名了全国计算机技术与软件专业技术资格(水平)考试,11月4号就要考试,因此…...
【Vue3】自定义指令
除了 Vue 内置的一系列指令 (比如 v-model 或 v-show) 之外,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为硬盘挂载目录,根据实际情况修改 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) ,在规范注释格式的基础上为函数文件或类文件自动生成函数签名&#…...
2019强网杯随便注bugktu sql注入
一.2019强网杯随便注入 过滤了一些函数,联合查询,报错,布尔,时间等都不能用了,尝试堆叠注入 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没有找,mybatis启用了默认的DefaultVFS,然后由于DefaultVFS的内部逻辑,从而导致了reader entry乱码。 去掉mybatis配置文件中关于别名的配置,然后在mapper.xml文件中使用完整的类名。 待删除的…...
【GIT版本控制】--什么是版本控制
一、为什么需要版本控制? 版本控制是在软件开发和许多其他领域中非常重要的工具,因为它解决了许多与协作、追踪更改和管理项目相关的问题。以下是一些主要原因,解释了为什么需要版本控制: 追踪更改历史: 版本控制系统允许您准确…...
ChatGPT付费创作系统V2.3.4独立版 +WEB端+ H5端 + 小程序最新前端
人类小徐提供的GPT付费体验系统最新版系统是一款基于ThinkPHP框架开发的AI问答小程序,是基于国外很火的ChatGPT进行开发的Ai智能问答小程序。当前全民热议ChatGPT,流量超级大,引流不要太简单!一键下单即可拥有自己的GPT࿰…...
GEE16: 区域日均降水量计算
Precipitation 1. 区域日均降水量计算2. 降水时间序列3. 降水数据年度时间序列对比分析 1. 区域日均降水量计算 今天分析一个计算区域日均降水量的方法: 数据信息: Climate Hazards Group InfraRed Precipitation with Station data (CHIRPS) is a…...
打开MySQL数据库
在命令行里输入mysql --version就可以查看: mysql -uroot -p之前设置的密码(不用输入)就可登录成功:...
玩转ChatGPT:DALL·E 3生成图像
一、写在前面 好久不更新咯,因为没有什么有意思的东西分享的。 今天更新,是因为GPT整合了自家的图像生成工具,名字叫作DALLE 3。 DALLE 3是OpenAI推出的一种生成图像的模型,它基于GPT-3架构进行训练,但是它的主要目…...
使用docker在3台服务器上搭建基于redis 6.x的一主两从三台均是哨兵模式
一、环境及版本说明 如果服务器已经安装了docker,则忽略此步骤,如果没有安装,则可以按照一下方式安装: 1. 在线安装(有互联网环境): 请看我这篇文章 传送阵>> 点我查看 2. 离线安装(内网环境):请看我这篇文章 传送阵>> 点我查看 说明:假设每台服务器已…...
Chapter03-Authentication vulnerabilities
文章目录 1. 身份验证简介1.1 What is authentication1.2 difference between authentication and authorization1.3 身份验证机制失效的原因1.4 身份验证机制失效的影响 2. 基于登录功能的漏洞2.1 密码爆破2.2 用户名枚举2.3 有缺陷的暴力破解防护2.3.1 如果用户登录尝试失败次…...
【力扣数据库知识手册笔记】索引
索引 索引的优缺点 优点1. 通过创建唯一性索引,可以保证数据库表中每一行数据的唯一性。2. 可以加快数据的检索速度(创建索引的主要原因)。3. 可以加速表和表之间的连接,实现数据的参考完整性。4. 可以在查询过程中,…...
练习(含atoi的模拟实现,自定义类型等练习)
一、结构体大小的计算及位段 (结构体大小计算及位段 详解请看:自定义类型:结构体进阶-CSDN博客) 1.在32位系统环境,编译选项为4字节对齐,那么sizeof(A)和sizeof(B)是多少? #pragma pack(4)st…...
智能仓储的未来:自动化、AI与数据分析如何重塑物流中心
当仓库学会“思考”,物流的终极形态正在诞生 想象这样的场景: 凌晨3点,某物流中心灯火通明却空无一人。AGV机器人集群根据实时订单动态规划路径;AI视觉系统在0.1秒内扫描包裹信息;数字孪生平台正模拟次日峰值流量压力…...
GC1808高性能24位立体声音频ADC芯片解析
1. 芯片概述 GC1808是一款24位立体声音频模数转换器(ADC),支持8kHz~96kHz采样率,集成Δ-Σ调制器、数字抗混叠滤波器和高通滤波器,适用于高保真音频采集场景。 2. 核心特性 高精度:24位分辨率,…...
重启Eureka集群中的节点,对已经注册的服务有什么影响
先看答案,如果正确地操作,重启Eureka集群中的节点,对已经注册的服务影响非常小,甚至可以做到无感知。 但如果操作不当,可能会引发短暂的服务发现问题。 下面我们从Eureka的核心工作原理来详细分析这个问题。 Eureka的…...
云原生玩法三问:构建自定义开发环境
云原生玩法三问:构建自定义开发环境 引言 临时运维一个古董项目,无文档,无环境,无交接人,俗称三无。 运行设备的环境老,本地环境版本高,ssh不过去。正好最近对 腾讯出品的云原生 cnb 感兴趣&…...
基于Springboot+Vue的办公管理系统
角色: 管理员、员工 技术: 后端: SpringBoot, Vue2, MySQL, Mybatis-Plus 前端: Vue2, Element-UI, Axios, Echarts, Vue-Router 核心功能: 该办公管理系统是一个综合性的企业内部管理平台,旨在提升企业运营效率和员工管理水…...
掌握 HTTP 请求:理解 cURL GET 语法
cURL 是一个强大的命令行工具,用于发送 HTTP 请求和与 Web 服务器交互。在 Web 开发和测试中,cURL 经常用于发送 GET 请求来获取服务器资源。本文将详细介绍 cURL GET 请求的语法和使用方法。 一、cURL 基本概念 cURL 是 "Client URL" 的缩写…...
