如何用C或C ++创build单个实例应用程序
为了创build一个实例应用程序,您的build议是什么?这样一次只允许一个进程运行? 文件锁,互斥锁还是什么?
一个好方法是:
#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 }
注意,locking允许你忽略陈旧的pid文件(即你不必删除它们)。 当应用程序因任何原因终止时,操作系统将为您释放文件locking。
Pid文件不是非常有用,因为它们可能是陈旧的(文件存在,但过程不)。 因此,应用程序可执行文件本身可以被locking,而不是创build和locking一个pid文件。
更高级的方法是使用预定义的套接字名称创build和绑定unix域套接字。 绑定成功为您的应用程序的第一个实例。 同样,当应用程序由于任何原因而终止时,操作系统解除绑定套接字。 当bind()
失败时,应用程序的另一个实例可以connect()
并使用此套接字将其命令行parameter passing给第一个实例。
这是C ++的解决scheme。 它使用Maxim的socketsbuild议。 我喜欢这个解决scheme比基于文件的locking解决scheme更好,因为如果进程崩溃并且不会删除locking文件,则基于文件的文件会失败。 另一个用户将无法删除该文件并将其locking。 当进程退出时,套接字会自动删除。
用法:
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; };
避免基于文件的locking
避免基于文件的locking机制来实现应用程序的单例实例总是好的。 用户总是可以将锁文件重命名为不同的名称,然后再次运行该应用程序,如下所示:
mv lockfile.pid lockfile1.pid
其中lockfile.pid
是在运行应用程序之前基于其检查存在的locking文件。
所以,对于只有内核直接可见的对象,最好使用lockingscheme。 所以,任何与文件系统有关的事情都是不可靠的。
所以最好的select是绑定到一个inet套接字。 请注意,unix域套接字驻留在文件系统中,并不可靠。
或者,您也可以使用DBUS来做到这一点。
对于windows,一个命名的内核对象(例如CreateEvent,CreateMutex)。 对于Unix,一个PID文件 – 创build一个文件,并写入你的进程ID。
这将取决于你想要避免哪个问题,通过强制你的应用程序只有一个实例和你考虑实例的范围。
对于deamon来说 – 通常的方法是创build一个/var/run/app.pid
文件。
对于用户应用程序,我遇到了更多的应用程序问题,使得我无法运行它们两次,而不是运行两次应用程序,而这些应用程序本来不应该运行。 所以关于“为什么和在哪个范围”的答案是非常重要的,并且可能会带来关于为什么和预期范围具体的答案。
似乎没有提到 – 可以在共享内存中创build互斥锁,但需要将其标记为共享属性(未经testing):
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);
也有共享内存信号量(但我没有find如何locking一个):
int sem_id = semget(SHARED_MEMORY_KEY, 1, 0);
你可以创build一个“匿名命名空间”AF_UNIX套接字。 这完全是Linux特有的,但具有实际上不存在文件系统的优点。
阅读unix(7)的手册页获取更多信息。
没有人提到过,但是sem_open()
在现代POSIX兼容的操作系统下创build了一个真实的命名信号量。 如果给一个信号量一个初始值1,它就成为一个互斥量(只要成功获得一个锁,它就被严格释放)。
使用几个基于sem_open()
的对象,可以创build所有常见的等效Windows命名对象 – 名为互斥体,命名信号量和命名事件。 命名为“manual”的事件设置为true会更难以模拟(需要四个信号量对象来正确模拟CreateEvent()
, SetEvent()
和ResetEvent()
)。 无论如何,我离题了。
或者,有一个名为共享内存。 你可以用命名共享内存中的“共享进程”属性来初始化一个pthread互斥体,然后所有进程都可以在用shm_open()
/ mmap()
打开共享内存的句柄之后安全地访问该互斥体对象。 如果sem_open()
可用于您的平台,则sem_open()
更容易(如果不是,则应该是出于理智的原因)。
无论使用什么方法,为了testing应用程序的单个实例,请使用wait函数的trylock()
变体(例如sem_trywait()
)。 如果该进程是唯一正在运行的进程,则会成功locking该互斥锁。 如果不是,它会立即失败。
不要忘记在应用程序退出时解锁并closures互斥锁。
基于maxim的答案中的提示,这里是我的双重angular色守护进程(即可以作为守护进程的一个单独的应用程序,以及作为与守护进程通信的客户机)的POSIX解决scheme。 这个scheme的优点是,当实例首先启动的时候,应该是守护进程,并且所有后续的执行都应该加载该守护进程的工作。 这是一个完整的例子,但缺乏一个真正的守护进程应该做的很多东西(例如,使用syslog
进行日志logging和fork
将自己置于后台 ,放弃权限等),但它已经很长,并且完全按照原样运行。 到目前为止,我只在Linux上testing过,但是IIRC应该是所有POSIX兼容的。
在这个例子中,客户端可以将传递给它们的整数作为第一个命令行参数发送,并通过套接字通过atoi
进行parsing,然后将其打印到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; }
我刚刚写了一个,经过testing。
#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; }