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

从零实现一个高性能 FTP 服务器(C++ / Linux)

目录一、搭建 TCP 服务器骨架服务器代码测试二、支持多客户端并发三、线程模型核心思路为什么使用 detach输出为什么会错乱四、函数重构重构后的结构五、FTP 协议基础控制连接数据连接六、命令解析行缓冲区命令解析为什么要转大写七、PASV 被动模式为什么需要数据连接PASV 工作流程第一步第二步动态端口八、LIST 命令为什么 LIST 要 accept遍历目录安全问题九、文件下载RETR基础实现十、文件上传STOR为什么 recv 返回 0 表示结束十一、用户身份认证命令权限控制十二、路径系统设计十三、路径规范化 Bug修复后的思路normalize 的意义十四、sendfile 零拷贝优化sendfile优势1. 更少的 CPU 消耗2. 更少的数据拷贝3. 更少的上下文切换实测十五、断点续传RESTSession 状态下载续传上传续传测试结果十六、线程池优化十七、线程池模型condition_variable为什么任务队列需要 mutex十八、epoll 高并发模型epoll 的核心优势1. 事件驱动2. 红黑树管理 fd3. 回调机制十九、epoll 工作流程创建 epoll注册 fd等待事件非阻塞 IO二十、总结一、搭建 TCP 服务器骨架网络服务器基本步骤socketbindlistenacceptrecv/send先实现一个最小 TCP Server服务器代码intmain(){intserver_fd,client_fd;structsockaddr_inserver_addr,client_addr;socklen_t client_lensizeof(client_addr);charbuf[1024];server_fdsocket(AF_INET,SOCK_STREAM,0);if(server_fd-1){cerrsocket failedendl;return1;}intopt1;setsockopt(server_fd,SOL_SOCKET,SO_REUSEADDR,opt,sizeof(opt));server_addr.sin_familyAF_INET;server_addr.sin_addr.s_addrINADDR_ANY;server_addr.sin_porthtons(2100);if(bind(server_fd,(structsockaddr*)server_addr,sizeof(server_addr))-1){cerrbind failedendl;close(server_fd);return1;}if(listen(server_fd,5)-1){cerrlisten failedendl;close(server_fd);return1;}coutlistening 2100...endl;client_fdaccept(server_fd,(structsockaddr*)client_addr,client_len);if(client_fd-1){cerraccept failedendl;close(server_fd);return1;}constchar*greeting220 ready\r\n;send(client_fd,greeting,strlen(greeting),0);while(true){memset(buf,0,sizeof(buf));intcountrecv(client_fd,buf,sizeof(buf)-1,0);if(count0){break;}buf[count]\0;cout[Received] : buf;}close(client_fd);close(server_fd);}测试服务端./server客户端telnet127.0.0.12100输入hello服务器即可收到数据二、支持多客户端并发当前服务器只能同时处理一个客户端因为accept-recv-recv-recv会一直阻塞。所以需要并发处理。最简单的方法主线程 accept每个客户端一个线程三、线程模型核心思路主线程 accept 新连接 工作线程 专门处理客户端代码threadthr(HandleClient,client_fd,client_ip,client_port);thr.detach();为什么使用 detachthr.detach();让线程独立运行。否则thr.join();会阻塞主线程。这样服务器就无法继续 accept 新客户端。输出为什么会错乱多个线程同时 couthelloabc123日志会交叉。因此需要mutex cout_mtx;配合lock_guardmutex保护输出。四、函数重构随着代码变多main 会越来越混乱。于是把逻辑拆分CreateServerSocketAcceptClientSendGreetingReceiveClientDataHandleClientRunServer这样整个结构会非常清晰。重构后的结构RunServer ├── CreateServerSocket ├── AcceptClient └── HandleClient ├── SendGreeting └── ReceiveClientData五、FTP 协议基础控制连接负责发送命令USER PASS LIST RETR STOR数据连接真正传输文件六、命令解析FTP 命令格式COMMAND arg\r\n例如USER alice\r\n因此需要解决 TCP 粘包按行解析提取命令和参数行缓冲区string line_buf;每次 recv 后line_buf.append(buf,count);然后寻找\r\n作为一条完整 FTP 命令。命令解析size_t spacecmd.find( );string opcmd.substr(0,space);string arg...例如USER alice解析后op USER arg alice为什么要转大写FTP 命令大小写不敏感。因此transform(op.begin(),op.end(),op.begin(),::toupper);统一转换。七、PASV 被动模式为什么需要数据连接FTP 协议规定控制连接发送命令数据连接传文件LIST / RETR / STOR 都必须走数据连接。PASV 工作流程第一步客户端发送PASV第二步服务器创建新的监听 socket随机绑定端口返回端口号例如227 Entering Passive Mode (127,0,0,1,19,136)其中port 19 * 256 136动态端口data_addr.sin_porthtons(0);端口设置为 0。让系统自动分配。再通过getsockname获取真实端口。八、LIST 命令LIST 的流程客户端PASV 服务器返回数据端口 客户端连接数据端口 客户端LIST 服务器accept 数据连接 服务器发送目录内容为什么 LIST 要 accept因为 PASV 时服务器只是 listen。真正的数据连接需要客户端主动 connect。因此 LIST 时必须accept(data_listen_fd,...)遍历目录DIR*diropendir(path.c_str());然后readdir(dir)获取目录项。安全问题if(path.find(..)!string::npos)防止目录穿越。否则客户端可能访问../../etc/passwd九、文件下载RETRFTP 下载流程PASV RETR filename服务器打开文件accept 数据连接send 文件内容基础实现while((nfread(buf,1,sizeof(buf),fp))0){send(data_client_fd,buf,n,0);}磁盘 - 用户态 - 内核态 - Socket会发生多次数据拷贝。后面会优化。十、文件上传STOR上传流程PASV STOR filename服务器recv-fwrite不断接收客户端数据。为什么 recv 返回 0 表示结束因为客户端关闭了数据连接。FTP 文件传输结束本质上就是关闭 data socket十一、用户身份认证FTP 标准流程USER xxx PASS xxx引入boollogged_in和string username命令权限控制在 LIST / RETR / STOR 前增加if(!logged_in)否则530 Please login with USER and PASS.十二、路径系统设计FTP 服务器需要维护当前工作目录例如/ /wo /wo/test因此需要实现CWDPWD路径规范化十三、路径规范化 Bug最开始出现的问题//wo导致LIST 失败原因是res/p;重复拼接了/。修复后的思路核心思想先拆路径再统一拼接例如/wo/test拆成[wo, test]最后统一生成。normalize 的意义它不仅解决//问题。还统一处理. ..所有路径逻辑统一入口处理。否则后面会出现大量边界 Bug。十四、sendfile 零拷贝优化最开始的下载流程磁盘 - 内核缓冲区 - 用户缓冲区 - Socket缓冲区存在用户态 / 内核态切换多次内存拷贝sendfileLinux 提供sendfile实现文件 - Socket直接在内核完成。代码sendfile(data_client_fd,file_fd,offset,remaining);优势1. 更少的 CPU 消耗2. 更少的数据拷贝3. 更少的上下文切换实测使用ddif/dev/zeroofbigfile.binbs1Mcount100生成 100MB 文件。测试下载lftp-p2100127.0.0.1-eget bigfile.bin; quit100MB 文件几乎瞬间完成。十五、断点续传RESTFTP 支持REST offset表示从 offset 开始继续传输Session 状态新增structClientSession{off_t restart_offset0;};每个客户端独立维护。下载续传核心off_t offsetsession.restart_offset;然后sendfile(...,offset,...)sendfile 会自动更新 offset。上传续传上传稍微复杂。需要fseek(fp,session.restart_offset,SEEK_SET);把文件指针移动到断点位置。测试结果中断上传后重新上传350 Restarting at xxx然后继续传输。最后 md5 校验一致。说明断点续传正确。十六、线程池优化前面的模型一个客户端 - 一个线程问题线程创建开销很大。高并发时线程数量暴涨上下文切换严重CPU 被调度拖死十七、线程池模型核心思想预先创建线程任务来了放入任务队列工作线程不断消费任务。condition_variable这里的关键cv.wait(lock,...)线程没有任务时睡眠避免空转浪费 CPU。为什么任务队列需要 mutex因为多个线程会同时pushpop必须加锁。十八、epoll 高并发模型epoll 的核心优势1. 事件驱动只返回真正活跃的 fd2. 红黑树管理 fd3. 回调机制效率极高。十九、epoll 工作流程创建 epollintepfdepoll_create1(0);注册 fdepoll_ctl(epfd,EPOLL_CTL_ADD,fd,ev);等待事件epoll_wait(epfd,events,max_events,-1);非阻塞 IO配合fcntl(fd,F_SETFL,O_NONBLOCK);真正实现单线程高并发二十、总结整个 FTP Server 的演进过程TCP Server ↓ 多线程并发 ↓ FTP 命令解析 ↓ PASV 数据连接 ↓ LIST / RETR / STOR ↓ 路径系统 ↓ 身份认证 ↓ sendfile 零拷贝 ↓ 断点续传 ↓ 线程池 ↓ epoll 高并发

