基本FTP客户端

QT C++实现的FTP下载客户端

环境说明

FTP服务器:CentOS7.8 + vsFTPD 3.0.2 安装设置见博文

CentOS vsftpd设置

客户端:win10+QT 5.15.2

实现的不是一个功能全的FTP客户端,而是程序中有从FTP服务器下载文件的需求,主要实现了下载的功能,包括断点续传,没有实现多线程下载。多线程下载的实现与断点续传有点关系,看懂了断点续传,实现多线程下载就简单了。

FTP协议是建立在TCP基础上的,在实现时用的就是Socket编程,客户端和服务端之间发送消息,消息的格式见上篇博文的最后几张图。

C++的简单FTP客户端实现(一)FTP基础知识

示例代码下载:

QT C++实现的FTP客户端,带断点续传功能

建立Socket连接

WSADATA dat;
int ret;//初始化,很重要
if (::WSAStartup(MAKEWORD(2,2),&dat) != 0)  //Windows Sockets Asynchronous启动
{cout<<"Init Failed: "<<GetLastError()<<endl;emit emitInfo(network , "Init Failed!\n");return -1;
}//创建Socket
controlSocket=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
if(controlSocket==INVALID_SOCKET)
{cout<<"Creating Control Socket Failed: "<<GetLastError()<<endl;emit emitInfo(network , "Creating Control Socket Failed.\n");return -1;
}//构建服务器访问参数结构体
serverAddr.sin_family=AF_INET;
serverAddr.sin_addr.S_un.S_addr=inet_addr(ip_addr.c_str()); //地址
serverAddr.sin_port=htons(PORT);            //端口
memset(serverAddr.sin_zero,0,sizeof(serverAddr.sin_zero));//连接
ret = ::connect(controlSocket,(struct sockaddr*)&serverAddr,sizeof(serverAddr));
if(ret==SOCKET_ERROR)
{cout<<"Control Socket Connecting Failed: "<<GetLastError()<<endl;emit emitInfo(network , "Control Socket Connecting Failed\n");return -1;
}

用户名密码登录:

//用户名
executeCmd("USER " + username);
if(recvControl(331) != 0)
{emit emitInfo(userpass, "");
}//密码
executeCmd("PASS " + password);
if(recvControl(230) != 0)
{emit emitInfo(userpass, "用户名或密码错误!");return -1;
}

更改目录

executeCmd("CWD "+tardir);
if(recvControl(250) != 0)
{emit emitInfo(directory, "FTP目录不存在!");return -1;
}

切换Binary模式

memset(buf, 0, BUFLEN);
executeCmd("TYPE I");
if(recvControl(200) != 0)
{emit emitInfo(filename, "切换BINARY模式失败!");return -1;
}

列出当前目录下所有文件

int FtpClient::listPwd()
{intoPasv();executeCmd("LIST -al");recvControl(150);memset(databuf, 0, DATABUFLEN);string fulllist;int ret = recv(dataSocket, databuf, DATABUFLEN-1, 0);while(ret>0){databuf[ret] = '\0';fulllist += databuf;ret = recv(dataSocket, databuf, DATABUFLEN-1, 0);}removeSpace(fulllist);int lastp, lastq, p, q;vector<string> eachrow;string rawrow;string item;filelist.clear();p = fulllist.find("\r\n");lastp = 0;while(p>=0){eachrow.clear();rawrow = fulllist.substr(lastp, p-lastp);q = rawrow.find(' ');lastq = 0;for(int i=0; i<8; i++){item = rawrow.substr(lastq, q-lastq);eachrow.push_back(item);lastq = q + 1;q = rawrow.find(' ', lastq);}item = rawrow.substr(lastq);eachrow.push_back(item);filelist.push_back(eachrow);lastp = p + 2;p = fulllist.find("\r\n", lastp);}closesocket(dataSocket);recvControl(226);return 0;
}

切换成PASV模式

int dataPort, ret;
//切换到被动模式
executeCmd("PASV");
recvControl(227);//返回的信息格式为---h1,h2,h3,h4,p1,p2
//其中h1,h2,h3,h4为服务器的地址,p1*256+p2为数据端口
dataPort = getPortNum();
//客户端数据传输socket
dataSocket = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
serverAddr.sin_port = htons(dataPort);    //更改连接参数中的port值
ret = ::connect(dataSocket,(struct sockaddr*)&serverAddr,sizeof(serverAddr));
if(ret == SOCKET_ERROR)
{cout<<"Data Socket connecting Failed: "<<GetLastError()<<endl;return -1;
}cout<<"Data Socket connecting is success."<<endl;
return 0;

下载文件:

