Qt5.10编写FTP客户端

Qt5.10编写FTP客户端

来源 https://zhuanlan.zhihu.com/p/35314228

自从Qt5删除了QFtp模块后,就没有了可方便使用的FTP类。根据官方的说法,是因为该模块实现质量不好被删除,而用Qt5的网络模块就可以轻松实现。对于初学者没了现成的工具就不知道该咋办了。本文从FTP协议开始讲起,先明白FTP协议是一个怎样的结构。然后讲解如何用代码去实现。

全文分两大部分,第一部分是FTP协议的讲解,第二部分是Qt5的代码实现。笔者会尽量用简洁明了的语句给列为讲清楚的,让我们开始吧~~

  1. FTP协议
  2. Qt5的代码实现

第一部分 FTP协议

什么是FTP协议?

就是传文件用的协议。整个过程就是先用TCP与服务器建立连接,然后发送命令,服务器执行并进行反馈。

FTP的两个端口

一般的客户端/服务器之间是开一个Socket端口,既传输命令又传输数据。而FTP的协议中说:我要开两个端口,一个传输命令,一个传输数据。

  • 传输命令端口(21)
  • 传输数据端口(20)

所以在编程的时候要创建两个QTcpSocket哦~

FTP的两种过程

分为被动模式和主动模式。这是从客户端的角度来说的。比如常见的被动模式意思就是服务器告诉你我们用哪个端口进行数据传输,我们(客户端)没有指定端口的权力,它(服务器)说用啥端口我们(客户端)就用啥端口。

而主动模式就是我们(客户端)自己定好用哪个端口,告诉了服务器,然后双方在这个端口下进行通信。

  • 被动模式(常用)

根据上文所述,既然都“被动”了,那我们客户端就等着服务器给我们分配端口号吧。

(1)首先我们任意打开一个端口N连接FTP服务器,并在Socket中写“PASV”命令。这里的N必须大于1024;

(2)然后服务器会返回个信息,类似“227 Entering Passive Mode(47,94,99,120,39,18)”这样。前四个是服务器IP地址,后两个是和分配的端口号相关的数。从这条信息我们就知道了服务器给我们分配的用于数据传输的端口号是:39*256+18;

(3)然后我们就打开第二个端口N+1和服务器的这个39*256+18端口进行连接,然后数据传输。

  • 主动模式

根据上文所述,既然都“主动”了,那我们客户端就自由分配进行数据传输的端口号吧。

(1)第一步还是任意打开两个端口N和N+1,先用N端口连接FTP服务器的21端口,同时第二个端口N+1进行监听。在第一个端口发送“port N+1”命令;

(2)这时服务器就会主动连接到我们的N+1端口上了。

FTP命令及响应码

FTP命令

命令字符串结尾要加‘\n‘
  • ABOR:中断数据连接程序
  • ACCT <account>:系统特权帐号
  • ALLO <bytes>:为服务器上的文件存储器分配字节
  • APPE <filename>:添加文件到服务器同名文件
  • CDUP <dir path>:改变服务器上的父目录
  • CWD <dir path>:改变服务器上的工作目录
  • DELE <filename>:删除服务器上的指定文件
  • HELP <command>:返回指定命令信息
  • LIST <name>:如果是文件名列出文件信息,如果是目录则列出文件列表
  • MODE <mode>:传输模式(S=流模式,B=块模式,C=压缩模式)
  • MKD <directory>:在服务器上建立指定目录
  • NLST <directory>:列出指定目录内容
  • NOOP:无动作,除了来自服务器上的承认
  • PASS <password>:系统登录密码
  • PASV:请求服务器等待数据连接
  • PORT <address>:IP 地址和两字节的端口 ID
  • PWD:显示当前工作目录
  • QUIT:从 FTP 服务器上退出登录
  • REIN:重新初始化登录状态连接
  • REST <offset>:由特定偏移量重启文件传递
  • RETR <filename>:从服务器上找回(复制)文件
  • RMD <directory>:在服务器上删除指定目录
  • RNFR <old path>:对旧路径重命名
  • RNTO <new path>:对新路径重命名
  • SITE <params>:由服务器提供的站点特殊参数
  • SMNT <pathname>:挂载指定文件结构
  • STAT <directory>:在当前程序或目录上返回信息
  • STOR <filename>:储存(复制)文件到服务器上
  • STOU <filename>:储存文件到服务器名称上
  • STRU <type>:数据结构(F=文件,R=记录,P=页面)
  • SYST:返回服务器使用的操作系统
  • TYPE <data type>:数据类型(A=ASCII,E=EBCDIC,I=binary)
  • USER <username>:系统登录的用户名

