深入理解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意思是“反应堆”,是一种事件驱动机制。 和普通函数调用的不同之处在于:应用程序不是主动的调用某个 API 完成处理,而是恰恰相反,Reactor逆置了事件处理流程,应用程序需要提供相应的接口并…...
微信小程序开发的投票评选系统设计与实现
摘要 越来越多信息化融入到我们生活当中的同时,也在改变着我们的生活和学习方式,当然,变化最明显的除了我们普通民众之外,要数高校学生的生活方式以及校园信息化的变革。智慧是改变生活和生产的一种来源,那么智慧的体…...
【校招VIP】算法考点之堆排
考点介绍: 排序算法属于数据结构和算法的基础内容,并且也是大厂笔试中的高频考点。 堆排序是使用一棵树存储序列这个课树只保证跟节点是这棵树中的最小值,但并不保证其他节点是按顺序的。因此他的排序是每次从堆中取得堆顶,取得 n…...
关于yarn安装时报“node“ is incompatible with this module的解决办法
前提: 在用vue写一个h5页面时,当在用yarn安装时,提示如下错误: The engine “node” is incompatible with this module. Expected version "^14.18.0 || ^16.14.0 || >18. 解决办法 我是使用命令忽略错误:…...
开源利器推荐:美团动态线程池框架的接入分享及效果展示
前言 蛮早前有些过关于线程池的使用及参数的一些参考配置,有兴趣的可以翻看以前的博文,但终究无法解决线程池的动态监控和实时修改。 以前读过美团早期发布的动态线程池框架的思路相关文章,但想要独自实现不是一件容易的事。 去年,…...
Linux目录结构与文件管理 (02)(四)
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 目录 前言 一、查看文件内容 二、创建文件 三、删除文件 四、 移动文件 五、复制文件 六、编辑文件内容 总结 前言 今天是在昨天的基础上继续学习,主要…...
对1GHz脉冲多普勒雷达进行快速和慢速处理生成5个移动目标的距离多普勒图研究(Matlab代码实现)
💥💥💞💞欢迎来到本博客❤️❤️💥💥 🏆博主优势:🌞🌞🌞博客内容尽量做到思维缜密,逻辑清晰,为了方便读者。 ⛳️座右铭&a…...
uni.uploadFile上传 PHP接收不到
开始这样,后端$file $request->file(file);接收不到 数据跑到param中去了 去掉Content-Type,就能接收到了 param只剩下...
2023年高教社杯 国赛数学建模思路 - 复盘:光照强度计算的优化模型
文章目录 0 赛题思路1 问题要求2 假设约定3 符号约定4 建立模型5 模型求解6 实现代码 建模资料 0 赛题思路 (赛题出来以后第一时间在CSDN分享) https://blog.csdn.net/dc_sinor?typeblog 1 问题要求 现在已知一个教室长为15米,宽为12米&…...
Netty简易聊天室
文章目录 本文目的参考说明环境说明maven依赖日志配置单元测试 功能介绍开发步骤 本文目的 通过一个简易的聊天室案例,讲述Netty的基本使用。同时分享案例代码。项目中用到了log4j2,junit5,同时分享这些基础组件的使用。项目中用到了awt&…...
Flutter Cannot run with sound null safety, because the following dependencies
flutter sdk 版本升级到2.0或者更高的版本后,运行之前的代码会报错 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问题的概述以及如何使用遗传算法进行求解已经在上一篇文章中说明了:遗传算法解决TSP问题. 但是,作为一种演化算法,遗传算法还存在着许多问题,比如早熟的情况,很容易在算法前期就已经收敛了,大量…...
【C++】—— C++11之线程库
前言: 在本期,我将给大家介绍的是 C11 中新引进的知识,即关于线程库的相关知识。 目录 (一)线程库的介绍 1、线程库的由来 2、线程库的简单介绍 (二)线程函数参数 (三…...
前端面试:【性能优化】前端缓存、CDN、懒加载和预加载
亲爱的前端开发者,Web性能对用户体验至关重要。如果你想让你的网站更快、更具吸引力,就需要关注前端性能优化。在这篇文章中,我们将深入探讨四个关键的性能优化策略:前端缓存、CDN(内容分发网络)、懒加载和…...
民族传统文化分享系统uniapp 微信小程序
管理员、用户可通过Android系统手机打开系统,注册登录后可进行管理员后端;首页、个人中心、用户管理、知识分类管理、知识资源管理、用户分享管理、意见反馈、系统管理,用户前端;首页、知识资源、用户分享、我的等。 本系统的使用…...
netty(二):NIO——处理可写事件
处理可写事件 什么情况下需要注册可写事件? 在服务端一次性无法把数据发送完的情况下,需要注册可写事件 服务端一次性是否能够把数据全部发送完成取决于服务端的缓冲区大小,该缓冲区不受程序控制 注册可写事件的步骤 判断ByteBuffer是否仍…...
PHP基本语法解析与应用指南
PHP(Hypertext Preprocessor)是一种广泛应用的开源脚本语言,特别适用于Web开发。本文将深入探讨PHP的基本语法,包括变量、数据类型、运算符、控制流等方面的内容。我们将详细介绍每个主题的基本概念、语法规则和常见应用ÿ…...
ICS PA1
ICS PA1 init.shmake 编译加速ISA计算机是个状态机程序是个状态机准备第一个客户程序parse_argsinit_randinit_loginit_meminit_isa load_img剩余的初始化工作运行第一个客户程序调试:零断点TUI 基础设施单步执行打印寄存器状态扫描内存 表达式求值词法分析递归求值…...
Java学数据结构(4)——散列表Hash table 散列函数 哈希冲突
目录 引出散列表Hash table关键字Key和散列函数(hash function)散列函数解决collision哈希冲突(碰撞)分离链接法(separate chaining)探测散列表(probing hash table)双散列(double hashing) Java标准库中的散列表总结 引出 1.散列表,key&…...
OVRL-V2: A simple state-of-art baseline for IMAGENAV and OBJECTNAV 论文阅读
论文信息 题目:OVRL-V2: A simple state-of-art baseline for IMAGENAV and OBJECTNAV 作者:Karmesh Yadav, Arjun Majumdar, Ram Ramrakhya 来源:arxiv 时间:2023 代码地址: https://github.com/ykarmesh…...
【Linux】shell脚本忽略错误继续执行
在 shell 脚本中,可以使用 set -e 命令来设置脚本在遇到错误时退出执行。如果你希望脚本忽略错误并继续执行,可以在脚本开头添加 set e 命令来取消该设置。 举例1 #!/bin/bash# 取消 set -e 的设置 set e# 执行命令,并忽略错误 rm somefile…...
如何在看板中有效管理突发紧急任务
在看板中有效管理突发紧急任务需要:设立专门的紧急任务通道、重新调整任务优先级、保持适度的WIP(Work-in-Progress)弹性、优化任务处理流程、提高团队应对突发情况的敏捷性。其中,设立专门的紧急任务通道尤为重要,这能…...
屋顶变身“发电站” ,中天合创屋面分布式光伏发电项目顺利并网!
5月28日,中天合创屋面分布式光伏发电项目顺利并网发电,该项目位于内蒙古自治区鄂尔多斯市乌审旗,项目利用中天合创聚乙烯、聚丙烯仓库屋面作为场地建设光伏电站,总装机容量为9.96MWp。 项目投运后,每年可节约标煤3670…...
【Web 进阶篇】优雅的接口设计:统一响应、全局异常处理与参数校验
系列回顾: 在上一篇中,我们成功地为应用集成了数据库,并使用 Spring Data JPA 实现了基本的 CRUD API。我们的应用现在能“记忆”数据了!但是,如果你仔细审视那些 API,会发现它们还很“粗糙”:有…...
BCS 2025|百度副总裁陈洋:智能体在安全领域的应用实践
6月5日,2025全球数字经济大会数字安全主论坛暨北京网络安全大会在国家会议中心隆重开幕。百度副总裁陈洋受邀出席,并作《智能体在安全领域的应用实践》主题演讲,分享了在智能体在安全领域的突破性实践。他指出,百度通过将安全能力…...
打手机检测算法AI智能分析网关V4守护公共/工业/医疗等多场景安全应用
一、方案背景 在现代生产与生活场景中,如工厂高危作业区、医院手术室、公共场景等,人员违规打手机的行为潜藏着巨大风险。传统依靠人工巡查的监管方式,存在效率低、覆盖面不足、判断主观性强等问题,难以满足对人员打手机行为精…...
wpf在image控件上快速显示内存图像
wpf在image控件上快速显示内存图像https://www.cnblogs.com/haodafeng/p/10431387.html 如果你在寻找能够快速在image控件刷新大图像(比如分辨率3000*3000的图像)的办法,尤其是想把内存中的裸数据(只有图像的数据,不包…...
uniapp 实现腾讯云IM群文件上传下载功能
UniApp 集成腾讯云IM实现群文件上传下载功能全攻略 一、功能背景与技术选型 在团队协作场景中,群文件共享是核心需求之一。本文将介绍如何基于腾讯云IMCOS,在uniapp中实现: 群内文件上传/下载文件元数据管理下载进度追踪跨平台文件预览 二…...
LCTF液晶可调谐滤波器在多光谱相机捕捉无人机目标检测中的作用
中达瑞和自2005年成立以来,一直在光谱成像领域深度钻研和发展,始终致力于研发高性能、高可靠性的光谱成像相机,为科研院校提供更优的产品和服务。在《低空背景下无人机目标的光谱特征研究及目标检测应用》这篇论文中提到中达瑞和 LCTF 作为多…...
JDK 17 序列化是怎么回事
如何序列化?其实很简单,就是根据每个类型,用工厂类调用。逐个完成。 没什么漂亮的代码,只有有效、稳定的代码。 代码中调用toJson toJson 代码 mapper.writeValueAsString ObjectMapper DefaultSerializerProvider 一堆实…...
