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

【计算机网络】非阻塞IO——select实现多路转接

🔥个人主页🔥:孤寂大仙V
🌈收录专栏🌈:计算机网络
🌹往期回顾🌹:【计算机网络】NAT、代理服务器、内网穿透、内网打洞、局域网中交换机
🔖流水不争,争的是滔滔不息


  • 一、非阻塞IO与多路转接
  • 二、select实现多路转接
    • select函数
    • 读就绪 写就绪
    • 非阻塞服务器 代码实现
      • 辅助数组
      • 启动方法
      • 事件派发
      • accept套接字 添加到辅助数组
      • 读数据
    • 源码:[非阻塞http服务](https://gitee.com/hanbuxuan/linux-warehouse/tree/master/Project/Network/Non-blocking%20IO/Select%20Server)
  • 三、select 的缺点

一、非阻塞IO与多路转接

非阻塞IO

IO分为阻塞IO和非阻塞IO,IO=拷贝+等的时间。非阻塞IO允许程序发起IO操作(网络读写等)后立即返回,不等待操作完成,通过回轮询或事件通知处理结果。阻塞IO会因为条件不满足卡住,直到条件就绪。非阻塞IO的关键是程序可以在IO操作未完成时继续执行其他任务。

多路转接

多路转接是实现非阻塞IO的一种高效的方式,通过监控多个IO描述符,通知程序哪些描述符已经就绪(可读、可写),让程序按需处理,避免阻塞。
路转接是一种技术,允许程序同时监控多个输入/输出(IO)事件(如网络套接字、文件描述符),当某些事件就绪(如数据可读、可写)时通知程序处理,而无需为每个IO操作分配单独的线程或阻塞等待。
实现多路转接可以用select、poll、epoll、等方式监控多个IO描述符的事件,当某个描述符就绪(网路),程序收到通知,处理对应的事件。

用多路转接,可以极大提高效率,在单个线程中处理多个并发IO操作,避免阻塞等待或者每个连接都创建线程。用多路转接实现非阻塞IO,程序无需等待某个IO操作完成,可以立即处理其他就绪的IO事件。

二、select实现多路转接

在这里插入图片描述
select负责一件事,就是等一次可以等待多个fd。等待多个fd,有任意一个或多个fd的事件就绪就会通知上层,告诉程序的调用发,哪些fd可以io了。select是等待多个fd的一种就绪事件通知机制。

select函数

#include <sys/select.h>int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout);

select的返回值:大于0是几就表示有几个fd就绪了。等于0超时了,在timeout内没有fd就绪。小于0,select报错。

参数

  1. nfds 是需要监视的最大的文件描述符值+1。

  2. 数 timeout 为结构 timeval, 用来设置 select()的等待时间。

  • NULL:表示select没有timeout,select一直被阻塞,直到某个文件描述符发生了事件。
  • 0:select 非阻塞,立即返回,检查当前是否有就绪事件。
  • 设置事件:timeout时间内,阻塞等待,超时就非阻塞返回。
  1. readfds、writefds、excepfds、分别是关系的读事件、写事件、异常事件。
  • 这三个参数的结构是一个整数数组,更严格来说是一个“位图”,使用位图中的位来表示监视的文件。
  • 这是三个参数,是输入输出型参数,比如说读事件,输入的时候是从用户态到内核态输出的时候是从内核态到用户态。输入的时候告诉内核,你要帮我关心哪些fd上的哪些事件,比特位的位置表示fd编号,比特位的内容,表示是否关心。返回的时候内核告诉用户,你让我关心的哪些fd上面的读事件已经就绪了,比特位的位置,表示fd的编号,比特位的内容,表示是否就绪。
  • 因为这个位图是输入输出型的,所以未来这个位图一定会被频繁变更。位图中有多少个比特位就决定了select最多关心多少个fd。

读就绪 写就绪

读就绪的含义
在 select 或 epoll 等多路转接机制中,“读就绪”表示文件描述符(如网络套接字、文件句柄)已准备好进行读取操作,即:
数据已到达,可以调用 read 或 recv 读取而不阻塞。
对于网络套接字,可能表示接收缓冲区有数据、连接建立完成、或连接关闭。

具体事件(以网络套接字为例):

  • 数据到达:远程端发送数据到套接字的接收缓冲区(如客户端收到服务器响应)。
  • 连接建立:监听套接字(server socket)收到新连接请求(如 accept 可立即执行)。
  • 连接关闭:对端关闭连接,recv 返回0(EOF,端点文件)。
  • 错误:套接字发生错误(如 ECONNRESET),可能通过读取检测。

写就绪的含义
定义:
在多路转接机制中,“写就绪”表示文件描述符(如网络套接字、文件句柄)已准备好进行写入操作,即:
数据可以写入而不阻塞,例如套接字的发送缓冲区有足够空间。
对于网络套接字,通常意味着可以调用 send 或 write 发送数据。

触发条件(以网络套接字为例):

  • 发送缓冲区有空间:套接字的发送缓冲区(TCP/UDP buffer)未满,可以写入数据。
  • 连接建立完成:客户端套接字完成连接(如 connect 后的TCP三次握手),可以发送数据。
  • 错误状态:套接字发生错误(如 ECONNREFUSED),可能通过写操作检测。

非阻塞服务器 代码实现

# pragma once#include "Common.hpp"
#include "Socket.hpp"
#include "Log.hpp"using namespace std;
using namespace LogModule;
using namespace SocketModule;class Selectserver
{
const static int size=8*sizeof(fd_set);
const static int defaultfd=-1;public:Selectserver(int port):_isrunning(false),_listensockfd(make_unique<TcpSocket>()){_listensockfd->BuildTcpSocketServer(port);for(int i=0;i<size;i++){_fd_array[i]=defaultfd;}_fd_array[0]=_listensockfd->FD();} void Start(){_isrunning=true;while(_isrunning){fd_set rfds; //定义fds集合FD_ZERO(&rfds);//清空集合int maxfd=defaultfd;for(int i=0;i<size;i++){if(_fd_array[i]==defaultfd)    //遍历辅助数组,跳过-1的continue;FD_SET(_fd_array[i],&rfds);    //每次进来都要重新设置rfdsif(maxfd<_fd_array[i])      //最大fd小于遍历到的fd更新{maxfd=_fd_array[i];} }PrintFd();int n=select(maxfd+1,&rfds,nullptr,nullptr,nullptr); //多路转接只关心读事件switch (n){case -1:LOG(LogLevel::ERROR)<<"select out";break;case 0:LOG(LogLevel::WARNING)<<"time out";default:LOG(LogLevel::INFO)<<"事件就绪";Dispatcher(rfds);   //派发break;}}}void Dispatcher(fd_set& rfds)   //事件派发{for(int i=0;i<size;i++){if(_fd_array[i]==defaultfd) continue;               //跳过没有fd的位置if(FD_ISSET(_fd_array[i],&rfds)){if(_fd_array[i]==_listensockfd->FD()) //是listen套接字{Accepter();}else        //accept套接字 干活{Recv(_fd_array[i],i);}}     }}void Accepter() //accept套接字{InetAddr client;int sockfd=_listensockfd->AcceptOrDie(&client);//accept了干活fdLOG(LogLevel::DEBUG)<<"accept a new client"<<client.StringAddr();int pos=0;for(;pos<size;pos++){if(_fd_array[pos]==defaultfd)   //遍历辅助数组,遇见-1跳出break;}if(pos==size){LOG(LogLevel::WARNING)<<"server full";close(sockfd);} else{_fd_array[pos]=sockfd;}}void Recv(int sockfd,int pos){char buffer[1024];ssize_t n=recv(sockfd,buffer,sizeof(buffer)-1,0);   //收信息if(n>0){buffer[n] = 0;cout << "client say@ "<< buffer <<endl;}else if(n==0)   //客户端退出{LOG(LogLevel::INFO)<<"client quit";_fd_array[pos]=defaultfd;close(sockfd);}else        //出现错误 异常{LOG(LogLevel::FATAL)<<"recv error";_fd_array[pos]=defaultfd;close(sockfd);}}void PrintFd(){cout << "_fd_array[]: ";for (int i = 0; i < size; i++){if (_fd_array[i] == defaultfd)continue;cout << _fd_array[i] << " ";}cout << "\r\n";}~Selectserver(){}
private:unique_ptr<Socket> _listensockfd;int _fd_array[size];    //辅助数组bool _isrunning;
};

辅助数组

读事件的集合是个位图结构,每次调用select都会修改之前的结构,是有破坏性的。

保留“原始的 fd 集合”,因为 select() 会修改你传进去的集合。select() 会清除掉那些没就绪的 fd!如果你不提前备份,就会把原来的 fd 集合搞没,下一轮 select 就找不到了。

比如说读事件,一开机读到的是listen套接字,下一次就是accept套接字,每次调用 select 时只保留最新的 connfd(accept 套接字),而忘了继续监听 listenfd,那它就“没了”——不会再监视新的连接了。


上面的代码是基于之前写的TCP阻塞的服务器改的非阻塞服务器,没写应用层代码。

    Selectserver(int port):_isrunning(false),_listensockfd(make_unique<TcpSocket>()){_listensockfd->BuildTcpSocketServer(port);for(int i=0;i<size;i++){_fd_array[i]=defaultfd;}_fd_array[0]=_listensockfd->FD();} 

构造服务器,创建辅助数组数组内数据全部设置为-1,把listen套接字放到这个辅助数组中。


启动方法

void Start(){_isrunning=true;while(_isrunning){fd_set rfds; //定义fds集合FD_ZERO(&rfds);//清空集合int maxfd=defaultfd;for(int i=0;i<size;i++){if(_fd_array[i]==defaultfd)    //遍历辅助数组,跳过-1的continue;FD_SET(_fd_array[i],&rfds);    //每次进来都要重新设置rfdsif(maxfd<_fd_array[i])      //最大fd小于遍历到的fd更新{maxfd=_fd_array[i];} }PrintFd();int n=select(maxfd+1,&rfds,nullptr,nullptr,nullptr); //多路转接只关心读事件switch (n){case -1:LOG(LogLevel::ERROR)<<"select out";break;case 0:LOG(LogLevel::WARNING)<<"time out";default:LOG(LogLevel::INFO)<<"事件就绪";Dispatcher(rfds);   //派发break;}}}

启动服务器,一直进行循环,定义fds集合清空集合定义最大的文件描述符。for循环遍历辅助数组,如果不是规定的套接字跳过。把辅助数组中的套接字重新设置进fds集合中,标记最大的文件描述符。
select多路转接(这里只关心读事件),对返回值进行判断,返回值大于0对事件进行派发(就是读事件中的文件描述符是listen套接字的去干listen套接字该干的事,是accept套接字去干accept套接字该干的事)


事件派发

void Dispatcher(fd_set& rfds)   //事件派发{for(int i=0;i<size;i++){if(_fd_array[i]==defaultfd) continue;               //跳过没有fd的位置if(FD_ISSET(_fd_array[i],&rfds)){if(_fd_array[i]==_listensockfd->FD()) //是listen套接字{Accepter();}else        //accept套接字 干活{Recv(_fd_array[i],i);}}     }}

事件派发,遍历辅助数组,跳过没有fd的位置,FD_ISSET检测文件描述符是否在fds中就绪,然后判断是listen套接字还是accept的套接字。


accept套接字 添加到辅助数组

void Accepter() //accept套接字{InetAddr client;int sockfd=_listensockfd->AcceptOrDie(&client);//accept了干活fdLOG(LogLevel::DEBUG)<<"accept a new client"<<client.StringAddr();int pos=0;for(;pos<size;pos++){if(_fd_array[pos]==defaultfd)   //遍历辅助数组,遇见-1跳出break;}if(pos==size){LOG(LogLevel::WARNING)<<"server full";close(sockfd);} else{_fd_array[pos]=sockfd;}}

有了accept的套接字要在辅助数组中填加accept的套接字。遍历辅助数组,遇见defaulted值就要把这个accept套接字添加进去。判断辅助数组是否满了。


读数据

void Recv(int sockfd,int pos){char buffer[1024];ssize_t n=recv(sockfd,buffer,sizeof(buffer)-1,0);   //收信息if(n>0){buffer[n] = 0;cout << "client say@ "<< buffer <<endl;}else if(n==0)   //客户端退出{LOG(LogLevel::INFO)<<"client quit";_fd_array[pos]=defaultfd;close(sockfd);}else        //出现错误 异常{LOG(LogLevel::FATAL)<<"recv error";_fd_array[pos]=defaultfd;close(sockfd);}}void PrintFd(){cout << "_fd_array[]: ";for (int i = 0; i < size; i++){if (_fd_array[i] == defaultfd)continue;cout << _fd_array[i] << " ";}cout << "\r\n";}~Selectserver(){}

到recv接收信息,已经是非阻塞的了,客户端退出要把辅助数组这个位置置为defaulted关闭文件描述符,出现异常也是上述操作。

在这里插入图片描述
在这里插入图片描述

源码:非阻塞http服务

三、select 的缺点

  1. 每次调用 select, 都需要手动设置 fd 集合, 从接口使用角度来说也非常不便。
  2. 每次调用 select, 都需要把 fd 集合从用户态拷贝到内核态, 这个开销在 fd 很多时会很大
  3. 同时每次调用 select 都需要在内核遍历传递进来的所有 fd, 这个开销在 fd 很多时也很大
  4. select 支持的文件描述符数量太小。

相关文章:

【计算机网络】非阻塞IO——select实现多路转接

&#x1f525;个人主页&#x1f525;&#xff1a;孤寂大仙V &#x1f308;收录专栏&#x1f308;&#xff1a;计算机网络 &#x1f339;往期回顾&#x1f339;&#xff1a;【计算机网络】NAT、代理服务器、内网穿透、内网打洞、局域网中交换机 &#x1f516;流水不争&#xff0…...

Figma 中构建 Master Control Panel (MCP) 的完整设计方案

以下是在 Figma 中构建 Master Control Panel (MCP) 的完整设计方案&#xff0c;专为设计系统管理而优化&#xff1a; 一、MCP 核心功能架构 #mermaid-svg-iZAnYxyYU4BtpeaE {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#merma…...

什么是权威解析服务器?权威解析服务器哪些作用?

域名系统&#xff08;DNS&#xff09;是互联网的核心基础设施之一&#xff0c;它将易于记忆的域名转换为计算机能够识别的IP地址。DNS服务器在这一过程中扮演着至关重要的角色&#xff0c;它们可以分为以下几种类型: 根DNS服务器 根DNS服务器位于DNS层级结构的最顶端&#xf…...

LeetCode--23.合并k个升序链表

解题思路&#xff1a; 1.获取信息&#xff1a; 给出了多个升序链表&#xff0c;要求合并成一个升序链表&#xff0c;返回首元结点 2.分析题目&#xff1a; 外面在21题的时候&#xff0c;讲了怎样合并两个升序链表为一个升序链表&#xff0c;不了解的&#xff0c;建议去看一下21…...

ComfyUI 工作流

目录 🧠 ComfyUI 是什么? ComfyUI 的特点 🔧 ComfyUI 工作流程(节点图) 📌 简单理解 如何安装? 🧠 ComfyUI 是什么? ComfyUI 是一个 Stable Diffusion 的图形化用户界面(GUI),专门用来生成图像。它通过 节点图(Node Graph)形式来让用户定义图像生成的每…...

使用glide 同步获取图片

在 Glide 中&#xff0c;可以使用asBitmap()方法来获取图片的Bitmap对象&#xff0c;进而同步地加载图片。以下是具体示例&#xff1a; String imageUrl "https://example.com/image.jpg"; Bitmap bitmap Glide.with(context).asBitmap().load(imageUrl).apply(ne…...

【推荐算法】NeuralCF:深度学习重构协同过滤的革命性突破

NeuralCF&#xff1a;深度学习重构协同过滤的革命性突破 一、算法背景知识&#xff1a;协同过滤的演进与局限1.1 协同过滤的发展历程1.2 传统矩阵分解的缺陷 二、算法理论/结构&#xff1a;NeuralCF架构设计2.1 基础NeuralCF结构2.2 双塔模型进阶结构2.3 模型实现流程对比 三、…...

负载均衡相关基本概念

负载均衡在系统架构设计中至关重要&#xff0c;其核心目标是合理分配负载&#xff0c;提升系统整体性能和可靠性。本文简要介绍了负载均衡的基本概念&#xff0c;包括四层和七层负载均衡、负载均衡的使用场景和实现方式、负载均衡的常用算法以及一些配置相关知识。 1、负载均衡…...

服务器中日志分析的作用都有哪些

服务器日志是用来检测和排查可疑行为的主要工具&#xff0c;运维团队可以通过分析和解读日志文件&#xff0c;发现服务器中潜在的网络安全威胁或异常活动&#xff0c;下面&#xff0c;就让小编和大家一起来了解一下服务器中日志分析的作用都有什么吧&#xff01; 对于服务器中的…...

【React】useId

在 React 中&#xff0c;useId 是 React 18 引入的一个 Hook&#xff0c;用于生成一个在组件生命周期中保持稳定的唯一 ID。它主要用于&#xff1a; 无障碍&#xff08;accessibility&#xff09;场景&#xff0c;比如表单控件需要一个 id 和 label 的 htmlFor 属性配对。服务…...

【51单片机】0. 基础软件安装

最近心血来潮对单片机感兴趣&#xff0c;想着以后工作不景气了就跳槽&#xff0c;兴趣使然为以后做打算吧&#xff0c;开始跟着江科大学习单片机 1. 需要安装的软件说明 使用到的软件包括&#xff1a; Keli&#xff1a;写嵌入式代码的 stc-isp&#xff1a;烧录&下载代码…...

集成电路设计:从概念到实现的完整解析优雅草卓伊凡

集成电路设计&#xff1a;从概念到实现的完整解析优雅草卓伊凡 一、集成电路设计&#xff1a;芯片制造的”灵魂蓝图” 1.1 什么是集成电路设计&#xff1f; 集成电路&#xff08;IC&#xff09;设计是指通过电子设计自动化&#xff08;EDA&#xff09;工具&#xff0c;将数百…...

动态规划之网格图模型(二)

文章目录 动态规划之网格图模型&#xff08;二&#xff09;LeetCode 931. 下降路径最小和思路Golang 代码 LeetCode 2684. 矩阵中移动的最大次数思路Golang 代码 LeetCode 2304. 网格中的最小路径代价思路Golang 代码 LeetCode 1289. 下降路径最小和 II思路Golang 代码 LeetCod…...

uniapp 集成腾讯云 IM 消息搜索功能

UniApp 集成腾讯云 IM 消息搜索功能实战指南 一、功能实现原理 腾讯云 IM 通过 消息漫游 服务端搜索接口 实现消息检索&#xff0c;核心机制如下&#xff1a; 数据存储&#xff1a;消息默认存储7天&#xff08;可扩展至30天&#xff09;索引构建&#xff1a;基于消息内容自…...

robot_lab——rsl_rl的train.py整体逻辑

文章目录 Go2机器人训练流程详细分析概述1. 训练启动流程1.1 命令行参数解析RSL-RL相关参数组Isaac Sim应用启动参数组 1.2 RL配置1.3 Isaac Sim启动 2. 环境配置加载2.1 Hydra配置系统 3. 环境创建与初始化3.1 Gym环境创建3.2 Manager系统初始化3.2.1 ObservationManager3.2.2…...

AI推荐系统演进史:从协同过滤到图神经网络与强化学习的融合

每一次滑动手机屏幕&#xff0c;电商平台向你推荐心仪商品的背后&#xff0c;是超过百亿量级的浮点运算。从早期的“猜你喜欢”到如今的“比你更懂你”&#xff0c;商品推荐引擎已悄然完成从简单规则到深度智能的技术跃迁。 一、协同过滤&#xff1a;推荐系统的基石与演进 协同…...

Java-IO流之压缩与解压缩流详解

Java-IO流之压缩与解压缩流详解 一、压缩与解压缩概述1.1 基本概念1.2 Java中的压缩类库1.3 核心类与接口 二、ZIP压缩与解压缩2.1 ZIP格式简介2.2 使用ZipOutputStream创建ZIP文件2.3 使用ZipInputStream读取ZIP文件 三、GZIP压缩与解压缩3.1 GZIP格式简介3.2 使用GZIPOutputS…...

.NET 原生驾驭 AI 新基建实战系列(三):Chroma ── 轻松构建智能应用的向量数据库

在人工智能AI和机器学习ML迅猛发展的今天&#xff0c;数据的存储和检索需求发生了巨大变化。传统的数据库擅长处理结构化数据&#xff0c;但在面对高维向量数据时往往力不从心。向量数据库作为一种新兴技术&#xff0c;专为AI应用设计&#xff0c;能够高效地存储和查询高维向量…...

有声书画本

有声书画本服务标准 有声喵连接 一、基础服务&#xff08;5r/w字&#xff09; 核心&#xff1a; 基础删&#xff08;快捷键AltD&#xff09;调&#xff0c;优化播讲流畅度 执行&#xff1a; 删除冗余旁白 删除角色动作/心理的重复描述&#xff08;例&#xff1a;小明冷笑道…...

StarRocks与Apache Iceberg:构建高效湖仓一体的实时分析平台

## 引言&#xff1a;数据湖的挑战与演进 在数据驱动的时代&#xff0c;企业数据湖需要同时满足海量存储、高性能查询、多引擎协作和实时更新等复杂需求。传统基于 Hive 的数据湖方案面临元数据管理低效、缺乏 ACID 事务支持、查询性能瓶颈等问题。在此背景下&#xff0c;**Sta…...

WebRTC 与 WebSocket 的关联关系

WebRTC&#xff08;Web Real-Time Communication&#xff09;与 WebSocket 作为重要技术&#xff0c;被广泛应用于各类实时交互场景。虽然它们在功能和特性上存在明显差异&#xff0c;但在实际应用中也有着紧密的关联&#xff0c;共同为用户提供流畅的实时交互体验。 一、WebR…...

8.RV1126-OPENCV 视频中添加LOGO

一.视频中添加 LOGO 图像大体流程 首先初始化VI,VENC模块并使能&#xff0c;然后创建两个线程&#xff1a;1.把LOGO灰度化&#xff0c;然后获取VI原始数据&#xff0c;其次把VI数据Mat化并创建一个感兴趣区域&#xff0c;最后把LOGO放感兴趣区域里并把数据发送给VENC。2.专门获…...

API管理是什么?API自动化测试怎么搭建?

目录 一、API管理是什么 &#xff08;一&#xff09;API管理的定义 &#xff08;二&#xff09;API管理的重要性 二、API管理的主要内容 &#xff08;一&#xff09;API设计 1. 遵循标准规范 2. 考虑可扩展性 3. 保证接口的易用性 &#xff08;二&#xff09;API开发 …...

Next.js+prisma开发一

1.初始化Next.js项目 #按版本安装 npx create-next-app13.4.5 如果最新版本 执行&#xff1a;npx create-next-applatest2. 安装Prima和客户端 npm install prisma --save-dev npm install prisma/client3.初始化Prisma&#xff0c;以SQLit举例 # 初始化 Prisma 并配置 SQLi…...

GIC v3 v4 虚拟化架构

ARMV8-A架构中包含了对虚拟化的支持。为了与架构保持匹配&#xff0c;GICV3也对虚拟化做了支持。新增了以下特性&#xff1a; 对CPU interface的硬件虚拟化虚拟中断maintenance 中断&#xff1a;用于通知监管程序&#xff08;例如hypervisor&#xff09;一些特定的虚拟机事件 …...

2025远离Deno和Fresh

原创作者&#xff1a;庄晓立&#xff08;LIIGO&#xff09; 原创时间&#xff1a;2025年6月6日 原创链接&#xff1a;https://blog.csdn.net/liigo/article/details/148479884 版权所有&#xff0c;转载请注明出处&#xff01; 相识 Deno&#xff0c;是Nodejs原开发者Ryan Da…...

相机camera开发之差异对比核查一:测试机和对比机的硬件配置差异对比

【关注我,后续持续新增专题博文,谢谢!!!】 上一篇我们讲了: 这一篇我们开始讲: 目录 一、背景 二、:Camera硬件配置差异 2.1:硬件配置差异核查项 2.2 :核查方式 2.3 :高通camx平台核查 2.4 :MTK平台核查...

Flask+LayUI开发手记(七):头像的上传及突破static目录限制

看了看&#xff0c;上篇开发手记是去年8月份写的&#xff0c;到现在差2个月整一年了。停更这么长时间&#xff0c;第一个原因是中间帮朋友忙一个活&#xff0c;那个技术架构是用springboot的&#xff0c;虽然前端也用layUI&#xff0c;但和Flask-python完全不搭界&#xff0c;所…...

uv管理spaCy语言模型

本文记录如何在使用uv管理python项目dependencies时&#xff0c;把spaCy的模型也纳入其中. spaCy 一、spaCy简介 spaCy是一个开源的自然语言处理&#xff08;NLP&#xff09;库&#xff0c;它主要用于处理文本数据。它支持多种语言&#xff0c;包括英语、中文等。它是由Expl…...

MiniExcel模板填充Excel导出

目录 1.官方文档 2. 把要导出的数据new一个匿名对象 3.导出 4.注意事项 5.模板制作 6.结果 1.官方文档 https://gitee.com/dotnetchina/MiniExcel/#%E6%A8%A1%E6%9D%BF%E5%A1%AB%E5%85%85-excel // 1. By POCO var value new {Name "Jack",CreateDate n…...