分享

运行单个实例

 feiyacz 2012-11-13

摘自:http://blog.chinaunix.net/space.php?uid=20196318;http://blog.csdn.net/magictong/article/details/3603015

对于很多服务来说,在同一个服务器上只能运行一个实例,那么通过什么方法来保证程序同一时刻只有一个实例运行呢?通过编写shell脚本来管理程序的启动、停止是个不错的方法。在启动时,shell脚本会创建进程标识文件(存储正在运行实例的pid)以表明已经有实例在运行,如果文件已存在,则说明已有实例在运行,不需要做任何事;在退出时,shell脚本会删除进程标识文件,表明没有实例运行。


shell脚本管理方法在应用程序之上再包了一层,那么能不能直接在程序开始运行时自己判断是否有实例在运行呢,答案是肯定的。原理其实差不多,还是要借助公用资源---文件,当然不仅仅是文件而已,还需要文件锁的支持。大致思路是这样的:程序在开始运行时对特定文件进行加锁(不存在则创建),如果加锁成功,则实例开始运行;如锁已经被占有,则说明已经有实例在运行,则程序直接退出;另外在实例运行完毕后对文件的锁也随着丢掉了。这样就能保证每次只有一个程序实例在运行。


具体步骤如下:

1. 打开特定文件(如/var/run/mydaemon.pid),如不存在则创建之;

2. 使用fcntl对文件整个区域加劝告锁。

3. 如果加锁成功,则继续执行后续代码,并将pid写入文件;如加锁不成功,说明已经有实例在运行,直接退出。


实现示例:

#include <stdio.h>

#include <stdlib.h>

#include <unistd.h>

#include <fcntl.h>

#include <printf.h>

#include <string.h>

#include <errno.h>

#include <sys/stat.h>

#define LOCKFILE "/var/run/mydaemon.pid"

#define LOCKMODE (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)


/* set advisory lock on file */

int lockfile(int fd)

{

        struct flock fl;

 

        fl.l_type = F_WRLCK;  /* write lock */

        fl.l_start = 0;

        fl.l_whence = SEEK_SET;

        fl.l_len = 0;  //lock the whole file

 

        return(fcntl(fd, F_SETLK, &fl));

}


int already_running(const char *filename)

{

        int fd;

        char buf[16];

 

        fd = open(filename, O_RDWR | O_CREAT, LOCKMODE);

        if (fd < 0) {

                printf(LOG_ERR, "can't open %s: %m\n", filename);

                exit(1);

        }

 

        /* 先获取文件锁 */

        if (lockfile(fd) == -1) {

                if (errno == EACCES || errno == EAGAIN) {

                        printf(LOG_ERR, "file: %s already locked", filename);

                        close(fd);

                        return 1;

                }

                printf(LOG_ERR, "can't lock %s: %m\n", filename);

                exit(1);

        }

        /* 写入运行实例的pid */

        ftruncate(fd, 0);

        sprintf(buf, "%ld", (long)getpid());

        write(fd, buf, strlen(buf) + 1);

        return 0;

}


int main(int argc, char *argv[])

{

        if (already_running(LOCKFILE))

                return 0;

        /* 在这里添加工作代码 */

        printf("start main...\n");

        sleep(100);

        printf("main done!\n");

        exit(0);

}


综述:让一个程序只运行一个实例的方法有多种,但是原理都类似,也就是在程序创建前,有窗口的程序在窗口创建前,检查系统中是否已经设置了某些特定标志了,如果有说明已经有一个实例在运行了,则当前程序通知用户怎样怎样,然后程序退出,当然方法有这么多,各自也就有自己的优缺点了。<注意下面的程序都是分块拷贝的>

 

方法一:
我用得做多的方法是创建互斥体Mutex,使用Mutex代码比较简洁,但是此时不能取得已经启动的实例窗口局柄,因此无法激活已经启动的实例窗口,代码如下:
// -------------------------------------------------------------------------
// 函数  : CreateSendingWNDList
// 功能  : 创建互斥量,用于保证只启动一个进程
// 返回值 : int 
//            成功   0
//     失败   -1
//     存在进程实例 1
// 附注  : 
// -------------------------------------------------------------------------
int CreateSendingWNDList(const TCHAR *pstrKSCoreAppName)
{
 //-------防止多次起动----------  
 HANDLE hMutex = ::CreateMutex(0, true, pstrKSCoreAppName);
 int nRet = 0;
 if (hMutex)
 { 
  if(GetLastError() == ERROR_ALREADY_EXISTS) 
  {
   nRet = 1;
  }
  else
  {
   nRet = 0;
  }
 }
 else
 {
  nRet = -1;
 }

 return nRet;
}

