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

深入解析poll函数:高效I/O多路复用技术

引言在上一篇文章中我们详细讲解了select函数的使用。select作为最基础的 I/O 多路复用机制虽然简单易用但存在两个明显的局限性文件描述符数量限制默认最多只能监控 1024 个描述符每次调用需要重新构建集合fd_set在select返回时会被修改导致每次循环都需要重建为了解决select的第一个限制描述符数量上限POSIX 标准引入了poll函数。poll使用动态数组代替fd_set的固定位图理论上可以监控任意数量的文件描述符。第一部分poll 函数基础一、函数原型#include poll.h int poll(struct pollfd *fds, nfds_t nfds, int timeout);二、核心数据结构struct pollfdpoll的核心是struct pollfd结构体每个被监控的文件描述符对应一个结构体struct pollfd { int fd; // 要监控的文件描述符-1 表示忽略此项 short events; // 请求监控的事件输入参数由用户设置 short revents; // 实际发生的事件输出参数由内核设置 };重要特性events和revents是分离的events由用户设置不会被poll修改revents由内核设置poll返回时填充这意味着不需要每次调用前重建事件这是比select更优秀的设计三、参数详解参数类型作用fdsstruct pollfd*待监控的文件描述符数组nfdsnfds_t数组中的有效元素个数timeoutint超时时间毫秒四、返回值返回值含义0就绪的文件描述符数量0超时没有就绪的描述符-1调用失败可通过 errno 获取错误码五、timeout 参数的特殊值值含义-1永久阻塞直到有描述符就绪0非阻塞轮询立即返回0等待指定毫秒数后超时六、与 select 超时设置的对比// select 使用 timeval 结构体秒 微秒 struct timeval tv; tv.tv_sec 5; // 5秒 tv.tv_usec 500000; // 500000微秒 0.5秒 // 总超时5.5秒 // poll 使用整数毫秒 int timeout 5500; // 5500毫秒 5.5秒poll的超时设置更加简洁直接用毫秒整数表示避免了select需要设置两个字段的麻烦。第二部分事件类型详解一、常用事件类型poll定义了一系列事件标志通过位或|组合使用事件常量含义可用于 events可用于 reventsPOLLIN数据可读包括普通数据和优先数据✅✅POLLPRI有紧急数据可读TCP 带外数据✅✅POLLOUT写操作不会阻塞✅✅POLLERR发生错误❌✅POLLHUP连接挂起对方关闭连接❌✅POLLNVAL无效请求描述符未打开❌✅POLLRDNORM普通数据可读✅✅POLLRDBAND优先带数据可读✅✅POLLWRNORM普通数据可写✅✅POLLWRBAND优先带数据可写✅✅二、事件分类说明三、关键理解POLLERR、POLLHUP、POLLNVAL这三个事件只能出现在revents中不需要也不能在events中设置。内核会自动检测这些异常状态// 检查异常事件的惯用写法 if (fds[i].revents POLLERR) { printf(描述符 %d 发生错误\n, fds[i].fd); } if (fds[i].revents POLLHUP) { printf(描述符 %d 对方关闭连接\n, fds[i].fd); } if (fds[i].revents POLLNVAL) { printf(描述符 %d 无效\n, fds[i].fd); }第三部分poll 的基本使用一、监控标准输入#include stdio.h #include stdlib.h #include unistd.h #include string.h #include poll.h int main() { struct pollfd fds[1]; char buffer[128]; // 初始化 pollfd 结构体 fds[0].fd STDIN_FILENO; // 监控标准输入 fds[0].events POLLIN; // 监控读事件 while (1) { printf(等待输入5秒超时...\n); // 调用 poll超时 5 秒5000 毫秒 int ret poll(fds, 1, 5000); if (ret -1) { perror(poll error); break; } else if (ret 0) { printf(5秒内没有输入超时\n\n); } else { // 检查标准输入是否有数据可读 if (fds[0].revents POLLIN) { int n read(STDIN_FILENO, buffer, sizeof(buffer) - 1); if (n 0) { buffer[n] \0; printf(输入内容: %s\n, buffer); } } // 检查异常事件 if (fds[0].revents POLLERR) { printf(标准输入发生错误\n); break; } if (fds[0].revents POLLHUP) { printf(标准输入已关闭\n); break; } } } return 0; }二、与 select 实现对比核心优势poll的events字段保留用户设置revents字段由内核填充。因此不需要每次调用前重新初始化只需要在事件处理完成后清零revents即可。第四部分poll 实现多客户端服务器一、数据结构设计#include stdio.h #include stdlib.h #include string.h #include unistd.h #include sys/socket.h #include netinet/in.h #include arpa/inet.h #include poll.h #define PORT 6000 #define MAX_CLIENTS 1024 #define BUFFER_SIZE 128二、创建监听套接字int create_listen_socket() { int listen_fd socket(AF_INET, SOCK_STREAM, 0); if (listen_fd -1) { perror(socket error); return -1; } // 端口复用 int opt 1; setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, opt, sizeof(opt)); struct sockaddr_in addr; memset(addr, 0, sizeof(addr)); addr.sin_family AF_INET; addr.sin_port htons(PORT); addr.sin_addr.s_addr htonl(INADDR_ANY); if (bind(listen_fd, (struct sockaddr*)addr, sizeof(addr)) -1) { perror(bind error); close(listen_fd); return -1; } if (listen(listen_fd, 5) -1) { perror(listen error); close(listen_fd); return -1; } printf(服务器启动成功端口%d\n, PORT); return listen_fd; }三、pollfd 数组管理struct pollfd fds[MAX_CLIENTS 1]; // 1 用于监听套接字 int nfds 0; // 当前有效元素个数 // 添加描述符到监控数组 void add_fd(int fd) { if (nfds MAX_CLIENTS 1) { printf(已达到最大连接数\n); return; } fds[nfds].fd fd; fds[nfds].events POLLIN; // 监控读事件 fds[nfds].revents 0; nfds; } // 从监控数组中移除描述符 void remove_fd(int index) { // 用最后一个元素覆盖要删除的元素保持数组连续性 fds[index] fds[nfds - 1]; nfds--; }删除策略说明四、事件处理函数void handle_events(int listen_fd) { // 1. 检查监听套接字新连接 if (fds[0].revents POLLIN) { struct sockaddr_in client_addr; socklen_t len sizeof(client_addr); int client_fd accept(listen_fd, (struct sockaddr*)client_addr, len); if (client_fd ! -1) { printf(新客户端连接fd%d, IP%s\n, client_fd, inet_ntoa(client_addr.sin_addr)); add_fd(client_fd); } } // 2. 检查所有客户端连接从索引1开始索引0是监听套接字 for (int i 1; i nfds; i) { int fd fds[i].fd; // 检查读事件 if (fds[i].revents POLLIN) { char buffer[BUFFER_SIZE]; int n recv(fd, buffer, BUFFER_SIZE - 1, 0); if (n 0) { // 客户端关闭或出错 printf(客户端 %d 断开连接\n, fd); close(fd); remove_fd(i); i--; // 调整索引因为数组元素被替换了 } else { buffer[n] \0; printf(收到数据 (fd%d): %s\n, fd, buffer); send(fd, OK, 2, 0); } } // 检查异常事件 if (fds[i].revents (POLLERR | POLLHUP | POLLNVAL)) { printf(客户端 %d 异常断开\n, fd); close(fd); remove_fd(i); i--; } } }五、主循环int main() { // 创建监听套接字 int listen_fd create_listen_socket(); if (listen_fd -1) { exit(1); } // 将监听套接字加入监控数组始终在索引0 add_fd(listen_fd); while (1) { // 调用 poll超时 2 秒 int ret poll(fds, nfds, 2000); if (ret -1) { perror(poll error); break; } else if (ret 0) { // 超时可执行定时任务 printf(poll timeout, 当前连接数: %d\n, nfds - 1); continue; } // 处理就绪事件 handle_events(listen_fd); } close(listen_fd); return 0; }六、关键设计说明1. 监听套接字固定放在 fds[0]2. 监听套接字的 POLLIN 含义对于监听套接字POLLIN表示有新连接到达accept不会阻塞if (fds[0].revents POLLIN) { int client_fd accept(listen_fd, NULL, NULL); // 不会阻塞因为一定有新连接 }第五部分poll 的注意事项一、fd 可以设为 -1 来忽略与select不同poll允许将fd设为 -1 来跳过某个数组元素fds[2].fd -1; // poll 会忽略此项这提供了另一种删除策略——不删除数组元素只是标记为无效。但会浪费数组空间。二、POLLHUP 和 POLLIN 可能同时出现当对方关闭连接时poll可能同时返回POLLHUP和POLLINif (fds[i].revents POLLIN) { int n recv(fd, buffer, size, 0); if (n 0) { // 对方关闭连接recv 返回 0 } } // 同时检查 POLLHUP 作为额外保障 if (fds[i].revents POLLHUP) { // 确认对方已关闭 }推荐做法优先处理POLLIN通过recv返回值判断连接状态将POLLHUP作为异常情况的补充检查。三、缓冲区截断问题// 如果接收缓冲区太小一次只能读取部分数据 char buffer[1]; // 只能读1个字节 recv(fd, buffer, 1, 0); // 剩余数据还在接收缓冲区中下次 poll 仍会报告 POLLIN当客户端发送 hello5个字节而服务器缓冲区只有1字节时需要多次 poll 才能读完所有数据。第六部分select 与 poll 对比一、核心对比表对比项selectpoll描述符集合fd_set固定位图struct pollfd 数组动态最大描述符数FD_SETSIZE默认1024无内置限制受系统资源限制事件与结果共用同一集合被修改分离events/revents每次调用前重建需要不需要events 不变超时精度微秒毫秒参数含义nfds最大值1nfds数组元素个数可移植性几乎所有平台POSIX 系统空描述符处理不支持fd-1 跳过该项删除元素FD_CLR多种方式替换、fd-1性能少量描述符相当相当性能大量描述符较差需遍历所有位较差需遍历整个数组二、poll 的优势1. 无描述符数量限制// select受 FD_SETSIZE 限制 fd_set readfds; // 最多 1024 个 // poll动态数组无固定上限 struct pollfd *fds malloc(sizeof(struct pollfd) * 100000);2. 事件与结果分离// select每次都需要重建 while (1) { FD_ZERO(readfds); // 必须重建 FD_SET(fd1, readfds); FD_SET(fd2, readfds); select(...); // readfds 已被修改 } // poll只需设置一次 events fds[0].events POLLIN; // 设置一次 fds[1].events POLLIN; while (1) { poll(fds, 2, -1); // 检查 reventsevents 保持不变 }3. 更简洁的超时设置// select需要分离的结构体 struct timeval tv {5, 0}; // 5秒 // poll直接毫秒数 int timeout 5000; // 5000毫秒三、poll 的不足1. 仍然是线性扫描每次调用poll内核需要遍历整个fds数组复杂度 O(n)2. 大量描述符时内存复制开销大需要在用户态和内核态之间复制整个fds数组3. 毫秒级精度相比select的微秒精度poll只能精确到毫秒实际应用中通常够用4. 不可知具体哪个描述符就绪与select一样需要遍历检查第七部分完整测试代码服务器端poll_server.c#include stdio.h #include stdlib.h #include string.h #include unistd.h #include sys/socket.h #include netinet/in.h #include arpa/inet.h #include poll.h #define PORT 6000 #define MAX_CLIENTS 1024 #define BUFFER_SIZE 128 struct pollfd fds[MAX_CLIENTS 1]; int nfds 0; void add_fd(int fd) { fds[nfds].fd fd; fds[nfds].events POLLIN; fds[nfds].revents 0; nfds; } void remove_fd(int index) { fds[index] fds[nfds - 1]; nfds--; } int create_listen_socket() { int listen_fd socket(AF_INET, SOCK_STREAM, 0); if (listen_fd -1) { perror(socket error); return -1; } int opt 1; setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, opt, sizeof(opt)); struct sockaddr_in addr; memset(addr, 0, sizeof(addr)); addr.sin_family AF_INET; addr.sin_port htons(PORT); addr.sin_addr.s_addr htonl(INADDR_ANY); if (bind(listen_fd, (struct sockaddr*)addr, sizeof(addr)) -1) { perror(bind error); close(listen_fd); return -1; } if (listen(listen_fd, 5) -1) { perror(listen error); close(listen_fd); return -1; } printf(poll 服务器启动端口%d\n, PORT); return listen_fd; } int main() { int listen_fd create_listen_socket(); if (listen_fd -1) exit(1); // 监听套接字在 fds[0] add_fd(listen_fd); while (1) { int ret poll(fds, nfds, 2000); if (ret -1) { perror(poll error); break; } if (ret 0) { printf(超时当前连接数: %d\n, nfds - 1); continue; } // 处理监听套接字 if (fds[0].revents POLLIN) { struct sockaddr_in client_addr; socklen_t len sizeof(client_addr); int client_fd accept(listen_fd, (struct sockaddr*)client_addr, len); if (client_fd ! -1) { printf(新连接: fd%d\n, client_fd); add_fd(client_fd); } } // 处理客户端连接 for (int i 1; i nfds; i) { if (fds[i].revents POLLIN) { char buffer[BUFFER_SIZE]; int n recv(fds[i].fd, buffer, BUFFER_SIZE - 1, 0); if (n 0) { printf(断开: fd%d\n, fds[i].fd); close(fds[i].fd); remove_fd(i); i--; } else { buffer[n] \0; printf(收到 fd%d: %s\n, fds[i].fd, buffer); send(fds[i].fd, OK, 2, 0); } } if (fds[i].revents (POLLERR | POLLHUP | POLLNVAL)) { printf(异常断开: fd%d\n, fds[i].fd); close(fds[i].fd); remove_fd(i); i--; } } } close(listen_fd); return 0; }测试# 编译服务器gcc poll_server.c -o poll_server./poll_server# 另开终端使用 telnet 或 nc 测试nc 127.0.0.1 6000# 输入任意内容应该收到 OK 回复总结一、poll 使用流程二、核心函数速查表函数/宏作用poll()监控多个文件描述符的状态POLLIN数据可读事件POLLOUT数据可写事件POLLERR错误事件仅 reventsPOLLHUP连接挂起仅 reventsPOLLNVAL无效描述符仅 revents三、select vs poll 选择建议场景推荐描述符数量 1024两者均可select 更跨平台描述符数量可能超过 1024使用 poll需要 Windows 兼容使用 select需要更简洁的代码使用 poll无需重建事件需要微秒精度超时使用 select追求极致性能海量连接使用 epoll下一篇文章四、poll 的优缺点总结优点缺点✅ 无描述符数量限制❌ 仍需线性遍历所有描述符✅ events/revents 分离无需重建❌ 大量描述符时内存复制开销大✅ 超时设置简洁毫秒整数❌ 不可知具体就绪的描述符✅ fd-1 可跳过无效项❌ 精确度到毫秒select 是微秒poll解决了select的描述符数量限制问题并提供了更优雅的事件管理方式events与revents分离。但它仍然需要线性遍历所有描述符在处理海量连接时效率不够理想。在下一篇文章中我们将学习 Linux 下的终极 I/O 多路复用方案 ——epoll它通过事件驱动和红黑树实现了 O(1) 的性能是构建高性能服务器的核心技术。学习建议动手实现poll版本的服务器加深理解对比select和poll的代码差异理解各自的设计思想注意POLLHUP和POLLIN同时出现的情况正确处理连接关闭重点理解替换法删除技巧这是保证 O(1) 删除的关键