int cur = 0;
ofstream ofile;
string localFile = localDir + "/" + localName;
QFileInfo fileinfo(QString::fromStdString(localFile));
int ss = fileinfo.size();ofile.open(localFile, ios_base::binary);if(intoPasv() == -1)
{ofile.close();emit emitInfo(network, "进入pasv模式失败!");return -1;
}executeCmd("RETR "+remoteName);
if(recvControl(150) != 0)
{ofile.close();emit emitInfo(filename, "FTP文件不存在!");return -1;
}memset(databuf, 0, DATABUFLEN);
int ret = recv(dataSocket, databuf, DATABUFLEN, 0);while(ret > 0)
{cur += ret;//cout << cur << " : " << size;emit emitProcess(cur, size);ofile.write(databuf, ret);ofile.flush();ret = recv(dataSocket, databuf, DATABUFLEN, 0);if(ret == -1){cout << "sending file, socker error!" << endl;emit emitInfo(network, "传输文件时失败,网络断开!");break;}
}ofile.close();

断点续传

实现断点续传,主要用到REST命令,从特定的偏移量开始传输文件。
先获取本地文件大小,与服务器文件比较,如果小于服务器文件大小,则开始断点续传,本地文件用append模式打开,从文件末尾写入。
设置偏移量REST 本地文件大小,之前还要切换成Binary模式,一般FTP服务器默认的是Ascii模式,Ascii模式是不能进行断点续传的。
核心代码:

