如何在C或C++中创建单个实例应用程序

who*_*hoi 52 c c++ linux single-instance

为了创建单个实例应用程序,您的建议是什么,以便一次只允许一个进程运行?文件锁,互斥还是什么?

Max*_*kin 60

一个好方法是:

#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
}
Run Code Online (Sandbox Code Playgroud)

请注意,锁定允许您忽略过时的pid文件(即您不必删除它们).当应用程序因任何原因终止时,操作系统会为您释放文件锁定.

Pid文件不是非常有用,因为它们可能是陈旧的(文件存在但过程不存在).因此,可以锁定应用程序可执行文件本身,而不是创建和锁定pid文件.

更高级的方法是使用预定义的套接字名称创建和绑定unix域套接字.绑定成功应用于您的应用程序的第一个实例.同样,当应用程序因任何原因终止时,操作系统会解除套接字的绑定.当bind()失败的应用程序的另一个实例可以connect()和使用此接口将其命令行参数传递到第一个实例.

  • @psusi:a)如果`open()`失败`flock()`也失败了,因此不需要检查`open()`的结果; b)文件可能存在但可能是陈旧的,需要锁定它以检查进程是否存在. (11认同)
  • 公平点,虽然我们在这里谈论Linux. (2认同)
  • 如果 `rc` 非零,但是 `errno != EWOULDBLOCK` 怎么办?完全检查 `errno` 有关系吗? (2认同)
  • @Gauthier http://www.tldp.org/LDP/Linux-Filesystem-Hierarchy/html/Linux-Filesystem-Hierarchy.html#tmp: **/tmp**: _这个目录主要包含临时需要的文件。许多程序使用它来创建锁定文件和临时存储数据..._ (2认同)

Mar*_*ata 13

这是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
}
Run Code Online (Sandbox Code Playgroud)

码:

#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;
};
Run Code Online (Sandbox Code Playgroud)

  • 当心。生成的子进程继承此文件描述符。所以进程完成后netstat显示端口5555被子进程使用 (2认同)

Eri*_*rik 5

对于Windows,是一个命名的内核对象(例如CreateEvent,CreateMutex)。对于Unix,一个pid文件-创建一个文件并向其中写入您的进程ID。


Mar*_*rkR 5

您可以创建一个“匿名命名空间”AF_UNIX 套接字。这完全是 Linux 特有的,但优点是实际上不必存在文件系统。

阅读 unix(7) 的手册页了解更多信息。


小智 5

避免基于文件的锁定

避免基于文件的锁定机制来实现应用程序的单例实例总是好的.用户始终可以将锁定文件重命名为其他名称,然后再次运行该应用程序,如下所示:

mv lockfile.pid lockfile1.pid
Run Code Online (Sandbox Code Playgroud)

lockfile.pid运行应用程序之前,基于哪个锁定文件检查是否存在.

因此,对于仅对内核直接可见的对象,最好使用锁定方案.因此,任何与文件系统有关的事情都是不可靠的.

所以最好的选择是绑定到inet套接字.请注意,unix域套接字驻留在文件系统中并且不可靠.

或者,您也可以使用DBUS来完成.

  • 另一方面,使用inet套接字,用户可以在应用程序之前打开该套接字,以防止它能够运行(DOS类型的攻击).所以这也不是万无一失的.通过[在文件系统上设置适当的权限]可以使基于文件的锁定安全(http://serverfault.com/q/159334/10513).但inet套接字没有权限控制谁可以打开它们. (4认同)