相关文章:

深入解析poll函数:高效I/O多路复用技术

引言在上一篇文章中,我们详细讲解了 select 函数的使用。select 作为最基础的 I/O 多路复用机制,虽然简单易用,但存在两个明显的局限性:文件描述符数量限制:默认最多只能监控 1024 个描述符每次调用需要重新构建集合&a…...

终极指南:Awoo Installer - 快速安装Switch游戏的完整教程

终极指南:Awoo Installer - 快速安装Switch游戏的完整教程 【免费下载链接】Awoo-Installer A No-Bullshit NSP, NSZ, XCI, and XCZ Installer for Nintendo Switch 项目地址: https://gitcode.com/gh_mirrors/aw/Awoo-Installer Awoo Installer是一款专为Ni…...

【深度解析】Hermes Agent:持久记忆、自学习闭环与桌面化 Autonomous AI 工作流实践

摘要 Hermes Agent 的核心价值不只是“带工具的聊天机器人”,而是面向长期运行的自主智能体系统。本文从持久记忆、自学习技能、工具编排和桌面化管理角度,解析其架构思想,并给出一个可落地的 Python 实战示例。背景介绍:从 Chatb…...

ViGEmBus完全指南:轻松解决Windows游戏手柄兼容性难题

ViGEmBus完全指南:轻松解决Windows游戏手柄兼容性难题 【免费下载链接】ViGEmBus Windows kernel-mode driver emulating well-known USB game controllers. 项目地址: https://gitcode.com/gh_mirrors/vi/ViGEmBus 你是否曾经遇到过这样的困扰:在…...

