簡體   English   中英

如何在C或C++創建單實例應用

[英]How to create a single instance application in C or C++

為了創建單個實例應用程序,您的建議是什么,以便一次只允許一個進程運行? 文件鎖、互斥鎖還是什么?

一個好的方法是:

#include <sys/file.h>
#include <errno.h>

int pid_file = open("/var/run/whatever.pid", O_CREAT | O_RDWR, 0666);
int rc = flock(pid_file, LOCK_EX | LOCK_NB);
if(rc) {
    if(EWOULDBLOCK == errno)
        ; // another instance is running
}
else {
    // this is the first instance
}

請注意,鎖定使您可以忽略陳舊的pid文件(即,您不必刪除它們)。 當應用程序由於任何原因終止時,操作系統會為您釋放文件鎖。

Pid文件並不是非常有用,因為它們可能是過時的(該文件存在但該過程沒有)。 因此,可以鎖定應用程序可執行文件本身,而不是創建和鎖定pid文件。

一種更高級的方法是使用預定義的套接字名稱創建和綁定Unix域套接字。 對於應用程序的第一個實例,綁定成功。 同樣,當應用程序由於任何原因終止時,操作系統將取消綁定套接字。 bind()失敗時,應用程序的另一個實例可以connect()並使用此套接字將其命令行參數傳遞給第一個實例。

這是C ++中的解決方案。 它使用Maxim的套接字推薦。 我比基於文件的鎖定解決方案更喜歡此解決方案,因為如果進程崩潰並且不刪除鎖定文件,則基於文件的解決方案將失敗。 另一個用戶將無法刪除該文件並將其鎖定。 進程退出時,套接字將自動刪除。

用法:

int main()
{
   SingletonProcess singleton(5555); // pick a port number to use that is specific to this app
   if (!singleton())
   {
     cerr << "process running already. See " << singleton.GetLockFileName() << endl;
     return 1;
   }
   ... rest of the app
}

碼:

#include <netinet/in.h>

class SingletonProcess
{
public:
    SingletonProcess(uint16_t port0)
            : socket_fd(-1)
              , rc(1)
              , port(port0)
    {
    }

    ~SingletonProcess()
    {
        if (socket_fd != -1)
        {
            close(socket_fd);
        }
    }

    bool operator()()
    {
        if (socket_fd == -1 || rc)
        {
            socket_fd = -1;
            rc = 1;

            if ((socket_fd = socket(AF_INET, SOCK_DGRAM, 0)) < 0)
            {
                throw std::runtime_error(std::string("Could not create socket: ") +  strerror(errno));
            }
            else
            {
                struct sockaddr_in name;
                name.sin_family = AF_INET;
                name.sin_port = htons (port);
                name.sin_addr.s_addr = htonl (INADDR_ANY);
                rc = bind (socket_fd, (struct sockaddr *) &name, sizeof (name));
            }
        }
        return (socket_fd != -1 && rc == 0);
    }

    std::string GetLockFileName()
    {
        return "port " + std::to_string(port);
    }

private:
    int socket_fd = -1;
    int rc;
    uint16_t port;
};

避免基於文件的鎖定

最好避免使用基於文件的鎖定機制來實現應用程序的單例實例。 用戶始終可以將鎖定文件重命名為其他名稱,然后再次運行該應用程序,如下所示:

mv lockfile.pid lockfile1.pid

其中lockfile.pid是基於鎖定文件的文件,在運行應用程序之前將根據該文件進行檢查。

因此,始終最好對僅對內核直接可見的對象使用鎖定方案。 因此,與文件系統有關的任何事情都不可靠。

因此,最好的選擇是綁定到inet套接字。 請注意,unix域套接字位於文件系統中,並且不可靠。

或者,您也可以使用DBUS進行操作。

對於Windows,是一個命名的內核對象(例如CreateEvent,CreateMutex)。 對於Unix,一個pid文件-創建一個文件並向其中寫入您的進程ID。

您可以創建一個“匿名名稱空間” AF_UNIX套接字。 這完全是特定於Linux的,但是具有的優點是實際上不必存在任何文件系統。

有關更多信息,請閱讀unix(7)的手冊頁。

