如何在macOS/Linux中用命名对象限制特定路径的应用实例数?
基于应用路径限制实例数的跨平台实现方案
针对「同路径仅允许一个实例,不同路径允许多实例」的需求,同时解决崩溃后残留资源的问题,推荐以下两种可靠方案:
方案一:文件独占锁(fcntl)
这是跨macOS/Linux最稳妥的方案,进程崩溃后系统会自动释放锁,不会留下残留资源。
实现思路
- 基于应用Bundle路径生成唯一的锁文件路径(比如用路径的SHA256哈希作为文件名,避免路径字符冲突和长度问题)。
- 打开锁文件(不存在则创建),通过
fcntl设置独占写锁:- 锁设置成功:当前是首个实例,持有锁直到进程退出。
- 设置失败且错误码为
EAGAIN/EACCES:已有同路径实例在运行。
代码示例(C)
#include <stdio.h> #include <stdlib.h> #include <fcntl.h> #include <unistd.h> #include <errno.h> #include <openssl/sha.h> // 需要链接libcrypto // 生成路径的SHA256哈希作为锁文件名 void get_lock_filename(const char* bundle_path, char* out_filename, size_t out_size) { unsigned char hash[SHA256_DIGEST_LENGTH]; SHA256((const unsigned char*)bundle_path, strlen(bundle_path), hash); // 转成十六进制字符串 for (int i = 0; i < SHA256_DIGEST_LENGTH; i++) { snprintf(out_filename + i*2, out_size - i*2, "%02x", hash[i]); } // 拼接临时目录路径 char tmp_path[PATH_MAX]; snprintf(tmp_path, sizeof(tmp_path), "/tmp/app_lock_%s", out_filename); strncpy(out_filename, tmp_path, out_size); out_filename[out_size-1] = '\0'; } bool IsAnotherInstanceRunning(const char* bundle_path) { char lock_file[PATH_MAX]; get_lock_filename(bundle_path, lock_file, sizeof(lock_file)); // 打开锁文件,不存在则创建,权限0644 int fd = open(lock_file, O_CREAT | O_RDWR, 0644); if (fd == -1) { perror("open lock file failed"); return true; // 异常情况默认视为有实例 } struct flock lock = {0}; lock.l_type = F_WRLCK; // 独占写锁 lock.l_whence = SEEK_SET; // 从文件开头 lock.l_start = 0; lock.l_len = 0; // 锁定整个文件 // 设置非阻塞锁 int ret = fcntl(fd, F_SETLK, &lock); if (ret == -1) { if (errno == EAGAIN || errno == EACCES) { close(fd); return true; // 锁已被持有,存在其他实例 } else { perror("fcntl failed"); close(fd); return true; } } // 保持文件描述符打开,进程退出时自动关闭释放锁 static int g_lock_fd = -1; g_lock_fd = fd; return false; }
注意事项
- 需要链接OpenSSL库(macOS下编译加
-lcrypto参数),如果不想依赖第三方库,可以替换成MD5哈希,或者沿用路径字符替换逻辑(把斜杠换成下划线后拼接临时目录)。 - 锁文件会留在
/tmp目录,但不会影响功能,下次启动时旧文件的锁已失效,不影响新实例加锁。
方案二:Unix域套接字(AF_UNIX)
利用Unix域套接字的绑定特性,崩溃后残留的套接字文件可通过连接检测清理。
实现思路
- 基于应用路径生成唯一的套接字路径(建议用哈希缩短路径,避免长度限制)。
- 创建套接字并尝试绑定:
- 绑定成功:当前是首个实例,保持套接字监听。
- 绑定失败且错误码为
EADDRINUSE:尝试连接该套接字,连接成功说明有实例运行;连接失败则删除残留套接字后重新绑定。
代码示例(C)
#include <stdio.h> #include <stdlib.h> #include <sys/socket.h> #include <sys/un.h> #include <unistd.h> #include <errno.h> #include <openssl/sha.h> void get_socket_path(const char* bundle_path, char* out_path, size_t out_size) { unsigned char hash[SHA256_DIGEST_LENGTH]; SHA256((const unsigned char*)bundle_path, strlen(bundle_path), hash); char hash_str[SHA256_DIGEST_LENGTH*2 + 1]; for (int i = 0; i < SHA256_DIGEST_LENGTH; i++) { snprintf(hash_str + i*2, sizeof(hash_str) - i*2, "%02x", hash[i]); } snprintf(out_path, out_size, "/tmp/app_sock_%s", hash_str); out_path[out_size-1] = '\0'; } bool IsAnotherInstanceRunning(const char* bundle_path) { char sock_path[PATH_MAX]; get_socket_path(bundle_path, sock_path, sizeof(sock_path)); int sock_fd = socket(AF_UNIX, SOCK_STREAM, 0); if (sock_fd == -1) { perror("socket create failed"); return true; } struct sockaddr_un addr = {0}; addr.sun_family = AF_UNIX; strncpy(addr.sun_path, sock_path, sizeof(addr.sun_path)-1); // 尝试绑定套接字 int ret = bind(sock_fd, (struct sockaddr*)&addr, sizeof(addr)); if (ret == 0) { // 绑定成功,开始监听(仅占位,无需处理连接) listen(sock_fd, 1); static int g_sock_fd = -1; g_sock_fd = sock_fd; return false; } else if (errno == EADDRINUSE) { // 套接字已存在,尝试连接检测是否有进程使用 int test_fd = socket(AF_UNIX, SOCK_STREAM, 0); ret = connect(test_fd, (struct sockaddr*)&addr, sizeof(addr)); close(test_fd); if (ret == 0) { // 连接成功,存在运行的实例 close(sock_fd); return true; } else { // 连接失败,说明套接字是残留的,删除后重新绑定 unlink(sock_path); ret = bind(sock_fd, (struct sockaddr*)&addr, sizeof(addr)); if (ret == 0) { listen(sock_fd, 1); static int g_sock_fd = -1; g_sock_fd = sock_fd; return false; } else { perror("rebind failed"); close(sock_fd); return true; } } } else { perror("bind failed"); close(sock_fd); return true; } }
注意事项
- macOS下Unix域套接字路径长度有限制(
sun_path最多104字符),必须用哈希缩短路径。 - 进程正常退出时可调用
unlink(sock_path)清理套接字文件,崩溃时系统不会自动清理,需通过连接检测处理残留。
为什么不推荐命名信号量
命名信号量是系统级持久化资源,进程崩溃后如果未调用sem_unlink,信号量会一直留存,导致后续同路径应用无法启动,必须手动清理(如ipcrm命令),完全不符合需求的可靠性要求。
内容的提问来源于stack exchange,提问作者c00000fd
相关产品推荐
相关产品推荐

