龙仪的家

导航

<2006年5月>
30123456
78910111213
14151617181920
21222324252627
28293031123
45678910

随笔分类

文章分类

收藏夹

随笔档案

文章档案

统计

我的常用网址

代码库之十三 - 目录监控类(0.1 060524)

项目要用到目录监控的功能,但是下载了几个代码都或多或少有丢失文件名的情况,所以研究了一下,希望能给大家节省些时间。
总结如下
1,调试过程中发现在处理ReadDirectoryChangesW的时候,如果后续处理耗时少,那么能够得到大部分甚至全部的内容后续处理耗时的话就会丢失,猜想可能windows本身提供了一个很小的缓冲,如果不持续调用ReadDirectoryChangesW的话,宝贵的数据就会被系统无情的抛弃,这就是所谓丢失现象,
2,根据typedef struct _FILE_NOTIFY_INFORMATION {
    DWORD NextEntryOffset;
    DWORD Action;
    DWORD FileNameLength;
    WCHAR FileName[1];
} FILE_NOTIFY_INFORMATION, *PFILE_NOTIFY_INFORMATION;
我们可以知道,这显然是个类链表的结构也就是这里有可能存储一系列的结果(如果一次只存一个结果显然系统很难设计),那么我们就要根据NextEntryOffset偏移将所有的数据都得到,这样才能防止数据丢失

3,   ReadDirectoryChangesW(//这里的BUFSIZE不要申请的太少,否则会因为系统堆积缓冲太多而不够用
    m_hDir,
    m_pBuf,
    BUFSIZE,
    TRUE,
    FILE_NOTIFY_CHANGE_FILE_NAME |
    FILE_NOTIFY_CHANGE_LAST_WRITE |
    FILE_NOTIFY_CHANGE_CREATION |
    FILE_NOTIFY_CHANGE_SIZE,
    &dwBytesReturned,
    NULL,
    NULL);
根据上述发现,特设计了一个双线程结构,其中用到了代码库中的类,lg_LoopBuffer在这里用并不合适,但用于测试,以及验证结果,暂时就将就用了。代码写的很粗糙,会根据反映逐步修改.
至于完成端口的方法我想原理应该和这个差不多,有兴趣的可以自己试一下.
另外如果对代码有什么想法欢迎讨论,大家的支持是我的动力!

--------------------------感谢hpho的建议--------------------------------------------------------
m_hDir需要在lg_DirMonitor中关掉,所以放在lg_DirMonitor
Watch_tag以后有可能不是固定的,而且内存是在lg_DirWatch分配的,所以觉得动态比较好
或者跟本上把Watch_tag直接用FILE_NOTIFY_INFORMATION 就算了
Watch_tag中的缓冲要保存多个FILE_NOTIFY_INFORMATION 所以不能直接定义

////////////////////////////////////////////////////////////H//////////////////////////////////////////////////////////////////////////////

#ifndef LGLIB_DIRMONITOR_H
#define LGLIB_DIRMONITOR_H

#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000
#include <string>
#include <queue>
#include "Lg_Tread.h"
#include "lg_MultiEvent.h"
#include "lg_LoopBuffer.h"
using namespace std;
//using namespace lglib;
#define BYBUF 1024
#define BUFSIZE 10240
namespace lglib
{
 class lg_DirMonitor;
 
 class lg_DirWatch : public lg_Thread
 {
 public:
  lg_DirWatch();
  virtual ~lg_DirWatch();
  BOOL Init(HANDLE dir,string name,lg_DirMonitor* pMo);
  void run();
 private:
  char *m_pBuf;
  HANDLE m_hDir;
  BOOL m_bIsInited;
  string m_name;
  lg_DirMonitor* m_pMonitor;
 };
 