似乎沒有提及-可以在共享內存中創建互斥鎖,但需要將其標記為由屬性共享(未經測試):

pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
pthread_mutexattr_setpshared(&attr, PTHREAD_PROCESS_SHARED);
pthread_mutex_t *mutex = shmat(SHARED_MEMORY_ID, NULL, 0);
pthread_mutex_init(mutex, &attr);

也有共享內存信號量(但我無法找到如何鎖定信號量):

int sem_id = semget(SHARED_MEMORY_KEY, 1, 0);

沒有人提到它,但是sem_open()在兼容POSIX的現代操作系統下創建了一個真實的信號量。 如果將信號量的初始值設置為1,它將變成一個互斥量(只要成功獲得鎖定,就必須嚴格釋放它)。

使用幾個基於sem_open()的對象,您可以創建所有常見的等效Windows命名對象-命名互斥體,命名信號量和命名事件。 將“ manual”設置為true的命名事件要模擬起來有點困難(它需要四個信號量對象才能正確地模擬CreateEvent()SetEvent()ResetEvent() )。 無論如何,我離題了。

或者,有一個命名的共享內存。 您可以在命名的共享內存中使用“共享進程”屬性初始化pthread互斥鎖,然后在使用shm_open() / mmap()打開共享內存的句柄之后,所有進程都可以安全地訪問該互斥對象。 如果sem_open()適用於您的平台,它會更容易(如果不是,則出於理智起見)。

無論使用哪種方法來測試應用程序的單個實例,請使用wait函數的trylock()變體(例如sem_trywait() )。 如果該進程是唯一運行的進程,它將成功鎖定互斥鎖。 如果不是,它將立即失敗。

不要忘記在應用程序退出時解鎖並關閉互斥鎖。

通過強制應用程序僅具有一個實例以及您考慮實例的范圍,將取決於您要避免的問題。

對於守護程序,通常的方法是擁有一個/var/run/app.pid文件。

對於用戶應用程序,我遇到了更多的問題,這些問題使我無法運行兩次,而不是能夠運行兩次不應該運行的應用程序。 因此,關於“為什么和在哪個范圍內”的答案非常重要,並且可能會針對原因和預期范圍帶來特定的答案。

根據maxim答案中的提示,這里是我的雙角色守護程序(即可以充當守護程序和與該守護程序通信的客戶端的單個應用程序)的POSIX解決方案。 該方案的優點是,當實例首先啟動時應該是守護程序,而隨后的所有執行都應在該守護程序上卸載工作,從而為問題提供了一個優雅的解決方案。 這是一個完整的示例,但是缺少真正的守護程序應做的工作(例如,使用syslog進行日志記錄和fork以正確地使其自身進入后台 ,放棄特權等),但是它已經很長了,可以按原樣正常工作。 到目前為止,我僅在Linux上進行了測試,但是IIRC應該全部與POSIX兼容。

在該示例中,客戶端可以將作為第一個命令行參數傳遞給它們的整數發送,並由atoi通過套接字將其解析到守護程序,該守護程序會將其打印到stdout 使用這種套接字,也可以傳輸數組,結構甚至文件描述符(請參見man 7 unix )。

#include <stdio.h>
#include <stddef.h>
#include <stdbool.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <signal.h>
#include <sys/socket.h>
#include <sys/un.h>

#define SOCKET_NAME "/tmp/exampled"

static int socket_fd = -1;
static bool isdaemon = false;
static bool run = true;

/* returns
 *   -1 on errors
 *    0 on successful server bindings
 *   1 on successful client connects
 */
