基于ftp协议的文件变化主动监听

这篇具有很好参考价值的文章主要介绍了基于ftp协议的文件变化主动监听。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

前言

文件传输协议(File Transfer Protocol,FTP)是用于在网络上进行文件传输的一套标准协议,它工作在 OSI 模型的第七层, TCP 模型的第四层, 即应用层, 使用 TCP 传输而不是 UDP, 客户在和服务器建立连接前要经过一个“三次握手”的过程, 保证客户与服务器之间的连接是可靠的, 而且是面向连接, 为数据传输提供可靠保证。以上是百度百科的官方解释,通俗点讲就是ftp是通过一种协议让你能够远程访问其他计算机上的文件数据,虽然ftp能够访问到远程服务器的文件,甚至可以下载远程服务器的文件,但是ftp没有被动监听功能,无法监听到远程服务器的文件变化,这时侯就需要我们在客户端去做主动监听。

实现思路

其实主动监听比较简单,主动监听的大致思路就是轮询,没错就是轮询很通俗易懂的一种思路。通过ftp协议连接到远程服务器,之后通过循环不断的执行查看文件列表操作,通过第一次查找的缓存目录去比对后续的查找结果;如果出现后一次文件名称在缓存列表中不存在则证明该文件是新增,如果出现后一次文件名称在缓存列表中存在且文件大小不同,则证明文件被更改,反之则证明该文件无变化;如果出现缓存列表中文件存在但是后一次文件列表中不存在的文件名称,则名称该文件是删除;如上变化除无变化状态以外其余都需要更改本地缓存,以保证每次对比的准确性;想必看完如上设计大家也会发现一些弊端,比如我一个文本文件原文件内容是abc,但是我后来更改成了abd,该种情况根据如上设计方式则无法监听到文件的变化,如果要实现较为精准的变化监听则最好将远程文件缓存到本地之后利用文件md5值的方式来判断,由于本文所述的监听方式主要是类似于系统日志文件这种不经常变化文件名称和篡改文件已有内容的场景,所以利用md5值比对的方式本文不会介绍及实现。

代码实现思路

代码设计中采用事件的方式,利用监听的形式来进行文件夹内容的监听,设计类有如下ListenerFileChangeThreadRunnable(文件变化监听线程类)、ListenerChangeRunnable(文件变化线程接口)、FileChangeType(文件变化类型枚举)、FileChangeEvent(文件变化事件接口)、FileChangeData(文件变化类)、FTPServiceImpl(FTP服务类)。
具体思路为利用commons-net软件包中提供的ftp相关的类进行ftp通信操作,通过监听时建立ListenerChangeRunnable线程来论询远程服务文件列表,当监听到实现思路中的变化时利用FileChangeEvent接口发送文件变化事件,并通过FileChangeType枚举类标记文件变化类型。

具体代码实现

依赖引入

<dependency>
    <groupId>commons-net</groupId>
    <artifactId>commons-net</artifactId>
    <version>3.6</version>
</dependency>

FTPService接口

public interface FTPService {

    /**
     * ftp登陆
     * @return boolean 是否登陆成功
     * */
    boolean login();

    /**
     * ftp登出
     * @return boolean 是否登出成功
     * */
    boolean loginOut();

    /**
     * 获取文件列表
     * @return FTPFile[] 文件列表
     * */
    FTPFile[] listFile();

    /**
     * 监听文件夹的改变
     * @param fileChangeEvent 文件改变事件
     * */
    void addListenerFileChange(FileChangeEvent fileChangeEvent);
}

FTPServiceImpl类

@Service
public class FTPServiceImpl implements FTPService {

    @Autowired
    private FTPConfig ftpConfig;

    private String SPLIT = ":";

    private ThreadLocal<FTPClient> currentFTPClient;

    private ThreadLocal<ListenerChangeRunnable> currentListener;

    public FTPServiceImpl() {
        this.currentFTPClient = new ThreadLocal<>();
        this.currentListener = new ThreadLocal<>();
    }