 class lg_DirMonitor : public lg_Thread
 {
 public:
  lg_DirMonitor(string strDirName);
  virtual ~lg_DirMonitor();
  BOOL Init();
  void run();
  void Save();
  void SetData(cpstr str,uint32 len);
 private:
  //缓冲块标志
  class Watch_tag
  {
  public:
   Watch_tag() : m_pstr(NULL),m_len(0)
   {
   }
   ~Watch_tag()
   {
    if(m_pstr){
     delete [] m_pstr;
     m_pstr = NULL;
    }
   }
   void SetData(cpstr str,uint32 len)
   {
    m_pstr = const_cast<char*>(str);
    m_len = len;
   }
   cpstr GetData(uint32& len)
   {
    len = m_len;
    return m_pstr;
   }
  private:
   char* m_pstr;
   uint32 m_len;
  };
  typedef queue<Watch_tag*> QList;

  int CopyToBuffer(const string head,const FILE_NOTIFY_INFORMATION * pfiNotify,const uint32 SerNum);

  enum Dir_Event
  {
   Exit = 0,
   ProcessData
  };
  
  HANDLE m_hDir;
  string m_strMoniDir;
  BOOL m_bIsInited;
  lg_DirWatch w1;
  lg_Mutex m_Mutex;
  QList m_Queue;
  lg_MultiEvent *m_MultiEvent;
  lg_LoopBuffer * m_LoopBuf;
 };
}
#endif //LGLIB_DIRMONITOR_H

////////////////////////////////////////////////////////////CPP//////////////////////////////////////////////////////////////////
#include "stdafx.h"
//#include "FileMon.h"
#include "DirMonitor.h"
#include "lg_Exception.h"
//使用VC6的时候注意:在winbase.h前面定义#define _WIN32_WINNT 0x0500
//否则无法通过编译
#include <winbase.h>

#include <io.h>
using namespace lglib;
#ifdef _DEBUG
#undef THIS_FILE
static char THIS_FILE[]=__FILE__;
#define new DEBUG_NEW
#endif

//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////
namespace lglib
{
 lg_DirMonitor::lg_DirMonitor(string strDirName)
 : m_strMoniDir(strDirName),m_bIsInited(FALSE),m_MultiEvent(NULL),
 m_LoopBuf(NULL),m_hDir(INVALID_HANDLE_VALUE)
 {
  lg_Thread::setThreadName("lg_DirMonitor");
 }