相关文章:

从零实现一个高性能 FTP 服务器(C++ / Linux)

目录一、搭建 TCP 服务器骨架服务器代码测试二、支持多客户端并发三、线程模型核心思路为什么使用 detach输出为什么会错乱四、函数重构重构后的结构五、FTP 协议基础控制连接数据连接六、命令解析行缓冲区命令解析为什么要转大写七、PASV 被动模式为什么需要数据连接&#xff…...

关注模块 API

关注用户 POST /api/v1/relations/followHeaders:Authorization: Bearer {token}Request: {"user_id": "target_user_id" }Response: {"code": 0,"data": {"relation_type": "following"} }接口语义设计 POST /…...

仪式感,从来与你无关

2.2万人点赞的扎心评论:仪式感,从来都与你无关 有2.2万个男生偷偷点了赞。 没有歇斯底里的控诉,没有长篇大论的抱怨,只有一句轻飘飘的陈述,和一句"兄弟,没绷住"。 但就是这两句话,像一根针,精准地扎破了无数男生藏在心里最深处的、不敢说出口的委屈。 01…...

LangChain 是什么?从零开始学会 LangChain 的工程实践指南

LangChain 是什么?从零开始学会 LangChain 的工程实践指南 1. 文章背景:为什么这个主题重要 在大模型应用开发中,很多人第一次接触 LangChain,是因为想快速做一个“基于大模型的应用”:例如知识库问答、RAG 检索增强生…...

