【问题标题】:Simple FTP Client C++简单的 FTP 客户端 C++
【发布时间】:2016-01-19 15:37:35
【问题描述】:

我正在尝试在 C/C++ 中创建一个简单的 FTP 客户端,它将执行简单的操作(连接、检索文件)。到目前为止我所做的是连接和登录。我使用套接字连接到端口 21,就像任何常规 FTP 客户端一样。我遇到的问题是连接到输入命令 PASV 时指定的端口。我收到消息,解析它,然后在输入 PASV 时从重播消息中计算端口。

227 Entering Passive Mode (a1, a2, a3, a4, p1, p2) 
DataPort = (p1 * 256) + p2

一旦有了端口,我就会尝试创建另一个套接字并以相同的方式连接到它。这就是我的问题所在。到目前为止,我的代码发布在下面。我不知道我是否需要以同样的方式再次获取服务器地址。我没有从服务器得到回复(如果我真的想得到一个,我不知道)请提出任何问题或疑虑,谢谢。

const int FTP_PORT = 21;        // Server Port
const int SIZE = 1024;          // Size of Buffers
char receiveBuff[SIZE];         // Buffer to send to the server
char sendBuff[SIZE];            // Buffer to receive from server
char pasvBuff[] = "pasv";       // Buffer to see if PASV Command was entered
char quitBuff[] = "QUIT";       // Buffer to see if QUIT Command was entered
char pasvMessage[100];          // String for PASV information

int main(int argc, char *argv[])
{
    int length = 0, i=0;
    int a1, a2, a3, a4, p1, p2, dataPort;       //PASV Information

    /* Get Server Name from User */
    if (argc != 2)
    {
        cerr << "Usage: " << argv[0] << " server" << endl;
        return 1;
    }

    /* Obtain Host (Server) Info  */
    struct hostent *host;
    host = gethostbyname(argv[1]);
    if (host == (struct hostent *)NULL)
    {
        perror("Client: gethostbyname");
        return 2;
    }

    /* Add Server Information */
    struct sockaddr_in servAdr;             // Internet address of server
    memset(&servAdr, 0, sizeof(servAdr));   // Clear structure
    servAdr.sin_family = AF_INET;           // Set address typedef
    memcpy(&servAdr.sin_addr, host->h_addr, host->h_length);
    servAdr.sin_port = htons(FTP_PORT);     // Use FTP port

    /* Create Socket to Connect to FTP Server */
    int origSock;                   // Original socket in client
    if ((origSock = socket(PF_INET, SOCK_STREAM, 0)) < 0)
    {
        perror("Client: generate error");
        return 3;
    }

    /* Connect to FTP Server on Port 21 */
    if (connect(origSock, (struct sockaddr *)&servAdr, sizeof(servAdr)) < 0)
    {
        perror("Client: connect error");
        return 4;
    }

    /* Get Conenct Message and Print to Screen */
    read(origSock, receiveBuff, sizeof(receiveBuff) - 1);
    write(fileno(stdout), receiveBuff, sizeof(receiveBuff) - 1);

    do
    {
        /* Clear Buffers */
        memset(receiveBuff, 0, SIZE);
        memset(sendBuff, 0, SIZE);

        write(fileno(stdout), "Please enter a FTP Command: ", 28);      // Write User Interface
        read(fileno(stdin), sendBuff, SIZE);                            // Read Command from User
        send(origSock, sendBuff, strlen(sendBuff) , 0);                 // Send Command to Server

        read(origSock, receiveBuff, sizeof(receiveBuff) - 1);           // Read Response from Server
        write(fileno(stdout), receiveBuff, sizeof(receiveBuff) - 1);    // Print Response from Server to screen

        /* If PASV Command was Entered */
        if (strncmp(sendBuff, pasvBuff, 4) == 0)
        {
            sscanf(receiveBuff, "227 Entering Passive Mode (%d,%d,%d,%d,%d,%d)", &a1,&a2,&a3,&a4,&p1,&p2);
            dataPort = (p1 * 256) + p2;

            struct sockaddr_in servAdr2;                // Internet address of server
            memset(&servAdr2, 0, sizeof(servAdr2));     // Clear structure
            servAdr2.sin_family = AF_INET;              // Set address typedef
            memcpy(&servAdr2.sin_addr, host->h_addr, host->h_length);
            servAdr.sin_port = htons(dataPort);         // Use FTP port

            /* Create Socket to Connect to FTP Server */
            int dataSock;                               // Data socket in client
            if ((dataSock = socket(PF_INET, SOCK_STREAM, 0)) < 0)
            {
                perror("Client: generate error");
                return 3;
            }

            /* Connect to FTP Server on Data Port */
            if (connect(dataSock, (struct sockaddr *)&servAdr, sizeof(servAdr)) < 0)
            {
                perror("Client: connect error");
                return 4;
            }

            read(dataSock, receiveBuff, sizeof(receiveBuff) - 1);
            write(fileno(stdout), receiveBuff, sizeof(receiveBuff) - 1);
        }

    } while (strncmp(sendBuff, quitBuff, 4) != 0);      // Go until QUIT Command is entered

    close(origSock);

    return 0;
}

