zoukankan      html  css  js  c++  java
  • 用 abstract unix socket 实现进程单实例运行 [2020-03-03]【转】

    转自:https://www.cnblogs.com/windydays/p/12536033.html

    一,问题背景

    很多时候,我们需要确保进程只有一个实例运行

    有几种方法:

    http://stackoverflow.com/questions/2964391/preventing-multiple-process-instances-on-linux

    http://stackoverflow.com/questions/5339200/how-to-create-a-single-instance-application-in-c-or-c

    https://github.com/qtproject/qt-solutions/tree/master/qtsingleapplication/src

    比较常规的做法,是对一个文件加文件锁 flock,比如对 pid 文件 flock( LOCK_EX|LOCK_NB )

    但是这种方法有些弊端:

    1. 如果文件被 mv 或者 rm,是会被绕过的。
    2. 如果磁盘故障比如磁盘满,目录没有写权限,会失败。

    二,abstract namespace unix socket

    http://linux.die.net/man/7/unix

    unix socket 有3种:

    1. 基于文件的
    2. socketpair 创建的,匿名的
    3. abstract namespace 的,Linux特有

    Linux 下, AF_UNIX socket 支持一种特殊的
    abstract namespace unix socket 。

    相比 普通的基于文件系统的 unix socket,abstract namespace unix socket :

    1. 没有磁盘文件
    2. 进程挂了以后自动删除,无残留文件
    3. 无需担心与 文件系统上的文件冲突,不需要关心文件系统上的绝对路径是否存在的问题

    在 lsof 的结果里面看起来,就是有一些 类似 @test_abstract_ns 这样的 文件项

    代码中使用也很简单, abstract namespace unix socket 在 bind 之前,sockaddr_un.sun_path[0] 设成 0x0 即可。

    三,代码

    于是我用 abstract unix socket 实现了一个 SysSem 工具类( 一个 system 范围的 semaphore ),
    用来:

    1. 让一个程序只启动一个实例。
    2. 让 x 进程等待 y 进程执行完 yyy 操作后,才能执行 xxx 操作。

    特点:

    1. 多进程/线程 并发安全。
    2. 当持有的进程被 kill ,OS自动释放,无残留。
    3. 没有磁盘文件,没有文件意外被删的各种情况。
    4. 不占用 tcp/udp 端口。
    5. 简单,不到 60行代码。
    
    #include <sys/socket.h>
    #include <sys/un.h>
    #include <unistd.h>
    
    #include <algorithm>
    #include <string>
    
    //
    // a semaphore with system scope.
    //
    // 1. no race conditions between Post() / GetValue() , better than flock().
    // 2. when a running process be killed, automatically release all.
    // 3. no file on disk, no accidently delete .
    // 4. no tcp/udp socket, no confliction, no port consumption.
    //
    class SysSem {
    public:
        SysSem() : _fd(-1) { memset(&_addr, 0, sizeof(_addr)); }
        ~SysSem();
    
        void Init(std::string id);
    
        bool Post();
        bool GetValue();
    
        const char* GetID() const;
    
    private:
        struct sockaddr_un _addr;
        int _fd;
    };
    
    void SysSem::Init(std::string id) {
        _addr.sun_family = AF_UNIX;
        const size_t len = std::min(id.size(), sizeof(_addr.sun_path) - 2);  // 2 = start null and end null byte
        // abstract namespace socket address , _addr.sun_path[0] is a null byte ('')
        memcpy(_addr.sun_path + 1, id.c_str(), len);
        // memcpy(_addr.sun_path + 0, id.c_str(), len);
    }
    
    const char* SysSem::GetID() const { return &_addr.sun_path[1]; }
    
    SysSem::~SysSem() {
        if (_fd >= 0) {
            ::close(_fd);
            _fd = -1;
        }
    }
    
    bool SysSem::Post() {
        _fd = ::socket(AF_UNIX, SOCK_STREAM, 0);
        if (_fd < 0) {
            return false;
        }
    
        if ((0 != ::bind(_fd, (struct sockaddr*)&_addr, sizeof(_addr))) || (0 != listen(_fd, 65536))) {
            return false;
        }
        return true;
    }
    
    bool SysSem::GetValue() {
        const int clientFD = ::socket(AF_UNIX, SOCK_STREAM, 0);
        if (clientFD < 0) {
            return false;
        }
        const bool ret = (0 == ::connect(clientFD, (struct sockaddr*)&_addr, sizeof(_addr)));
        ::close(clientFD);
        return ret;
    }
    
    #include <assert.h>
    #include <stdio.h>
    
    int main(int argc, char** argv) {
        if (argc != 3) {
            fprintf(stderr, "usage: %s abstract-path post/get
    ", argv[0]);
            exit(1);
        }
    
        SysSem inst;
        inst.Init(argv[1]);
    
        if (0 == strcasecmp(argv[2], "post")) {
            assert(inst.Post());
            SysSem check;
            check.Init(argv[1]);
            assert(check.GetValue());
            printf("ok, i am the only one under %s. running ...
    ", inst.GetID());
            pause();
    
        } else if (0 == strcasecmp(argv[2], "get")) {
            assert(inst.GetValue());
            printf("a process is running under %s. 
    ", inst.GetID());
        } else {
            printf("unknown cmd 
    ");
        }
    
        return 0;
    }
    
    
    【作者】张昺华
    【大饼教你学系列】https://edu.csdn.net/course/detail/10393
    【新浪微博】 张昺华--sky
    【twitter】 @sky2030_
    【微信公众号】 张昺华
    本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利.
  • 相关阅读:
    Day 20 初识面向对象
    Day 16 常用模块
    Day 15 正则表达式 re模块
    D14 模块 导入模块 开发目录规范
    Day 13 迭代器,生成器,内置函数
    Day 12 递归,二分算法,推导式,匿名函数
    Day 11 闭包函数.装饰器
    D10 函数(二) 嵌套,命名空间作用域
    D09 函数(一) 返回值,参数
    Day 07 Day08 字符编码与文件处理
  • 原文地址:https://www.cnblogs.com/sky-heaven/p/15250269.html
Copyright © 2011-2022 走看看