从零构建AI编程助手:Groundhog项目解析与Rust实现

1. 项目概述:一个从零开始理解AI编程助手的教学项目如果你和我一样,对Cursor、GitHub Copilot这类AI编程助手背后的工作原理感到好奇,甚至有点“黑盒”恐惧,那么这个叫Groundhog的项目,可能就是为你量身打造的。它不是…...

抖音无水印下载器完整指南:5分钟快速上手免费批量下载

抖音无水印下载器完整指南:5分钟快速上手免费批量下载 【免费下载链接】douyin-downloader A practical Douyin downloader for both single-item and profile batch downloads, with progress display, retries, SQLite deduplication, and browser fallback suppo…...

PyCharm直连Spark集群:一站式配置与避坑指南

1. 为什么需要PyCharm直连Spark集群? 作为数据工程师,我经常需要在本地开发Spark应用,然后部署到远程集群执行。传统方式是本地写完代码后,手动上传到服务器再用spark-submit提交,这个过程既繁琐又容易出错。直到发现P…...

douyin-downloader:抖音内容获取的技术架构与实践应用

douyin-downloader:抖音内容获取的技术架构与实践应用 【免费下载链接】douyin-downloader A practical Douyin downloader for both single-item and profile batch downloads, with progress display, retries, SQLite deduplication, and browser fallback suppo…...

