06 retrieveFileStream 之后需要调用 completePendingCommand 否则业务代码会存在问题
Posted 蓝风9
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了06 retrieveFileStream 之后需要调用 completePendingCommand 否则业务代码会存在问题相关的知识,希望对你有一定的参考价值。
前言
问题是这样的
之前 同事碰到了这样的一个问题, 说是基于 ftp 客户端更新文件名字 更新失败
然后 看了一下, 原来是 调用了 retrieveFileStream, 然后 没有同步等待 数据传输完成, 然后 之后直接调用了 rename 的方法
然后 发现 rename 返回的是 false, 并且 文件名称也没有更新
然后 这里 我们就来看一下 这个问题的相关细节
测试用例
主要做的事情如下
1. 删除了相关的两个文件
2. 上传 test.txt
3. 获取 test.txt 的内容
4. 更新 test.txt 为 test_updated.txt
/**
* Test19RenameBeforeConsumeIO
*
* @author Jerry.X.He <970655147@qq.com>
* @version 1.0
* @date 2022-09-16 14:53
*/
public class Test19RenameBeforeConsumeIO
// Test19RenameBeforeConsumeIO
public static void main(String[] args) throws Exception
FTPClient client = new FTPClient();
// client.connect("localhost", 21);
// client.login("root", "root");
client.connect("192.168.0.21", 21);
client.login("root", "root");
client.setDataTimeout(1000 * 1000);
client.setConnectTimeout(1000 * 1000);
client.setSoTimeout(1000 * 1000);
client.setDefaultTimeout(1000 * 1000);
client.enterLocalPassiveMode();
String targetFile = "test.txt";
String targetFileUpdated = "test_updated.txt";
boolean deleteFileStatus = client.deleteFile(targetFile);
boolean deleteFileUpdatedStatus = client.deleteFile(targetFileUpdated);
System.out.println(String.format(" delete targetFile : %s ", deleteFileStatus));
System.out.println(String.format(" delete targetFileUpdated : %s ", deleteFileUpdatedStatus));
// boolean storeResult = client.storeFile(targetFile, new FileInputStream("/Users/jerry/Tmp/sql02.txt"));
boolean storeResult = client.storeFile(targetFile, new FileInputStream("/Users/jerry/Downloads/h5player.zip"));
System.out.println(String.format(" store file : %s ", storeResult));
System.out.println(" __passivePort : " + client.getPassivePort());
long start01 = System.currentTimeMillis();
InputStream is = client.retrieveFileStream(targetFile);
long start02 = System.currentTimeMillis();
String content = Tools.getContent(is);
// client.completePendingCommand();
boolean renameStatus = client.rename(targetFile, targetFileUpdated);
// client.rnfr(targetFile);
long start03 = System.currentTimeMillis();
// client.rnto(targetFileUpdated);
// client.getReply();
long start04 = System.currentTimeMillis();
long cost01 = start02 - start01;
long cost02 = start03 - start02;
long cost03 = start04 - start03;
System.out.println(String.format(" cost1 : %s, cost2 : %s, cost3 : %s ", cost01, cost02, cost03));
FTPFile[] childFiles = client.listFiles();
for (FTPFile file : childFiles)
System.out.println(file.toFormattedString());
int x = 0;
有这个 completePendingCommand 的情况, 是可以 正常 执行完上面一系列的流程的
但是 没有 completePendingCommand 的话, 就回报错了
我们这里 来看一下 具体的情况
retrieveFileStream 的请求的流程
在执行 retrieveFileStream 之前目前 ftpClient 保存的 replyString 是 storeFile 响应的结果信息
ftpClient 保存的 replyString 是服务器传输给客户端的将要开始进行数据传输的一个通知
ftpClient 保存的 replyString 是服务器传输给客户端数据传输完成的一个通知
其实 最核心的问题就是 retrieveFileStream 只发送了一次请求, 但是 服务端有两次响应
第一次是一个 非阻塞的通知结果, 第二次是传输完成之后的一个通知结果
所以 completePendingCommand 中的具体的实现 其实是阻塞等待 消费者第二个 传输完成之后的通知结果
retrieveFileStream 处理的流程
我们来看一下 retrieveFileStream 的服务端的具体的处理
第一次 非阻塞的通知结果 是在 727 行响应给客户端的
第二次 传输完成之后的一个通知结果 是在 768 行响应给客户端的
调试一下异常场景
我们发现 rename 之后拿到的 replyString 是 "226 Transfer complete."
然而 这个是 retrieveFileStream 的第二个响应
看一下 rename 里面, 原来 rename 是两条 ftp 命令组合而来的
rnfr 是传递原文件名, rnto 是传递更新之后的文件名
我们看一下 正常情况下 rnfr 的响应, 是一个 ”350 Ready for RNTO.“
而我们上面 拿到的是 retrieveFileStream 的第二个响应, 所以 我们只需要在 retrieveFileStream 操作完成之后, 消费掉 retrieveFileStream 的第二个响应的第二个响应就行了
问题的处理
在 retrieveFileStream 处理之后, 其他的请求处理之前同步消费 retrieveFileStream 的第二个响应即可
可以直接 completePendingCommand 或者调用 getReply
当然 completePendingCommand 会更好一些, 还有一个 状态的判断
// client.completePendingCommand();
client.getReply();
异常场景 rnfr 怎么样了?
rnfr 发送给了服务器
服务器那边 单独一个进程来处理的客户端这边的 process_post_login 来处理的各个请求, 是同步的
因此 rnfr 命令是已经发送到了 服务器, 但是 rnfr 中 getReply 是阻塞等待在 retrieveFileStream 的传输完成, 传输完成之后 拿到 retrieveFileStream 的第二个响应 "226 Transfer complete."
然后 后面走的 "if (!FTPReply.isPositiveIntermediate(rnfr(from))) " 的判断
这个命令传输过去了, 服务器 也处理了, 下一个客户端请求 listFiles 拿到的是 rfnr 的响应结果, 然后 listFiles 报错了, 效果如下
怎么在异常场景下面更新文件名?
rename 在异常场景下面发送了 rnfr 的请求, 并且消费了 retrieveFileStream 的第二个响应
那么这时候 只要按照 rename 的逻辑, 消费掉 rnfr 的响应, 然后再发送 rnto 的请求就可以了
完
以上是关于06 retrieveFileStream 之后需要调用 completePendingCommand 否则业务代码会存在问题的主要内容,如果未能解决你的问题,请参考以下文章
07 来自于网友的 retrieveFileStream 的一个问题, 导致系统程序异常
07 来自于网友的 retrieveFileStream 的一个问题, 导致系统程序异常