Python EXE逆向工程完全指南:使用python-exe-unpacker快速反编译打包程序

Python EXE逆向工程完全指南:使用python-exe-unpacker快速反编译打包程序 【免费下载链接】python-exe-unpacker A helper script for unpacking and decompiling EXEs compiled from python code. 项目地址: https://gitcode.com/gh_mirrors/py/python-exe-unpa…...

Pure Live:3大平台聚合,打造你的专属纯净直播空间

Pure Live:3大平台聚合,打造你的专属纯净直播空间 【免费下载链接】pure_live A Flutter project can make you watch live with ease. 项目地址: https://gitcode.com/gh_mirrors/pu/pure_live 你是否厌倦了在多个直播应用间来回切换&#xff1f…...

【RK3588-AI-004】RK3588 AI专属依赖环境预装(Python、OpenCV、基础编译工具)

📖 专栏介绍 本专栏为RK3588 端侧AI开发零基础实战教程,专为嵌入式AI入门、模型部署、视觉开发学习者打造。全程实操、无废话、避坑优化,从零搭建RK3588专属AI开发环境,手把手教学,新手也能轻松上手。 ✅ 硬件适配&am…...

深入拆解 MySQL InnoDB 隔离级别:从 MVCC 到临键锁

前言 关于 MySQL InnoDB 的事务隔离级别,90% 的开发者都存在至少一个致命误区: 误区1:RR(可重复读) 临键锁 彻底解决了幻读误区2:Serializable 只是比 RR 加的锁更多,本质还是用 MVCC误区3&a…...