 lg_DirMonitor::~lg_DirMonitor()
 {
  if(INVALID_HANDLE_VALUE != m_hDir)
   CloseHandle(m_hDir);
  if(m_MultiEvent)
   m_MultiEvent->set(Exit);
  if(m_bIsInited)
   quit();
  for(int i = 0;i < m_Queue.size();i++)
  {
   delete m_Queue.front();
  }
  m_Queue.empty();
  Sleep(1);
  if(m_MultiEvent)
  {
   delete m_MultiEvent;
   m_MultiEvent = NULL;
  }
  if(m_LoopBuf)
  {
   delete m_LoopBuf;
   m_LoopBuf = NULL;
  }
 }
 void lg_DirMonitor::Save()
 {
  CString Filename;
  Filename.Format("d:\\FileRec.txt");
  m_LoopBuf->Save(Filename.GetBuffer(0));
 }
 void lg_DirMonitor::SetData(cpstr str,uint32 len)
 {
  Watch_tag* p = new Watch_tag;
  p->SetData(str,len);
  m_Mutex.lock(__FILE__,__LINE__);
  m_Queue.push(p);
  m_Mutex.unlock(__FILE__,__LINE__);
  m_MultiEvent->set(ProcessData);
 }
 BOOL lg_DirMonitor::Init()
 {
  if(INVALID_HANDLE_VALUE != m_hDir)
   throw lg_Exception("lg_DirMonitor::Init()","句柄已经有效,重复初始化!",0x00000001);
  if(m_strMoniDir.size() == 0)
   throw lg_Exception("lg_DirMonitor::Init()","所提供的路径为空!",0x00000002);
  if(access(m_strMoniDir.c_str(),0) != 0)
   throw lg_Exception("lg_DirMonitor::Init()","提供的路径不存在!",0x00000003);
  m_hDir = CreateFile(
   m_strMoniDir.c_str(),
   FILE_LIST_DIRECTORY,
   FILE_SHARE_READ|FILE_SHARE_DELETE|FILE_SHARE_WRITE,
   NULL,
   OPEN_EXISTING,
   //如果FILE_FLAG_OVERLAPPED标志不使用,就不能直接CloseHandle,否则调用会挂起
   FILE_FLAG_BACKUP_SEMANTICS|FILE_FLAG_OVERLAPPED,   // file attributes
   NULL);
  if ( INVALID_HANDLE_VALUE == m_hDir )
  {
   throw lg_Exception("lg_DirMonitor::Init()","打开文件失败",0x00000004);
  }
  m_MultiEvent = new lg_MultiEvent(2);
  m_LoopBuf = new lg_LoopBuffer;
  m_LoopBuf->InitBuffer(1024000);
  m_bIsInited = TRUE;
  start();
  w1.Init(m_hDir,"-1-",this);
  return TRUE;
 }
 int lg_DirMonitor::CopyToBuffer(const string head,const FILE_NOTIFY_INFORMATION * pfiNotify,const uint32 SerNum)
 {
  char Output[1024];
  WCHAR wcFileName[BYBUF] = {0};
  memcpy( wcFileName, pfiNotify->FileName, pfiNotify->FileNameLength );
  char cFileName[BYBUF];
  WideCharToMultiByte( CP_ACP, 0, wcFileName, -1,
   cFileName, BYBUF, NULL, NULL );
  sprintf(Output,"%s<%s><%ld>\r\n",head.c_str(),cFileName,SerNum);
  return m_LoopBuf->WriteBuffer(reinterpret_cast<uint8*>(Output),strlen(Output));
 }
 void lg_DirMonitor::run()
 {
  uint32 LenTemp;
  uint32 dwFileModify = 0;
  uint32 dwFileAdd = 0;
  uint32 dwFileRemove = 0;
  while(isCanLoop())
  {
   //从列表中取出数据
   Dir_Event Event = (Dir_Event)m_MultiEvent->wait();
   switch(Event)
   {
   case Exit:
    LGTRACE(Output_Console,"收到退出信号");
    return;
    break;
   case ProcessData:
    m_MultiEvent->reset(ProcessData);
    uint32 count = m_Queue.size();
    
    for(int32 i = 0;i < count;i++)
    {
     Watch_tag* p = m_Queue.front();
     uint32 offset;
     //实际上系统利用这个缓冲区保存一系列结果,如果只取一个值就会丢失结果
     FILE_NOTIFY_INFORMATION * pfiNotifyInfo = (FILE_NOTIFY_INFORMATION *)p->GetData(LenTemp);
     do{
      offset = pfiNotifyInfo->NextEntryOffset;

      switch(pfiNotifyInfo->Action)
      {
      case FILE_ACTION_MODIFIED:
       {
        int Ret = CopyToBuffer("ModifyFile:",pfiNotifyInfo,++dwFileModify);
        if(Ret < 0)
         LGTRACE(Output_Console,"FILE_ACTION_MODIFIED-缓冲满!");
        break;
       }
      case FILE_ACTION_ADDED:
       {
        int Ret = CopyToBuffer("AddFile:",pfiNotifyInfo,++dwFileAdd);
        if(Ret < 0)
         LGTRACE(Output_Console,"FILE_ACTION_ADDED-缓冲满!");
        break;
       }
      case FILE_ACTION_REMOVED:
       {
        int Ret = CopyToBuffer("RemoveFile:",pfiNotifyInfo,++dwFileRemove);
        if(Ret < 0)
         LGTRACE(Output_Console,"FILE_ACTION_REMOVED-缓冲满!");
        break;
       }
      }
      pfiNotifyInfo = (PFILE_NOTIFY_INFORMATION)((uint8*)pfiNotifyInfo + offset);
      
     }while(offset > 0 );
     m_Mutex.lock(__FILE__,__LINE__);
     m_Queue.pop();
     m_Mutex.unlock(__FILE__,__LINE__);
     delete p;
    }
   }
   Sleep(1);
  }
  LGTRACE(Output_Console,"正常退出");
 }
 lg_DirWatch::lg_DirWatch() : m_bIsInited(FALSE),m_hDir(INVALID_HANDLE_VALUE),m_pBuf(NULL),m_pMonitor(NULL)
 {
  lg_Thread::setThreadName("lg_DirWatch");
 }
 lg_DirWatch::~lg_DirWatch()
 {
  if(m_bIsInited)
   quit();

  if(m_pBuf)
  {
   delete [] m_pBuf;
   m_pBuf = NULL;
  }
 }
 BOOL lg_DirWatch::Init(HANDLE dir,string name,lg_DirMonitor* pMo)
 {
  m_hDir = dir;
  m_name = name;
  m_pMonitor = pMo;
  m_bIsInited = TRUE;
  start();
  return TRUE;
 }
 void lg_DirWatch::run()
 {
  BOOL bSucceed;
  uint32 dwBytesReturned = 0;

  while(isCanLoop())
  {
   m_pBuf = new char[BUFSIZE];
   dwBytesReturned = 0;
   
   bSucceed = ReadDirectoryChangesW(
    m_hDir,
    m_pBuf,
    BUFSIZE,
    TRUE,
    FILE_NOTIFY_CHANGE_FILE_NAME |
    FILE_NOTIFY_CHANGE_LAST_WRITE |
    FILE_NOTIFY_CHANGE_CREATION |
    FILE_NOTIFY_CHANGE_SIZE,
    &dwBytesReturned,
    NULL,
    NULL);
   
   if (bSucceed)
   {
    //这里尽快处理避免丢失结果
    m_pMonitor->SetData(m_pBuf,dwBytesReturned);
    m_pBuf = NULL;
   }else{
    DWORD Err = GetLastError();
    uint32 nTempSize = 2048;
    char* szTemp = new char[ nTempSize ];
    
    // 格式化错误信息
    FormatMessage( FORMAT_MESSAGE_FROM_SYSTEM, NULL, Err,
     MAKELANGID( LANG_NEUTRAL, SUBLANG_DEFAULT ),
     szTemp,
     nTempSize,
     NULL);
    TRACE("%s\n",szTemp);
    delete [] szTemp;
    LGTRACE(Output_Console,"lg_DirWatch遇到错误退出");
    return;
   }
  }
  LGTRACE(Output_Console,"lg_DirWatch正常退出");
 }

}