int singleton_connect(const char *name) {
    int len, tmpd;
    struct sockaddr_un addr = {0};

    if ((tmpd = socket(AF_UNIX, SOCK_DGRAM, 0)) < 0) {
        printf("Could not create socket: '%s'.\n", strerror(errno));
        return -1;
    }

    /* fill in socket address structure */
    addr.sun_family = AF_UNIX;
    strcpy(addr.sun_path, name);
    len = offsetof(struct sockaddr_un, sun_path) + strlen(name);

    int ret;
    unsigned int retries = 1;
    do {
        /* bind the name to the descriptor */
        ret = bind(tmpd, (struct sockaddr *)&addr, len);
        /* if this succeeds there was no daemon before */
        if (ret == 0) {
            socket_fd = tmpd;
            isdaemon = true;
            return 0;
        } else {
            if (errno == EADDRINUSE) {
                ret = connect(tmpd, (struct sockaddr *) &addr, sizeof(struct sockaddr_un));
                if (ret != 0) {
                    if (errno == ECONNREFUSED) {
                        printf("Could not connect to socket - assuming daemon died.\n");
                        unlink(name);
                        continue;
                    }
                    printf("Could not connect to socket: '%s'.\n", strerror(errno));
                    continue;
                }
                printf("Daemon is already running.\n");
                socket_fd = tmpd;
                return 1;
            }
            printf("Could not bind to socket: '%s'.\n", strerror(errno));
            continue;
        }
    } while (retries-- > 0);

    printf("Could neither connect to an existing daemon nor become one.\n");
    close(tmpd);
    return -1;
}

static void cleanup(void) {
    if (socket_fd >= 0) {
        if (isdaemon) {
            if (unlink(SOCKET_NAME) < 0)
                printf("Could not remove FIFO.\n");
        } else
            close(socket_fd);
    }
}

static void handler(int sig) {
    run = false;
}

int main(int argc, char **argv) {
    switch (singleton_connect(SOCKET_NAME)) {
        case 0: { /* Daemon */

            struct sigaction sa;
            sa.sa_handler = &handler;
            sigemptyset(&sa.sa_mask);
            if (sigaction(SIGINT, &sa, NULL) != 0 || sigaction(SIGQUIT, &sa, NULL) != 0 || sigaction(SIGTERM, &sa, NULL) != 0) {
                printf("Could not set up signal handlers!\n");
                cleanup();
                return EXIT_FAILURE;
            }

            struct msghdr msg = {0};
            struct iovec iovec;
            int client_arg;
            iovec.iov_base = &client_arg;
            iovec.iov_len = sizeof(client_arg);
            msg.msg_iov = &iovec;
            msg.msg_iovlen = 1;

            while (run) {
                int ret = recvmsg(socket_fd, &msg, MSG_DONTWAIT);
                if (ret != sizeof(client_arg)) {
                    if (errno != EAGAIN && errno != EWOULDBLOCK) {
                        printf("Error while accessing socket: %s\n", strerror(errno));
                        exit(1);
                    }
                    printf("No further client_args in socket.\n");
                } else {
                    printf("received client_arg=%d\n", client_arg);
                }

                /* do daemon stuff */
                sleep(1);
            }
            printf("Dropped out of daemon loop. Shutting down.\n");
            cleanup();
            return EXIT_FAILURE;
        }
        case 1: { /* Client */
            if (argc < 2) {
                printf("Usage: %s <int>\n", argv[0]);
                return EXIT_FAILURE;
            }
            struct iovec iovec;
            struct msghdr msg = {0};
            int client_arg = atoi(argv[1]);
            iovec.iov_base = &client_arg;
            iovec.iov_len = sizeof(client_arg);
            msg.msg_iov = &iovec;
            msg.msg_iovlen = 1;
            int ret = sendmsg(socket_fd, &msg, 0);
            if (ret != sizeof(client_arg)) {
                if (ret < 0)
                    printf("Could not send device address to daemon: '%s'!\n", strerror(errno));
                else
                    printf("Could not send device address to daemon completely!\n");
                cleanup();
                return EXIT_FAILURE;
            }
            printf("Sent client_arg (%d) to daemon.\n", client_arg);
            break;
        }
        default:
            cleanup();
            return EXIT_FAILURE;
    }

    cleanup();
    return EXIT_SUCCESS;
}

我剛剛寫了一個,並進行了測試。

#define PID_FILE "/tmp/pidfile"
static void create_pidfile(void) {
    int fd = open(PID_FILE, O_RDWR | O_CREAT | O_EXCL, 0);

    close(fd);
}

int main(void) {
    int fd = open(PID_FILE, O_RDONLY);
    if (fd > 0) {
        close(fd);
        return 0;
    }

    // make sure only one instance is running
    create_pidfile();
}