// 在创建窗口前调用下面代码
switch(CreateSendingWNDList(g_strKSCoreAppName))
{
case 0: 
 // 正常启动
 // TODO……

 break;  
case 1:  
 // 已存在进程,退出
 {
  ::MessageBox(NULL, TEXT("已经有一个实例在运行了。"), TEXT("注意"), MB_OK);
 }

case -1:// 无法创建,退出
default: 
 return FALSE;
}

 

方法二:
一般来说,使程序只运行一个实例的最简单的方法当然是使用FindWindow()查找主窗口,如果主窗口已经存在了,当然说明已经有一个实例运行了。代码如下:
// 这种方法有缺陷,窗口名字改变之后就再也找不到了,FindWindow()的参数ClassName和Caption比较难取得。
HWND hWnd = FindWindow(NULL, TEXT("SingleInstanceFW"));
if(IsWindow(hWnd))
{
 ::MessageBox(NULL, TEXT("已经有一个实例在运行了。"), TEXT("注意"), MB_OK);
 ::ShowWindow(hWnd, SW_NORMAL);     // 显示
 ::SetForegroundWindow(hWnd);       // 激活
 return FALSE;
}

 

方法三:

这种方法相比上面两种方法,避免上面两种方法的缺点,通过SetProp()为程序主窗口设置一个特殊的Property,然后在启动时遍历所有的窗口,找出包含着个Property的窗口局柄

。【这个附加的窗口属性在窗口销毁时也应该销毁】这个方法的缺点就是代码比较多一点,如下:

// 声明全局的 属性 名和 属性值
TCHAR g_strKSCoreAppName[] = _T("AFX_KSInstall_CPP__12036F8B_8301_46e2_ADC5_A14A44A85877__");
HANDLE g_hValue = (HANDLE)1022;

// 定义枚举窗口回调函数
BOOL CALLBACK EnumWndProc(HWND hwnd, LPARAM lParam)
{
 //TCHAR str[200] = {0};
 //::GetWindowText(hwnd, str, 200);
 HANDLE h = GetProp(hwnd, g_strKSCoreAppName);
 if(h == g_hValue)
 {
  *(HWND*)lParam = hwnd;
  return FALSE;
 }
 return TRUE;
}

// 主窗口创建前判断
HWND oldHWnd = NULL;
::EnumWindows(EnumWndProc,(LPARAM)&oldHWnd);    //枚举所有运行的窗口
if (oldHWnd != NULL)
{
 ::MessageBox(NULL, TEXT("已经有一个实例在运行了。"), TEXT("注意"), MB_OK);

 ::ShowWindow(oldHWnd, SW_NORMAL);     // 显示
 ::SetForegroundWindow(oldHWnd);       // 激活
 return FALSE;
}

// 主窗口创建后设置,为窗口附加一个属性
::SetProp(m_hWnd, g_strKSCoreAppName, g_hValue);

// 主窗口退出时移除该附加属性
::RemoveProp(m_hWnd, g_strKSCoreAppName);

 

方法四:

上面的方法二和方法三都有一个弊病,不知道大家发现没,那就是依赖于窗口的存在,没有窗口的程序怎么办了,用方法一是可以的,不过方法一不太适合即时修改状态,譬如我想提供选项给用户,可以即时修改是否允许多实例,像KMP就提供了即时修改是否允许多实例,使用全局变量是一个比较好的解决方案,使用全局共享变量的方法则主要是在VC框架程序中通过编译器来实现的。通过#pragma data_seg预编译指令创建一个新节,在此节中可用volatile关键字定义一个变量,而且必须对其进行初始化。Volatile关键字指定了变量可以为外部进程访问。最后,为了使该变量能够在进程互斥过程中发挥作用,还要将其设置为共享变量,同时允许具有读、写访问权限。这可以通过#pragma comment预编译指令来通知编译器。下面给出使用了全局变量的进程互斥代码清单:

#pragma data_seg("Shared") 
int volatile g_lAppInstance = 0; 
#pragma data_seg() 
#pragma comment(linker,"/section:Shared,RWS")

if (0 == g_lAppInstance)
{
 g_lAppInstance = 1;
}
else if (1 == g_lAppInstance)
{
 ::MessageBox(NULL, TEXT("已经有一个实例在运行了。"), TEXT("注意"), MB_OK);
 return FALSE;
}
else
{
 // 直接启动
}

【注意,代码应该放在程序的入口处】

其实上面的方法可以两种进行组合来实现一些比较特殊的需求,具体怎样就自己去想了。

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多