学完 socket 网络编程之后,只会写一些小demo,往往还缺一个更接近真实服务的练习项目:既能用标准客户端对接,代码量又不会太大。整理了一个网络编程的项目 Tinyftp------一个用 C 写的简易 FTP 服务器,大约三千行代码,这个项目参考 vsftpd,十分具备学习意义。
1. 项目概览
Tinyftp 适合作为网络编程练手项目,主要因为:
- 兼容常见 FTP 命令,可用 FileZilla 等标准客户端
- 功能较完整:登录、PORT/PASV、上传下载、目录操作、限速、断点续传、连接数限制
- 架构清晰:多进程 + nobody 特权分离,接近精简版 vsftpd
一句话总结:代码量不是太大,符合RFC协议规范,框架还不错。
2. FTP 基础
FTP 使用两根连接:
控制连接
持续存在,传输命令与应答,例如 USER、PASS、RETR,以及 220、230、226 等状态码。
数据连接
临时建立,用于传文件、列目录;传输结束后关闭。

数据连接有两种模式:
- PORT(主动):服务器连接客户端
- PASV(被动) :客户端连接服务器

Tinyftp 两种模式均已实现。主动模式需绑定 20 端口,相关特权操作由 nobody 进程完成,这是源码阅读的重点之一。
3. 源码阅读顺序与模块分工
tinyftpd.conf------ 配置项main.c------ 监听与 acceptsession.c------ 会话内二次 forkftpproto.c------ 命令表与协议主循环privparent.c、privsock.*------ 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 主循环可记为四步:
- 发送欢迎应答
220 readline读取一行命令- 拆分命令与参数,命令转大写
- 查表调用处理函数;未知命令返回
500
应答格式统一为:
text
状态码 + 空格 + 说明文字 + \r\n
多数应用层协议服务器的骨架与此类似:读入 → 解析 → 分发 → 回写。
6. 登录流程
未使用自建用户表,而是对接系统账号:
USER→getpwnam,存在则返回331PASS→ 读取 shadow,经crypt校验,成功返回230seteuid/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,可将以下内容串起来:
- 应用层协议如何用「状态码 + 命令表」组织
- FTP 双连接模型,以及 PORT / PASV 的连接方向
- 特权分离思想,nobody 进程的职责
- 进程间协作与文件描述符传递
