Tinyftp 源码学习笔记:3000 行 C 实现的 FTP 服务器

学完 socket 网络编程之后,只会写一些小demo,往往还缺一个更接近真实服务的练习项目:既能用标准客户端对接,代码量又不会太大。整理了一个网络编程的项目 Tinyftp------一个用 C 写的简易 FTP 服务器,大约三千行代码,这个项目参考 vsftpd,十分具备学习意义。

1. 项目概览

Tinyftp 适合作为网络编程练手项目,主要因为:

  • 兼容常见 FTP 命令,可用 FileZilla 等标准客户端
  • 功能较完整:登录、PORT/PASV、上传下载、目录操作、限速、断点续传、连接数限制
  • 架构清晰:多进程 + nobody 特权分离,接近精简版 vsftpd

一句话总结:代码量不是太大,符合RFC协议规范,框架还不错。

2. FTP 基础

FTP 使用两根连接:

控制连接

持续存在,传输命令与应答,例如 USERPASSRETR,以及 220230226 等状态码。

数据连接

临时建立,用于传文件、列目录;传输结束后关闭。

数据连接有两种模式:

  • PORT(主动):服务器连接客户端
  • PASV(被动) :客户端连接服务器

    Tinyftp 两种模式均已实现。主动模式需绑定 20 端口,相关特权操作由 nobody 进程完成,这是源码阅读的重点之一。

3. 源码阅读顺序与模块分工

  1. tinyftpd.conf ------ 配置项
  2. main.c ------ 监听与 accept
  3. session.c ------ 会话内二次 fork
  4. ftpproto.c ------ 命令表与协议主循环
  5. privparent.cprivsock.* ------ nobody 与 FD 传递

src/ 模块大致如下:

文件 作用
main.c 监听、连接数限制、fork 会话
session.c 会话启动:服务进程 / nobody
ftpproto.c 命令分发、上传下载、LIST 等
privparent.c nobody:绑 20 端口、建立数据连接
privsock.* 父子进程通信、传递文件描述符
hash.* 按 IP 统计连接数
ftpcodes.h FTP 状态码
parseconf / tunable 配置加载

常见配置:listen_port(示例多为 5188)、PASV/PORT 开关、最大连接数、上下行限速、空闲超时等。

4. 核心结构:三层进程模型

第一层:主进程

以 root 启动,读取配置并 listen。

客户端接入后:accept → 统计该 IP 连接数 → fork:

  • 子进程:持有 connfd,进入 begin_session
  • 父进程:关闭 connfd,继续监听;在 SIGCHLD 中回收子进程并更新连接计数

超过限制时返回 421

这一层把「接客」与「处理会话」分开。

第二层:会话内再 fork

begin_session() 中再次 fork:

  • 子进程 handle_child:解析命令、传输数据;登录成功后切换为普通用户权限
  • 父进程 handle_parent:降权为 nobody,负责特权相关操作

可概括为:

协议处理运行在较低权限;需要特权(如绑定 20 端口)时,由 nobody 代为执行。

第三层:socketpair 与传 FD

父子进程通过内部通道交换命令,例如:

  • 按 PORT 信息连接客户端
  • 开启 PASV 监听 / accept
  • 成功后将 data_fd 传回服务进程

5. 协议主循环:命令表驱动

ftpproto.c 采用表驱动方式组织命令,例如:

text 复制代码
USER → do_user
PASS → do_pass
PORT → do_port
PASV → do_pasv
RETR → do_retr
STOR → do_stor
LIST → do_list
REST → do_rest
(另有 CWD / MKD / DELE / ABOR 等)

handle_child 主循环可记为四步:

  1. 发送欢迎应答 220
  2. readline 读取一行命令
  3. 拆分命令与参数,命令转大写
  4. 查表调用处理函数;未知命令返回 500

应答格式统一为:

text 复制代码
状态码 + 空格 + 说明文字 + \r\n

多数应用层协议服务器的骨架与此类似:读入 → 解析 → 分发 → 回写

6. 登录流程

未使用自建用户表,而是对接系统账号:

  1. USERgetpwnam,存在则返回 331
  2. PASS → 读取 shadow,经 crypt 校验,成功返回 230
  3. seteuid / setegid,并 chdir 到用户家目录

因此需要 root 启动(读取 shadow 后再降权)。

作为学习项目结构清晰;若部署到公网,需考虑网络安全问题。

7. PORT / PASV 流程笔记

PORT(主动)

客户端告知服务器目标 IP 与端口。

服务器先返回 200;真正进行 LIST / RETR / STOR 时,由 nobody 从 20 端口发起 connect,再将数据套接字 FD 交回服务进程。

PASV(被动)

服务进程请求 nobody 监听随机端口,返回类似:

text 复制代码
227 Entering Passive Mode (h1,h2,h3,h4,p1,p2)

客户端连入后,accept 得到的 FD 同样回传给服务进程用于传数。

两种模式最终都会进入 get_transfer_fd()

若尚未执行 PORT 或 PASV,则返回 425 Use PORT or PASV first

阅读建议:可先沿 PASV → LIST/RETR 跟完整条路径,再对照 PORT 实现。

8. 其他值得记录的点

  • REST:断点续传偏移
  • limit_rate:根据当前速率 sleep,实现限速
  • ABOR :传输过程中中断(与 SIGURG 相关)
  • 连接数限制:总连接数 / 每 IP 连接数,配合哈希表维护

这些点使项目更接近可用的 FTP 服务实现,而不只是最小 demo。

9. 编译与运行备忘

git clone 仓库后进入目录,按 README 编译(部分版本脚本名为 bulid.sh)。

注意:

  • 需以 root 运行
  • 监听端口以 tinyftpd.conf 为准
  • 可用 FileZilla 连接本机验证
  • 建议仅用于学习,勿直接作为公网 FTP 服务暴露

10. 小结

通过阅读 Tinyftp,可将以下内容串起来:

  1. 应用层协议如何用「状态码 + 命令表」组织
  2. FTP 双连接模型,以及 PORT / PASV 的连接方向
  3. 特权分离思想,nobody 进程的职责
  4. 进程间协作与文件描述符传递

项目链接

相关推荐
K成长日志1 小时前
BLE链路层--比特流处理
网络·物联网·网络协议·蓝牙·iot·ble·无线
怪奇云呼军1 小时前
闪电智能VoiceAgent 如何管理呼入、接听、桥接和挂断状态?
java·前端·网络·数据库·人工智能
予昊2 小时前
Linux 资源控制实战分析:用 LXC 亲手造一个 “迷你虚拟机“
linux·运维·服务器
x_x-F2 小时前
DMA技术详解:解放CPU的数据搬运工
网络
xiaoxiangsiyan2 小时前
RHCE之2026年全套考点详解
运维·前端·网络·chrome·学习·rhce
秋田君2 小时前
QT_实战TCP 聊天程序说明文档以及实现
数据库·qt·tcp/ip
腾科IT教育2 小时前
网络安全与网络空间安全:概念、产业与防护建议
网络·安全·web安全·网络安全·网络空间安全
0Kilobyte2 小时前
自建拨号服务器上网
网络
caimouse2 小时前
ReactOS 图形系统分析(33):字体子系统 — font.c
c语言·开发语言·reactos