只需在單獨的線程上運行以下代碼:

void lock() {
  while(1) {
    ofstream closer("myapplock.locker", ios::trunc);
    closer << "locked";
    closer.close();
  }
}

運行它作為您的主要代碼:

int main() {
  ifstream reader("myapplock.locker");
  string s;
  reader >> s;
  if (s != "locked") {
  //your code
  }
  return 0;
}

這是基於sem_open的解決方案

/*
*compile with :
*gcc single.c -o single -pthread
*/

/*
 * run multiple instance on 'single', and check the behavior
 */
#include <stdio.h>
#include <fcntl.h>    
#include <sys/stat.h>        
#include <semaphore.h>
#include <unistd.h>
#include <errno.h>

#define SEM_NAME "/mysem_911"

int main()
{
  sem_t *sem;
  int rc; 

  sem = sem_open(SEM_NAME, O_CREAT, S_IRWXU, 1); 
  if(sem==SEM_FAILED){
    printf("sem_open: failed errno:%d\n", errno);
  }

  rc=sem_trywait(sem);

  if(rc == 0){ 
    printf("Obtained lock !!!\n");
    sleep(10);
    //sem_post(sem);
    sem_unlink(SEM_NAME);
  }else{
    printf("Lock not obtained\n");
  }
}

關於不同答案的評論之一說:“我發現sem_open()相當缺乏”。 我不確定所缺少的細節

所有學分歸馬克·拉卡塔所有。 我只是做了一些非常小的修飾。

主程序

#include "singleton.hpp"
#include <iostream>
using namespace std;
int main()
{
   SingletonProcess singleton(5555); // pick a port number to use that is specific to this app
   if (!singleton())
   {
     cerr << "process running already. See " << singleton.GetLockFileName() << endl;
     return 1;
   }
   // ... rest of the app
}

單例.hpp

#include <netinet/in.h>
#include <unistd.h>
#include <cerrno>
#include <string>
#include <cstring>
#include <stdexcept>

using namespace std;
class SingletonProcess
{
public:
    SingletonProcess(uint16_t port0)
            : socket_fd(-1)
              , rc(1)
              , port(port0)
    {
    }

    ~SingletonProcess()
    {
        if (socket_fd != -1)
        {
            close(socket_fd);
        }
    }

    bool operator()()
    {
        if (socket_fd == -1 || rc)
        {
            socket_fd = -1;
            rc = 1;

            if ((socket_fd = socket(AF_INET, SOCK_DGRAM, 0)) < 0)
            {
                throw std::runtime_error(std::string("Could not create socket: ") +  strerror(errno));
            }
            else
            {
                struct sockaddr_in name;
                name.sin_family = AF_INET;
                name.sin_port = htons (port);
                name.sin_addr.s_addr = htonl (INADDR_ANY);
                rc = bind (socket_fd, (struct sockaddr *) &name, sizeof (name));
            }
        }
        return (socket_fd != -1 && rc == 0);
    }

    std::string GetLockFileName()
    {
        return "port " + std::to_string(port);
    }

private:
    int socket_fd = -1;
    int rc;
    uint16_t port;
};
#include <windows.h>

int main(int argc, char *argv[])
{
    // ensure only one running instance
    HANDLE hMutexH`enter code here`andle = CreateMutex(NULL, TRUE, L"my.mutex.name");
    if (GetLastError() == ERROR_ALREADY_EXISTS)
    {
        return 0;
    }
    
    // rest of the program

    ReleaseMutex(hMutexHandle);
    CloseHandle(hMutexHandle);
    
    return 0;
}

來自: 這里

在 Windows 上,您還可以創建一個共享數據段並使用互鎖的 function 來測試第一次出現,例如

#include <Windows.h>
#include <stdio.h>
#include <conio.h>

#pragma data_seg("Shared")
volatile LONG lock = 0;
#pragma data_seg()
#pragma comment(linker, "/SECTION:Shared,RWS")

void main()
{
  if (InterlockedExchange(&lock, 1) == 0)
    printf("first\n");
  else
    printf("other\n");

  getch();
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM