用 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 ('\0')
    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\n", 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 ...\n", inst.GetID());
        pause();

    } else if (0 == strcasecmp(argv[2], "get")) {
        assert(inst.GetValue());
        printf("a process is running under %s. \n", inst.GetID());
    } else {
        printf("unknown cmd \n");
    }

    return 0;
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章