FTP 协议介绍与 C 语言开发案例

一、FTP 协议概述

文件传输协议(File Transfer Protocol, FTP)是用于在网络上进行文件传输的标准协议,它基于客户端 - 服务器模型,使用 TCP 作为传输层协议,默认通过 20 和 21 两个端口进行通信。其中,端口 21 用于控制连接(发送命令和接收响应),端口 20 用于数据传输(上传或下载文件)。

FTP 协议的主要特点包括:

  1. 命令与响应机制:客户端通过发送标准命令(如 USER、PASS、LIST、RETR、STOR 等)与服务器交互,服务器返回三位数字的状态码及描述信息。

  2. 两种工作模式

    • 主动模式(Active Mode):服务器主动连接客户端的数据端口(通常是 20)。
    • 被动模式(Passive Mode):服务器开放一个临时端口,客户端主动连接该端口进行数据传输,适合在防火墙环境下使用。
  3. 文件传输类型:支持 ASCII 文本模式和二进制模式,确保不同系统间文件格式的兼容性。

  4. 用户认证:通常需要用户名和密码进行身份验证,也支持匿名访问(使用 anonymous 作为用户名)。

二、FTP 协议的工作流程

FTP 会话的基本工作流程如下:

  1. 建立控制连接:客户端连接到服务器的 21 端口,进行初始握手和协议协商。

  2. 用户认证:客户端发送 USER 和 PASS 命令进行身份验证。

  3. 命令交互:客户端发送各种命令(如改变目录 CWD、列出文件 LIST 等),服务器返回响应。

  4. 数据传输:当需要上传或下载文件时,客户端和服务器建立数据连接(主动或被动模式),完成文件传输。

  5. 关闭连接:会话结束后,关闭数据连接和控制连接。

三、C 语言实现 FTP 客户端

下面我们用 C 语言实现一个简单的 FTP 客户端,支持基本的文件传输功能。这个示例将展示如何使用 socket 编程实现 FTP 协议的核心功能。

复制代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <netdb.h>

#define FTP_CONTROL_PORT 21
#define BUFFER_SIZE 1024
#define COMMAND_SIZE 256

// 全局变量
int control_socket;
char response[BUFFER_SIZE];

// 函数声明
int connect_to_server(const char *host, int port);
void send_command(const char *command);
int receive_response();
void login(const char *user, const char *pass);
void list_files();
void download_file(const char *filename);
void upload_file(const char *filename);
void disconnect();

