• 用 abstract unix socket 实现进程单实例运行 [2020-03-03]


    一,问题背景

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

    有几种方法:

    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;
    }
    
    
    
  • 相关阅读:
    itextpd f生成 pdf 文件
    java word文档 转 html文件
    Activiti之 Exclusive Gateway
    activiti入门
    lucene分词器与搜索
    这款小程序 能让你和孙悟空一样 可以七十二变
    西游记中神兽谛听的能力 这款小程序也有
    想拥有一款钢铁侠Jarvis管家的软件吗?
    微信小程序开发填坑指南V1
    NaviSoft31.源码开发完成
  • 原文地址:https://www.cnblogs.com/windydays/p/12536033.html
Copyright © 2020-2023  润新知