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

FTP客户端实现(文件传输)

文章目录

    • 🧱 一、FTP 基础架构回顾
    • 🚀 二、FTP 客户端的核心结构
    • 🔗 三、连接与登录过程
      • 📌 1. `ftp_create()`
      • 📌 2. `ftp_connect()`
      • 📌 3. `ftp_login()`
    • 📁 四、上传文件实现(`ftp_upload_file()`)
      • 🚩 流程总览:
      • ✨ 细节讲解
        • ✅ 1. 判断 remote_path 是目录 or 文件路径
        • ✅ 2. 递归创建远程目录
        • ✅ 3. 进入 PASV 模式(被动模式)
        • ✅ 4. 发起数据连接(客户端主动连接)
        • ✅ 5. 发送 STOR 命令
        • ✅ 6. 发送文件内容(TCP)
    • 🔚 五、退出与清理
    • 📢 六、辅助函数详解
      • ✅ `send_command()`
      • ✅ `read_reply()`
    • 📌 七、完整上传流程演示(例子)
    • ✅ 总结:功能完整的 FTP 上传模块


这段代码实现了一个 FTP 客户端核心功能模块,可完成如下关键任务:

✅ 建立连接
✅ 登录认证
✅ 递归创建远程目录
✅ 上传文件(支持上传到目录)
✅ 退出登录


下面我将按照功能模块,详细讲解整个 FTP 上传流程的实现原理和关键函数


🧱 一、FTP 基础架构回顾

FTP 通信包括两个通道:

  • 控制通道(control channel):用于发送 FTP 命令和接收响应(TCP 21)
  • 数据通道(data channel):用于传输文件、目录等(通过 PASVPORT 创建)

本实现采用 PASV(被动模式) 建立数据通道,即客户端主动连接服务端开放的端口。


🚀 二、FTP 客户端的核心结构

typedef struct {int control_sock; // 控制通道 socketstruct sockaddr_in server_addr;
} FTPClient;

🔗 三、连接与登录过程

📌 1. ftp_create()

  • 创建控制通道 socket:socket(AF_INET, SOCK_STREAM, 0)
  • 配置远程 IP 和端口(默认 21)
  • 返回已初始化的 FTPClient 指针

