programing

C 또는 C++에서 단일 인스턴스 응용 프로그램을 만드는 방법

goodcopy 2022. 8. 3. 21:26
반응형

C 또는 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 파일을 무시할 수 있습니다(즉, 삭제할 필요가 없습니다).어떤 이유로든 응용 프로그램이 종료되면 OS가 파일 잠금을 해제합니다.

PID 파일은 오래될 수 있기 때문에 그다지 유용하지 않습니다(파일은 존재하지만 프로세스는 존재하지 않습니다).따라서 pid 파일을 만들고 잠그는 대신 응용 프로그램 실행 파일 자체를 잠글 수 있습니다.

보다 고도의 방법은 미리 정의된 소켓 이름을 사용하여 UNIX 도메인 소켓을 만들고 바인드하는 것입니다.Bind는 응용 프로그램의 첫 번째 인스턴스에 대해 성공합니다.어떤 이유로든 애플리케이션이 종료되면 OS는 소켓을 언바인드합니다.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;
};

윈도우즈의 경우 명명된 커널 개체(예: CreateEvent, CreateMutex)입니다.unix의 경우 pid-file - 파일을 만들고 프로세스 ID를 씁니다.

「익명의 네임스페이스」AF_ 를 작성할 수 있습니다.UNIX 소켓이는 완전히 Linux 전용이지만 파일 시스템이 실제로 존재하지 않아도 된다는 장점이 있습니다.

자세한 내용은 unix(7)의 man 페이지를 참조하십시오.

파일 기반 잠금 방지

애플리케이션의 싱글톤 인스턴스를 구현하려면 파일 기반 잠금 메커니즘을 항상 피하는 것이 좋습니다.사용자는 항상 잠금 파일의 이름을 다른 이름으로 변경하고 다음과 같이 응용 프로그램을 다시 실행할 수 있습니다.

mv lockfile.pid lockfile1.pid

서 ★★★★★lockfile.pid는 응용 프로그램을 실행하기 전에 이 존재 여부를 확인하는 기반 잠금 파일입니다.

따라서 커널에서만 직접 볼 수 있는 오브젝트에 대해서는 항상 잠금 방식을 사용하는 것이 좋습니다.따라서 파일 시스템과 관련된 모든 것은 신뢰할 수 없습니다.

따라서 가장 좋은 옵션은 inet 소켓에 바인드하는 것입니다.UNIX 도메인소켓은 파일시스템 내에 존재하며 신뢰성이 낮다는 점에 주의해 주십시오.

또는 DBUS를 사용하여 실행할 수도 있습니다.

언급되지 않은 것 같습니다.공유 메모리에 뮤텍스를 생성할 수 있지만 속성별로 공유로 표시해야 합니다(테스트되지 않음).

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);

아무도 언급하지 않았지만 최신 POSIX 호환 OS에서 실제 이름 있는 세마포어를 만듭니다.세마포에 초기값 1을 지정하면 뮤텍스가 됩니다(잠금이 정상적으로 취득된 경우에만 엄격하게 해제됩니다).

개의 ★★★★★★★★★★★★★★ ★sem_open()- objects - 、 named 、 events 모든 동등한 Windows 이름 오브젝트를 할 수 ."manual된 명명된 에뮬레이트하기가 ("manual" true"를 합니다).CreateEvent(),SetEvent() , , , , 입니다.ResetEvent()무튼,, 딴돌돌돌다다 다다다다다

이치노는 이름 process"할 수 . 모든 프로세스가 pthread mutex를 사용하여 핸들을 연 후 수 .그 후 모든 프로세스가 공유 메모리에 대한 핸들을 연 후 해당 mutex 개체에 안전하게 액세스할 수 있습니다.shm_open()/mmap()sem_open()사용하시는 플랫폼에서 사용할 수 있다면 더 쉬울 것입니다(그렇지 않다면 온전성을 위해 해야 합니다).

하려면 를 합니다.trylock()wait 함수의 바리안트(예를 들어). 프로세스가 유일한 실행일 경우 mutex는 정상적으로 잠깁니다.그렇지 않으면 즉시 실패합니다.

애플리케이션 종료 시 뮤텍스의 잠금을 해제하고 닫는 것을 잊지 마십시오.

응용 프로그램에 인스턴스가1개만 존재하도록 강제함으로써 회피하는 문제와 인스턴스가 고려되는 범위에 따라 달라집니다.

: 은 「」를 사용하는 입니다.「 「 」 、 RADIUS 」/var/run/app.pidfilename을 클릭합니다.

사용자 애플리케이션의 경우 실행하지 말았어야 할 애플리케이션을 두 번 실행할 수 있는 것보다 두 번 실행할 수 없는 애플리케이션에 더 많은 문제가 있었습니다.따라서 "왜, 어떤 범위에 관한 것인가"에 대한 답변은 매우 중요하며, 그 이유와 의도한 범위에 대한 구체적인 답변을 얻을 수 있을 것입니다.

여기서 maxim의 답변에 근거한 힌트는 듀얼 롤 데몬의 POSIX 솔루션(즉, 데몬 및 그 데몬과 통신하는 클라이언트로서 동작할 수 있는 단일 애플리케이션)입니다.이 스킴은 인스턴스가 처음 시작되었을 때 데몬이 되어 이후의 모든 실행은 해당 데몬에서 작업을 로드하기만 하면 되는 문제에 대한 우아한 해결책을 제공할 수 있다는 장점이 있습니다.이것은 완전한 예이지만 실제 데몬이 수행해야 할 많은 작업(예를 들어 로깅에 사용하고 백그라운드에서 올바르게 처리하며 권한을 삭제하는 등)이 부족하지만, 이미 상당히 길고 완전히 그대로 작동하고 있습니다.지금까지 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()이 부족하다는 것을 알았다"고 되어 있습니다.무엇이 부족한지에 대한 자세한 내용은 잘 모르겠습니다.

모든 학점은 마크 라카타에게 돌아간다.아주 사소한 손질만 했을 뿐이에요.

main.cpp

#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;
}

송신원: 여기

언급URL : https://stackoverflow.com/questions/5339200/how-to-create-a-single-instance-application-in-c-or-c

반응형