别再复制粘贴了!手把手教你从零搭建STM32F429 MDK5工程模板(附完整源码包)

从零构建STM32F429工程模板:避开新手90%的踩坑点 第一次拿到STM32F429开发板时,我盯着满屏的英文文档和零散的教程发愣——网上能找到的要么是过时的Keil4配置指南,要么直接丢给你一个现成工程文件。这种"复制粘贴式"的学习让我在后…...

开源营销技能图谱:构建个人与团队的数字化能力体系

1. 项目概述:一个营销人的开源技能库如果你在营销行业摸爬滚打过几年,大概率会和我有一样的感受:这个领域变化太快了。今天还在研究信息流广告的OCPM出价,明天可能就要琢磨AIGC内容生成;刚把SEO的站内优化搞明白&#…...

WelsonJS:基于Windows原生WSH的现代JavaScript桌面应用开发框架

1. 项目概述:WelsonJS,一个被低估的Windows原生JavaScript框架如果你是一名Windows平台的开发者,或者经常需要处理一些自动化、脚本任务,你可能对Node.js、Electron甚至PowerShell都很熟悉。但今天我想聊一个有点“复古”却又极其…...

从“砖头”到“复活”:一个大众车机蓝牙解锁的完整逆向工程记录

从“砖头”到“复活”:一个大众车机蓝牙解锁的完整逆向工程记录 当一台原本功能完整的车载娱乐系统因为缺少关键协议握手而变成"砖头",你会怎么做?这个问题困扰着许多汽车电子爱好者和安全研究人员。本文记录了我如何通过逆向工程手…...

JetBrains IDE重置插件:终极免费解决方案告别30天试用期限制

JetBrains IDE重置插件:终极免费解决方案告别30天试用期限制 【免费下载链接】ide-eval-resetter 项目地址: https://gitcode.com/gh_mirrors/id/ide-eval-resetter 你是否曾经在项目开发的关键时刻,突然被JetBrains IDE弹出的"试用期已到期…...

基于Neo4j与G6构建技能图谱:从图数据库原理到开源项目实战

1. 项目概述:一个技能图谱的构建与探索工具最近在整理个人知识体系时,我一直在寻找一个能帮我将零散技能点串联起来,形成可视化“技能树”的工具。市面上很多笔记软件要么太重,要么太轻,要么就是纯粹的文档管理&#x…...

Go语言轻量级HTTP代理curxy:开发调试与本地环境配置利器

1. 项目概述:一个轻量级的HTTP代理工具最近在折腾一些本地开发环境,特别是需要处理跨域请求或者模拟不同网络环境的时候,总是绕不开代理工具。市面上的方案很多,从功能强大的Nginx、Caddy,到各种语言的中间件&#xff…...

Obsidian插件Quiz Generator:用AI将笔记自动转化为互动测验

1. 项目概述:用AI将笔记变成互动测验 如果你和我一样,是个重度Obsidian用户,同时又经常需要备考、复习或者制作教学材料,那你肯定体会过手动从笔记里出题的痛苦。把一段段精心整理的知识点,转化成一道道能检验理解程度…...

TeamHero:基于规则引擎的智能任务自动化分配系统设计与实战

1. 项目概述与核心价值 最近在GitHub上看到一个挺有意思的项目,叫“TeamHero”,作者是sagiyaacoby。乍一看这个名字,你可能会联想到团队协作或者英雄联盟,但实际上,它是一个专注于自动化团队管理与任务分发的工具。简…...

避开这些坑!用Verilog写2ASK/2FSK调制解调模块时的常见错误与调试技巧

避开这些坑!用Verilog写2ASK/2FSK调制解调模块时的常见错误与调试技巧 在数字通信系统的FPGA实现中,2ASK和2FSK作为基础调制方式常被用于教学和原型验证。但看似简单的调制解调模块,实际开发中却暗藏诸多"陷阱"。本文将从工程实践角…...

告别混乱!用这3张图理清AUTOSAR BSW模块的层级与依赖关系

告别混乱!用这3张图理清AUTOSAR BSW模块的层级与依赖关系 在汽车电子系统开发中,AUTOSAR架构的复杂性常常让开发者陷入模块关系的迷宫。当你面对几十个BSW(基础软件)模块时,是否经常困惑于它们究竟属于哪个层级&#x…...

ESPAsyncWebServer库在Arduino IDE下的完整安装与避坑指南(附依赖库下载)

ESPAsyncWebServer库在Arduino IDE下的完整安装与避坑指南 第一次接触ESPAsyncWebServer时,我花了整整一个下午才把环境配置成功。作为过来人,我深知新手在Arduino IDE中安装这个库会遇到哪些"坑"——从依赖库版本不匹配到文件路径错误&#x…...

