-
2021-11-24 11:30:34
上传遇到这种情况,去FTP服务器上看看,如果文件也确实存在,只不过内容是空的。最简单的办法就是试试关闭服务器防火墙(直接在服务器上敲命令或手动关闭即可),如果关闭防火墙,文件能正常上传,那就好办了。ftp默认的上传模式是主动上传,如果防火墙不能关闭,在代码里上传模式改成被动上传就行。
先看代码:
public Result uploadFile(@RequestParam(value = "file") MultipartFile file) { try { String fileName = file.getOriginalFilename(); String suffixName = fileName.substring(fileName.indexOf(".") + 1); Map<Object, Object> map = new HashMap<>(); map.put("filePath", FileStoreUtil.uploadFile(file.getBytes(), suffixName, false, "文件名")); return Result.success(map); } catch (Exception e) { e.printStackTrace(); return Result.error("文件上传失败,失败原因:" + e.getMessage()); } }
这里因为上传服务要做兼容,config对象的子类为oss和ftp两个对象。
/** * @param fileContent byte[]文件字节数组对象 * @param suffixName 文件名后缀名 * @param fileName 文件前缀名 * @param isTemp 是否加入临时目录标识 */ public static String uploadFile(byte[] fileContent, String suffixName, boolean isTemp,String fileName) throws Exception { StringBuilder str = new StringBuilder(); if (isTemp) { str.append("temp").append("/"); } //如果有业务号或者id也可以拼进去 if (StringUtil.isNotEmpty(fileName)) { str.append(fileName).append(".").append(suffixName); }else{//拼接日期和随机数 str.append(DateUtil.getYear()).append("/") .append(DateUtil.getMonth()).append("/") .append(DateUtil.getDay()).append("/") .append(System.currentTimeMillis()) .append(new Random().nextInt(999)) .append(".").append(suffixName); } String name = str.toString(); //判断下用什么服务器做文件管理 Config config; if ("ftp".equals(GlobalParamConfig.FILE_STORY_WAY)) { String model = GlobalParamConfig.FILE_FTP_MODEL;//这里是从nacos获取模式配置,也可以用字典等等。 if(DictDefinition.FILE_FTP_PASSIVE_MODE.equals(model)){//被动模式 config = new FtpConfig("host", "ip", "username", "password", model); }else {//默认为主动模式 config = new FtpConfig("host", "ip", "username", "password", null); } } else if ("oss".equals(GlobalParamConfig.FILE_STORY_WAY)) {//用oss阿里云 config = new OSSConfig("url", "accesskeyid","bucket","accesskeysecret","isencrypt"); } else { throw new CrmException("文件上传与下载方式,服务配置错误"); } FileStore fileStore = new FtpUtil(config); boolean storeFile = fileStore.storeFile(name, new ByteArrayInputStream(fileContent)); return name; }
这里创建ftp的客户端,做一些常规设置,然后判断是否把上传模式改为被动模式。
public FtpUtil(String ip, int port, String userName, String password, String model) { try { //创建FTPClient连接,并登录 this.ftpClient = new FTPClient(); this.ftpClient.connect(ip, port); this.ftpClient.setControlEncoding("GBK"); FTPClientConfig config = new FTPClientConfig("WINDOWS"); config.setServerLanguageCode("zh"); this.ftpClient.login(userName, password); if (model != null && !"".equals(model)) {//判断设置被动模式 this.ftpClient.enterLocalPassiveMode(); } //reply是连接后,ftp给的整数值回复码,判断回复码是否正常 int reply = this.ftpClient.getReplyCode(); if (!FTPReply.isPositiveCompletion(reply)) { this.ftpClient.disconnect(); throw new Exception("连接ftp服务器出错,请联系管理员处理!"); } } catch (Exception var8) { System.err.println("%%%% Error Creating FTPConnection %%%%"); log.severe("连接ftp服务器出错!"); var8.printStackTrace(); } }
后面就是做上传文件操作了。这里有两种方法实现,我这边用的第二个方法。
public boolean storeFile(String path, InputStream inputStream) { boolean success = false; String fileType = FileUtils.getFileType(path); String tempFile = FileUtils.creteTempPath(fileType); try { FileUtils.writeBytesToFile(input2byte(inputStream), tempFile); byte[] encrypt = AES.encrypt(FileUtils.readBytesFromFile(new File(tempFile))); FileUtils.writeBytesToFile(encrypt, tempFile); success = this.ftpUtil.upload(new FileInputStream(new File(tempFile)), path); } catch (Exception var7) { var7.printStackTrace(); } FileUtils.deleteFile(tempFile); this.ftpUtil.disConFtp(); return success; } public boolean storeFile(String path, InputStream inputStream) { boolean success = ftpUtil.upload(inputStream, path); //关闭ftp连接 ftpUtil.disConFtp(); return success; }
通过upload把文件上传到ftp服务器上。
public boolean upload(InputStream fis, String targetFile) { targetFile = "/" + targetFile; String targetPath = targetFile.substring(0, targetFile.lastIndexOf("/")); String fileName = targetFile.substring(targetFile.lastIndexOf("/") + 1); boolean success = false; try { this.mkDir(targetPath); this.ftpClient.setFileType(2); success = this.ftpClient.storeFile(fileName, fis);//FTPClient自带的上传文件方法 } catch (IOException var10) { var10.printStackTrace(); log.severe("上传出错,请重新上传"); } finally { IOUtils.closeQuietly(fis); } //因为调用upload的方法,后面有关闭ftp连接,这里直接返回就可以了 return success; } //完成操作,断开连接 public void disConFtp() { try { if (null != this.ftpClient) { this.ftpClient.logout(); if (this.ftpClient.isConnected()) { this.ftpClient.disconnect(); } } } catch (IOException var2) { log.severe("退出ftp异常,检查网络!"); var2.printStackTrace(); } } //判断文件路径,切换到该路径 private synchronized void mkDir(String filePath) throws IOException { String[] fileName = filePath.split("/"); for(int i = 0; i < fileName.length; ++i) { String targetPath = fileName[i]; if (targetPath != null && !"".equals(targetPath)) { //判断路径不为空,就切换到该目录下 boolean temp = this.ftpClient.changeWorkingDirectory(targetPath); if (!temp) { //目录不存在就创建,然后切换到该目录下 this.ftpClient.makeDirectory(targetPath); this.ftpClient.changeWorkingDirectory(targetPath); } } } }
这差不多快是全部的ftp上传代码了,最后说下这个主动和被动上传吧。
主动上传:对FTP服务器的管理有利,但对客户端的管理不利。因为FTP服务器企图与客户端的高位随机端口建立连接,而这个端口很有可能被客户端的防火墙阻塞掉。
被动上传:对FTP客户端的管理有利,但对服务器端的管理不利。因为客户端要与服务器端建立两个连接,其中一个连到一个高位随机端口,而这个端口很有可能被服务器端的防火墙阻塞掉。
既然FTP服务器的管理员需要他们的服务器有最多的客户连接,那么必须得支持被动FTP。我们可以通过为FTP服务器指定一个有限的端口范围来减小服务器高位端口的暴露。这样,不在这个范围的任何端口会被服务器的防火墙阻塞。虽然这没有消除所有针对服务器的危险,但它大大减少了危险。
主动或被动模式都是有利有弊,需要考虑相应的场景采用适合的模式。一般内网用被动模式 ,外网连接时用主动模式,服务器相应改动,使用应当参考实际应用场景。
更多相关内容 -
php实现通过ftp上传图片文件
2020-11-02 15:25:42可以实现ftp上传图片文件到指定ftp目录php实现通过ftp上传图片文件,用户无需手动更改目录,上传时可以选择是否上传到ftp位置,可有本地跟ftp可选 -
shell脚本实现ftp上传下载文件功能
2020-09-14 23:06:57主要介绍了shell脚本实现ftp上传下载文件功能,本文给大家介绍的非常详细,具有一定的参考借鉴价值 ,需要的朋友可以参考下 -
易语言-纯API实现FTP上传文件
2021-06-28 23:48:11纯API实现FTP上传文件功能。 -
Python实现FTP上传文件或文件夹实例(递归)
2020-09-21 10:50:13本篇文章主要介绍了Python实现FTP上传文件或文件夹实例(递归),具有一定的参考价值,有兴趣的可以了解一下。 -
windows下定时利用bat脚本实现ftp上传下载
2020-09-21 07:56:15主要为大家详细介绍了windows下定时利用bat脚本实现ftp上传和下载,具有一定的参考价值,感兴趣的小伙伴们可以参考一下 -
非常好用的ftp上传工具
2018-04-04 11:12:05很好用的ftp上传工具,上传速度也很快,很好用的ftp上传工具,上传速度也很快,很好用的ftp上传工具,上传速度也很快, -
winform实现FTP上传、下载、删除文件
2018-06-12 10:46:30winform实现FTP上传、下载、删除文件。自己写的Demo,亲测可用! -
C#实现FTP上传下载等操作实例
2018-02-17 14:28:53C#实现FTP上传下载等操作的实例,功能强大,C#实现FTP上传下载等操作的实例! -
C语言实现FTP上传文件(支持windows和linux)
2018-05-03 22:10:41C语言实现FTP上传文件(支持windows和linux和ARM的交叉编译环境) -
[ASP.NET]web实现用FTP上传、下载文件
2018-06-19 17:20:19[ASP.NET]web实现用FTP上传、下载文件。对应博客文章: https://blog.csdn.net/djk8888/article/details/80736524 -
linux下C语言实现FTP上传文件源码200行代码
2017-02-24 14:48:03linux下C语言实现FTP上传文件源码 一个函数搞定FTP上传 直接编译可用 int main() { ftp_upload("ipx.xxx.xxx.xxx",21,"username","password","./main.c","upload_name"); return 0; } -
VB6 FTP 上传 下载 源代码
2016-11-23 13:09:38VB6 FTP 上传 下载 源代码,用的API,不是那种控件版本,拿来就能用. VB6 FTP 上传 下载 源代码,用的API,不是那种控件版本,拿来就能用. VB6 FTP 上传 下载 源代码,用的API,不是那种控件版本,拿来就能用. -
php实现ftp上传文件
2016-05-31 15:47:00php实现ftp上传文件 -
用kettle设计ftp上传操作手册
2017-07-12 13:16:39利用kettle数据抽取工具,编写了一个输出文本数据兵上传到ftp服务器的操作手册 -
VC++通过FTP上传下载文件
2016-02-19 09:56:06VC++通过FTP上传下载文件;包含了代码实现和简易破解实用的ftp服务端;通过vc++写了一个客户端程序,含有基本的ftp上传下载功能;服务器端这里提供了一个ftp服务器,自己可以随意配置就可以;简单实用,分享! -
实现android端用FTP上传文件功能
2015-07-23 18:11:19实现android客户端FTP上传文件功能,提供android端源代码,附带FTP服务器工具。利用FTP服务器工具可以测试上传文件功能,可以把android端源代码集成到项目工程中使用。 -
C# FTP上传下载文件
2017-04-19 15:54:41FTP上传下载文件 -
FTP上传下载C++实现
2015-12-09 16:04:01支持文件夹嵌套上传下载 调用MFC类实现 本地文件遍历采用Boost库 -
c# 实现文件FTP上传服务器
2013-11-16 13:53:46c# 实现文件FTP上传至服务器 WebClient上传文件至服务器(不带进度条) 要上传的文件(全路径格式) <param name="strUrlDirPath">Web服务器文件夹路径 <returns>True/False是否上传成功</returns> -
android实现ftp上传、下载文件,支持文件夹哦
2015-05-22 16:41:33android实现ftp上传、下载文件,支持文件夹,android实现ftp上传、下载文件,支持文件夹android实现ftp上传、下载文件,支持文件夹 -
ftp上传文件命令,Linux中使用ftp命令上传文件
2021-05-10 21:57:44Linux中如何使用ftp命令,包括如何连接ftp服务器,上传or下载文件以及创建文件夹。虽然现在有很多ftp桌面应用(例如:iis7服务器管理工具),但是在服务器、SSH、远程会话中掌握命令行ftp的使用还是很有必要的。IIS7...Linux中如何使用ftp命令,包括如何连接ftp服务器,上传or下载文件以及创建文件夹。虽然现在有很多ftp桌面应用(例如:iis7服务器管理工具),但是在服务器、SSH、远程会话中掌握命令行ftp的使用还是很有必要的。
IIS7服务器管理工具可以批量管理、定时上传下载、同步操作、数据备份、到期提醒、自动更新。IIS7服务器管理工具适用于Windows操作系统和liunx操作系统;支持Ftp客户端批量操作。
1.ftp命令
使用格式:ftp [-v] [-d] [-i] [-n] [-g] [-s:filename] [-a] [-w:windowsize] [computer]
-v:禁止显示远程服务器响应信息
-n:禁止自动登录
-i:多文件传输过程中关闭交互提示
-d:启用调试,显示所有客户端与服务器端传递的命令
-g:禁用文件名通配符,允许在本地文件和路径名中使用
实例:ftp 122.122.122.122 2222
输入用户名、密码,即可完成登录。
2.目录操作
FTP命令可以列出、移动、创建文件夹。
ls -- 打印目录列表
!ls -- 查看本地目录
cd -- 改变目录
mkdir -- 创建文件夹
3.使用FTP下载文件
在下载一个文件之前,首先需要使用lcd命令设定本地接收目录位置。
lcd -- 指定下载目录
get file.txt [file_new.txt] -- 下载文件 支持更改名字
mget *.txt -- 使用mget + 通配符下载多个文件
4.使用FTP上传文件
put file.txt [file_new.txt] -- 上传文件 支持更改名字
put /home/data/cwh/file.txt -- 绝对路径
mput *.txt -- 上传多个文件
5.关闭FTP连接
完成FTP工作后,为了安全起见需要关闭连接。有3个命令可以关闭连接:
bye -- 关闭连接
exit -- 关闭连接
quit -- 关闭连接
close -- 关闭连接
6.其他命令
? -- 查询ftp命令
help -- 查询ftp命令
open [ftp server name] -- 和指定的远程Linux FTP服务器连接
user [user name] [password] -- 使用指定远程Linux FTP服务器的用户登录
pwd -- 显示当前路径
ls -- 列出目录和文件
dir -- 列出目录和文件(同上)
mkdir [foldname] -- 创建指定目录
rmdir [foldname] -- 删除指定目录
cd -- 切换目录
delete [filename] -- 删除文件
rename [filename] [newfilename] -- 重命名
close -- 关闭连接 但保留FTP命令参数提示
disconnect -- 关闭连接 但保留FTP命令参数提示(同上)
bye -- 结束连接
quit -- 结束连接
! -- 直接从远程Linux FTP进入到本地Shell中
exit -- (接上步)从本地Shell环境返回远程Linux FTP中
!ls -- 列出本地机器目录和文件
lcd [foldname] -- 更改本地工作目录
binary -- 使用二进制传输文件
prompt -- 切换提示(使用mput或mget上传下载多个文件时避免提示)
case -- 在使用mget时,将远程主机文件名的大写转换为小写字母
cdup -- 进入远程主机目录的父目录
system -- 显示远程主机的操作系统类型
hash -- 每传输1024字节,显示一个hash符号(#)
status -- 显示当前ftp状态
Connected to122.122.122.122.
No proxy connection.
Mode: stream; Type: binary; Form: non-print; Structure: file
Verbose: on; Bell: off; Prompting: off; Globbing: on
Store unique: off; Receive unique: off
Case: off; CR stripping: on
Ntrans: off
Nmap: off
Hash mark printing: off; Use of PORT cmds: on
Tick counter printing: off
7、一个完整的例子
1“开始”→“运行”→输入“FTP” 2open 122.122.122.122 2222 3用户名 4密码 5查看目录 6-- dir 文件及目录 7-- ls 只查看文件 8mkdir xxx 新建目录 9cd xxx 切换目录10binary 采用二进制传输11lcd /home/data 定位本地目录12!dir 查看本地目录及文件13put xyz.txt 上传文件(mput)14get xyz.txt 下载文件(mget)15delete *.txt 删除文件16cd.. 返回上一级17rmdir xxx 删除空文件夹18bye 退出ftp服务器19pwd 查看当前目录
-
c++ 实现 ftp上传 下载
2013-08-21 20:43:55windows c++简单的 ftp上传下载 -
ftp上传文件,下载文件,重命名文件
2015-03-30 14:33:07ftp上传文件,下载文件,重命名文件. -
php通过ftp上传文件
2020-06-24 10:33:29php通过ftp上传文件 条件: 利用php -m 查看是否有开启ftp扩展(没有则 利用php --ini 查看php.ini路径 开启ftp扩展 php_ftp) 使用方法: <?php /** * 1.利用php -m 查看是否有开启ftp扩展 * 2.利用...php通过ftp上传文件
条件:
利用php -m 查看是否有开启ftp扩展(没有则 利用php --ini 查看php.ini路径 开启ftp扩展 php_ftp)
使用方法:<?php /** * 1.利用php -m 查看是否有开启ftp扩展 * 2.利用php --ini 查看php.ini文化路径 * 3.开启ftp扩展 php_ftp */ require './ftp.php';//加载ftp文件 $config = [ 'host'=>'127.0.0.1',//ftp地址 'user'=>'root',//账户 'pass'=>'root',//密码 'port'=>21,//端口 ]; $ftp = new Ftp($config);//实例化对象 $result =$ftp->connect();//连接服务器 if ( ! $result){ echo $ftp->get_error_msg();//获取链接错误 } $local_file = 'test.txt';//本地文件地址 $remote_file = date('Y-m').'/text.txt';//上传的目录文件地址 //例子如下: // //上传文件 if ($ftp->upload($local_file,$remote_file)){ echo "上传成功"; }else{ echo "上传失败"; } // //删除整个目录 $remote_path=date('Y-m'); if ($ftp->delete_dir($remote_path)){ echo "目录删除成功"; }else{ echo "目录删除失败"; } // //下载文件 $local_file2 = 'index.html'; $remote_file2='index.html'; if ($ftp->download($local_file2,$remote_file2)){ echo "下载成功"; }else{ echo "下载失败"; } //移动文件|重命名文件 $local_file3 = 'indexs.html'; $remote_file3='index.html'; if ($ftp->remane($local_file3,$remote_file3)){ echo "移动成功"; }else{ echo "移动失败"; } $ftp->close();//关闭连接
类库文件:ftp.php
<?php /** * Created by PhpStorm. * User: 123456 * Date: 2018/9/20 * Time: 11:15 * @author sunjiaqiang * @email 1366017911@qq.com */ class Ftp { private $host = ''; //远程服务器地址 private $user = ''; //ftp用户名 private $pass = ''; //ftp密码 private $port = 21; //ftp登录端口 private $error = ''; //最后失败时的错误信息 protected $conn; //ftp登录资源 /** * 可以在实例化类的时候配置数据,也可以在下面的connect方法中配置数据 * Ftp constructor. * @param array $config */ public function __construct(array $config = []) { empty($config) or $this->initialize($config); } /** * 初始化数据 * @param array $config 配置文件数组 */ public function initialize(array $config = []) { $this->host = $config['host']; $this->user = $config['user']; $this->pass = $config['pass']; $this->port = isset($config['port']) ?: 21; } /** * 连接及登录ftp * @param array $config 配置文件数组 * @return bool */ public function connect(array $config = []) { empty($config) or $this->initialize($config); if (false == ($this->conn = @ftp_connect($this->host))) { $this->error = "主机连接失败"; return false; } if (!$this->_login()) { $this->error = "服务器登录失败"; return false; } return true; } /** * 上传文件到ftp服务器 * @param string $local_file 本地文件路径 * @param string $remote_file 服务器文件地址 * @param bool $permissions 文件夹权限 * @param string $mode 上传模式(ascii和binary其中之一) */ public function upload($local_file = '', $remote_file = '', $mode = 'auto', $permissions = null) { if (!file_exists($local_file)) { $this->error = "本地文件不存在"; return false; } if ($mode == 'auto') { $ext = $this->_get_ext($local_file); $mode = $this->_set_type($ext); } //创建文件夹 $this->_create_remote_dir($remote_file); $mode = ($mode == 'ascii') ? FTP_ASCII : FTP_BINARY; $result = @ftp_put($this->conn, $remote_file, $local_file, $mode); //同步上传 if ($result === false) { $this->error = "文件上传失败"; return false; } return true; } /** * 从ftp服务器下载文件到本地 * @param string $local_file 本地文件地址 * @param string $remote_file 远程文件地址 * @param string $mode 上传模式(ascii和binary其中之一) */ public function download($local_file = '', $remote_file = '', $mode = 'auto') { if ($mode == 'auto') { $ext = $this->_get_ext($remote_file); $mode = $this->_set_type($ext); } $mode = ($mode == 'ascii') ? FTP_ASCII : FTP_BINARY; $result = @ftp_get($this->conn, $local_file, $remote_file, $mode); if ($result === false) { return false; } return true; } /** * 删除ftp服务器端文件 * @param string $remote_file 文件地址 */ public function delete_file(string $remote_file = '') { $result = @ftp_delete($this->conn, $remote_file); if ($result === false) { return false; } return true; } /** * ftp创建多级目录 * @param string $remote_file 要上传的远程图片地址 */ private function _create_remote_dir($remote_file = '', $permissions = null) { $remote_dir = dirname($remote_file); $path_arr = explode('/', $remote_dir); // 取目录数组 //$file_name = array_pop($path_arr); // 弹出文件名 $path_div = count($path_arr); // 取层数 foreach ($path_arr as $val) // 创建目录 { if (@ftp_chdir($this->conn, $val) == false) { $tmp = @ftp_mkdir($this->conn, $val); //此处创建目录时不用使用绝对路径(不要使用:2018-02-20/ceshi/ceshi2,这种路径),因为下面ftp_chdir已经已经把目录切换成当前目录 if ($tmp == false) { echo "目录创建失败,请检查权限及路径是否正确!"; exit; } if ($permissions !== null) { //修改目录权限 $this->_chmod($val, $permissions); } @ftp_chdir($this->conn, $val); } } for ($i = 0; $i < $path_div; $i++) // 回退到根,因为上面的目录切换导致当前目录不在根目录 { @ftp_cdup($this->conn); } } /** * 递归删除ftp端目录 * @param string $remote_dir ftp目录地址 */ public function delete_dir(string $remote_dir = '') { $list = $this->list_file($remote_dir); if (!empty($list)) { $count = count($list); for ($i = 0; $i < $count; $i++) { if (!preg_match('#\.#', $list[$i]) && !@ftp_delete($this->conn, $list[$i])) { //这是一个目录,递归删除 $this->delete_dir($list[$i]); } else { $this->delete_file($list[$i]); } } } if (@ftp_rmdir($this->conn, $remote_dir) === false) { return false; } return true; } /** * 更改 FTP 服务器上的文件或目录名 * @param string $old_file 旧文件/文件夹名 * @param string $new_file 新文件/文件夹名 */ public function remane(string $old_file = '', string $new_file = '') { $result = @ftp_rename($this->conn, $old_file, $new_file); if ($result === false) { $this->error = "移动失败"; return false; } return true; } /** * 列出ftp指定目录 * @param string $remote_path */ public function list_file(string $remote_path = '') { $contents = @ftp_nlist($this->conn, $remote_path); return $contents; } /** * 获取文件的后缀名 * @param string $local_file */ private function _get_ext($local_file = '') { return (($dot = strrpos($local_file, '.')) == false) ? 'txt' : substr($local_file, $dot + 1); } /** * 根据文件后缀获取上传编码 * @param string $ext */ private function _set_type($ext = '') { //如果传输的文件是文本文件,可以使用ASCII模式,如果不是文本文件,最好使用BINARY模式传输。 return in_array($ext, ['txt', 'text', 'php', 'phps', 'php4', 'js', 'css', 'htm', 'html', 'phtml', 'shtml', 'log', 'xml'], true) ? 'ascii' : 'binary'; } /** * 修改目录权限 * @param $path 目录路径 * @param int $mode 权限值 */ private function _chmod($path, $mode = 0755) { if (false == @ftp_chmod($this->conn, $path, $mode)) { return false; } return true; } /** * 登录Ftp服务器 */ private function _login() { return @ftp_login($this->conn, $this->user, $this->pass); } /** * 获取上传错误信息 */ public function get_error_msg() { return $this->error; } /** * 关闭ftp连接 * @return bool */ public function close() { return $this->conn ? @ftp_close($this->conn_id) : false; } }
-
Qt三种方式实现FTP上传功能
2021-09-02 22:18:06先来说说QFtp,QFtp是Qt4中的专门负责Ftp传输的类,Ftp相关的常用接口都已经包含了,比如说:创建目录、删除目录、删除文件、获取文件列表、上传、下载等等常规操作。 int setProxy(const QString &host, quFTP协议
FTP的中文名称是“文件传输协议”,是File Transfer Protocol三个英文单词的缩写。FTP协议是TCP/IP协议组中的协议之一,其传输效率非常高,在网络上传输大的文件时,经常采用该协议。
FTP协议有分主动模式
和被动模式
,有关介绍可以看这里,本文不做过多介绍。三种方式
废话不多说,直接先上结果。
三种方式实现Qt项目中的Ftp传输功能:
1、Qt4 QFtp
2、Qt5 QNetworkAccessManager
3、POCO中的 FTPClientSession接下来我们一一分析这几种方式的优缺点。
QFtp
先来说说QFtp,QFtp是Qt4中的专门负责Ftp传输的类,Ftp相关的常用接口都已经包含了,比如说:创建目录、删除目录、删除文件、获取文件列表、上传、下载等等常规操作。
int setProxy(const QString &host, quint16 port); int connectToHost(const QString &host, quint16 port=21); int login(const QString &user = QString(), const QString &password = QString()); int close(); int setTransferMode(TransferMode mode); int list(const QString &dir = QString()); int cd(const QString &dir); int get(const QString &file, QIODevice *dev=0, TransferType type = Binary); int put(const QByteArray &data, const QString &file, TransferType type = Binary); int put(QIODevice *dev, const QString &file, TransferType type = Binary); int remove(const QString &file); int mkdir(const QString &dir); int rmdir(const QString &dir); int rename(const QString &oldname, const QString &newname);
光看名字就知道其功能了,可以说是很全面的了。但不行的是这么好用的东西竟然被Qt5给抛弃了,取而代之的是用 QNetworkAccessManager 来实现。
QNetworkAccessManager
QNetworkAccessManager 是Qt里面专门负责网络请求的模块,包含了http的post、get,还有一个put,而Qt5中要实现Ftp上传功能就通过put来完成,下载通过get来完成。
QNetworkReply *put(const QNetworkRequest &request, QIODevice *data) QNetworkReply *put(const QNetworkRequest &request, const QByteArray &data) QNetworkReply *put(const QNetworkRequest &request, QHttpMultiPart *multiPart) QNetworkReply *get(const QNetworkRequest &request)
遗憾的是,没有办法通过QNetworkAccessManager 实现Ftp创建目录、删除目录、删除文件、获取文件列表等功能。只能做上传下载操作。
FTPClientSession
最后来看FTPClientSession,这是POCO中网络模块里面负责实现FTP的类,POCO是目前最流行的C++轻量级封装库之一,里面包含了很多常用的封装库。
而FTPClientSession提供了FTP的很多常用接口,如下:
void setTimeout(const Poco::Timespan& timeout); Poco::Timespan getTimeout() const; void setPassive(bool flag, bool useRFC1738 = true); bool getPassive() const; virtual void open(const std::string& host, Poco::UInt16 port, const std::string& username = "", const std::string& password = ""); virtual void login(const std::string& username, const std::string& password); void logout(); void close(); std::string systemType(); void setFileType(FileType type); FileType getFileType() const; void setWorkingDirectory(const std::string& path); std::string getWorkingDirectory(); void cdup(); void rename(const std::string& oldName, const std::string& newName); void remove(const std::string& path); void createDirectory(const std::string& path); void removeDirectory(const std::string& path); std::istream& beginDownload(const std::string& path); void endDownload(); std::ostream& beginUpload(const std::string& path); void endUpload(); std::istream& beginList(const std::string& path = "", bool extended = false); void endList(); void abort(); int sendCommand(const std::string& command, std::string& response); int sendCommand(const std::string& command, const std::string& arg, std::string& response); bool isOpen() const; bool isLoggedIn() const; bool isSecure() const; const std::string& welcomeMessage();
可以看到,FTPClientSession提供的这些接口基本包含了所有的Ftp上传的需求。
以上三种方式都可以实现FTP的上传,但是这三种各有各的优缺点,本人在使用过程中遇到了各种各样的问题,这三种都有结合使用,那么,接下来整理一下这段时间在使用这三种方式的区别和优缺点。
三者区别
QFtp
首先来看QFtp,由于在Qt5中已经移除了该模块,所以如果要使用它的话就需要自己下载源码进行编译,还要解决各种编码问题,其实挺麻烦的。
优点:接口完整,而且使用方便,该有的接口都有
缺点:要自己编译源码,或者可以直接使用源码嵌入到项目中,然后要解决编码问题;对于异常处理不是太好,比如突然断网,收不到异常信号,也不会报错,甚至连接状态都不会改变,联网后不能继续上传,会一直假死,这种情况下处理异常就很不友好了,需要自己去解决这个问题。QNetworkAccessManager
优点:简单,集成度高;对于异常的处理比较友好,比如断网后,会立马收到信号反馈,用户可以及时去处理剩下的任务,而且在超时时间内如果连接上网络后可以继续接着上一次的任务进行上传,这一点非常好,给开发省了不少事情。
缺点:太过于简单了,很多接口都没有,就只能做个上传下载,不能创建文件夹、删除文件等操作。
FTPClientSession
优点:使用简单,而且基本ftp的基本操作接口都有提供,对异常处理可以抛出异常,通过try catch进行捕捉,比如说删除不存在的文件夹、创建已存在的文件夹等,这些操作会直接导致程序崩溃退出,需要捕捉异常去处理;
缺点:没有提供上传下载进度的接口或回调,如果需要展示进度功能的话,就很麻烦了,需要自己计算上传了多少字节,还剩多少字节。对网络异常的处理也不是很友好,毕竟不能像Qt那样直接发信号通知。
另外,FTPClientSession
可以通过beginList
接口获取服务器上的文件列表,然后通过读取流的方式得到列表内容,但是如果文件名称中带有空格,那么获取到的文件名就会分开了,不是完整的文件名,比如,有个文件名叫 “abc 12 ed.mp3” ,通过流读取到的内容是 “abc” , “12” , “ed.mp3” ,这样读取文件列表就会有很大的问题了。而通过QFtp读取文件列表就是正常的。还有一个非常重要的问题,FTP上传的数据有两种方式,一种是ASCII码上传,一种是通过二进制数据上传,这两种方式上传会影响上传后的文件大小,而据说QNetworkAccessManager 默认是通过二进制流上传,但是上传后文件大小变了,具体问题可以看这里,就是因为上传方式导致的,而QNetworkAccessManager 是不能修改上传方式的,这个问题比较严重了。QFtp和FTPClientSession都提供了上传方式的修改,从这一点上来说,QNetworkAccessManager 就很不适合了。
就我个人使用这三种方式的经验来看,QFtp是比较更适合的,能够满足使用需求,不过需要自己处理一些异常情况。
本篇只是简单介绍这三种方式优缺点,接下来会在后期的文章中单独列举demo展示如何使用。以及在实际的代码中遇到的各种各样的坑。
-
MFC下通过FTP上传文件
2013-08-26 11:03:46vs下用C++语言编写的FTP上传文件客户端,有MFC的简单界面,仅供参考 -
QT FTP上传文件
2019-08-06 13:58:07QT FTP上传文件两台电脑通过网线建立本地连接,保证网关在同一段;服务器端打开ftp;客户端网页测试远程访问;客户端cmd测试远程访问;客户端程序测试远程访问。 两台电脑通过网线建立本地连接,保证网关在同一段; ... -
C#WinForm做FTP上传下载获取文件列表齐全
2012-09-28 16:40:21C#WinForm做FTP上传下载获取文件列表齐全 -
Android使用ftp方式实现文件上传和下载
2020-08-19 02:17:44主要介绍了Android使用ftp方式实现文件上传和下载,需要的朋友可以参考下