FTP响应码

  • 110:新文件指示器上的重启标记
  • 120:服务器准备就绪的时间(分钟数)
  • 125:打开数据连接,开始传输
  • 150:打开连接
  • 200:成功
  • 202:命令没有执行
  • 211:系统状态回复
  • 212:目录状态回复
  • 213:文件状态回复
  • 214:帮助信息回复
  • 215:系统类型回复
  • 220:服务就绪
  • 221:退出网络
  • 225:打开数据连接
  • 226:结束数据连接
  • 227:进入被动模式(IP 地址、ID 端口)
  • 230:登录因特网
  • 250:文件行为完成
  • 257:路径名建立
  • 331:要求密码
  • 332:要求帐号
  • 350:文件行为暂停
  • 421:服务关闭
  • 425:无法打开数据连接
  • 426:结束连接
  • 450:文件不可用
  • 451:遇到本地错误
  • 452:磁盘空间不足
  • 500:无效命令
  • 501:错误参数
  • 502:命令没有执行
  • 503:错误指令序列
  • 504:无效命令参数
  • 530:未登录网络
  • 532:存储文件需要帐号
  • 550:文件不可用
  • 551:不知道的页类型
  • 552:超过存储分配
  • 553:文件名不允许

第二部分 FTP的Qt5的代码实现

网络编程三步骤:Socket连接、Write/Read读写、Close关闭
  • 被动模式的代码实现

一般的服务器都是采用被动模式,所以先讲解这部分。

Qt5.10编写FTP客户端

1/3 Socket连接

/* 构造Socket,并连接 */
socket = new QTcpSocket(this);
connect(socket,&QTcpSocket::connected,this,&MainWindow::slotConnected);
connect(socket,&QTcpSocket::readyRead,this,&MainWindow::dataReceived);
socket->connectToHost("47.94.99.120",21);

有两个槽,slotConnected()槽的作用是如果Socket和服务器建立连接,就在textEdit上显示“已建立Socket连接”的字样;dataeceived()槽的作用是显示服务器反馈回来的信息。

  • void slotConnected()槽
void MainWindow::slotConnected()
{
    ui->textEdit->append("已建立Socket连接");
}
  • void dataReceived()槽
void MainWindow::dataReceived()
{
    while (socket->bytesAvailable())
    {
        /* 读取Socket并存入datagram */
        QByteArray datagram = socket->readAll();

        /* 展示在textEdit */
        QString str = QString::fromLocal8Bit(datagram);
        ui->textEdit->append(str);
    }
}

看下效果:

Qt5.10编写FTP客户端

通过连接,服务器反馈回“220 (vsFTPd 3.0.2)”的信息。由上文所述,220响应码的含义是“服务就绪”。那么接下来就是write/read过程了。

2/3 Write/Read读写

“发送命令”按钮的代码:

void MainWindow::on_pbSend_clicked()
{
    QByteArray command = ui->lineEdit->text().toLatin1();
    command += ‘\n‘;
    socket->write(command);
}
注意:命令结尾不要忘了加个‘\n‘回车符号。

Qt5.10编写FTP客户端

发送 USEuser_name 用户名

Qt5.10编写FTP客户端

发送 PASS xxxxxxx 密码

Qt5.10编写FTP客户端

发送 PASV 命令

到此为止,服务器给我们回复“227 Entering Passive Mode(47,94,99,120,39,16)”的响应码,这是要告诉我们“应该采用被动模式”,后面还附带了服务器IP地址,以及两个变量。通过计算得知,服务器给我们分配的数据传输端口号是:39*256+16=10000。

接下来就是再构造一个Socket,端口号为10000,去连接服务器的20端口号(传输数据用)。然后各种命令,接受的文件也用Socket的read()等函数去接受,然后QFile各种存文件操作等等等等。


接下来的步骤就不写了,FTP协议属于应用层的部分,具体如何按照这个标准去组织语言就是列位发挥的部分了。

刚开始我写FTP协议的时候摸不着头脑,也不懂HTTP协议、FTP协议等内容,后来经过摸索才知道:哦,这就是应用层的东西。人家都已经告诉你如何去实现FTP的过程了,我们就照着这个过程写程序就是咯。

所以整个过程还是很简单的,就是用Socket去你来我往的交流而已,没有涉及多么高大上的东西。FTP协议很简单吧~


使用Linux自带工具实现定时下载FTP文件(4月8日新增)

总共分两步骤:编写下载脚本、添加定时任务。

编写下载脚本

#!/bin/bash

ftp -n << EOF
open xxx.xxx.xxx.xxx       #FTP服务器地址
user username password     #用户名、密码
binary
lcd /home/psx/data         #设置文件保存位置
prompt
mget *
close
bye

EOF

其中,prompt意思是关闭交互。

添加定时任务(每分钟执行一次)

打开终端输入:

crontab -e

编写内容:

*/1 * * * * /bin/bash /home/psx/download_ftp

download_ftp是我自己写的脚本,你改成自己的脚本路径。*号和空格不能少。然后按ESC,按shift+:,输入wq保存退出即可立即生效。

关于crontab参数含义可以网上搜索下看看,非常简单。

=========== End

相关推荐