用于防止多开的奇葩思路分享(Rust)

如题,从 【无用软件】sc_starter,ScreenCapture的快捷键增强 开始继续掰扯

目的

防止软件多开

可行的部分思路

  1. 文件锁
  2. 线程互斥锁(只会抄C++,rust没有能直接抄的代码……)
  3. 管道
  4. 邪门办法

我的邪门办法(其实算是低级文件锁吧大概)

  1. 程序启动时自动创建一个时间文件(精确到秒)
  2. 同时循环移除其他创建时间较早的时间文件
代码
fn get_time(config_dir: &PathBuf) -> PathBuf {
    //获取当前系统秒数并创建文件,同时如果有旧的删除旧的,为实现单实例做准备
    let seconds = match std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH) {
        Ok(n) => String::from("TIME") + n.as_secs().to_string().as_str(),
        Err(_) => String::from("0"),
    };
    let time_check_file = config_dir.join(&seconds);
    if !time_check_file.exists() {
        match fs::File::create(&time_check_file) {
            Ok(_) => true,
            Err(_) => panic!("No permissions."),
        };
    } else {
        exit(-1);
    }

    //遍历配置文件夹中的文件
    let mut time_file_nums: Vec<u64> = Vec::new();
    for entry in fs::read_dir(config_dir).unwrap() {
        let path = entry.unwrap().path();
        let name = path.file_stem().unwrap().to_str().unwrap();
        if name.starts_with("TIME") {
            time_file_nums.push((name.split_at(4).1).parse::<u64>().unwrap());
        }
    }
    while time_file_nums.len() > 1 {
        if time_file_nums[0] < time_file_nums[1] {
            let _ = fs::remove_file(config_dir.join(format!("{}{}", "TIME", time_file_nums[0])))
                .unwrap();
            time_file_nums.remove(0);
        } else {
            let _ = fs::remove_file(config_dir.join(format!("{}{}", "TIME", time_file_nums[1])))
                .unwrap();
            time_file_nums.remove(1);
        }
    }

    time_check_file
}
  1. 采用多线程,一个线程执行主任务,另一个线程定期对上述时间文件是否存在进行检测
  2. 如果不存在则退出程序
代码
fn avoid_multiple(check_file: &PathBuf) -> JoinHandle<()> {
    //避免多开,仍然不是很完善……
    let file_path = check_file.clone();
    let handle = thread::spawn(move || loop {
        if file_path.exists() {
            thread::sleep(time::Duration::from_secs(1))
        } else {
            exit(-1);
        }
    });
    handle
}

预想中的运行

  • 如果同时打开了多次程序因为创建文件失败自动退出,只留一个
  • 如果间隔一阵子再打开一次程序,则通过1s一次的查询自动退出之前的程序并以新程序为主体重新注册快捷键

存在的问题

  1. 时间间隔太长,但是时间短了以后cpu占用又高(无法接受0.1%以上的cpu占用)
  2. 极端情况无法完美应对:用户在1s,2s时分别打开了一次程序,由于程序是2s后才会注册快捷键,且1s遍历一次,因此可能会出现至多1s的时间使软件多开为2个
  3. 启动时间长,2s以后程序才会响应快捷键还不慢吗……
  4. 无法加速启动速度。如上所言,加速启动速度就必然意味着cpu占用升高……
  5. 在CPU占满、磁盘性能极低等极端情况下无法较好响应。因为是文件锁,且需要遍历文件夹,因此如果用户往文件夹里面恶意塞文件或者磁盘性能极低导致遍历时间远超1s,那这个代码就完全废了……

理论上的改进办法

  1. 换正规办法,如线程互斥锁(废话,这不是不会吗)
  2. 换新电脑然后把循环时间改成200ms……这样cpu占用还是<0.1%……

大家有啥意见或可供参考的代码吗

通过env::set_var()设置一个环境变量,启动的时候检测一下

1 个赞

主要头疼的其实是强制关闭与关机响应

如果被taskkill掉,那就没办法了。优点当然是极其简单,两行代码解决

我一般用文件锁,这是搞开发板看见人家写的驱动学到的

1 个赞

邪道方法:搞个子进程,启动时检测到子进程就退出,为了防止用户手快,子进程创建的时候也检测一下,子进程创建失败带着主进程一起退出

很好的邪道方法,思路加一

我用的办法是开端口

程序启动就检测特定端口有没有被占用,没有的话就占了这个端口,被占用的话说明已经启动有实例在运行了

不怕kill和关机

1 个赞

文件锁防多开(保留第一个运行实例,后续实例启动直接退出):

  • 以独占方式打开打开一个标志文件,退出时释放(或删除)
  • 实例启动时如果独占打开失败,说明已有实例运行,退出
  • 实例被 kill 或关机,文件也会释放

一直有个小问题,如果一开始指定的那个端口已经被其他程序占用了怎么办呢

我也想这么干啊 :smiling_face_with_tear: 我再找找rust到底怎么独占读取文件……头疼这玩意好久了

文件鎖不好用嗎?

我自己造出来的这个性能问题太过严重,又没看明白(也没找明白)正规的怎么用这不是……

于是只能找点偏门办法了

太草了,去知乎提问,大佬甩了个package过来,解决了……

原来是我搜错了关键词……原理上面写过,也是用的Mutex

2 个赞

知乎竟然真能得到答案啊。

一点点踩坑经验记录:

  1. 多线程需要记得wait运行时间最长的那个
  2. 本package不能在非主线程中应用
  3. panic!()只能退出当前所在的线程而不能退出整个程序而exit()可以

抱歉回复晚了,,自己选一个不常用的端口就好了,尤其是很靠后面的端口, 冲突的可能还是比较小的

看到已经有解决方案是最好了,毕竟我这个属于是自己用的旁门左道

高位的端口有很高的概率被连接占用,另外windows系统会随机划一部分端口保留用途