📌 2. ftp_connect()

  • 使用 connect() 发起 TCP 控制连接
  • 成功后读取服务端欢迎信息(FTP 状态码应该是 220
read_reply(...) == 220 → 成功

📌 3. ftp_login()

  • 先发送用户名命令:USER xxx\r\n
    • 响应应为 331,表示继续输入密码
  • 再发送密码命令:PASS xxx\r\n
    • 响应应为 230,表示登录成功

📁 四、上传文件实现(ftp_upload_file()

🚩 流程总览:

→ 判断 remote_path 是目录还是完整路径
→ 如果是目录:拼接本地文件名
→ 递归创建远程目录(MKD)
→ 进入 PASV 模式,获取数据连接地址和端口
→ 发起数据连接(客户端连接服务端返回的地址/端口)
→ 发送 STOR 上传命令
→ 传输文件内容
→ 关闭数据连接并读取完成响应(226)

✨ 细节讲解

✅ 1. 判断 remote_path 是目录 or 文件路径
if (remote_path[strlen(remote_path) - 1] == '/') {// 是目录 → 拼接 local_file 的文件名
}

例:

local_file = "/home/user/a.txt"
remote_path = "upload/abc/"
→ final_remote_file = "upload/abc/a.txt"

✅ 2. 递归创建远程目录
ftp_create_remote_path(client, remote_dir);

内部调用:

MKD /upload
MKD /upload/abc
MKD /upload/abc/xyz

错误 550 表示已存在,也视作成功。


✅ 3. 进入 PASV 模式(被动模式)
send_command(..., "PASV\r\n");
read_reply(...); // 解析响应 227→ 解析返回的 IP 和端口:
"227 Entering Passive Mode (192,168,1,10,14,78)"
→ port = 14*256 + 78 = 3678

服务端会在该端口等待数据连接。


✅ 4. 发起数据连接(客户端主动连接)
data_sock = socket(...)
connect(data_sock, ...)

目标 IP = 控制连接 IP,端口 = PASV 返回的端口。


✅ 5. 发送 STOR 命令
STOR upload/abc/a.txt\r\n
→ 服务端响应 150:准备传输数据

✅ 6. 发送文件内容(TCP)
while (fread(...) > 0) {send(data_sock, ...);
}

成功发送完后:

  • 关闭 data_sock
  • 读取控制通道响应是否为 226 → 传输完成

🔚 五、退出与清理

ftp_quit(client);
→ 发送 QUIT\r\n,服务端应返回 221ftp_destroy(client);
→ 关闭 socket,释放内存

📢 六、辅助函数详解

send_command()

send(sock, command, strlen(command), 0);

发送命令字符串,例如:

"USER test\r\n"
"STOR /path/file.txt\r\n"

read_reply()

recv(sock, buffer, ...);
→ 返回第一段数字作为 FTP 响应码(例如 220331226

📌 七、完整上传流程演示(例子)

local_file = "/home/user/a.txt"
remote_path = "upload/test/"  // 目录

程序实际执行逻辑:

  1. 拼接路径 → upload/test/a.txt
  2. 创建路径:MKD /upload、/upload/test
  3. PASV 模式,服务端返回数据端口
  4. 建立数据连接
  5. STOR upload/test/a.txt
  6. 发送文件数据
  7. 等待 226 响应
  8. QUIT → 221

✅ 总结:功能完整的 FTP 上传模块

功能是否支持
控制连接建立
用户登录认证
被动模式数据传输
自动创建多级目录
上传任意路径文件
退出与资源释放

核心代码:

#include "ftpclient.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>#define BUF_SIZE 1024// 内部函数声明
static int send_command(int sock, const char* command);
static int read_reply(int sock, char* buffer, size_t size);
static int ftp_create_dir(FTPClient* client, const char* remote_dir);// 创建客户端
FTPClient* ftp_create(const char* server_ip, int server_port) {FTPClient* client = (FTPClient*)malloc(sizeof(FTPClient));if (!client) return NULL;client->control_sock = socket(AF_INET, SOCK_STREAM, 0);if (client->control_sock < 0) {free(client);return NULL;}memset(&client->server_addr, 0, sizeof(client->server_addr));client->server_addr.sin_family = AF_INET;client->server_addr.sin_port = htons(server_port);inet_pton(AF_INET, server_ip, &client->server_addr.sin_addr);return client;
}void ftp_destroy(FTPClient* client) {if (client) {if (client->control_sock >= 0)close(client->control_sock);free(client);}
}int ftp_connect(FTPClient* client) {if (connect(client->control_sock, (struct sockaddr*)&client->server_addr, sizeof(client->server_addr)) < 0)return -1;char buffer[BUF_SIZE];return read_reply(client->control_sock, buffer, sizeof(buffer)) == 220 ? 0 : -1;
}int ftp_login(FTPClient* client, const char* username, const char* password) {char command[BUF_SIZE];char buffer[BUF_SIZE];snprintf(command, sizeof(command), "USER %s\r\n", username);if (send_command(client->control_sock, command) < 0) return -1;if (read_reply(client->control_sock, buffer, sizeof(buffer)) != 331) return -1;snprintf(command, sizeof(command), "PASS %s\r\n", password);if (send_command(client->control_sock, command) < 0) return -1;if (read_reply(client->control_sock, buffer, sizeof(buffer)) != 230) return -1;return 0;
}int ftp_upload_file(FTPClient* client, const char* local_file, const char* remote_path) {FILE* file = fopen(local_file, "rb");if (!file) return -1;char buffer[BUF_SIZE];// 处理 remote_file:判断是否是目录char final_remote_file[BUF_SIZE];if (remote_path[strlen(remote_path) - 1] == '/') {// 提取文件名const char* file_name = strrchr(local_file, '/');file_name = file_name ? file_name + 1 : local_file;snprintf(final_remote_file, sizeof(final_remote_file), "%s%s", remote_path, file_name);} else {strncpy(final_remote_file, remote_path, sizeof(final_remote_file));final_remote_file[sizeof(final_remote_file) - 1] = '\0';}// 提取目录路径并创建char remote_dir[BUF_SIZE];strncpy(remote_dir, final_remote_file, sizeof(remote_dir));remote_dir[sizeof(remote_dir) - 1] = '\0';char* last_slash = strrchr(remote_dir, '/');if (last_slash) {*last_slash = '\0';ftp_create_remote_path(client, remote_dir); // 忽略失败}// PASV 模式if (send_command(client->control_sock, "PASV\r\n") < 0) {fclose(file);return -1;}if (read_reply(client->control_sock, buffer, sizeof(buffer)) != 227) {fclose(file);return -1;}int ip1, ip2, ip3, ip4, p1, p2;sscanf(buffer, "227 Entering Passive Mode (%d,%d,%d,%d,%d,%d)",&ip1, &ip2, &ip3, &ip4, &p1, &p2);int data_port = p1 * 256 + p2;int data_sock = socket(AF_INET, SOCK_STREAM, 0);if (data_sock < 0) {fclose(file);return -1;}struct sockaddr_in data_addr;memset(&data_addr, 0, sizeof(data_addr));data_addr.sin_family = AF_INET;data_addr.sin_port = htons(data_port);data_addr.sin_addr = client->server_addr.sin_addr;if (connect(data_sock, (struct sockaddr*)&data_addr, sizeof(data_addr)) < 0) {close(data_sock);fclose(file);return -1;}snprintf(buffer, sizeof(buffer), "STOR %s\r\n", final_remote_file);if (send_command(client->control_sock, buffer) < 0) {close(data_sock);fclose(file);return -1;}if (read_reply(client->control_sock, buffer, sizeof(buffer)) != 150) {close(data_sock);fclose(file);return -1;}size_t bytes_read;while ((bytes_read = fread(buffer, 1, sizeof(buffer), file)) > 0) {if (send(data_sock, buffer, bytes_read, 0) < 0) {close(data_sock);fclose(file);return -1;}}close(data_sock);fclose(file);return (read_reply(client->control_sock, buffer, sizeof(buffer)) == 226) ? 0 : -1;
}void ftp_quit(FTPClient* client) {send_command(client->control_sock, "QUIT\r\n");char buffer[BUF_SIZE];read_reply(client->control_sock, buffer, sizeof(buffer));
}// 创建路径:递归 MKD
int ftp_create_remote_path(FTPClient* client, const char* remote_path) {char path[BUF_SIZE];strncpy(path, remote_path, sizeof(path));path[sizeof(path) - 1] = '\0';char temp[BUF_SIZE] = {0};char* token = strtok(path, "/");while (token) {strcat(temp, "/");strcat(temp, token);ftp_create_dir(client, temp);  // 忽略失败token = strtok(NULL, "/");}return 0;
}static int ftp_create_dir(FTPClient* client, const char* remote_dir) {char buffer[BUF_SIZE];snprintf(buffer, sizeof(buffer), "MKD %s\r\n", remote_dir);if (send_command(client->control_sock, buffer) < 0) return -1;int code = read_reply(client->control_sock, buffer, sizeof(buffer));return (code == 257 || code == 550) ? 0 : -1; // 550 表示目录已存在
}static int send_command(int sock, const char* command) {return (send(sock, command, strlen(command), 0) == strlen(command)) ? 0 : -1;
}static int read_reply(int sock, char* buffer, size_t size) {int bytes_read = recv(sock, buffer, size - 1, 0);if (bytes_read <= 0) return -1;buffer[bytes_read] = '\0';return atoi(buffer);
}

相关文章:

  • DreamDiffusion的mae_for_eeg.py网络架构
  • 基于maven-jar-plugin打造一款自动识别主类的maven打包插件
  • [Spring]SSM整合
  • 游戏引擎学习第238天:让 OpenGL 使用我们的屏幕坐标
  • 基于Redis实现RAG架构的技术解析与实践指南
  • idea中运行groovy程序报错
  • 【perf】perf工具的使用生成火焰图
  • 基于 OpenCV 的图像与视频处理
  • Kubernetes(k8s)学习笔记(二)--k8s 集群安装
  • React+TS编写轮播图
  • 计算机视觉cv入门之Haarcascade的基本使用方法(人脸识别为例)
  • 【后端】【Django】Django 模型中的 `clean()` 方法详解:数据校验的最后防线
  • 【人工智能】推荐开源企业级OCR大模型InternVL3
  • css3新特性第四章(渐变)
  • 【条形码识别改名工具】如何批量识别图片条形码,并以条码内容批量重命名,基于WPF和Zxing的开发总结
  • 【iOS】alloc init new底层原理
  • 嵌入式---零点漂移(Zero Drift)
  • 网络设备基础运维全攻略:华为/思科核心操作与巡检指南
  • IDEA多环节实现优雅配置
  • IDEA在Git提交时添加.ignore忽略文件,解决为什么Git中有时候使用.gitignore也无法忽略一些文件
  • 纪念沈渭滨︱沈渭滨先生与新修《清史》
  • 南部战区回应菲护卫艇非法侵入中国黄岩岛领海:依法警告驱离
  • 2025中国互联网企业家座谈会在京召开
  • 从6家试点扩展至全行业,券商并表监管有何看点?
  • 人民文学奖颁出,董宇辉获传播贡献奖
  • 网信部门持续整治利用未成年人形象不当牟利问题