我们在编写应用程序的过程中,通常会遇到这样的情况,因为某些原因,程序被系统多次或者反复调用,导致程序耗尽系统内存或者出现不可预知的错误,最终影响到程序原本的设计需求,这显然不是我们所希望看到的结果,但是如果在程序内部加上必要的判断,不管系统是否多次调用,都能防止同一时刻多个同样的程序同时运行。
网络上防止程序二次启动的方法大同小异,最常见的便是程序启动时先检测指定文件是否存在,如果不存在则创建一个文件,退出时删除该文件,否则认为该程序已启动,直接退出。但是这样做有一个潜在的问题,万一程序在退出前崩溃了,指定文件还未来得及删除,则程序将无法再次启动。另外一个问题是,有些特定文件系统是只读属性的,压根就无法创建文件,所以这一方案也不可行。
上面那种办法虽然存在一定限制以及潜在的缺陷,但至少是支持大多数平台的,对于跨平台的程序而言比较有优势。如果程序只运行在特定平台,我们也可以使用各平台特有的办法实现。
Linux平台下,我们可以调用popen函数执行ps命令,结合awk、grep跟wc命令统计当前进程中与当前程序名字相同的进程数量,即执行“ps x | awk ‘{print $5}’ | grep “当前程序名” | wc -l”,看得到的结果是否大于1,倘若大于1则说明在当前程序启动以前已经启动过同样的程序了,当前程序作退出处理。这种方法的存在的隐患也很大,必须确保没有同名或名字相似的其他程序存在,也不允许该程序有多种命名方式,否则判断结果将出现误差。
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
int main(int argc, char *argv[])
{
int i = 0;
FILE *stream;
char recvBuf[256] = {0};
char cmdBuf[256] = {0};
sprintf(cmdBuf, "ps x | awk '{print $5}' | grep %s | wc -l", argv[0]);
stream = popen(cmdBuf, "r");
fread(recvBuf, sizeof(char), sizeof(recvBuf)-1, stream);
pclose(stream);
if (atoi(recvBuf) > 1)
{
printf("The number of programs: %s\n", recvBuf);
printf("The program %s already exist, so exit!\n", argv[0]);
exit(1);
}
//sleep for test
while(1) sleep(10);
return 0;
}
Windows平台下,大多程序都用CreateMutex方式来限制多开。使用API函数CreateMutex来创建命名互斥对象来实现程序互斥是一个比较通用的方法。对于界面程序,我们可以在IninInstance()函数加入互斥对象,对于后台或者控制台程序,我们可以在main()开始的部分加入互斥对象。
#include <Windows.h>
int main(int argc, char* argv[])
{
HANDLE hMutex = ::CreateMutex(NULL, FALSE, “MYCOMPANY MYAPP.EXE”);
if( (GetLastError() == ERROR_ALREADY_EXISTS) ||
(GetLastError() == ERROR_ACCESS_DENIED) )
{
CloseHanle( hMutex );
printf(“程序已经运行!系统不允许重复运行!\n”);
return FALSE;
}
// main code here
return 0;
}
以上是网上广为传播的两类最普遍的解决程序多重启动的方法,都在不同程度上存在一定的局限跟隐患。本人想到了一种几乎兼容所有平台且方便使用的方法,那就是在程序启动以后,开始真正的任务之前,先尝试绑定一个系统端口,如果绑定成功,则继续执行其他主要任务,否则端口被其他程序绑定,说明已经有一个同样的程序正在执行,当前程序作退出处理。这样做的坏处是浪费了一个端口,不过这对于大多数业务来说是值得的。需要注意的是,端口默认无法复用,不能人为地设置端口复用,而且该端口应该尽可能取得生僻,以确保不会恰巧被其他程序所占用。以Linux平台下C语言实现为例,其他平台代码大同小异:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <errno.h>
int main(void)
{
int fd;
struct sockaddr_in addr;
fd = socket(PF_INET, SOCK_STREAM, 0);
if (fd < 0)
{
perror("socket");
return -1;
}
memset(&addr, 0, sizeof(addr));
addr.sin_family = PF_INET;
addr.sin_port = htons(1987);
addr.sin_addr.s_addr = INADDR_ANY;
if (bind(fd, (struct sockaddr *)&addr, sizeof(struct sockaddr)) < 0)
{
perror("bind");
printf("The program already exist, so exit!\n");
close(fd);
exit(1);
}
else
{
while (1) sleep(10);
}
return 0;
}