// 连接到FTP服务器
int connect_to_server(const char *host, int port) {
    struct hostent *server;
    struct sockaddr_in server_addr;
    
    // 创建套接字
    if ((control_socket = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
        perror("socket creation error");
        return -1;
    }
    
    // 获取主机信息
    if ((server = gethostbyname(host)) == NULL) {
        perror("host not found");
        close(control_socket);
        return -1;
    }
    
    // 设置服务器地址
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    memcpy(&server_addr.sin_addr.s_addr, server->h_addr, server->h_length);
    server_addr.sin_port = htons(port);
    
    // 连接服务器
    if (connect(control_socket, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
        perror("connection failed");
        close(control_socket);
        return -1;
    }
    
    // 接收欢迎信息
    if (receive_response() != 220) {
        fprintf(stderr, "Unexpected response from server\n");
        close(control_socket);
        return -1;
    }
    
    printf("Connected to %s:%d\n", host, port);
    return 0;
}

// 发送命令到服务器
void send_command(const char *command) {
    printf("Sending: %s\n", command);
    if (send(control_socket, command, strlen(command), 0) < 0) {
        perror("send failed");
    }
}

// 接收服务器响应
int receive_response() {
    memset(response, 0, BUFFER_SIZE);
    ssize_t bytes_received = recv(control_socket, response, BUFFER_SIZE - 1, 0);
    
    if (bytes_received < 0) {
        perror("receive failed");
        return -1;
    }
    
    printf("Server response: %s", response);
    
    // 提取状态码
    int status_code = 0;
    if (sscanf(response, "%d", &status_code) != 1) {
        fprintf(stderr, "Failed to parse status code\n");
        return -1;
    }
    
    return status_code;
}

// 用户登录
void login(const char *user, const char *pass) {
    char command[COMMAND_SIZE];
    
    // 发送用户名
    snprintf(command, COMMAND_SIZE, "USER %s\r\n", user);
    send_command(command);
    if (receive_response() != 331) {
        fprintf(stderr, "Username not accepted\n");
        return;
    }
    
    // 发送密码
    snprintf(command, COMMAND_SIZE, "PASS %s\r\n", pass);
    send_command(command);
    if (receive_response() != 230) {
        fprintf(stderr, "Password not accepted\n");
        return;
    }
    
    printf("Login successful\n");
}

// 列出远程目录文件
void list_files() {
    char command[COMMAND_SIZE];
    int data_socket;
    struct sockaddr_in data_addr;
    socklen_t data_addr_len = sizeof(data_addr);
    
    // 进入被动模式
    send_command("PASV\r\n");
    if (receive_response() != 227) {
        fprintf(stderr, "Failed to enter passive mode\n");
        return;
    }
    
    // 解析被动模式返回的IP和端口
    int h1, h2, h3, h4, p1, p2;
    char *p = strstr(response, "(");
    if (p == NULL || sscanf(p, "(%d,%d,%d,%d,%d,%d)", &h1, &h2, &h3, &h4, &p1, &p2) != 6) {
        fprintf(stderr, "Failed to parse passive mode response\n");
        return;
    }
    
    // 计算数据端口
    int data_port = p1 * 256 + p2;
    
    // 创建数据套接字并连接
    if ((data_socket = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
        perror("data socket creation error");
        return;
    }
    
    memset(&data_addr, 0, sizeof(data_addr));
    data_addr.sin_family = AF_INET;
    data_addr.sin_addr.s_addr = htonl((h1 << 24) | (h2 << 16) | (h3 << 8) | h4);
    data_addr.sin_port = htons(data_port);
    
    if (connect(data_socket, (struct sockaddr *)&data_addr, sizeof(data_addr)) < 0) {
        perror("data connection failed");
        close(data_socket);
        return;
    }
    
    // 发送LIST命令
    send_command("LIST\r\n");
    if (receive_response() != 150) {
        fprintf(stderr, "Failed to start listing files\n");
        close(data_socket);
        return;
    }
    
    // 接收文件列表
    char buffer[BUFFER_SIZE];
    ssize_t bytes;
    while ((bytes = recv(data_socket, buffer, BUFFER_SIZE - 1, 0)) > 0) {
        buffer[bytes] = '\0';
        printf("%s", buffer);
    }
    
    close(data_socket);
    
    // 接收完成响应
    receive_response();
}

// 下载文件
void download_file(const char *filename) {
    char command[COMMAND_SIZE];
    int data_socket;
    struct sockaddr_in data_addr;
    socklen_t data_addr_len = sizeof(data_addr);
    
    // 进入被动模式
    send_command("PASV\r\n");
    if (receive_response() != 227) {
        fprintf(stderr, "Failed to enter passive mode\n");
        return;
    }
    
    // 解析被动模式返回的IP和端口
    int h1, h2, h3, h4, p1, p2;
    char *p = strstr(response, "(");
    if (p == NULL || sscanf(p, "(%d,%d,%d,%d,%d,%d)", &h1, &h2, &h3, &h4, &p1, &p2) != 6) {
        fprintf(stderr, "Failed to parse passive mode response\n");
        return;
    }
    
    // 计算数据端口
    int data_port = p1 * 256 + p2;
    
    // 创建数据套接字并连接
    if ((data_socket = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
        perror("data socket creation error");
        return;
    }
    
    memset(&data_addr, 0, sizeof(data_addr));
    data_addr.sin_family = AF_INET;
    data_addr.sin_addr.s_addr = htonl((h1 << 24) | (h2 << 16) | (h3 << 8) | h4);
    data_addr.sin_port = htons(data_port);
    
    if (connect(data_socket, (struct sockaddr *)&data_addr, sizeof(data_addr)) < 0) {
        perror("data connection failed");
        close(data_socket);
        return;
    }
    
    // 发送RETR命令
    snprintf(command, COMMAND_SIZE, "RETR %s\r\n", filename);
    send_command(command);
    if (receive_response() != 150) {
        fprintf(stderr, "Failed to start file transfer\n");
        close(data_socket);
        return;
    }
    
    // 接收文件数据并写入本地文件
    FILE *file = fopen(filename, "wb");
    if (file == NULL) {
        perror("failed to open file");
        close(data_socket);
        return;
    }
    
    char buffer[BUFFER_SIZE];
    ssize_t bytes;
    while ((bytes = recv(data_socket, buffer, BUFFER_SIZE, 0)) > 0) {
        fwrite(buffer, 1, bytes, file);
    }
    
    fclose(file);
    close(data_socket);
    
    // 接收完成响应
    receive_response();
    printf("File %s downloaded successfully\n", filename);
}

// 上传文件
void upload_file(const char *filename) {
    char command[COMMAND_SIZE];
    int data_socket;
    struct sockaddr_in data_addr;
    socklen_t data_addr_len = sizeof(data_addr);
    
    // 进入被动模式
    send_command("PASV\r\n");
    if (receive_response() != 227) {
        fprintf(stderr, "Failed to enter passive mode\n");
        return;
    }
    
    // 解析被动模式返回的IP和端口
    int h1, h2, h3, h4, p1, p2;
    char *p = strstr(response, "(");
    if (p == NULL || sscanf(p, "(%d,%d,%d,%d,%d,%d)", &h1, &h2, &h3, &h4, &p1, &p2) != 6) {
        fprintf(stderr, "Failed to parse passive mode response\n");
        return;
    }
    
    // 计算数据端口
    int data_port = p1 * 256 + p2;
    
    // 创建数据套接字并连接
    if ((data_socket = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
        perror("data socket creation error");
        return;
    }
    
    memset(&data_addr, 0, sizeof(data_addr));
    data_addr.sin_family = AF_INET;
    data_addr.sin_addr.s_addr = htonl((h1 << 24) | (h2 << 16) | (h3 << 8) | h4);
    data_addr.sin_port = htons(data_port);
    
    if (connect(data_socket, (struct sockaddr *)&data_addr, sizeof(data_addr)) < 0) {
        perror("data connection failed");
        close(data_socket);
        return;
    }
    
    // 发送STOR命令
    snprintf(command, COMMAND_SIZE, "STOR %s\r\n", filename);
    send_command(command);
    if (receive_response() != 150) {
        fprintf(stderr, "Failed to start file transfer\n");
        close(data_socket);
        return;
    }
    
    // 打开本地文件
    FILE *file = fopen(filename, "rb");
    if (file == NULL) {
        perror("failed to open file");
        close(data_socket);
        return;
    }
    
    // 读取文件内容并发送
    char buffer[BUFFER_SIZE];
    size_t bytes;
    while ((bytes = fread(buffer, 1, BUFFER_SIZE, file)) > 0) {
        send(data_socket, buffer, bytes, 0);
    }
    
    fclose(file);
    close(data_socket);
    
    // 接收完成响应
    receive_response();
    printf("File %s uploaded successfully\n", filename);
}

// 断开连接
void disconnect() {
    send_command("QUIT\r\n");
    receive_response();
    close(control_socket);
    printf("Disconnected from server\n");
}

int main(int argc, char *argv[]) {
    if (argc < 4) {
        printf("Usage: %s <host> <username> <password> [filename]\n", argv[0]);
        printf("If filename is provided, it will be downloaded or uploaded.\n");
        printf("If no filename is provided, the remote directory will be listed.\n");
        return 1;
    }
    
    const char *host = argv[1];
    const char *username = argv[2];
    const char *password = argv[3];
    const char *filename = (argc > 4) ? argv[4] : NULL;
    
    // 连接到服务器
    if (connect_to_server(host, FTP_CONTROL_PORT) != 0) {
        return 1;
    }
    
    // 登录
    login(username, password);
    
    // 根据参数执行操作
    if (filename) {
        // 检查文件是否存在,存在则上传,不存在则下载
        if (access(filename, F_OK) != -1) {
            upload_file(filename);
        } else {
            download_file(filename);
        }
    } else {
        // 列出文件
        list_files();
    }
    
    // 断开连接
    disconnect();
    
    return 0;
}
四、代码说明

上述代码实现了一个简单但功能完整的 FTP 客户端,主要包含以下几个部分:

  1. 连接管理connect_to_server函数负责建立与 FTP 服务器的控制连接,并接收欢迎信息。

  2. 命令处理send_commandreceive_response函数分别用于发送命令和接收服务器响应,处理状态码。

  3. 用户认证login函数实现了用户名和密码的认证过程。

  4. 文件列表list_files函数通过被动模式建立数据连接,并获取远程目录的文件列表。

  5. 文件下载download_file函数实现了从服务器下载文件的功能,使用二进制模式传输数据。

  6. 文件上传upload_file函数实现了向服务器上传文件的功能,同样使用二进制模式。

  7. 连接断开disconnect函数发送 QUIT 命令并关闭控制连接。

五、编译和使用方法

编译程序:

复制代码
gcc ftp_client.c -o ftp_client

使用示例:

  1. 列出远程目录:./ftp_client ftp.example.com username password
  2. 下载文件:./ftp_client ftp.example.com username password remote_file.txt
  3. 上传文件:./ftp_client ftp.example.com username password local_file.txt
六、FTP 协议的安全性考虑

虽然 FTP 是一个广泛使用的协议,但它有一个明显的安全缺陷:所有数据(包括用户名、密码和文件内容)都是以明文形式传输的。为了解决这个问题,出现了以下几种改进方案:

  1. FTPS:在 FTP 基础上加入 SSL/TLS 加密,提供数据传输的安全性。

  2. SFTP:基于 SSH 协议的文件传输协议,提供更安全的文件传输环境。

  3. HTTPS:虽然不是专门的文件传输协议,但可以通过 Web 服务器实现安全的文件上传和下载。

在实际应用中,特别是涉及敏感数据时,建议使用更安全的替代方案。

七、总结

FTP 协议是互联网上最早的文件传输协议之一,虽然存在安全问题,但由于其简单性和广泛支持,仍然在许多场景中被使用。通过 C 语言实现 FTP 客户端,我们可以深入理解网络协议的工作原理和 socket 编程技术。这个示例展示了如何使用 C 语言实现基本的网络通信、协议解析和文件操作,为进一步开发更复杂的网络应用提供了基础。

相关推荐
白总Server33 分钟前
React-fiber架构
开发语言·网络·网络协议·golang·scala·核心·fiber
leo·Thomas5 小时前
IPv4 地址嵌入 IPv6 的前缀转换方式详解
网络协议·ipv6
00后程序员张8 小时前
响应式架构下的调试挑战:WebDebugX 如何帮助前端稳住场面?
websocket·网络协议·tcp/ip·http·网络安全·https·udp
ip小哥8 小时前
我的爬虫夜未眠:一场与IP限流的攻防战
爬虫·网络协议·tcp/ip
hgdlip8 小时前
如何重新设置网络ip地址?全面解析多种方法
网络·网络协议·tcp/ip
Hello.Reader11 小时前
零基础弄懂 ngx_http_slice_module分片缓存加速
网络协议·http·缓存
源远流长jerry11 小时前
计算机网络相关面试题
chrome·网络协议·http·tcp
jiaxingcode14 小时前
HTTPS和HTTP区别
服务器·网络协议·https
IP管家16 小时前
跨境支付风控失效?用代理 IP 构建「地域 - 设备 - 行为」三维防护网
网络·网络协议·tcp/ip·网络安全