int cur = 0;
ofstream ofile;
string localFile = localDir + "/" + localName;
QFileInfo fileinfo(QString::fromStdString(localFile));
int ss = fileinfo.size();if(resume)
{if(ss > 0){if(ss >= size){// 本地文件比ftp上大或相等,默认覆盖cout << "s >= size-----" << endl;//ofile.seekp(0, std::ios::beg);ofile.open(localFile, ios_base::binary);}else{if(setTypeI() == -1){//cout << "设置BINARY模式失败,不能断点续传,从头开始!" << endl;//ofile.seekp(0, std::ios::beg); // 设置BINARY模式失败,不能断点续传,从头开始//ofile.open(localFile, ios_base::binary);emit emitInfo(network, "设置BINARY模式失败!");return -1;}else{if(restFile(ss) == -1){//ofile.open(localFile, ios_base::binary);//ofile.seekp(0, std::ios::beg); // 设置断点续传失败,从头开始emit emitInfo(network, "设置续传模式失败!");return -1;}else{cout << "begin resume break-point!" << endl;ofile.open(localFile, ios_base::binary|ios_base::app);cur += ss;}}}}else{ofile.open(localFile, ios_base::binary);}
}
else
{ofile.open(localFile, ios_base::binary);
}if(intoPasv() == -1)
{ofile.close();emit emitInfo(network, "进入pasv模式失败!");return -1;
}executeCmd("RETR "+remoteName);
if(recvControl(150) != 0)
{ofile.close();emit emitInfo(filename, "FTP文件不存在!");return -1;
}memset(databuf, 0, DATABUFLEN);
int ret = recv(dataSocket, databuf, DATABUFLEN, 0);while(ret > 0)
{cur += ret;//cout << cur << " : " << size;emit emitProcess(cur, size);ofile.write(databuf, ret);ofile.flush();ret = recv(dataSocket, databuf, DATABUFLEN, 0);if(ret == -1){cout << "sending file, socker error!" << endl;emit emitInfo(network, "传输文件时失败,网络断开!");break;}
}ofile.close();

类封装

主要包含了三个类:
class FtpClient : public QObject

class ClientThread : public QThread

class ClientManager : public QObject

ClientManager类是对外的接口类,FtpClient类是ftp客户端,进行与服务器进行交互的类,ClientThread是线程执行类,把一系列的ftpclient类的调用封装在一个线程函数中。
FtpClient类向ClientManager类通过signal报告信息,是否登录成功,文件下载进度等。ClientManager类接收到后根据需要把重要的信息向上再次抛出signal。

上层程序调用是首先响应signal
connect(&m_Client, SIGNAL(emitProcess(int,int)), this, SLOT(on_emitDownloadSize(int,int)));
connect(&m_Client, SIGNAL(emitError(int,QString)), this, SLOT(on_emitError(int,QString)));
然后就是调用两个函数实现文件下载
m_ClientManager.setDownloadInfo(ui->textEdit1->toPlainText(), ui->textEdit1_2->toPlainText(), ui->textEdit1_3->toPlainText(), ui->textEdit1_4->toPlainText(), ui->textEdit1_5->toPlainText(), ui->textEdit1_6->toPlainText(), ui->textEdit1_7->toPlainText());
m_ ClientManager.startDownload();

把程序运行过程中与FTP服务器交互的主要信息打印出来如下:

多线程下载

我的程序中没有实现多线程下载。如果要做也是要用REST命令。根据线程数,计算每个线程下载的偏移量。每个线程各自通过pasv模式向服务端连接数据端口,然后设置平移量,下载特定大小的字节后就停止下载,最后再本地把几个文件段拼接起来。

C++的简单FTP客户端实现(二)编程相关推荐

  1. 计算机网络 简单FTP客户端软件的实现

    一.原理概述 1.1 FTP原理概述 文件传送协议FTP(File Transfer Protocol)是TCP/IP体系的一个重要协议,它采用Internet标准文件传输协议FTP的用户界面,向用户 ...

  2. C++ 简单FTP客户端软件开发

    题目 简单FTP客户端软件开发(100分)网络环境中的一项基本应用就是将文件从一台计算机中复制到另一台可能相距很远的计算机中.而文件传送协议FTP是因特网上使用得最广泛的文件传送协议.FTP使用客户服 ...

  3. 使用FTP(IOS FTP客户端开发教程)

    本文翻译自新近Wrox出版社出版的,由Peter van de Put所著的<Professional.iOS.Programming>.该书题材比较新颖,结构合理,是一本不错的IOS开发 ...

  4. FTP客户端简单例程

    这一次我自己在Linux下写了一个简单的FTP客户端,支持的操作只有那些最常用的.这个程序已经通过简单的一些测试,目前实现的操作有:ls, dir, pwd, cd, ascii, binary, p ...

  5. python16进制字节序_第 1 章 套接字、IPv4和简单的客户端/服务器编程

    第 1 章 套接字.IPv4和简单的客户端/服务器编程 本章攻略: 打印设备名和IPv4地址 获取远程设备的IP地址 将IPv4地址转换成不同的格式 通过指定的端口和协议找到服务名 主机字节序和网络字 ...

  6. Golang实现一个简单的FTP客户端

    使用Golang语言实现的一个简单的FTP客户端:Github源码:Golang实现一个简单的FTP客户端

  7. 网络编程--ftp客户端的实现(c#版)

    .net2.0对ftp有了一个很好的封装,但是确容易让人忽略ftp的真正内部实现,下面是我实现的ftp客户端的功能,其主要步骤是这样的: 1.创建一个FtpWebRequest对象,指向ftp服务器的 ...

  8. 简单vsftpd安装配置和 ftp客户端操作笔记

    #安装vsftpd yum install vsftpd #创建要登录ftp的本地用户 useradd -s /bin/false   用户名         #创建禁止ssh登录的用户 passwd ...

  9. java关闭ftp 连接_Java语言实现简单FTP软件------gt;连接管理模块的实现:主机与服务器之间的连接与关闭操作(八) - 移动编程 - ITeye博客...

    (1)FTP连接 运行FTP客户端后,首先是连接FTP服务器,需要输入FTP服务器的IP地址及用户名.密码以及端口号后点击连接按钮开始连接FTP服务器,连接流程图如下图所示. 点击"连接&q ...

  10. python编写ftp客户端_用Python写FTP客户端程序

    0 前言: ftp客户端相信大家都用过,那么我们为什么还要用Python写ftp客户端呢? 我想有两个原因: 一是写出更好的ftp客户端应用程序,方便大家使用: 二是定制一些特殊服务,例如每天定时下载 ...

最新文章

  1. iso8601时间格式_ISO8601与dayjs的使用
  2. python:array和list转换以及数组切片
  3. Scala import导包用法
  4. js--------1.时间
  5. 天池 在线编程 有序队列
  6. mysql int tinyint_MySQL中int(M)和tinyint(M)数值类型中M值的意义
  7. 开心果 | 即使天天看的图标 你未必都认识
  8. Linux里怎么进行路由跟踪,[Linux] traceroute 路由跟踪指令用例
  9. PAGE:像Visual Studio一样设计Python GUI窗体
  10. 深度森林DF21、deep forest、gcForest
  11. 【学习笔记】C语言 随机数的生成原理分析和各类随机数公式
  12. 过桥问题--马儿赛跑问题--智力题
  13. 以太坊2.0协议核心Beacon链详解
  14. System.Net.Sockets空间
  15. IOS swift开发——获取设备定位信息
  16. 《置身事内》读书笔记第一章 地方政府的权利与事务
  17. 用计算机写作文主题,用计算机写作文教学设计.doc
  18. 一起来玩玩WebGL--第一弹
  19. urllib库(二)parse模块:urlparse()/urlsplit(),parse_qs()/parse_qsl(),urlunparse()/urlunsplit(),urlencode()
  20. SpringCloud 基础教程(八)-Hystrix熔断器(上)

热门文章

  1. 【空间单细胞组学】第2期:联合单细胞和bulk转录组鉴定了结直肠癌中两种上皮肿瘤细胞状态,并完善了CMS分型
  2. 对概念模型的简单介绍
  3. qq空间批量下载别人的qq相册
  4. 海康大华宇视安防摄像机平台RTSP直播流拉转输出RTSP/RTMP/HLS/HTTP-FLV并获取直播流地址
  5. CodeForces - 1152 B. Neko Performs Cat Furrier Transform
  6. 幂函数展开c语言,第三章幂函数展开.pdf
  7. java囧囧西游之大闹天宫下载_最新囧囧西游之大闹天宫榜单下载_九游
  8. pandas学习-中期测试
  9. 关于DSP的中断操作
  10. 安全架构--12--企业隐私合规体系建设总结