2026.5.12【芯片设计面试经验分享】上海车载芯片设计公司

一、主管面试 1、介绍下负责的cpu的九级流水线都有哪级? 指令预取、PC取指、指令译码、发射(双发射)、执行1(alu、运算)、执行2(乘法、移位)、访存、写回、提交/重排 2、负责的spyglass cdc 一般…...

编译和链接+预处理

编译(compile)和链接(link)在以前我们提到过,C语言是一门编译型的计算机语言,C语言的源代码都是文本文件,文本文件本身无法运行,电脑不能执行C语言代码,计算机能够执行的…...

数分-MySQL基础01

数分-MySQL基础01基础概念MySQL数据库对象MySQL的架构MySQL客户端和服务器端连接方式命令行连接方式图形化客户端连接SQL语言分类通用语法(所有数据库)DDL语句数据库DDL数据表DDL表字段DDL数据类型字段约束基础概念 数据库(Database, DB&…...

Spring AI Alibaba 1.x 系列【55】Interrupts 中断机制:静态中断源码分析

文章目录 1. interruptBefore 模式1.1 中断判断逻辑1.2 构建中断元数据1.3 返回中断响应1.4 初始化【中断执行】上下文1.5 合并状态(BUG)1.6 执行结束 2. interruptsAfter 模式2.1 设置 INTERRUPT_AFTER 标记2.2 动态计算下一个节点 3. 中断时机对比 1. …...

【Linux驱动开发】第11天:设备树(Device Tree)超详细全解:从诞生背景到工作原理

一、设备树的诞生背景:传统驱动的致命痛点 在设备树出现之前(Linux 3.0之前),Linux内核采用硬编码的方式描述所有硬件信息。这意味着: 每一个开发板的寄存器地址、中断号、GPIO号,都直接写死在驱动代码里换…...

【Linux驱动开发】第10天:设备树零基础入门——DTS/DTB/DTC全解+编译流程

目录 为什么需要设备树?传统驱动的终极痛点DTS/DTB/DTC 大白话定义核心区别三者关系完整编译流程图最简单的DTS示例语法解析设备树编译与反编译实操命令内核如何加载和使用设备树核心总结面试必背考点 1. 为什么需要设备树?传统驱动的终极痛点 在设备树…...

TowerPersonalProperty.cs

TowerPersonalProperty 是塔的标准化攻击组件,攻击节奏(CD管理)子弹发射(从对象池获取并配置)视觉表现(旋转、动画、音效、特效)经济交互(升级/出售价格计算与金币变更)它…...

[工具] 数学题库生成器(小学,初中,高中全包括) 面向中小学数学教学的自动出题工具,覆盖从小学一年级到高中三年级共 7 个学段、33 种题型

数学题库生成器(小学,初中,高中全包括) 基本覆盖各个年级的重点题型生成,并导出为word,可以显示解题步骤。# 数学题库生成器 MathMaster 数学题库生成器(MathMaster)是一款面向中小学…...

硬件工程师,每天5分钟(5)——为什么 DDR5 最怕地不好?回流路径,才是高速设计真正的灵魂

讲透: 回流路径 为什么 Split Plane 最危险 为什么加地孔有时候能救命 为什么 GPS 会被 DDR 干扰 为什么 EMC 挂的根因常是地 🚗《硬件工程师,每天5分钟》第5篇 🔥《为什么 DDR5 最怕地不好?回流路径,才是高…...

11.三层网络VXLAN

先把之前基于flat模式创建的虚机,全部删除 控制节点配置:1.修改配置文件/etc/neutron/neutron.conf 将[DEFAULT]区域 core_plugin ml2 service_plugins 修改为 core_plugin ml2 service_plugins router allow_overlapping_ips True2.修改/etc/neutro…...

数采网关的应用与特点

摘要在工业自动化、智能制造和物联网(IoT)快速发展的背景下,数据采集网关(数采网关)作为连接现场设备与上层管理系统的关键枢纽,发挥着至关重要的作用。它能够实现工业设备数据的实时采集、协议转换、边缘计…...

第2章:文档加载与智能分块——RAG的第一步

本章你将收获:支持PDF(含表格)、Word、Markdown、网页、CSV等10+格式的完整加载代码;五种分块策略的深度对比(固定大小、递归字符、语义、文档结构、按标题);元数据保留与增强的工程方法;处理100页混合格式技术手册的完整实战;以及分块参数调优的最佳实践。 📌 本章…...

西门子PLC对接须知:从通信到编程的实战指南

在工业自动化领域,西门子S7系列PLC凭借强大的功能和广泛的兼容性,成为众多企业的首选。无论是设备集成、数据采集还是系统升级,掌握PLC对接的核心要点,是保障项目高效落地的关键。本文将从通信连接、编程架构、数据处理三个维度&a…...

ComfyUI全面掌握-知识点详解——ComfyUI 开发与扩展基础(开发指南+环境搭建)

本文为「ComfyUI 全面掌握」系列第 23 篇,是高阶进阶章节的第一篇知识点详解博客。作为开发系列的起点,本文将带你系统了解 ComfyUI 社区贡献流程,并手把手搭建完整的自定义节点开发环境,为后续的节点开发与发布奠定坚实的技术基础…...

STM32矩阵按键详解——4×4行列扫描与非阻塞消抖(硬件总结六)

前言 独立按键虽然简单,但当产品需要十几个按键时,每个按键独占一个GPIO的接法就变得很不经济。矩阵按键通过“行列”的交叉结构,仅用NM个GPIO即可驱动NM个按键。以最常见的44矩阵为例,16个按键仅需8个GPIO,引脚利用率…...

鸿蒙中的自由流转

鸿蒙自由流转是 ‌HarmonyOS(鸿蒙系统)‌ 实现多设备协同的核心能力之一,旨在打破设备边界,让应用和服务在不同终端间无缝流转,提升用户体验。‌什么是鸿蒙自由流转?‌鸿蒙自由流转是指用户在多个搭载 Harm…...

RUST编程学习.2语法

目录 前言 一、思维导图 二、Rust语法专属 1.迭代器 2.生命周期 总结 前言 在进行编译器下载后,就可以写代码进行编译调试了,在这之前就是要学习rust语法,在学习的过程中我整理了一版思维导图,最直观的感觉就是rust的语法很…...

【SSD】闪存1

闪存的特点 闪存是非易失存储器,掉电了数据也不会丢失,但是闪存不能够覆写,必须按块擦除,按页写入。 闪存的基本单元 闪存的基本单元是Cell,一种类Nmos的双层浮栅MOS管 MOS管 首先理解什么是MOS管:(金…...

性价比高的国产PLM软件公司

在制造业领域,不少企业都面临着研发效率低下、协同困难等问题。比如某电子制造企业,研发部门与生产部门之间信息沟通不畅,图纸版本管理混乱,导致产品研发周期延长,生产成本增加,新品上市时间比预期晚了近30…...

分布式团队的代码协作规范:从分支策略到提交信息格式

在分布式团队模式下,代码协作的地域分散、时区差异和沟通成本,给版本控制和质量保障带来了严峻挑战。作为软件测试从业者,我们不仅是代码质量的“守门员”,更需要深入理解并推动执行规范的代码协作流程,从分支管理到提…...

几十万买的数字孪生低代码平台集体落灰?被隐瞒的落地真相,终于说透了

在政企数字化采购圈子里,一直有个特别讽刺、且年年重复上演的现象。很多企业、政府单位,手握专项数字化预算,毫不犹豫花几十万重金购入数字孪生、3D可视化低代码平台。采购前被厂商的宣传话术打动:零代码拖拽、人人上手、无需专业…...

在家办公效率低?试试这个“空间切换”技巧

一、软件测试从业者居家办公的效率困境对于软件测试从业者而言,居家办公看似摆脱了办公室的嘈杂与束缚,实则面临着诸多独特的效率挑战。测试工作本身就需要高度的专注与严谨,从需求分析、用例设计到缺陷跟踪,每一个环节都容不得半…...