学完 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—— 监听与 accept session.c—— 会话内二次 fork ftpproto.c—— 命令表与协议主循环 privparent.cprivsock.*—— nobody 与 FD 传递
src/ 模块大致如下:
main.c | |
session.c | |
ftpproto.c | |
privparent.c | |
privsock.* | |
hash.* | |
ftpcodes.h | |
parseconftunable |
常见配置: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 采用表驱动方式组织命令,例如:
USER → do_userPASS → do_passPORT → do_portPASV → do_pasvRETR → do_retrSTOR → do_storLIST → do_listREST → do_rest(另有 CWD / MKD / DELE / ABOR 等)
handle_child 主循环可记为四步:
发送欢迎应答 220readline读取一行命令 拆分命令与参数,命令转大写 查表调用处理函数;未知命令返回 500
应答格式统一为:
状态码 + 空格 + 说明文字 + \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 监听随机端口,返回类似:
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 进程的职责 进程间协作与文件描述符传递
夜雨聆风