posted on 2006-05-23 10:13 龙仪 阅读(2085) 评论(6)  编辑 收藏

评论

# re: 代码库之十三 - 目录监控类 2006-05-24 00:28 hpho

随便说几点, 不对请见谅:
1,
typedef list<Watch_tag*> WList;

lg_DirWatch ::m_pList;
后面竟然没用到!

2,
FILE_ACTION_ADDED,FILE_ACTION_REMOVED,FILE_ACTION_MODIFIED下的代码几乎一样, 是否应该写成函数呢!?

3,lg_DirMonitor::Init()里的CreateFile()和m_hDir只是为了传给
lg_DirWatch而没有在其它成员函数内使用, 那是不是应该把所有m_hDir相关的都移到lg_DirWatch里呢!?

4,
明显Watch_tag里所指的每一块内存的大小都一定是BUFSIZE, 那又何苦去NEW呢?倒不如Watch_tag就有一个BUFSIZE大小的BUFFER, 这样还可以把NEW内存和NEW Watch_tag合二为一.
或者跟本上把Watch_tag直接用FILE_NOTIFY_INFORMATION 就算了
<这个提出是搞不明白你为什么要
FILE_NOTIFY_INFORMATION * pfiNotifyInfo = (FILE_NOTIFY_INFORMATION *)p->GetData(LenTemp);>