    @Override
    public boolean login() {
        FTPClient ftpClient = new FTPClient();
        try {
            ftpClient.connect(ftpConfig.getFtpIp(), ftpConfig.getFtpPort());
            ftpClient.login(ftpConfig.getUsername(), ftpConfig.getPassword());
            ftpClient.setControlEncoding("gb2312");

            ftpClient.changeWorkingDirectory(new String(ftpConfig.getWorkspace().getBytes("GBK"), "iso-8859-1"));
            this.currentFTPClient.set(ftpClient);
            return Boolean.TRUE;
        } catch (Exception e) {
            return Boolean.FALSE;
        }
    }

    @Override
    public boolean loginOut() {
        try {
            currentFTPClient.get().logout();
            currentFTPClient.get().disconnect();
            return Boolean.TRUE;
        } catch (Exception e) {
            return Boolean.FALSE;
        }
    }

    @Override
    public FTPFile[] listFile() {
        FTPClient ftpClient = this.currentFTPClient.get();
        try {
            return ftpClient.listFiles();
        } catch (Exception e) {
            return null;
        }
    }

    @Override
    public void addListenerFileChange(FileChangeEvent fileChangeEvent) {
        FTPClient ftpClient = this.currentFTPClient.get();
        ListenerFileChangeThreadRunnable listenerFileChangeThread = new ListenerFileChangeThreadRunnable(ftpClient, fileChangeEvent);
        this.currentListener.set(listenerFileChangeThread);
        new Thread(listenerFileChangeThread).start();
    }
}

FileChangeEvent接口

public interface FileChangeEvent {

    /**
     * 文件发生改变时触发此方法
     * @param fileChangeData 文件发生了改变
     * */
    @Function
    void change(FileChangeData fileChangeData);
}

FileChangeData实体类

@Data
public class FileChangeData {

    /**
     * 文件信息
     * */
    private FTPFile ftpFile;

    /**
     * 文件改变类型
     * */
    private FileChangeType eventType;

    /**
     * 文件名称
     * */
    private String fileName;

    /**
     * 文件大小
     * */
    private Long fileSize;

    /**
     * FTPClient
     * */
    private FTPClient ftpClient;

    /**
     * 获取文件输入流
     * @return InputStream
     * */
    public InputStream getInputStream() {
        //如果是删除事件则不能够获取流
        if (Objects.equals(eventType, FileChangeType.FILE_DELETED)) {
            return null;
        }

        try {
            return ftpClient.retrieveFileStream(this.fileName);
        } catch (IOException e) {
            return null;
        }
    }
}

FileChangeType枚举

public enum FileChangeType {
    FILE_UPDATE(0, "文件更新"),
    FILE_ADD(1, "文件添加"),
    FILE_DELETED(2, "文件删除");

    @Getter
    private Integer type;

    @Getter
    private String desc;

    FileChangeType(Integer type, String desc) {
        this.type = type;
        this.desc = desc;
    }
}

ListenerChangeRunnable枚举

public interface ListenerChangeRunnable extends Runnable {

    /**
     * 停止监听文件
     * @return boolean 是否停止成功
     * */
    boolean stopListener();
}

ListenerFileChangeThreadRunnable实现类

@Slf4j
public class ListenerFileChangeThreadRunnable implements ListenerChangeRunnable {

    private final FTPClient ftpClient;

    private volatile boolean stop;

    private final Map<String, Long> fileMemory;

    private final FileChangeEvent fileChangeEvent;

    public ListenerFileChangeThreadRunnable(FTPClient ftpClient, FileChangeEvent fileChangeEvent) {
        this.ftpClient = ftpClient;
        this.fileChangeEvent = fileChangeEvent;
        this.fileMemory = new HashMap<>();
    }