SITS2026正式生效倒计时47天:你的AIAgent容错设计还停留在“try-catch”阶段?

更多请点击: https://intelliparadigm.com 第一章:SITS2026标准核心要义与AIAgent容错设计范式跃迁 SITS2026(Software Intelligence Trust & Safety Standard 2026)首次将“可验证容错边界”(Verifiable Fault T…...

大模型监控告警失效的9大隐形陷阱(SITS技术委员会2024压力测试实录)

更多请点击: https://intelliparadigm.com 第一章:大模型监控告警失效的9大隐形陷阱(SITS技术委员会2024压力测试实录) 在2024年SITS技术委员会开展的跨平台大模型服务压力测试中,超63%的生产级LLM推理集群遭遇了“告…...

AI应用安全实战:使用SecurityLayer构建防护中间件

1. 项目概述:一个为AI应用量身定制的安全防护层最近在折腾AI应用开发,特别是那些需要调用外部API或者处理敏感用户输入的场景,安全问题总是让人头疼。你辛辛苦苦搭了个智能客服,结果用户输入一串精心构造的恶意提示词,…...

第四部分-Docker网络与存储——18. 自定义网络

18. 自定义网络 1. 自定义网络概述 自定义网络允许用户根据需求创建具有特定配置的网络,相比默认的 bridge 网络,提供了更好的隔离性、DNS 解析和灵活性。 ┌────────────────────────────────────────────…...

局域网文件传输终极指南:3步实现跨平台文件秒传

局域网文件传输终极指南:3步实现跨平台文件秒传 【免费下载链接】LAN-Share Cross platform LAN File transfer application built with Qt C framework 项目地址: https://gitcode.com/gh_mirrors/la/LAN-Share 还在为电脑间传文件而烦恼吗?U盘太…...

Xplorer文件属性查看器:全面掌控文件信息的终极指南

Xplorer文件属性查看器:全面掌控文件信息的终极指南 【免费下载链接】xplorer Xplorer, a customizable, modern file manager 项目地址: https://gitcode.com/gh_mirrors/xp/xplorer 在日常文件管理中,你是否经常需要快速查看文件的详细信息&…...

NVIDIA Profile Inspector深度指南:解锁显卡隐藏性能的完整教程

NVIDIA Profile Inspector深度指南:解锁显卡隐藏性能的完整教程 【免费下载链接】nvidiaProfileInspector 项目地址: https://gitcode.com/gh_mirrors/nv/nvidiaProfileInspector 还在为游戏画面撕裂、输入延迟高、帧率不稳定而烦恼吗?NVIDIA Pr…...

终极指南:八大网盘直链下载助手完整使用教程,告别限速烦恼

终极指南:八大网盘直链下载助手完整使用教程,告别限速烦恼 【免费下载链接】Online-disk-direct-link-download-assistant 一个基于 JavaScript 的网盘文件下载地址获取工具。基于【网盘直链下载助手】修改 ,支持 百度网盘 / 阿里云盘 / 中国…...

网盘直链下载助手完整教程:告别限速,解锁九大网盘真实下载链接

网盘直链下载助手完整教程:告别限速,解锁九大网盘真实下载链接 【免费下载链接】Online-disk-direct-link-download-assistant 一个基于 JavaScript 的网盘文件下载地址获取工具。基于【网盘直链下载助手】修改 ,支持 百度网盘 / 阿里云盘 / …...

基于RAG与LangChain的法律AI助手:从技术原理到开源实践

1. 项目概述:当AI遇上法律,一个开源法律智能助手的诞生最近几年,AI大模型的热潮席卷了各行各业,从写代码到画图,从客服到教育,似乎没有哪个领域能置身事外。作为一名在技术圈摸爬滚打多年的从业者&#xff…...