这个应该是典型生产者(lg_DirWatch)/消费者(lg_DirMonitor)模型吧?
感觉有点责任不清.

# re: 代码库之十三 - 目录监控类 2006-05-24 08:58 chenj

ReadDirectoryChangesW确实会丢数据,采用完成端口多线程方式,直接把取到的数据拷给缓冲区返回,让另外线程去处理,如果丢失数据的话一般都能确切知道,因为返回ERROR,这时候调用全目录扫描--当然需要建立原来保护文件的映像。
另外如果调用ReadDirectoryChangesW改成服务的话,由于优先级比较低,丢数据的情况就比较多了。

# re: 代码库之十三 - 目录监控类 2006-05-24 11:35 晓寒

个人建议,如此写法固然不错,但是看起来的确乱,不若把主要思路讲一下,然后给一个测试工程的连接。如果没有空间,砸玻璃的ftp就是很好的地方。 :P

# re: 代码库之十三 - 目录监控类(0.1 060524) 2006-05-24 12:36 龙仪

思路都讲了,主要是懒不想长篇大论,代码在这里看效果是比较差,但如果你真的想用,肯定会弄明白.

# re: 代码库之十三 - 目录监控类(0.1 060524) 2006-05-24 13:33 Diviner

写个驱动就不会丢了。

# re: 代码库之十三 - 目录监控类(0.1 060524) 2006-05-24 13:38 hpho

"m_hDir需要在lg_DirMonitor中关掉,所以放在lg_DirMonitor里"
lg_DirMonitor就包含一个lg_DirWatch然而m_hDir除了在lg_DirMonitor里创建和消毁就没用了, 那就是说lg_DirMonitor没有维护这个句柄的责任!
m_hDir的Create()和CloseHandle都应该放在lg_DirWatch里, 当然可以这样写lg_DirWatch::Release(){CloseHandle(m_hDir);}或换一角度看吧,
lg_DirWatch的核心句柄需要lg_DirMonitor传入这意味着lg_DirWatch依赖lg_DirMonitor, 这样无论是单独类调试或分解这个类到其它地方使用都不会是好事.

Watch_tag的设计大概明白了但仍然觉得不太好, 因为它等同于auto_ptr差不多的东西, 这样的类有一定危险性.

class Watch_tag{
     HANDLE m_hDir;
     int size, offset;
     unsigned char* buff;
     FILE_NOTIFY_INFORMATION* fni;

public:
     Watch_tag(HANDLE h, int sz=BUFSIZE)
           :m_hDir(h), size(sz){
           offset=0;
           fni=-1;
           buff=new unsigned char[sz];
           memset(buff, ...);
     }

    ~Watch_tag(){
           delete[] buff;
    }

    int setDirInfo(){
          int ret=ReadDirectoryChanges(....);
          fni=(FILE_NOTIFY_INFORMATION*)buff;
          return ret;
    }

    operator bool(){
         return offset<0||offset>0;
    }

      FILE_NOTIFY_INFORMATION* operator->(){
           return fni;
      }

       void operator ++(){
          offset = fni->NextEntryOffset;
          fni=(PFILE_NOTIFY_INFORMATION)((uint8*)fni + offset);
       }
};

lg_Monitor::run(){
....
    for(int32 i = 0;i < count;i++){
     Watch_tag* pWatch = m_Queue.front();
     while(*pWatch){
          switch(pWatch->Action){...}
          pWatch++;
     }
    }
}

lg_DirWatch::run(){
    Watch_tag pWatch=new Watch_tag(m_hDir);
    if(pWatch->setDirInfo())
        m_pMonitor->SetData(pWatch);
    pWatch=NULL;
}

标题  
姓名  
主页
验证码 *
内容   
  登录  使用高级评论  Top
[使用Ctrl+Enter键可以直接提交]