    @Override
    public void run() {
        while (!stop) {
            try {
                FTPFile[] ftpFiles = ftpClient.listFiles();

                //判断文件被删除
                if (fileMemory.size() > 0) {
                    Set<String> fileNames = new HashSet<>();
                    for (FTPFile ftpFile : ftpFiles) {
                        if (ftpFile.isDirectory()) {
                            log.info("文件夹不做删除判断");
                            continue;
                        }
                        fileNames.add(ftpFile.getName());
                    }
                    Set<Map.Entry<String, Long>> entries = fileMemory.entrySet();
                    for (Map.Entry<String, Long> map : entries) {
                        if (!fileNames.contains(map.getKey())) {
//                            log.info("文件{}被删除了", map.getKey());
                            FileChangeData fileChangeData = new FileChangeData();
                            fileChangeData.setEventType(FileChangeType.FILE_DELETED);
                            fileChangeData.setFileName(map.getKey());
                            fileChangeData.setFileSize(map.getValue());
                            fileMemory.remove(map.getKey());
                            fileChangeEvent.change(fileChangeData);
                        }
                    }
                }
                //判断文件是否有更改或新增
                for (FTPFile ftpFile: ftpFiles) {
                    //判断是否为文件夹
                    if (ftpFile.isDirectory()) {
//                        log.info("{}为文件不进行监听操作", ftpFile.getName());
                        continue;
                    }
                    FileChangeData fileChangeData = new FileChangeData();
                    fileChangeData.setFileName(ftpFile.getName());
                    fileChangeData.setFileSize(ftpFile.getSize());
                    fileChangeData.setFtpFile(ftpFile);
                    //文件是否存在于缓存文件列表中
                    if (fileMemory.containsKey(ftpFile.getName())) {
//                        log.info("文件{}在内存中已经存在,进行大小判断", ftpFile.getName());
                        if (!Objects.equals(fileMemory.get(ftpFile.getName()), ftpFile.getSize())) {
//                            log.info("文件{}在内存中已经存在且大小不一致,进行更新缓存操作", ftpFile.getName());
                            fileMemory.put(ftpFile.getName(), ftpFile.getSize());
                            fileChangeData.setEventType(FileChangeType.FILE_UPDATE);
                            fileChangeEvent.change(fileChangeData);
                        }
                        continue;
                    }
//                    log.info("文件{}在内存中不存在进行缓存操作", ftpFile.getName());
                    fileMemory.put(ftpFile.getName(), ftpFile.getSize());
                    fileChangeData.setEventType(FileChangeType.FILE_ADD);
                    fileChangeEvent.change(fileChangeData);
                }
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
            try {
                TimeUnit.SECONDS.sleep(5);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }

    @Override
    public boolean stopListener() {
        this.stop = Boolean.TRUE;
        this.fileMemory.clear();
        return this.stop;
    }
}

FTPConfig配置类

@Data
@Configuration
public class FTPConfig {

    @Value("${ftp.ip:127.0.0.1}")
    private String ftpIp;

    @Value("${ftp.port:21}")
    private Integer ftpPort;

    @Value("${ftp.username:root}")
    private String username;

    @Value("${ftp.password:root}")
    private String password;

    @Value("${ftp.workspace:root}")
    private String workspace;
}

使用举例

@SpringBootTest
class SendEmailApplicationTests {
   @Autowired
   private FTPService ftpService;
   @Test
   void ftpTest() {
        ftpService.login();
        FTPFile[] ftpFiles = ftpService.listFile();
        for (FTPFile file : ftpFiles) {
            System.out.println(String.format("filename:%s,filesize:%s", file.getName(), file.getSize()));
        }
        ftpService.addListenerFileChange(ftpFile -> {
            System.out.println(String.format("文件%s被改变了,文件改变类型%s", ftpFile.getFileName(), ftpFile.getEventType().getDesc()));
        });
    } 
}

结语

如上只是面对ftp文件变化监听问题的一种实现思路及具体实现代码,但并非最优解,如关于本篇内容存在疑问欢迎评论提问或者私聊,同时如果大家对于该种场景需求较多作者可以将该部分内容封装为starter供大家学习使用。文章来源地址https://www.toymoban.com/news/detail-732901.html

到了这里,关于基于ftp协议的文件变化主动监听的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 正确解决:FTP文件夹错误,将文件复制到FTP服务器时发生错误。请检查是否有权限将文件放到该服务器上。

         FTP文件夹错误,将文件复制到FTP服务器时发生错误。请检查是否有权限将文件放到该服务器上。         谷歌了半天,全是同个答案,压根无法解决,无语到爆炸 (内心:几十篇文章,全都一个样,他们是怎么做到几十个人在那里互抄的,然后还没用):      

    2024年02月15日
    浏览(288)
  • 打开ftp服务器上的文件夹时发生错误,请检查是否有权限访问该文件夹,出现下面的错误提示,200 Type set to A 501 Server cannot accept argument.错误

    新建ftp服务器以后,使用用户名密码访问时,出现下面的错误提示,200 Type set to A  501 Server cannot accept argument.,如下图: 出现上述原因不是ftp服务器有问题,而是访问的客户端有问题,解决如下: 首先打开ie浏览器,然后找到  internet选项  点击 高级 设置下滑  找到  浏览

    2024年02月13日
    浏览(60)
  • 【win系统之服务器SMB协议】共享文件夹

    SMB 是一种客户机/服务器、请求/响应协议。通过 SMB 协议,客户端应用程序可以在各种网络环境下读、写服务器上的文件,以及对服务器程序提出服务请求。也可以通过 SMB 协议,应用程序可以访问远程服务器端的文件、以及打印机等等 一、建一个共享文件夹 ,将鼠标移到该

    2024年02月10日
    浏览(53)
  • 使用Java局域网读取windows共享文件夹(smb协议)

    使用Java局域网读取windows共享文件夹(smb协议) SMB(全称是Server Message Block)是一个网络协议名,它能被用于Web连接和客户端与服务器之间的信息沟通。SMB最初是IBM的贝瑞·费根鲍姆(Barry Feigenbaum)研制的,其目的是将DOS操作系统中的本地文件接口“中断13”改造为网络文件系统

    2024年02月19日
    浏览(49)
  • Win10文件夹共享(有密码的安全共享)(SMB协议共享)

    局域网内(无安全问题,比如自己家里wifi)无密码访问,参考之前的操作视频 【电脑文件全平台共享、播放器推荐】手机、电视、平板播放硬盘中的音、视频资源 下面讲解公共网络如办公室网络、咖啡厅网络等等环境下带密码的安全共享方式。 将插到电脑上的移动硬盘里面

    2024年04月14日
    浏览(66)
  • Unity中读取Json文件:基于Assets/Resources文件夹

    我好生气😤,Python,JS里面一两行代码能够搞定的Json读取,在Unity中使用C#读取Json文件超多坑,爬出来一个又来一个。 主要是JsonUtility.FromJson太不给力了。 最好的方式是,使用 https://github.com/jilleJr/Newtonsoft.Json-for-Unity 这个第三方库。详情见下。 Step 1: 先把对应的Json File放到

    2024年02月12日
    浏览(57)
  • 基于Ubuntu20.04创建共享文件夹

    提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 前言——什么是共享文件夹? 一、怎么创建共享文件夹 1.window设置 2.虚拟机设置 总结 Ubuntu系统是安装在 VMware 虚拟机中的,两者之间经常要互传文件。 所以需要共享文件夹,所谓共享文件夹

    2024年02月08日
    浏览(55)
  • 实现对文件夹的动态检测功能——基于QT

    作者:小 琛 欢迎转载,请标明出处 个人遇到的需求:业务中,需要和其它模块对接,完成某类文件的生成、删除…等一系列操作,如果通过和其它模块定接口的方式,所需要的接口量很多并且所需要考虑的细节也很多。同时有一个很重要的点:这些文件是公用的,也就是说

    2024年02月09日
    浏览(39)
  • 【头歌】——数据分析与实践-基于Python语言的文件与文件夹管理-文本 文件处理-利用csv模块进行csv文件的读写操作

    第1关 创建子文件夹 第2关 删除带有只读属性的文件 第3关 批量复制文件夹中的所有文件 未通过本题,如果您通过了本题欢迎补充到评论区,有时间我会整理进来 第1关 读取宋词文件,根据词人建立多个文件 第2关 读取宋词文件,并根据词人建立多个文件夹 第3关 读取宋词文

    2024年01月25日
    浏览(73)
  • Java基于ftp协议实现文件的上传和下载

    相比其他协议,如 HTTP 协议,FTP 协议要复杂一些。与一般的 C/S 应用不同点在于一般的C/S 应用程序一般只会建立一个 Socket 连接,这个连接同时处理服务器端和客户端的连接命令和数据传输。而FTP协议中将命令与数据分开传送的方法提高了效率。 FTP 使用 2 个端口,一个数据

    2024年02月11日
    浏览(50)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包