【问题讨论】:

  • “C/C++”不是一个东西。
  • “我没有从服务器得到响应” 太宽泛了。有很多情况会发生这种情况。防火墙、NAT 映射、端口限制。特别是你应该检查任何错误情况,你可能会遇到套接字操作。
  • 差不多,我看看我是否真的正确地进行了第二次连接?这就是我遇到麻烦的地方,我想我会从服务器得到某种响应。
  • 您需要查看read() 的返回值,以了解您实际从套接字中读取了多少数据。
  • 因为还没有什么要读的。您还没有发送任何命令来告诉服务器实际发送任何数据。 PASV 只是打开服务器的数据端口。您必须发送STORRETR 命令才能通过开放数据端口执行传输。

标签: c++ sockets ftp


【解决方案1】:

当您解析PASV 回复时,您正在填充servAdr2 变量,但它的sin_port 字段除外。您正在将报告的端口分配给 servAdr.sin_port 字段。然后,您将使用servAdr 而不是servAdr2 连接数据套接字。因此,您实际上是将数据套接字连接到报告端口上服务器的原始 IP 地址,而不是连接到报告的 IP 地址(可能与服务器 IP 不同)。 a1-a4 是您应该连接的 IP 地址的 IPv4 八位字节。

也就是说,如果服务器支持EPSV 命令,您确实应该使用它。解析比PASV 容易得多,因为PASV 没有标准化格式(因此请准备好解析多种供应商特定的格式)。 EPSV 通过以机器可解析的方式标准化格式来解决这个问题。

至于为什么你没有得到任何响应,那是因为你没有告诉服务器通过开放的数据连接发送任何文件。发送PASV 只会打开服务器的数据端口。连接到它之后,您必须在控制套接字上发送STORRETR 命令才能通过数据套接字实际执行文件传输。在传输完成后,您还必须在控制套接字上读取服务器的最终响应,然后才能发送任何新命令。

【讨论】:

  • 非常感谢您的帮助。我的转移工作主要是。我做 PASV(打开数据端口),然后做 RETR(到控制端口),我在控制端口上读取并得到一个 150 代码。我读取数据端口并显示文本文档中的文本。在那之后,我的程序挂起,似乎在等待读取,但不是。我看到你提到我必须从控制端口读取最终响应,我尝试过但我仍然挂起。
  • 听起来您没有处理服务器关闭数据连接以发出 EOF 信号的情况。您必须从数据套接字读取直到它断开连接,然后从控制套接字读取响应以完成传输。顺便说一句,您阅读回复的方式是完全错误的。您没有考虑到 TCP 是一种流传输,read() 可以返回比请求更少的字节,并且没有协议的概念。 FTP 是基于文本行的协议,因此您应该在 CRLF 分隔的行中读取控制套接字,而不是在原始缓冲区中。
  • 我是特意在客户端关闭了端口,还是服务端要关闭。如何检测服务器何时发送 EOF 信号?
  • 发送方关闭其数据连接的一端以发出 EOF 信号,然后接收方关闭其一端进行清理。对于RETR,服务器在完成向您发送文件后关闭连接。对于STOR,当您完成向服务器发送文件时关闭数据套接字。至于如何检测断开连接,read() 会告诉你,所以你要注意它的返回值: 0 = 读取的字节数。虽然你真的应该使用recv() 而不是read()(同样的规则适用于recv())。
  • 在深入研究建立在 TCP 之上的 FTP 之前,您似乎还没有学习 TCP 编程的基础知识。您可能想退后一步,专注于学习处理 TCP 编程的正确方法,您似乎还有一点需要学习,然后您可以将其应用于 FTP 编程。
【解决方案2】:

这是我编写的一个 C++ 套接字库,它具有 FTP 客户端(连接、检索列表和文件)。代码中的cmets解释

https://github.com/pedro-vicente/lib_netsockets

基本上:

FTP 使用两个 TCP 连接来传输文件:一个控制连接和一个数据连接

在端口 21 上连接一个套接字(控制套接字)到一个 ftp 服务器

在套接字上接收来自 ftp 服务器的消息(代码:220)

使用命令 USER 将登录信息发送到 ftp 服务器并等待确认 【331】

使用命令 PASS 发送密码并等待确认您已登录服务器 (230)

接收文件:

使用被动模式:发送命令 PASV

接收带有 IP 地址和端口 (227) 的答案,解析此消息。

用给定的配置连接第二个套接字(一个数据套接字)

在控制插座上使用命令RETR

通过数据套接字接收数据,关闭数据套接字。

在控制套接字上使用命令 QUIT 离开会话。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-08-09
    • 1970-01-01
    • 1970-01-01
    • 2015-12-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-10-26
    相关资源
    最近更新 更多